serve

package
v0.8.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 5, 2026 License: Apache-2.0 Imports: 44 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// AnthropicAPIURL is the default Anthropic models endpoint.
	AnthropicAPIURL = "https://api.anthropic.com/v1/models"
)
View Source
const DefaultPort = 8484

DefaultPort is the default listen port for devcell serve.

View Source
const WorkspaceFeedContentType = "application/x-msts-radc+xml; charset=utf-8"

Variables

This section is empty.

Functions

func AuthMiddleware

func AuthMiddleware(secret string, next http.Handler) http.Handler

AuthMiddleware returns a handler that checks the Authorization: Bearer <secret> header. If secret is empty, all requests are allowed (no auth).

func BuildRDPFile added in v0.8.0

func BuildRDPFile(host string, port int, username string, appProgram string) string

func DebugLoggingMiddleware added in v0.8.0

func DebugLoggingMiddleware(next http.Handler) http.Handler

DebugLoggingMiddleware logs full request headers and response bodies to stderr.

func DefaultCredentialsPath

func DefaultCredentialsPath() string

DefaultCredentialsPath returns the default path to Claude's credentials.

func EncodeFeed added in v0.8.0

func EncodeFeed(cells []Cell, baseHost string, now time.Time) ([]byte, error)

func GenerateAPIKey

func GenerateAPIKey() string

GenerateAPIKey creates a random API key for use when none is configured.

func GenerateIcon added in v0.8.0

func GenerateIcon(title string, size int) ([]byte, error)

func GeneratePreview added in v0.8.0

func GeneratePreview(title string, width, height int) ([]byte, error)

func GenerateSelfSignedCert added in v0.8.0

func GenerateSelfSignedCert(extraHosts ...string) (tls.Certificate, error)

func LoggingMiddleware added in v0.6.0

func LoggingMiddleware(next http.Handler) http.Handler

LoggingMiddleware logs every HTTP request with method, path, status, and duration.

func NewChatHandler

func NewChatHandler(exec Executor, logPrompts bool, systemPrompt string) http.Handler

NewChatHandler returns an http.Handler for POST /v1/chat/completions.

