Idiomatic Go client wrapping the FastAPI surface in server.py — Healthz, Run, UploadFile/UploadReader, and admin token CRUD. stdlib net/http only, context-first signatures, typed errors (ErrAuth sentinel, RunFailure for /run 502s, APIError for other 4xx/5xx, TransportError for network/EOF). RunResult.Result is captured as json.RawMessage and materialized via .AsJSON(out) / .AsText() because claude returns either parsed JSON or plain text depending on prompt. UploadFile streams via io.Pipe + multipart without buffering the file in memory. Module: gitea.sulkta.com/Sulkta-Coop/clawdforge/clients/go Includes cmd/cf-cli demo binary and httptest-based test suite (13 tests).
62 lines
2 KiB
Go
62 lines
2 KiB
Go
package clawdforge
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
// ErrAuth is the sentinel returned for 401/403 responses from clawdforge.
|
|
// Use errors.Is(err, ErrAuth) to detect auth failures.
|
|
var ErrAuth = errors.New("clawdforge: authentication failed")
|
|
|
|
// APIError carries a non-2xx response from clawdforge — status code plus
|
|
// the response body (truncated to a sane size). It is returned for any
|
|
// HTTP status >= 400 that isn't an auth failure.
|
|
type APIError struct {
|
|
StatusCode int
|
|
// Body is the raw response body (truncated). Useful for debugging.
|
|
Body string
|
|
// Message is a short human-readable summary derived from the body
|
|
// when the body is JSON of the form {"error": "..."} or {"detail": "..."}.
|
|
Message string
|
|
}
|
|
|
|
func (e *APIError) Error() string {
|
|
if e.Message != "" {
|
|
return fmt.Sprintf("clawdforge: HTTP %d: %s", e.StatusCode, e.Message)
|
|
}
|
|
return fmt.Sprintf("clawdforge: HTTP %d: %s", e.StatusCode, e.Body)
|
|
}
|
|
|
|
// TransportError wraps a low-level network/transport failure (DNS, connect
|
|
// refused, TLS, EOF, context cancellation, etc.). The wrapped error is
|
|
// available via errors.Unwrap.
|
|
type TransportError struct {
|
|
Op string
|
|
Err error
|
|
}
|
|
|
|
func (e *TransportError) Error() string {
|
|
return fmt.Sprintf("clawdforge: transport %s: %v", e.Op, e.Err)
|
|
}
|
|
|
|
func (e *TransportError) Unwrap() error { return e.Err }
|
|
|
|
// RunFailure represents a 502 response from /run — clawdforge accepted the
|
|
// request but `claude -p` failed (timeout, non-zero exit, etc.). The body
|
|
// fields mirror the server's failure shape.
|
|
//
|
|
// RunFailure satisfies APIError-like semantics by also embedding the status
|
|
// code via the underlying APIError, but it's a distinct type so callers can
|
|
// branch on errors.As(err, &cf.RunFailure{}).
|
|
type RunFailure struct {
|
|
StatusCode int
|
|
Err string `json:"error"`
|
|
Stderr string `json:"stderr"`
|
|
DurationMS int `json:"duration_ms"`
|
|
StopReason string `json:"stop_reason"`
|
|
}
|
|
|
|
func (e *RunFailure) Error() string {
|
|
return fmt.Sprintf("clawdforge: run failed (stop_reason=%s): %s", e.StopReason, e.Err)
|
|
}
|