clawdforge/clients/go/models.go
Kayos 3c62613c30 clients/go: initial Go SDK for clawdforge
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).
2026-04-28 22:36:56 -07:00

95 lines
3 KiB
Go

package clawdforge
import (
"encoding/json"
"fmt"
)
// Healthz is the parsed response from GET /healthz.
type Healthz struct {
OK bool `json:"ok"`
ClaudePresent bool `json:"claude_present"`
ClaudeVersion string `json:"claude_version"`
}
// RunRequest is the body for POST /run. Matches server.py RunRequest exactly.
//
// Only Prompt is required. Model, System, Files, TimeoutSecs are zero-value
// optional — empty strings/slices and zero ints are omitted from the wire.
type RunRequest struct {
Prompt string `json:"prompt"`
Model string `json:"model,omitempty"`
System string `json:"system,omitempty"`
Files []string `json:"files,omitempty"`
TimeoutSecs int `json:"timeout_secs,omitempty"`
}
// RunResult is the parsed response from POST /run on success (HTTP 200).
//
// Result is captured raw because the upstream may return either a parsed
// JSON value (object/array/etc) or a plain string. Use AsJSON or AsText
// to materialize it.
type RunResult struct {
OK bool `json:"ok"`
Result json.RawMessage `json:"result"`
DurationMS int `json:"duration_ms"`
StopReason string `json:"stop_reason"`
}
// AsJSON unmarshals the Result field into out. Use this when your prompt
// asked claude for JSON and you have a target type.
//
// var data map[string]string
// if err := res.AsJSON(&data); err != nil { ... }
func (r *RunResult) AsJSON(out any) error {
if len(r.Result) == 0 {
return fmt.Errorf("clawdforge: empty result")
}
if err := json.Unmarshal(r.Result, out); err != nil {
return fmt.Errorf("clawdforge: result is not JSON: %w", err)
}
return nil
}
// AsText returns the Result as a string. Works for both JSON-string results
// (e.g. claude returned "hello") and structured JSON (returns the raw JSON
// representation as a string in that case).
func (r *RunResult) AsText() (string, error) {
if len(r.Result) == 0 {
return "", fmt.Errorf("clawdforge: empty result")
}
// First try to decode as a plain JSON string
var s string
if err := json.Unmarshal(r.Result, &s); err == nil {
return s, nil
}
// Otherwise return the raw JSON encoding
return string(r.Result), nil
}
// FileToken is the parsed response from POST /files.
type FileToken struct {
FileToken string `json:"file_token"`
TTLSecs int `json:"ttl_secs"`
Size int64 `json:"size"`
}
// AppToken is one entry from GET /admin/tokens, also returned (with the
// plaintext Token populated) by POST /admin/tokens.
type AppToken struct {
Name string `json:"name"`
Token string `json:"token,omitempty"` // only set on create
IPCidrs []string `json:"ip_cidrs,omitempty"`
CreatedAt int64 `json:"created_at,omitempty"`
}
// TokenList is the response from GET /admin/tokens.
type TokenList struct {
Tokens []AppToken `json:"tokens"`
}
// CreateTokenRequest is the body for POST /admin/tokens.
type CreateTokenRequest struct {
Name string `json:"name"`
IPCidrs []string `json:"ip_cidrs,omitempty"`
}