@Summary Send a chat completion request @Description Accepts an OpenAI-compatible chat completion request and routes it to the appropriate @Description LLM agent binary (Claude Code or OpenCode) running inside the DevCell container. @Description @Description The `model` field determines which agent handles the request: @Description - `"claude"` or `"anthropic"` — routes to Claude Code CLI @Description - `"opencode"` — routes to OpenCode CLI @Description - `"claude/claude-sonnet-4-5"` — routes to Claude Code with a specific sub-model @Description @Description Only the **last user message** in the `messages` array is sent as the prompt to the agent. @Description The response is a single-choice completion with finish_reason "stop" on success or "error" on failure. @Description @Description **Honored fields:** @Description - `reasoning_effort` (`low` / `medium` / `high`) → maps to the `claude --effort` CLI flag @Description to control thinking budget. Other values (including Claude's `xhigh`/`max`) are silently dropped. @Description @Description **Example request:** @Description ```json @Description {"model": "claude", "messages": [{"role": "user", "content": "explain this repo"}]} @Description ``` @Tags chat @Accept json @Produce json @Param request body ChatRequest true "Chat completion request" @Success 200 {object} ChatResponse "Successful completion" @Failure 400 {string} string "Invalid JSON, missing model, empty messages, or unknown model prefix" @Failure 401 {string} string "Missing or invalid Bearer token" @Failure 405 {string} string "Only POST is allowed" @Security BearerAuth @Router /v1/chat/completions [post]

func NewModelsHandler

func NewModelsHandler(lookPath LookPathFunc, ac AnthropicClient) http.Handler

NewModelsHandler returns an http.Handler for GET /v1/models.

@Summary List available models @Description Returns all models that can be used in chat completion requests. @Description @Description Models are discovered dynamically at request time: @Description 1. If the `claude` binary is found, the server tries the Anthropic API to list real model IDs @Description (e.g. `anthropic/claude-sonnet-4-5-20250514`). If the API is unreachable, it falls back to @Description aliases: `anthropic/opus`, `anthropic/sonnet`, `anthropic/haiku`. @Description 2. If the `opencode` binary is found, `opencode` is added as an available model. @Description @Description Use any returned `id` value as the `model` field in `/v1/chat/completions`. @Tags models @Produce json @Success 200 {object} ModelsResponse "List of available models" @Failure 401 {string} string "Missing or invalid Bearer token" @Failure 405 {string} string "Only GET is allowed" @Security BearerAuth @Router /v1/models [get]

func NewResponseCancelHandler added in v0.7.0

func NewResponseCancelHandler(store *JobStore) http.Handler

NewResponseCancelHandler returns an http.Handler for POST /v1/responses/{id}/cancel.

Cancels an in-progress background job. The job's context is cancelled and status flips to "cancelled" immediately; the underlying goroutine may continue running but its result is discarded (Complete is a no-op once status == "cancelled").

@Summary Cancel an in-progress background Response @Description Cancels a background `/v1/responses` job. Idempotent — calling @Description cancel on a job that has already completed or been cancelled @Description returns 200 with the current state. @Tags responses @Produce json @Param id path string true "Response id (resp_...)" @Success 200 {object} ResponsesObject "Job state after cancel" @Failure 404 {object} APIError "Response id not found" @Security BearerAuth @Router /v1/responses/{id}/cancel [post]

func NewResponseGetHandler added in v0.7.0

func NewResponseGetHandler(store *JobStore) http.Handler

NewResponseGetHandler returns an http.Handler for GET /v1/responses/{id}.

Returns the current state of a background job as a ResponsesObject. While the job is in-progress, Output / OutputText are empty. After the job terminates (completed / failed / cancelled), the response is fully populated and identical to what a synchronous POST would return.

@Summary Retrieve a Response by id @Description Polls the state of a background `/v1/responses` job. Submit a @Description response with `"background": true` to get back a 202 + `id`, then @Description GET `/v1/responses/{id}` until `status` is terminal @Description (`completed`, `failed`, `cancelled`). @Tags responses @Produce json @Param id path string true "Response id (resp_...)" @Success 200 {object} ResponsesObject "Current state of the response" @Failure 404 {object} APIError "Response id not found (never existed or evicted)" @Security BearerAuth @Router /v1/responses/{id} [get]

func NewResponsesHandler added in v0.6.0

func NewResponsesHandler(exec Executor, store *JobStore, logPrompts bool, systemPrompt string) http.Handler

NewResponsesHandler returns an http.Handler for POST /v1/responses.

@Summary Create a model response (Responses API) @Description OpenAI-compatible Responses API endpoint. Accepts a request shaped like @Description `client.responses.create` from the official SDKs and returns a Response object. @Description @Description The `model` field selects the agent (same routing as `/v1/chat/completions`): @Description - `"anthropic/sonnet"`, `"claude/<id>"` — routes to the Claude Code CLI @Description - `"opencode"` — routes to the OpenCode CLI @Description @Description The `input` field is either a string or an array of input items @Description (`{"role": "user|assistant|system", "content": "..."}` or with typed content parts). @Description The `instructions` field is prepended as a system message. @Description @Description **Statelessness:** devcell does not persist responses. `previous_response_id` @Description is accepted for compatibility but ignored — clients must send full conversation @Description history every request. @Description @Description **Streaming** is supported for the claude agent: set `"stream": true` and the @Description handler returns Server-Sent Events with token-level deltas. Opencode falls back to @Description the buffered path even when stream is set. @Description @Description **Background mode** is supported: set `"background": true` to receive `202 Accepted` @Description with a stub response containing only `id` and `status: "in_progress"`. Poll @Description `GET /v1/responses/{id}` until `status` is terminal (`completed`, `failed`, or @Description `cancelled`). Use `POST /v1/responses/{id}/cancel` to abort in-progress jobs. @Description Combining `stream: true` with `background: true` returns 400 — pick one mode. @Description @Description **Honored fields beyond core:** @Description - `reasoning.effort` (`low` / `medium` / `high`) → maps to the `claude --effort` CLI flag @Description to control thinking budget. Other values (including Claude's `xhigh`/`max`) are silently dropped. @Description @Description **Unsupported fields** (`tools`, `response_format`, `temperature`, `top_p`, @Description `max_output_tokens`, `metadata`, `store`, `service_tier`, etc.) are accepted to keep @Description SDK clients happy but have no effect — devcell shells out to a CLI agent and @Description cannot honor them. @Description @Description **Example request:** @Description ```json @Description {"model": "anthropic/sonnet", "input": "What is 2+2?"} @Description ``` @Description @Description **Reading the response:** most clients use the top-level `output_text` field. @Description SDK clients use the `output[].content[].text` structure. @Tags responses @Accept json @Produce json @Param request body ResponsesRequest true "Responses API request" @Success 200 {object} ResponsesObject "Successful response (synchronous)" @Success 202 {object} ResponsesObject "Background job accepted; poll GET /v1/responses/{id}" @Failure 400 {object} APIError "Invalid JSON, missing model/input, unknown model, or unsupported stream+background combination" @Failure 401 {string} string "Missing or invalid Bearer token" @Failure 405 {object} APIError "Only POST is allowed" @Security BearerAuth @Router /v1/responses [post]

func NewSOAPReconnectStub added in v0.8.0

func NewSOAPReconnectStub() http.Handler

func ReadClaudeCredentials

func ReadClaudeCredentials(path string) string

ReadClaudeCredentials reads the OAuth access token from Claude's credentials file.

func WorkspaceRoutes added in v0.8.0

func WorkspaceRoutes(enum CellEnumerator, publicHost string, opts ...WorkspaceOpt) *http.ServeMux

Types

type APIError added in v0.6.0

type APIError struct {
	Error APIErrorBody `json:"error"`
}

APIError is the OpenAI-shaped error envelope returned by /v1/responses for HTTP-level errors (4xx / 5xx).

type APIErrorBody added in v0.6.0

type APIErrorBody struct {
	Message string `json:"message" example:"streaming is not supported"`
	Type    string `json:"type" example:"invalid_request_error"`
	Code    string `json:"code,omitempty" example:"streaming_unsupported"`
}

APIErrorBody is the inner error object.

type AnthropicClient

type AnthropicClient interface {
	FetchModels() ([]ModelInfo, error)
}

AnthropicClient abstracts Anthropic API calls for testability.

type Cell added in v0.8.0

type Cell struct {
	ID         string
	Title      string
	Host       string
	Port       int
	Type       string // "Desktop" or "RemoteApp"
	AppProgram string // RemoteApp program alias (e.g. "||xterm")
}

func MockResources added in v0.8.0

func MockResources() []Cell

type CellEnumerator added in v0.8.0

type CellEnumerator interface {
	ListCells() []Cell
}

type ChatChoice

type ChatChoice struct {
	// Index of this choice (always 0 — single-choice responses).
	Index int `json:"index" example:"0"`
	// The assistant's response message.
	Message ChatMessage `json:"message"`
	// Finish reason: "stop" on success, "error" if the agent exited non-zero.
	FinishReason string `json:"finish_reason" example:"stop"`
}

ChatChoice is a single choice in the response.

type ChatMessage

type ChatMessage struct {
	// Role of the message author: "system", "user", or "assistant".
	Role string `json:"role" example:"user"`
	// The message content (prompt text for user, response text for assistant).
	Content string `json:"content" example:"Explain the main function in this repo"`
}

ChatMessage is an OpenAI-compatible message.

type ChatRequest

type ChatRequest struct {
	// Model selects the agent. Use "claude", "anthropic", or "opencode" as a prefix.
	// Append a sub-model with a slash: "claude/claude-sonnet-4-5".
	Model string `json:"model" example:"claude"`
	// Messages is the conversation history. The last user message is used as the prompt.
	Messages []ChatMessage `json:"messages"`
	// ReasoningEffort, when set, controls Claude's thinking budget for this request.
	// Valid values: "low", "medium", "high". Other values are silently dropped.
	// Maps to the `claude --effort` CLI flag. Has no effect on the opencode agent.
	ReasoningEffort string `json:"reasoning_effort,omitempty" example:"high"`
	// Stream, when true, emits Server-Sent Events with token-level deltas.
	// Supported only for the claude agent (opencode falls back to buffered).
	Stream bool `json:"stream,omitempty" example:"false"`
	// StreamOptions configures streaming behavior. Honored only when Stream is true.
	StreamOptions *ChatStreamOptions `json:"stream_options,omitempty"`
}

ChatRequest is the OpenAI-compatible chat completions request.

type ChatResponse

type ChatResponse struct {
	// Unique completion ID (format: chatcmpl-<hex>).
	ID string `json:"id" example:"chatcmpl-a1b2c3d4e5f6"`
	// Object type (always "chat.completion").
	Object string `json:"object" example:"chat.completion"`
	// Unix timestamp of when the response was created.
	Created int64 `json:"created" example:"1714000000"`
	// The model that was requested.
	Model string `json:"model" example:"claude"`
	// Response choices (always a single element).
	Choices []ChatChoice `json:"choices"`
	// Token usage (stubbed, reserved for future use).
	Usage ChatUsage `json:"usage"`
}

ChatResponse is the OpenAI-compatible chat completions response.

type ChatStreamChoice added in v0.6.0

type ChatStreamChoice struct {
	Index        int             `json:"index"`
	Delta        ChatStreamDelta `json:"delta"`
	FinishReason *string         `json:"finish_reason"`
}

ChatStreamChoice is one element of a chat.completion.chunk's choices array. The `delta` carries the incremental content; `finish_reason` is null on every chunk except the last.

type ChatStreamChunk added in v0.6.0

type ChatStreamChunk struct {
	ID      string             `json:"id"`
	Object  string             `json:"object"` // always "chat.completion.chunk"
	Created int64              `json:"created"`
	Model   string             `json:"model"`
	Choices []ChatStreamChoice `json:"choices"`
	Usage   *ChatUsage         `json:"usage,omitempty"`
}

ChatStreamChunk is the JSON payload of one SSE `data:` frame.

type ChatStreamDelta added in v0.6.0

type ChatStreamDelta struct {
	Role    string `json:"role,omitempty"`
	Content string `json:"content,omitempty"`
}

ChatStreamDelta is the OpenAI per-chunk delta. Role is sent on the first chunk; content on each text-delta chunk; both empty on the terminal chunk.

type ChatStreamOptions added in v0.6.0

type ChatStreamOptions struct {
	// IncludeUsage, when true, embeds the token-usage object in the
	// final SSE chunk. Off by default to match OpenAI's contract.
	IncludeUsage bool `json:"include_usage,omitempty" example:"true"`
}

ChatStreamOptions mirrors OpenAI's stream_options object.

type ChatUsage

type ChatUsage struct {
	PromptTokens     int `json:"prompt_tokens" example:"42"`
	CompletionTokens int `json:"completion_tokens" example:"7"`
	TotalTokens      int `json:"total_tokens" example:"49"`
}

ChatUsage tracks token usage. Populated from claude --output-format=json (input + cache_creation + cache_read merged into prompt_tokens to match OpenAI semantics). Zero-valued for opencode and for claude paths where JSON parsing fell back to raw stdout.

type CompositeEnumerator added in v0.8.0

type CompositeEnumerator struct {
	// contains filtered or unexported fields
}

func NewCompositeEnumerator added in v0.8.0

func NewCompositeEnumerator(sources ...CellEnumerator) *CompositeEnumerator

func (*CompositeEnumerator) ListCells added in v0.8.0

func (c *CompositeEnumerator) ListCells() []Cell

type DockerAPIClient added in v0.8.0

type DockerAPIClient interface {
	ContainerList(ctx context.Context, options container.ListOptions) ([]container.Summary, error)
}

type DockerEnumerator added in v0.8.0

type DockerEnumerator struct {
	// contains filtered or unexported fields
}

func NewDockerEnumerator added in v0.8.0

func NewDockerEnumerator() (*DockerEnumerator, error)

func (*DockerEnumerator) ListCells added in v0.8.0

func (d *DockerEnumerator) ListCells() []Cell

type ExecOpts added in v0.6.0

type ExecOpts struct {
	// Agent is the binary name ("claude" or "opencode").
	Agent string
	// Prompt is the assembled prompt string passed via -p / positional arg.
	Prompt string
	// Model is the optional sub-model (e.g. "sonnet" or "claude-sonnet-4-5"). Empty = agent default.
	Model string
	// Effort, when set, is passed as --effort to the claude CLI.
	// Valid values: "low", "medium", "high". Empty = CLI default.
	Effort string
	// SystemPrompt, when set, is passed as --append-system-prompt to claude.
	// Operator-level baseline (set on `cell serve` startup), composes with
	// any per-request `instructions` / `system` role from the OpenAI body —
	// it does NOT override them. Ignored for opencode (no equivalent flag).
	SystemPrompt string
}

ExecOpts is the bundle of arguments passed to Executor.Run.

Adding a new CLI-flag mapping (e.g. --max-budget-usd) means adding a field here rather than widening Run's signature.

type ExecResult

type ExecResult struct {
	Stdout   string
	Stderr   string
	ExitCode int
	// Usage carries token-and-cost telemetry parsed from the agent's
	// machine-readable output (claude --output-format=json today).
	// Zero-valued when the agent doesn't emit usage data (opencode) or
	// when JSON parsing falls back to raw stdout.
	Usage Usage
}

ExecResult holds the output of an agent execution.

type Executor

type Executor interface {
	Run(opts ExecOpts) ExecResult
}

Executor runs an agent command and returns the result.

type Folder added in v0.8.0

type Folder struct {
	Name string `xml:"Name,attr"`
}

type Folders added in v0.8.0

type Folders struct {
	Folder []Folder `xml:"Folder"`
}

type HealthResponse added in v0.6.0

type HealthResponse struct {
	// Server status — "ok" when the server is running and ready to accept requests.
	Status string `json:"status" example:"ok"`
	// Composite version string matching `cell --version` output.
	// Format: `<version>-<build_date>-<commit>`.
	Version string `json:"version" example:"v0.1.0-2026-04-26-abc1234"`
	// Semantic version tag from the build (e.g. "v0.1.0").
	VersionTag string `json:"version_tag" example:"v0.1.0"`
	// Git commit hash this binary was built from.
	Commit string `json:"commit" example:"abc1234"`
	// Build date (UTC).
	BuildDate string `json:"build_date" example:"2026-04-26"`
}

HealthResponse is the health check response body.

Version fields are injected at build time via -ldflags by the `task cell:build` / `task swag:generate` flow. An unbuilt-via-task binary will report defaults: version=v0.0.0, commit=none, build_date=unknown.

type HostingTerminalServer added in v0.8.0

type HostingTerminalServer struct {
	ResourceFile      ResourceFile      `xml:"ResourceFile"`
	TerminalServerRef TerminalServerRef `xml:"TerminalServerRef"`
}

type HostingTerminalServers added in v0.8.0

type HostingTerminalServers struct {
	HTS []HostingTerminalServer `xml:"HostingTerminalServer"`
}

type IconElement added in v0.8.0

type IconElement struct {
	Dimensions string `xml:"Dimensions,attr,omitempty"`
	FileType   string `xml:"FileType,attr"`
	FileURL    string `xml:"FileURL,attr"`
}

type Icons added in v0.8.0

type Icons struct {
	IconRaw *IconElement `xml:"IconRaw,omitempty"`
	Icon32  *IconElement `xml:"Icon32,omitempty"`
	Icon256 *IconElement `xml:"Icon256,omitempty"`
}

type Job added in v0.7.0

type Job struct {
	ID         string
	Status     string // "in_progress" | "completed" | "failed" | "cancelled"
	Result     *ResponsesObject
	Cancel     context.CancelFunc
	CreatedAt  time.Time
	FinishedAt time.Time
}

Job tracks the state of a single background /v1/responses request.

Jobs are created with status "in_progress" when a client submits a request with "background": true. A goroutine runs the agent, populates Result, and transitions Status to "completed" / "failed". Clients poll GET /v1/responses/{id} to retrieve the final ResponsesObject.

type JobStore added in v0.7.0

type JobStore struct {
	// contains filtered or unexported fields
}

JobStore is the in-memory job registry for background responses.

Single-process cell serve, so a mutex-protected map is sufficient. Jobs survive only while the server runs — clients should not assume cross-restart durability.

func NewJobStore added in v0.7.0

func NewJobStore() *JobStore

NewJobStore returns an empty JobStore.

func (*JobStore) Cancel added in v0.7.0

func (s *JobStore) Cancel(id string) (Job, bool)

Cancel marks the job as cancelled and triggers its context cancel func. Returns (snapshot, true) if the job was found, (Job{}, false) otherwise. Idempotent: cancelling an already-cancelled job is a no-op success.

func (*JobStore) Complete added in v0.7.0

func (s *JobStore) Complete(id string, status string, result *ResponsesObject)

Complete transitions the job to a terminal state with the given result. status must be "completed" or "failed".

func (*JobStore) Create added in v0.7.0

func (s *JobStore) Create(id string, cancel context.CancelFunc) *Job

Create inserts a new job in "in_progress" state and returns it. cancel is the context.CancelFunc bound to the goroutine running the agent.

func (*JobStore) Get added in v0.7.0

func (s *JobStore) Get(id string) (Job, bool)

Get returns a snapshot of the job by id, or (Job{}, false) if not found. The snapshot is a value copy taken under the read lock; callers can read its fields without further synchronization. Mutations to the snapshot do not affect the stored job.

func (*JobStore) Sweep added in v0.7.0

func (s *JobStore) Sweep(now time.Time, ttl time.Duration) int

Sweep evicts terminal jobs whose FinishedAt is older than now-ttl. In-progress jobs are never evicted regardless of age. Returns the count of evicted entries.

Intended for periodic invocation from a goroutine bound to the server lifetime; the caller chooses cadence and ttl.

type LookPathFunc

type LookPathFunc func(name string) (string, error)

LookPathFunc matches exec.LookPath signature.

type MockEnumerator added in v0.8.0

type MockEnumerator struct{}

func NewMockEnumerator added in v0.8.0

func NewMockEnumerator() *MockEnumerator

func (*MockEnumerator) ListCells added in v0.8.0

func (m *MockEnumerator) ListCells() []Cell

type ModelInfo

type ModelInfo struct {
	// Model identifier — use this value in the chat completions "model" field.
	ID string `json:"id" example:"anthropic/claude-sonnet-4-5-20250514"`
	// Object type (always "model").
	Object string `json:"object" example:"model"`
	// Unix timestamp when the model was discovered.
	Created int64 `json:"created" example:"1714000000"`
	// Owner of the model: "anthropic" for API-discovered models, "devcell" for local agents.
	OwnedBy string `json:"owned_by" example:"anthropic"`
}

ModelInfo represents a single model in the OpenAI /v1/models response.

func DiscoverModels

func DiscoverModels(lookPath LookPathFunc, ac AnthropicClient) []ModelInfo

DiscoverModels probes for installed agent binaries and returns available models. When claude is found, tries the Anthropic API first (via credentials), falls back to hardcoded aliases.

func FetchAnthropicModels

func FetchAnthropicModels(baseURL, token string) ([]ModelInfo, error)

FetchAnthropicModels hits the Anthropic API to get available models. Returns nil, nil if token is empty (no-op).

type ModelsResponse

type ModelsResponse struct {
	// Object type (always "list").
	Object string `json:"object" example:"list"`
	// Available models discovered from installed agents and the Anthropic API.
	Data []ModelInfo `json:"data"`
}

ModelsResponse is the OpenAI-compatible /v1/models response.

type Publisher added in v0.8.0

type Publisher struct {
	LastUpdated     string          `xml:"LastUpdated,attr"`
	Name            string          `xml:"Name,attr"`
	ID              string          `xml:"ID,attr"`
	Description     string          `xml:"Description,attr"`
	Resources       Resources       `xml:"Resources"`
	TerminalServers TerminalServers `xml:"TerminalServers"`
}

type RealAnthropicClient

type RealAnthropicClient struct {
	CredentialsPath string // path to .credentials.json
	APIURL          string // override for testing; defaults to AnthropicAPIURL
}

RealAnthropicClient reads credentials and hits the Anthropic API.

func (*RealAnthropicClient) FetchModels

func (c *RealAnthropicClient) FetchModels() ([]ModelInfo, error)

FetchModels reads the Claude OAuth token and fetches models from the Anthropic API.

type Resource added in v0.8.0

type Resource struct {
	ID                     string                 `xml:"ID,attr"`
	Alias                  string                 `xml:"Alias,attr"`
	Title                  string                 `xml:"Title,attr"`
	LastUpdated            string                 `xml:"LastUpdated,attr"`
	Type                   string                 `xml:"Type,attr"`
	ShowByDefault          string                 `xml:"ShowByDefault,attr"`
	Icons                  Icons                  `xml:"Icons"`
	FileExtensions         string                 `xml:"FileExtensions"`
	Folders                Folders                `xml:"Folders"`
	HostingTerminalServers HostingTerminalServers `xml:"HostingTerminalServers"`
}

type ResourceCollection added in v0.8.0

type ResourceCollection struct {
	XMLName           xml.Name  `xml:"http://schemas.microsoft.com/ts/2007/05/tswf ResourceCollection"`
	PubDate           string    `xml:"PubDate,attr"`
	SchemaVersion     string    `xml:"SchemaVersion,attr"`
	SupportsReconnect string    `xml:"SupportsReconnect,attr,omitempty"`
	Publisher         Publisher `xml:"Publisher"`
}

type ResourceFile added in v0.8.0

type ResourceFile struct {
	FileExtension string `xml:"FileExtension,attr"`
	URL           string `xml:"URL,attr"`
}

type Resources added in v0.8.0

type Resources struct {
	Resource []Resource `xml:"Resource"`
}

type ResponsesError added in v0.6.0

type ResponsesError struct {
	// Short error code, e.g. "server_error".
	Code string `json:"code,omitempty" example:"server_error"`
	// Human-readable message — typically the agent's stderr.
	Message string `json:"message" example:"agent failed"`
}

ResponsesError describes a model-side failure (exit != 0 from the agent CLI).

Note: HTTP-level errors (400, 401, 405) use a different envelope at the top of the response — see APIError.

type ResponsesIncompleteDetails added in v0.6.0

type ResponsesIncompleteDetails struct {
	Reason string `json:"reason,omitempty"`
}

ResponsesIncompleteDetails is reserved — always null in devcell.

type ResponsesInputTokensDetails added in v0.6.0

type ResponsesInputTokensDetails struct {
	CachedTokens int `json:"cached_tokens" example:"0"`
}

ResponsesInputTokensDetails carries the cached-input breakdown.

type ResponsesObject added in v0.6.0

type ResponsesObject struct {
	// Unique response ID (format: resp_<hex>).
	ID string `json:"id" example:"resp_a1b2c3d4e5f6"`
	// Object type — always "response".
	Object string `json:"object" example:"response"`
	// Unix timestamp (seconds) when the response was created.
	CreatedAt int64 `json:"created_at" example:"1714000000"`
	// Status: "completed" on success, "failed" if the agent exited non-zero.
	Status string `json:"status" example:"completed"`
	// Model echo of the requested model string.
	Model string `json:"model" example:"anthropic/sonnet"`
	// Output items generated by the model.
	Output []ResponsesOutputItem `json:"output"`
	// Convenience field: concatenation of all output_text parts in Output.
	// Most clients (n8n, simple scripts) read this directly.
	OutputText string `json:"output_text" example:"Hello, world!"`
	// Token usage (stubbed at zero — reserved for future use).
	Usage ResponsesUsage `json:"usage"`
	// Error populated when Status == "failed", null otherwise.
	Error *ResponsesError `json:"error"`
	// IncompleteDetails is reserved — always null.
	IncompleteDetails *ResponsesIncompleteDetails `json:"incomplete_details"`
	// Echo of the input instructions, or null if not set.
	Instructions *string `json:"instructions"`
	// Echo of input metadata, or null.
	Metadata map[string]string `json:"metadata"`
	// ParallelToolCalls — echo of input or default true.
	ParallelToolCalls bool `json:"parallel_tool_calls" example:"true"`
	// PreviousResponseID — always null (stateless).
	PreviousResponseID *string `json:"previous_response_id"`
	// Reasoning config — echoes the input reasoning object (with normalized
	// effort if it was applied), or null if no reasoning was sent.
	Reasoning *ResponsesReasoningConfig `json:"reasoning"`
	// Store flag — echo of input or default true.
	Store bool `json:"store" example:"true"`
	// Sampling temperature — echo of input or default 1.0 (devcell ignores it).
	Temperature float64 `json:"temperature" example:"1.0"`
	// ToolChoice — always "auto".
	ToolChoice string `json:"tool_choice" example:"auto"`
	// Tools — always empty array (no tools bridged).
	Tools []any `json:"tools"`
	// Top-p — echo of input or default 1.0 (devcell ignores it).
	TopP float64 `json:"top_p" example:"1.0"`
	// Truncation — "disabled" by default.
	Truncation string `json:"truncation" example:"disabled"`
	// User — echo of input or empty.
	User string `json:"user,omitempty"`
}

ResponsesObject is the OpenAI Responses-API response body.

type ResponsesOutputContentPart added in v0.6.0

type ResponsesOutputContentPart struct {
	// Always "output_text" for text responses.
	Type string `json:"type" example:"output_text"`
	// The actual text.
	Text string `json:"text" example:"Hello, world!"`
	// Annotations on the text (always empty — reserved for future use).
	Annotations []any `json:"annotations"`
}

ResponsesOutputContentPart is one part of an output message's content array.

type ResponsesOutputItem added in v0.6.0

type ResponsesOutputItem struct {
	// Item type — always "message".
	Type string `json:"type" example:"message"`
	// Unique item ID (format: msg_<hex>).
	ID string `json:"id" example:"msg_a1b2c3d4e5f6"`
	// Status of this item — "completed" on success.
	Status string `json:"status" example:"completed"`
	// Role of the message author — always "assistant".
	Role string `json:"role" example:"assistant"`
	// Content parts of the message.
	Content []ResponsesOutputContentPart `json:"content"`
}

ResponsesOutputItem is a single item in the output array.

devcell only emits "message" items (assistant messages). Reasoning, tool_call, and other variants are not produced.

type ResponsesOutputTokensDetails added in v0.6.0

type ResponsesOutputTokensDetails struct {
	ReasoningTokens int `json:"reasoning_tokens" example:"0"`
}

ResponsesOutputTokensDetails is reserved for reasoning-model splits (reasoning_tokens vs visible output). Always zero today; included for schema completeness.

type ResponsesReasoningConfig added in v0.6.0

type ResponsesReasoningConfig struct {
	// Effort: "low", "medium", or "high". Other values are silently dropped.
	Effort string `json:"effort,omitempty" example:"high"`
	// Summary controls reasoning summary verbosity. Accepted, ignored.
	Summary string `json:"summary,omitempty"`
	// GenerateSummary is a deprecated alias of Summary. Accepted, ignored.
	GenerateSummary string `json:"generate_summary,omitempty"`
}

ResponsesReasoningConfig is the OpenAI-spec reasoning object.

The Responses API allows clients to control thinking budget on reasoning models. devcell honors `effort` (mapping it to `claude --effort`) and ignores other fields.

type ResponsesRequest added in v0.6.0

type ResponsesRequest struct {
	// Model selects the agent. Use "claude", "anthropic", or "opencode" as a prefix.
	// Append a sub-model with a slash: "anthropic/sonnet" or "anthropic/claude-sonnet-4-5".
	Model string `json:"model" example:"anthropic/sonnet"`

	// Input is either a string OR an array of input items.
	// String form: a single user message.
	// Array form: a multi-turn conversation; each item has role + content.
	Input json.RawMessage `json:"input" swaggertype:"string" example:"hello"`

	// Instructions is an optional system prompt prepended to the conversation.
	Instructions string `json:"instructions,omitempty" example:"be brief"`

	// Stream, if true, returns 400 — streaming is not supported.
	Stream bool `json:"stream,omitempty"`

	// Reasoning carries reasoning-model controls. Only `reasoning.effort`
	// (low|medium|high) is honored — it maps to `claude --effort`. Other
	// fields (summary, generate_summary) are accepted and ignored.
	Reasoning *ResponsesReasoningConfig `json:"reasoning,omitempty"`

	// Accepted, ignored — kept for client compatibility.
	PreviousResponseID string          `json:"previous_response_id,omitempty"`
	Tools              json.RawMessage `json:"tools,omitempty" swaggerignore:"true"`
	ToolChoice         json.RawMessage `json:"tool_choice,omitempty" swaggerignore:"true"`
	ResponseFormat     json.RawMessage `json:"response_format,omitempty" swaggerignore:"true"`
	Text               json.RawMessage `json:"text,omitempty" swaggerignore:"true"`
	Temperature        *float64        `json:"temperature,omitempty"`
	TopP               *float64        `json:"top_p,omitempty"`
	MaxOutputTokens    *int            `json:"max_output_tokens,omitempty"`
	Metadata           json.RawMessage `json:"metadata,omitempty" swaggerignore:"true"`
	Store              *bool           `json:"store,omitempty"`
	ParallelToolCalls  *bool           `json:"parallel_tool_calls,omitempty"`
	Truncation         string          `json:"truncation,omitempty"`
	User               string          `json:"user,omitempty"`
	Background         *bool           `json:"background,omitempty"`
	Include            []string        `json:"include,omitempty"`
}

ResponsesRequest is the OpenAI Responses-API request body.

All fields are accepted, but only Model, Input, Instructions, and Stream affect behavior. Tools, ResponseFormat, Reasoning, sampling params and other fields are decoded for compatibility and silently ignored — devcell shells out to a CLI agent and cannot honor them.

type ResponsesUsage added in v0.6.0

type ResponsesUsage struct {
	InputTokens         int                           `json:"input_tokens" example:"42"`
	InputTokensDetails  *ResponsesInputTokensDetails  `json:"input_tokens_details,omitempty"`
	OutputTokens        int                           `json:"output_tokens" example:"7"`
	OutputTokensDetails *ResponsesOutputTokensDetails `json:"output_tokens_details,omitempty"`
	TotalTokens         int                           `json:"total_tokens" example:"49"`
}

ResponsesUsage tracks token usage. Populated from claude --output-format=json. The Responses API has its own naming (input_tokens / output_tokens) and exposes cached-token detail under input_tokens_details.cached_tokens — that's claude's cache_read_input_tokens and is real money saved, so we surface it.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server is the devcell HTTP API server.

func NewServer

func NewServer(exec Executor, port int) *Server

NewServer creates a Server. Use port=0 to let the OS pick a free port. Uses exec.LookPath for model discovery and RealAnthropicClient by default.

func (*Server) APIKey

func (s *Server) APIKey() string

APIKey returns the configured API key.

func (*Server) HTTPAddr added in v0.8.0

func (s *Server) HTTPAddr() string

func (*Server) SetAPIKey

func (s *Server) SetAPIKey(key string)

SetAPIKey sets the API key for bearer auth. Empty disables auth.

func (*Server) SetAnthropicClient

func (s *Server) SetAnthropicClient(ac AnthropicClient)

SetAnthropicClient overrides the Anthropic API client (for testing).

func (*Server) SetDebug added in v0.8.0

func (s *Server) SetDebug(v bool)

func (*Server) SetLogPrompts added in v0.6.0

func (s *Server) SetLogPrompts(v bool)

SetLogPrompts enables or disables full prompt + response body logging.

When true, /v1/chat/completions and /v1/responses handlers log the assembled prompt and the model's reply at INFO level. Off by default — prompts often contain secrets, PII, or large pasted content from upstream tools (n8n flows, agents, etc.), so this is opt-in.

func (*Server) SetLookPath

func (s *Server) SetLookPath(fn LookPathFunc)

SetLookPath overrides the binary discovery function (for testing).

func (*Server) SetSystemPrompt added in v0.6.0

func (s *Server) SetSystemPrompt(p string)

SetSystemPrompt sets the operator-level system prompt passed to claude as --append-system-prompt on every /v1/chat/completions and /v1/responses request. Empty disables the flag (default). Composes with — does not override — any per-request `instructions` / `system` role from the body.

func (*Server) SetTLS added in v0.8.0

func (s *Server) SetTLS(v bool)

func (*Server) SetWorkspace added in v0.8.0

func (s *Server) SetWorkspace(enabled, mock bool, host string)

func (*Server) Start

func (s *Server) Start(ctx context.Context) (addr string, errCh chan error)

Start begins listening and returns the address and an error channel. The server shuts down when ctx is cancelled.

type ShellExecutor

type ShellExecutor struct{}

ShellExecutor runs agent binaries as subprocesses.

func (*ShellExecutor) Run

func (e *ShellExecutor) Run(opts ExecOpts) ExecResult

Run executes the agent binary with the given options.

type StreamEvent added in v0.6.0

type StreamEvent struct {
	Kind StreamEventKind
	// MessageID populated on MessageStart (the upstream message id).
	MessageID string
	// Model populated on MessageStart.
	Model string
	// Delta populated on TextDelta — incremental text since the previous
	// delta, not cumulative.
	Delta string
	// StopReason populated on MessageStop — "end_turn" / "max_tokens" / …
	StopReason string
	// Final populated on Result — reuses claude_json.go's typed envelope
	// for the terminal usage + cost payload.
	Final *claudeJSONResult
	// Err populated on Error — terminal; the caller should stop reading.
	Err error
}

StreamEvent is the canonical, OpenAI-agnostic event the SSE formatters consume. One source (claude scanner), two sinks (Chat Completions and Responses).

type StreamEventKind added in v0.6.0

type StreamEventKind int

StreamEventKind discriminates the canonical event sent on the channel.

const (
	StreamEventMessageStart StreamEventKind
	StreamEventTextDelta
	StreamEventMessageStop
	StreamEventResult
	StreamEventError
)

type TerminalServer added in v0.8.0

type TerminalServer struct {
	ID          string `xml:"ID,attr"`
	Name        string `xml:"Name,attr"`
	LastUpdated string `xml:"LastUpdated,attr"`
}

type TerminalServerRef added in v0.8.0

type TerminalServerRef struct {
	Ref string `xml:"Ref,attr"`
}

type TerminalServers added in v0.8.0

type TerminalServers struct {
	TS []TerminalServer `xml:"TerminalServer"`
}

type Usage added in v0.6.0

type Usage struct {
	InputTokens              int
	OutputTokens             int
	CacheCreationInputTokens int
	CacheReadInputTokens     int
	// TotalCostUSD is what claude reports; opencode doesn't surface cost.
	TotalCostUSD float64
}

Usage is the agent-side token/cost view for one request. Mapped into OpenAI's per-API shape (ChatUsage / ResponsesUsage) at handler time.

Field naming follows Anthropic's wire format so the parser is a 1:1 JSON decode of claude's `usage` object — the OpenAI mapping (prompt_tokens = input + cache_creation + cache_read; completion = output) is done in the handlers, not here, so other agents can plug in with their own native shape.

type WorkspaceOpt added in v0.8.0

type WorkspaceOpt func(*workspaceCfg)

func WithCertDER added in v0.8.0

func WithCertDER(der []byte) WorkspaceOpt

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL