Documentation
¶
Overview ¶
Package omnigent is a Go client library for the omnigent server.
Scope ¶
This release carries Client and its options, the redirect and credential policy, the error surface, the 52-variant Event union, and Client.Stream for reading one session's server-sent events.
It also carries the two namespaces. Client.Sessions covers a session's lifecycle, the events posted to it, and its listings; Client.Files covers the session-scoped file routes. Every listing is an iter.Seq2 that follows the server's cursor, and Sessions.ChildrenTree walks a subtree under bounds the caller sets.
It also carries the turn loop. Client.Chat binds a chat to one session, and Chat.Send posts a prompt and reads until the turn ends, running the tools in a ToolRegistry and answering the approvals the server raises. BlockStream folds those events into the Block set a renderer switches over, and the transforms in transform.go drop or merge parts of it.
A caller who wants the answer rather than the sequence uses Chat.Query, which performs that fold and returns the text and the files, or Chat.QueryStream to read the text as it arrives.
Where a turn ends is a caller's choice, because two harness families put it in different places; see TurnEnd. The stricter rule is the default.
Building against this package needs Go 1.25 or newer. go.mod declares that floor and CI builds it. The floor tracks the consumer rather than the language features used here, which reach back to 1.23 for iter.Seq2.
client, err := omnigent.New("http://127.0.0.1:6767", omnigent.WithBearerToken(token))
if err != nil {
return err
}
defer client.Close()
for event, err := range client.Stream(ctx, sessionID, omnigent.StreamOptions{}) {
if err != nil {
return err
}
if delta, ok := event.(omnigent.OutputTextDeltaEvent); ok {
fmt.Print(delta.Delta)
}
}
Optional fields ¶
On a type this package decodes, every optional field is a pointer, so a caller can tell "the server sent zero" from "the server sent nothing". Slices and maps are the exception: nil already carries that distinction, and a pointer to a slice reads badly at a call site. Ptr is how a caller sets one.
On a request type the caller fills — SessionCreateRequest and SessionEventInput — an optional field is a plain value with omitempty, and leaving it zero is how a caller declines to send it. Where the server needs an explicit value rather than an absence, a named method carries it rather than a magic empty string: Sessions.ClearModelOverride, not ModelOverride = "".
The conformance tests enforce the first rule for every mirrored type. A hand-authored type is outside that gate, so it holds the rule by review.
Enumerated values ¶
enums.go names every value the description declares for an enumerated field. The fields themselves stay plain strings, so a value this build has never seen still decodes rather than failing. A switch over those constants therefore needs a default arm.
Generated types ¶
The wire types come from spec/openapi.json, a pinned snapshot of the server's OpenAPI document and this package's contract of record. bin/generate.sh runs spec/preprocess.py over it and then oapi-codegen, into internal/api. Every type in this package's own files is a one-line declaration over that package, so the field documentation lives in internal/api/api.gen.go and in the document itself, not here. See docs/adr/0001-generate-wire-types-behind-a-facade.md.
Tests hold the types to the description: the mapping is complete in both directions, every exported field names a property the description declares, its Go type and optionality match, a container's declared value or element type matches, every declared enum value has a constant, and the decoder's variant set equals the discriminator mapping.
What they do not check is presence: a property the description declares and this package omits passes. That is deliberate, because reaching every route is not a goal — the surface is meant to be smaller than the document, not equal to it. So this package cannot contradict the server about a field it declares, and can be silent about one it does not.
The consequence to keep in mind: a route or field the document does not carry is a hand-written contract nothing checks, in either direction. Four are reached today, and each is named where it is used.
The events route is registered include_in_schema=false, so neither its body nor its responses appear in the document. SessionEventInput and EventAccepted are this package's own statement of that shape, and Sessions.Interrupt and Sessions.Compact ride it.
The create route takes a raw body and dispatches on Content-Type, so the document carries no request schema and SessionCreateRequest is hand-written.
The session file routes publish an empty response schema, so SessionFile is hand-written and the file surface sits wholly outside the gate. It keeps the decoded body on SessionFile.Raw for that reason.
The agent and item listings type their payload as heterogeneous, so Sessions.ListAgents and Sessions.ListItems narrow it by hand.
Turns ¶
A turn is one prompt and the events it produces. Chat.Send drives one: it subscribes, posts the prompt, and reads until the turn ends.
The prompt is posted from StreamOptions.OnSubscribed, so the subscription always exists first and the turn cannot be answered with nobody listening. Two consequences a caller can rely on: a stream that fails to open posts nothing, and a Turn nobody reads posts nothing. A Turn is single-use for the same reason a second post is not free — it would be a second turn the caller did not ask for, and the server would answer both.
Two things run inside the loop, before the turn's end is read, because the server parks a turn on each of them and the terminal event only follows once they are answered: a client tool call, and an approval request. Both are answered even when they fail — an unregistered tool posts an output naming the mismatch rather than leaving the turn parked, and an approval with no decision is declined.
Declining is this package's own behaviour and not a policy. It cannot know what a caller would approve, and accepting authorises the pending tool to run with the session owner's execution identity — not the approver's. A caller with a policy supplies StreamHooks.OnElicitation; a hook that panics also declines.
One obligation is the caller's. A session that may have a response still running needs TurnOptions.PriorResponseIDs, built from Sessions.Get — its [SessionResponse.ActiveResponseID] names the response in flight. Without them a response that predates this turn can end this read, and the caller gets another turn's ending. Give Chat.Send a context with a deadline too: TurnEndsOnIdleStatus waits for an edge that a mismatched harness never sends, and the stream's heartbeat means no timeout of this package's fires.
What upstream has and this package does not ¶
The Python client's public surface is the reference for this one, and two of its symbols are deliberately absent rather than pending. This section states what will not be built.
LocalServer starts a server process and waits for it to listen. Managing a server's lifecycle is not a client's job in Go, where a caller already has os/exec and a health check, and a helper that owned a subprocess would own its signals and its logs too.
tool is a decorator the server's runtime consumes to load tools inside an agent image. A Go caller registers the client half with ToolRegistry, which is the part that runs in this process.
Timeouts ¶
Go's http.Client.Timeout is a deadline on the whole exchange including reading the response body, so any non-zero value severs a healthy long-lived stream. This package therefore keeps three clients over one transport. Unary calls carry a whole-exchange timeout. The streaming client's is zero, and so is the transfer client's: a file's duration is its size over the network's rate, so the bound that stops a wedged call is the bound that makes a large upload impossible, and one number cannot be both.
The unary bound defaults to 90 seconds, and WithUnaryTimeout moves it. It is that long because the slowest routes wait on a runner before they answer and send no byte early, so a shorter deadline aborts calls the server was still going to answer — and abandoning a session create leaks the session the server goes on to make. The arithmetic behind the number sits beside the constant in client.go. A deadline for one call still belongs on that call's context: this bound is the backstop against a wedged connection, not a latency policy.
A transfer — SessionFiles.Upload or SessionFiles.Download — is bounded by its context instead, so a large file is limited by what the caller allows rather than by a number sized for an RPC. WithTransferTimeout sets one ceiling for every transfer when a caller wants one. Header latency stays bounded either way by the transport, so a server that accepts a connection and then says nothing still fails fast.
Liveness on the stream is enforced instead by an idle monitor — the server emits a heartbeat frame every 15 seconds of queue silence, so a read that blocks for longer than StreamOptions.IdleTimeout means the transport is gone. The monitor measures time blocked on a read, not wall-clock time: it is suspended while the caller's own loop body runs, so a slow event handler cannot be mistaken for a dead server. Response-header latency stays bounded for both clients by the one transport's ResponseHeaderTimeout, which is the unary bound or a 30-second floor, whichever is larger — the floor because opening a stream waits on the same relay subscription that a post does.
Cancellation ¶
Every call takes a context.Context, and cancelling it is the only way to stop a stream. Note what that does and does not do: dropping the stream ends the *subscription*, not the agent's turn. The turn runs on the server side independently of any subscriber. To actually stop work, post an interrupt with Sessions.Interrupt.
Errors ¶
A server response and every rejected argument are matchable with errors.Is against one of the sentinels in errors.go. A transport or codec failure is returned wrapped and matches none of them, so a caller that switches on the sentinels needs a default arm. A server response also unwraps to APIError with errors.As for the status, the server's error code, and the X-Request-Id to quote when reporting it. ErrInvalidArgument is the odd one out: it means this package rejected the call before sending anything.
Security ¶
A client that holds a credential has to be careful about four things, and this package's answer to each is fail-closed rather than best-effort.
Transport. A credential must not cross a network in clear. New therefore refuses a plain-http base URL once an auth option is supplied, unless the host is loopback — which is why DefaultBaseURL works as it is: nothing leaves the machine. A deployment where the plaintext hop is genuinely not a network (a sidecar, a port-forward, a mesh that terminates TLS ahead of you) opts in with WithInsecureCredentialTransport. There is no warn-and-continue option: a library has no logger to warn into, and silence is how a token ends up on a shared segment.
Redirects. Neither of this package's http.Clients follows a redirect off the base URL's host, down from https to http, or across a method rewrite. Go's own rule strips only Authorization, Cookie, Www-Authenticate and Cookie2 on a cross-host hop, which leaves a custom identity header — WithAuthHeader's, the trusted-proxy one — travelling to whatever host the response named; it compares hostnames and not schemes, so an https-to-http hop keeps the credential and loses the encryption; a cross-host 307 or 308 replays the request body, which here is the caller's prompt; and a 302 on a POST becomes a GET, so a dropped write would return 200. All four are ErrUnsafeRedirect. A caller supplying an http.Client with its own CheckRedirect keeps it, and owns that.
Base URLs. A base URL carrying userinfo is rejected rather than quietly becoming Basic auth on every request, and no error from New echoes the base URL's password — the parse-failure path reports the reason without the value, because url.Parse's own error quotes back what it was handed.
Error values. APIError.Error renders the status, the server's error code and message, and the request id, and never the response body: a non-2xx body may come from a proxy rather than this API, and an error string is the one thing that reliably reaches a log aggregator. APIError.Header has the headers that carry a credential by specification removed. See APIError for the detail.
What this package does not do: it never touches TLS configuration. There is no option to skip verification, pin a root, or reach a tls.Config, because a client that can be talked into trusting anything is not a security boundary. A deployment with a private CA configures it where such things belong — the system trust store, or a transport the caller builds and passes to WithHTTPClient.
Reconnection ¶
The stream is live-tail only. There is no resume: the server emits no SSE id: field, honours no Last-Event-ID, and drops events published while nobody is subscribed. When a stream ends with ErrStreamInterrupted or ErrStreamIdle, recover by fetching the session snapshot, opening a fresh stream, and deduping persisted items by id. Reconnection is routine rather than exceptional — some deployments cap HTTP stream duration at a few minutes.
Recover by fetching the snapshot with Sessions.Get, opening a fresh stream, and deduping the persisted items from Sessions.ListItems by id.
Index ¶
- Constants
- Variables
- func ChildSessionBusy(child ChildSessionSummary) bool
- func FormatToolArgsBrief(name string, arguments map[string]any) string
- func IsBlock[T Block](block Block) bool
- func IsTerminalTaskStatus(status string) bool
- func Pipe(seq iter.Seq2[Block, error], transforms ...Transform) iter.Seq2[Block, error]
- func Ptr[T any](v T) *T
- type APIError
- type AgentObject
- type Block
- type BlockContext
- type BlockStream
- type BrowserActionRequestEvent
- type Chat
- type ChatOptions
- type ChildSessionList
- type ChildSessionSummary
- type Client
- type ClientTaskCancelEvent
- type CompactionBlock
- type CompactionCompletedEvent
- type CompactionData
- type CompactionFailedEvent
- type CompactionInProgressEvent
- type ConversationDeleted
- type ConversationItem
- type ConversationRef
- type DeleteSessionOptions
- type ElicitationAction
- type ElicitationCtx
- type ElicitationRequestEvent
- type ElicitationRequestParams
- type ElicitationResolvedEvent
- type ElicitationResult
- type Error
- type ErrorBlock
- type ErrorData
- type ErrorDetail
- type ErrorEvent
- type Event
- type EventAccepted
- type FileBlock
- type FileOutputCtx
- type Files
- type FunctionCallData
- type FunctionCallOutputData
- type GetSessionOptions
- type InProgressEvent
- type IncompleteDetails
- type IncompleteEvent
- type ListAgentsOptions
- type ListFilesOptions
- type ListSessionsOptions
- type MCPServerStartup
- type MCPServerSummary
- type MessageData
- type ModelUsage
- type NativeModelOption
- type NativeReasoningEffortOption
- type NativeToolBlock
- type NativeToolData
- type Option
- func WithAuthHeader(name, value string) Option
- func WithBearerToken(token string) Option
- func WithHTTPClient(httpClient *http.Client) Option
- func WithInsecureCredentialTransport() Option
- func WithInternalClientOrigin(origin string) Option
- func WithSessionCookie(name, value string) Option
- func WithStreamIdleTimeout(d time.Duration) Option
- func WithTransferTimeout(d time.Duration) Option
- func WithUnaryTimeout(d time.Duration) Option
- func WithUserAgent(userAgent string) Option
- type OutputFileDoneEvent
- type OutputItemDoneEvent
- type OutputTextDeltaEvent
- type Page
- type PaginatedList
- type PolicyDeniedEvent
- type PolicySummary
- type PresenceViewer
- type QueryResult
- type QueryStream
- type QueuedEvent
- type ReasoningBlock
- type ReasoningChunk
- type ReasoningData
- type ReasoningStartBlock
- type ReasoningStartCtx
- type ReasoningStartedEvent
- type ReasoningSummaryTextDeltaEvent
- type ReasoningTextDeltaEvent
- type ResolveOnlineRunnerOptions
- type ResourceEventData
- type ResponseCancelledEvent
- type ResponseCompletedEvent
- type ResponseCreatedEvent
- type ResponseEndBlock
- type ResponseEndCtx
- type ResponseFailedEvent
- type ResponseHeartbeatEvent
- type ResponseObject
- type ResponseStartBlock
- type ResponseStartCtx
- type RetryBlock
- type RetryCtx
- type RetryErrorDetail
- type RetryEvent
- type RoutingDecisionData
- type RunnerInfo
- type SandboxStatus
- type ServerErrorCtx
- type SessionAgentChangedEvent
- type SessionChangedFilesInvalidatedEvent
- type SessionChildSessionUpdatedEvent
- type SessionCollaborationModeEvent
- type SessionCreateRequest
- type SessionCreatedEvent
- type SessionEventInput
- type SessionFile
- type SessionFiles
- func (s *SessionFiles) Delete(ctx context.Context, fileID string) error
- func (s *SessionFiles) Download(ctx context.Context, fileID string, w io.Writer, maxBytes int64) (int64, error)
- func (s *SessionFiles) Get(ctx context.Context, fileID string) (*SessionFile, error)
- func (s *SessionFiles) List(ctx context.Context, opts ListFilesOptions) iter.Seq2[SessionFile, error]
- func (s *SessionFiles) SessionID() string
- func (s *SessionFiles) Upload(ctx context.Context, filename string, content io.Reader) (*SessionFile, error)
- type SessionForkRequest
- type SessionGitOptions
- type SessionHeartbeatEvent
- type SessionInputConsumedEvent
- type SessionInputConsumedPayload
- type SessionInterruptedEvent
- type SessionInterruptedPayload
- type SessionItem
- type SessionItemsOptions
- type SessionKind
- type SessionList
- type SessionListItem
- type SessionMCPStartupEvent
- type SessionModelEvent
- type SessionModelOptionsEvent
- type SessionPresenceEvent
- type SessionReasoningEffortEvent
- type SessionResourceCreatedEvent
- type SessionResourceDeletedEvent
- type SessionResponse
- type SessionSandboxStatusEvent
- type SessionSkillsEvent
- type SessionSortBy
- type SessionStatusEvent
- type SessionSupersededEvent
- type SessionTerminalActivityEvent
- type SessionTerminalPendingEvent
- type SessionTodosEvent
- type SessionUsageEvent
- type Sessions
- func (s *Sessions) BindRunner(ctx context.Context, sessionID, runnerID string) (*SessionResponse, error)
- func (s *Sessions) Children(ctx context.Context, sessionID string) iter.Seq2[ChildSessionSummary, error]
- func (s *Sessions) ChildrenTree(ctx context.Context, sessionID string, opts TreeOptions) (*TreeNode, error)
- func (s *Sessions) ClearModelOverride(ctx context.Context, sessionID string) (*SessionResponse, error)
- func (s *Sessions) ClearReasoningEffort(ctx context.Context, sessionID string) (*SessionResponse, error)
- func (s *Sessions) Compact(ctx context.Context, sessionID string) error
- func (s *Sessions) Create(ctx context.Context, req SessionCreateRequest) (*SessionResponse, error)
- func (s *Sessions) Delete(ctx context.Context, sessionID string, opts DeleteSessionOptions) (*ConversationDeleted, error)
- func (s *Sessions) Fork(ctx context.Context, sessionID string, req SessionForkRequest) (*SessionResponse, error)
- func (s *Sessions) Get(ctx context.Context, sessionID string, opts GetSessionOptions) (*SessionResponse, error)
- func (s *Sessions) Interrupt(ctx context.Context, sessionID string) error
- func (s *Sessions) List(ctx context.Context, opts ListSessionsOptions) iter.Seq2[SessionListItem, error]
- func (s *Sessions) ListAgents(ctx context.Context, opts ListAgentsOptions) iter.Seq2[AgentObject, error]
- func (s *Sessions) ListItems(ctx context.Context, sessionID string, opts SessionItemsOptions) iter.Seq2[ConversationItem, error]
- func (s *Sessions) PostEvent(ctx context.Context, sessionID string, input SessionEventInput) (*EventAccepted, error)
- func (s *Sessions) ResolveAgent(ctx context.Context, agentName string) (*AgentObject, error)
- func (s *Sessions) ResolveElicitation(ctx context.Context, sessionID, elicitationID string, result ElicitationResult) error
- func (s *Sessions) ResolveOnlineRunner(ctx context.Context, opts ResolveOnlineRunnerOptions) (string, error)
- func (s *Sessions) SendMessage(ctx context.Context, sessionID, text string) (*EventAccepted, error)
- func (s *Sessions) SetArchived(ctx context.Context, sessionID string, archived bool) (*SessionResponse, error)
- func (s *Sessions) SetExternalID(ctx context.Context, sessionID, externalID string) (*SessionResponse, error)
- func (s *Sessions) SetModelOverride(ctx context.Context, sessionID, model string) (*SessionResponse, error)
- func (s *Sessions) SetReasoningEffort(ctx context.Context, sessionID, effort string) (*SessionResponse, error)
- func (s *Sessions) SubtreeBusy(ctx context.Context, sessionID string, opts TreeOptions) (busy, complete bool, err error)
- func (s *Sessions) Update(ctx context.Context, sessionID string, req UpdateSessionRequest) (*SessionResponse, error)
- type SkillSummary
- type SkippedFrame
- type SlashCommandData
- type SortOrder
- type StreamHooks
- type StreamOptions
- type Subscription
- type TerminalCommandData
- type TextChunk
- type TextDone
- type ToolCallEndCtx
- type ToolCallInfo
- type ToolCallStartCtx
- type ToolExecution
- type ToolFunc
- type ToolGroup
- type ToolOutputDeltaEvent
- type ToolRegistry
- type ToolResultBlock
- type Transform
- type TreeNode
- type TreeOptions
- type Turn
- type TurnCancelledEvent
- type TurnCompletedEvent
- type TurnEnd
- type TurnFailedEvent
- type TurnOptions
- type TurnStartedEvent
- type UnknownEvent
- type UpdateSessionRequest
- type Usage
- type UsageDetails
- type ValidationError
Constants ¶
const ( // InputTypeMessage is a conversation turn. Its Data is a role plus content // parts. [Sessions.SendMessage] builds and posts one for the plain-text case; // anything richer goes through [Sessions.PostEvent] directly. InputTypeMessage = "message" // InputTypeFunctionCallOutput returns the result of a tool the client ran. // Its Data carries call_id and output. InputTypeFunctionCallOutput = "function_call_output" // InputTypeInterrupt cancels the turn in flight. This — not cancelling a // stream's context — is how a caller stops an agent. InputTypeInterrupt = "interrupt" // InputTypeStopSession halts the session. Owner-level access only. InputTypeStopSession = "stop_session" // InputTypeCompact asks the server to compact the conversation history. InputTypeCompact = "compact" // InputTypeApproval answers an outstanding elicitation. Its Data is an // elicitation_id plus the [ElicitationResult] fields. // [Sessions.ResolveElicitation] is the route that takes them as arguments, and // is what a caller answering an elicitation wants. InputTypeApproval = "approval" )
Discriminators for SessionEventInput.Type. The first names an item the session records; the rest are control signals that queue no item.
const ( ElicitationRequestParamsModeForm = "form" ElicitationRequestParamsModeURL = "url" )
The values ElicitationRequestParams.Mode carries.
MCP-standard discriminator. "form" collects structured input via requestedSchema; "url" directs upstream to an external URL for OAuth / out-of-band interaction.
const ( ErrorDataSourceLLM = "llm" ErrorDataSourceExecution = "execution" ErrorDataSourceTool = "tool" )
The values ErrorData.Source carries.
Error source, e.g. "execution".
const ( ErrorEventSourceLLM = "llm" ErrorEventSourceExecution = "execution" ErrorEventSourceTool = "tool" )
The values ErrorEvent.Source carries.
Origin of the error — "llm" for LLM-call failures, "execution" for timeouts, "tool" for tool failures (currently emitted by retry exhaustion paths).
const ( MCPServerStartupStatusStarting = "starting" MCPServerStartupStatusReady = "ready" MCPServerStartupStatusFailed = "failed" MCPServerStartupStatusCancelled = "cancelled" )
The values MCPServerStartup.Status carries.
Latest startup state reported by the harness, mirroring Codex's McpServerStartupState enum.
const ( MessageDataRoleUser = "user" MessageDataRoleAssistant = "assistant" )
The values MessageData.Role carries.
"user" or "assistant".
const ( RetryEventSourceLLM = "llm" RetryEventSourceTool = "tool" )
The values RetryEvent.Source carries.
Origin of the retried failure — "llm" for LLM-call retries, "tool" for tool-call retries.
const ( RoutingDecisionDataScopeSession = "session" RoutingDecisionDataScopeTurn = "turn" RoutingDecisionDataScopeChildSession = "child_session" RoutingDecisionDataScopeNativeSubagent = "native_subagent" )
The values RoutingDecisionData.Scope carries.
What the decision governs — "session" (auto-harness session routing), "turn" (per-turn routing), "child_session" (an Omnigent-spawned sub-agent) or "native_subagent" (a Task / spawn_agent spawn routed inside the harness).
const ( SandboxStatusStageProvisioning = "provisioning" SandboxStatusStageCloning = "cloning" SandboxStatusStageStarting = "starting" SandboxStatusStageConnecting = "connecting" SandboxStatusStageReady = "ready" SandboxStatusStageFailed = "failed" )
The values SandboxStatus.Stage carries.
Current launch stage, e.g.
const ( SessionListItemStatusIdle = "idle" SessionListItemStatusRunning = "running" SessionListItemStatusWaiting = "waiting" SessionListItemStatusFailed = "failed" )
The values SessionListItem.Status carries.
Derived session lifecycle status.
const ( SessionResponseStatusIdle = "idle" SessionResponseStatusRunning = "running" SessionResponseStatusWaiting = "waiting" SessionResponseStatusFailed = "failed" )
The values SessionResponse.Status carries.
Session lifecycle status. One of "idle" (no loop running), "running" (loop executing), "waiting" (loop parked on background work / sub-agents), or "failed" (terminal failure).
const ( SessionSandboxStatusEventStageProvisioning = "provisioning" SessionSandboxStatusEventStageCloning = "cloning" SessionSandboxStatusEventStageStarting = "starting" SessionSandboxStatusEventStageConnecting = "connecting" SessionSandboxStatusEventStageReady = "ready" SessionSandboxStatusEventStageFailed = "failed" )
The values SessionSandboxStatusEvent.Stage carries.
The launch stage just entered, e.g. "provisioning" — see SandboxStatus for the full pipeline order.
const ( SessionStatusEventStatusIdle = "idle" SessionStatusEventStatusLaunching = "launching" SessionStatusEventStatusRunning = "running" SessionStatusEventStatusWaiting = "waiting" SessionStatusEventStatusFailed = "failed" )
The values SessionStatusEvent.Status carries.
New session status.
const ( SlashCommandDataKindSkill = "skill" SlashCommandDataKindCommand = "command" )
The values SlashCommandData.Kind carries.
"skill" for plugin/Skill invocations, "command" for surfaced CLI built-ins (/effort, /clear, /compact, /model, /ultrareview). The web renderer uses this to pick the prefix label and icon.
const ( TerminalCommandDataKindInput = "input" TerminalCommandDataKindOutput = "output" )
The values TerminalCommandData.Kind carries.
"input" for the command text, "output" for the combined stdout/stderr result.
const DefaultBaseURL = "http://127.0.0.1:6767"
DefaultBaseURL is the server's advertised self-hosted address. It is plain http, which is legitimate because it is loopback: nothing leaves the machine. See the package doc's security notes for what that does and does not permit.
const DefaultMaxToolCalls = 1024
DefaultMaxToolCalls is the tool-call budget a turn gets when ChatOptions.MaxToolCalls is zero.
Chosen to sit well above a legitimate turn rather than close to one, because reaching it parks the agent. A session snapshot returns the newest 100 items, which is the server's own signal about the scale of a turn, so this leaves an order of magnitude of headroom. A caller doing something unusual raises it; the value is in the bound being finite at all, since nothing else stops a server that issues a fresh call id per ask.
Variables ¶
var ( // ErrInvalidArgument is the one sentinel here that is not a server // response: this package rejected an argument before sending anything. An // empty session id, a create with no agent id, an input with no type, an // option handed a nil or empty value. Retrying cannot help — fix the call. ErrInvalidArgument = errors.New("invalid argument") // ErrInvalidInput is a 400: the request was understood and rejected. ErrInvalidInput = errors.New("invalid input") // was not accepted. Supply credentials with one of the auth options. ErrUnauthorized = errors.New("unauthorized") // ErrForbidden is a 403: the caller is known but lacks the required access // level on the session. Note the asymmetry — reading a session needs less // than sending to it, which needs less than deleting it. ErrForbidden = errors.New("forbidden") // ErrNotFound is a 404, which does NOT prove the session is absent. The // server also answers 404 when the caller has no access at all, so as not // to leak session existence. Treat it as "absent or invisible to you"; do // not paper over a permissions misconfiguration by retrying. ErrNotFound = errors.New("not found") // ErrConflict is a 409: the resource already exists, or the request // conflicts with current state. ErrConflict = errors.New("conflict") // ErrHarnessNotConfigured is a 412: the session's agent has no usable // harness configured server-side. Retrying will not help. ErrHarnessNotConfigured = errors.New("harness not configured") // ErrValidation is a 422: the request body did not match the schema. The // per-field detail is available from [APIError.ValidationErrors]. ErrValidation = errors.New("request validation failed") // serve the request. Usually transient. ErrUnavailable = errors.New("service unavailable") // ErrServer matches any 5xx, including the ones with their own sentinel. ErrServer = errors.New("server error") // ErrStreamInterrupted reports that a stream's body ended without the // server's terminal sentinel. This is the normal failure mode, not an edge // case: it covers a dropped transport, a subscriber-queue overflow (which // deliberately omits the sentinel to signal loss), and deployment-level // caps on stream duration. Events published while nobody was subscribed are // gone, so recover with a snapshot rather than by retrying blind. ErrStreamInterrupted = errors.New("session stream ended without its terminal sentinel") // ErrStreamIdle reports that no frame arrived within the idle timeout. The // server keeps a 15-second heartbeat precisely so this means a dead // transport rather than a slow agent — a turn may legitimately produce no // output for minutes while a tool runs, and the heartbeat continues. // Recover exactly as for [ErrStreamInterrupted]. ErrStreamIdle = errors.New("session stream idle past its timeout") // ErrStreamProtocol reports a frame this package could not parse: a data // payload that is not a JSON object, or one carrying no discriminator. ErrStreamProtocol = errors.New("malformed session stream frame") // ErrStreamFrameTooLarge reports a frame past the size this package will // accumulate. It is a kind of [ErrStreamProtocol] and matches that too: a // well-formed frame ends, and one that does not is either a broken server or // a hostile one. Recover exactly as for [ErrStreamInterrupted]. ErrStreamFrameTooLarge = fmt.Errorf("%w: frame exceeds the client's size limit", ErrStreamProtocol) // ErrListingUnbounded reports a paged listing a walk could not finish. Four // shapes raise it: a page claiming more while returning nothing, a page claiming // more with no cursor to follow, a cursor the server had already returned, and a // page count reaching the walk's ceiling. // // The first two settle after one request, so "unbounded" describes the listing's // contract rather than the cost of discovering it: the server has promised a // continuation it gave no way to reach, which is the same defect the other two // reach by spending requests on it. // // The server decides when a listing ends, so a walk cannot rely on it. This is // what the walk raises instead of continuing, and retrying will not help until // the server's paging is fixed. Anything the walk yielded before the error is // real, and incomplete. ErrListingUnbounded = errors.New("listing did not reach an end") // ErrTruncated reports that a response was larger than the bound the caller // supplied, so what was written is a prefix rather than the whole thing. // // Not [ErrInvalidArgument]: the caller's arguments were accepted and the // request was made. The server chose the length. Retry with a larger bound, or // treat the prefix as all that was wanted. ErrTruncated = errors.New("response exceeds the caller's bound") // ErrUnsafeRedirect reports a redirect this package would not follow: one // leaving the host the base URL named, one stepping down from https to // plain http, one rewriting a write as a read, or a chain that never // arrived anywhere within the hop limit. // // It is not a server error and retrying will not help. For the first two it // means the server — or something in front of it — answered with a location // this client will not carry the caller's credentials to; for the third, that // following it would have reported a dropped write as a success; for the // fourth, that the location chain is looping. Fix the base URL, or the proxy // in front of the server. The package overview, under Redirects, has the // reasoning and what Go's own rule does instead. ErrUnsafeRedirect = errors.New("refused to follow an unsafe redirect") // ErrToolCallDuplicated reports a call the wire delivered twice, which this // client ran once. // // Not a failure: the answer went back for the first delivery. Reported because a // silent drop is indistinguishable from a call this client never saw, and a // caller counting tool use would be quietly wrong. ErrToolCallDuplicated = errors.New("the call was already run") // ErrToolCallBudget means one turn asked this client to run more tools than // [ChatOptions.MaxToolCalls] allows. // // A legitimate turn does not reach it. A server that issues a fresh call id // per ask does, which is the case the budget exists for. ErrToolCallBudget = errors.New("the turn exceeded its tool-call budget") // ErrInputDenied means the server refused an input synchronously, saying why. // // Distinct from a transport failure: the send reached the server and the // server answered. A denied prompt is the case worth naming, because the // turn it would have started never begins, so nothing on the stream can end // it and the read otherwise runs to the caller's deadline. ErrInputDenied = errors.New("the server denied the input") // ErrHookPanicked reports that a caller-supplied hook panicked. // // The turn continues, and an approval hook that panics declines. Reported // because the server chooses the fields a hook reads, so it chooses the input // that trips one — and an unreported panic is a denial of service with no signal // where a caller looks. ErrHookPanicked = errors.New("a hook panicked") // ErrTurnAlreadyRead reports a second attempt to read one turn. // // Refused rather than repeated: reading again would post the prompt a second // time, and the server would answer both. A caller wanting two turns asks for // two. ErrTurnAlreadyRead = errors.New("this turn was already read") // ErrTurnIncomplete reports that the event stream ended before the turn did. // // The turn may still be running server-side. Reported rather than treated as an // end, because a caller reading the sequence as complete would take a partial // answer for the whole one. ErrTurnIncomplete = errors.New("the stream ended before the turn did") // ErrToolNotRegistered reports that the agent called a tool this client does // not have. // // The turn is not left parked on it: an output naming the mismatch is posted // anyway, because a server waiting for a call it will never receive reads as a // hung agent rather than as a missing tool. ErrToolNotRegistered = errors.New("no such tool is registered") // ErrToolFailed reports that a registered tool returned an error or panicked. // // Its output is posted as the error text, for the same reason: the turn is // parked on the call, so a failing tool still has to answer. ErrToolFailed = errors.New("the tool failed") // ErrTurnFailed reports that the server ended a turn without an answer. // // The turn reached the server and the server reported a failure, so the wrapped // message is the server's own reason. Distinct from a transport failure, which // says nothing about whether the turn ran. ErrTurnFailed = errors.New("the turn failed") // ErrTurnSuperseded reports that the session a turn was reading has been // replaced, and names the conversation that replaced it. // // Not followed automatically: a caller holding the old session id would keep // addressing a retired conversation, and which session to address next is the // caller's decision. ErrTurnSuperseded = errors.New("the session was superseded") // ErrRedirectNotFollowed reports a redirect this package could not follow, // as distinct from one it refused. An upload streams its body, so there is // nothing to replay at the new location and net/http hands the response back // rather than consulting the redirect policy. // // The location was on the server the base URL names, so nothing was sent // anywhere else and this is a configuration to fix rather than an attempt to // divert a credential: point the base URL at the route that serves the // upload. A location naming another server is [ErrUnsafeRedirect] instead. ErrRedirectNotFollowed = errors.New("could not follow a redirect") // ErrResponseTooLarge means a successful response's body exceeded what this // package will decode. // // The size is not a caller's choice, because the caller does not choose the // body. A server the caller cannot see is the only party writing it, and a // decode is the one place this package holds a whole response in memory at // once. See maxResponseBytes. ErrResponseTooLarge = errors.New("the response is too large to decode") )
Sentinels for the failure classes the server actually distinguishes. Match them with errors.Is against any error this package returns; reach the details with errors.As and APIError.
The set mirrors the server's own error-code table rather than inventing per-resource variants: a 404 is a 404 whatever endpoint produced it.
Functions ¶
func ChildSessionBusy ¶ added in v0.2.0
func ChildSessionBusy(child ChildSessionSummary) bool
ChildSessionBusy reports whether a child session has work outstanding.
Two signals, in the order the server itself resolves them. Busy is the live answer from the session loop, so it wins when the server sends it. Otherwise fall back to the current task's status, where a non-terminal status means work is outstanding and a missing status means none is.
Reading both matters: Busy is nil on a summary the server built without the live cache, and treating nil as idle reports a working subtree as finished.
func FormatToolArgsBrief ¶ added in v0.2.0
FormatToolArgsBrief renders a tool call's arguments as one line.
Exported so a caller re-rendering a recorded turn produces the same summary the live stream produced. Without one source of truth, "what you saw then" and "what this draws now" diverge the moment either side changes.
func IsBlock ¶ added in v0.2.0
IsBlock reports whether a block is of one variant, for SkipBlocks and OnlyBlocks.
Instantiated per variant — IsBlock[ReasoningBlock] — so a caller names the type and the compiler checks it belongs to the union.
func IsTerminalTaskStatus ¶ added in v0.2.0
IsTerminalTaskStatus reports whether a task status means the work has stopped.
A status this build has never seen counts as non-terminal, which is the safe direction: treating unknown work as finished reports a busy subtree as idle.
func Pipe ¶ added in v0.2.0
Pipe composes transforms left to right, so Pipe(seq, a, b) reads as "a, then b".
Written this way round because it matches the order a caller says it: drop the reasoning, then merge the text. The nesting that produces, b(a(seq)), is the reverse, which is why this exists rather than leaving callers to nest by hand.
func Ptr ¶
func Ptr[T any](v T) *T
Ptr returns a pointer to v.
Every optional field in this package is a pointer, so that a caller can tell "the server sent zero" from "the server sent nothing", and so that a request can distinguish "set this to the empty string" from "leave it alone". Ptr is how a caller writes the former without declaring a variable per field.
client.SetModelOverride(ctx, id, omnigent.Ptr("")) // clear the override
client.SetModelOverride(ctx, id, nil) // leave it alone
Types ¶
type APIError ¶
type APIError struct {
// StatusCode is the HTTP status. It, not the message text, is what the
// sentinels are matched on.
StatusCode int
// Code is the server's own error code — "not_found", "runner_unavailable",
// and so on — from a {"error": {"code", "message"}} envelope. Empty when
// the response used FastAPI's {"detail": ...} shape or was not JSON.
Code string
// Message is the human-readable message from either envelope. May be empty.
Message string
// Title is a short headline naming what went wrong, e.g. "Claude Code can't
// run as root". The server sets it when it recognised the failure, so a
// caller can show it instead of the raw Code. Empty when absent.
Title string
// Cause is a sentence or two explaining why the request failed. It is paired
// with Title. Empty when absent.
Cause string
// Remediation is a concrete next step, sometimes a command to run. Empty
// when the server has no single clear fix to name.
Remediation string
// Detail is the raw value of a {"detail": ...} envelope. For a 422 it is a
// list; see [APIError.ValidationErrors]. Nil when absent.
Detail json.RawMessage
// Body is the response body, truncated to a bounded size. Retained because
// an auth proxy or gateway ahead of the server may answer with something
// that is not JSON at all, and that is exactly why [APIError.Error] does not
// render it: what a proxy puts in a body is not this API's, and may be a
// credential. Treat it as untrusted bytes to be looked at, not logged.
Body []byte
// ContentType is the response's Content-Type header, useful for telling a
// server error apart from a proxy's HTML interstitial. Shorthand for
// Header.Get("Content-Type").
ContentType string
// RequestID is the server's X-Request-Id, which its middleware stamps on
// every response and logs alongside the failure. Quote it when reporting a
// server-side problem: it is the only handle that ties this error to the
// server's own record of it. Empty if the response never reached the
// server's middleware — a proxy or gateway error, say.
RequestID string
// Header is the response's headers, less the ones that carry a credential:
// see this type's doc. It is here for the correlation and rate-limit headers
// a deployment may add beyond the two this type documents. Nil is possible; use
// Header.Get, which tolerates it.
Header http.Header
}
APIError is a non-2xx response from the server.
The server speaks two error envelopes and neither is guaranteed, so read the fields defensively: Code and Message come from the structured envelope, Detail from FastAPI's own, and Body always holds what actually arrived.
What this does not put in a log line ¶
APIError.Error renders the status, the error code and message, the title when the server supplied one, and RequestID — what a bug report needs. It never renders Body. A non-2xx body need not come from this API at all: an auth proxy answering 302-to-login or 502 can put a CSRF token, a signed state parameter or an echoed request header in it, and Error's return value is the one string that reliably reaches a log aggregator. Body is still here for the caller who needs to see it, and callers who log it choose to.
Code, Message, Title, Cause and Remediation are parsed out of that same body, so they carry the same caveat one step weaker: they are populated only when the body matched an envelope this API defines, which a foreign responder usually does not produce, and what reaches the log is then named strings rather than opaque bytes. A proxy that happens to answer in the same shape is rendered — bounded to those fields.
Header is the response's headers with the ones that carry a credential by specification removed: Set-Cookie and Set-Cookie2, which mint a client credential, and the Authorization pair, which has no business in a response. A deployment's own bespoke secret header is not something this package can enumerate, so treat Header as untrusted for logging too.
func (*APIError) Error ¶
Error implements error. It appends the request id when the server supplied one, so a copied-out error message is enough to find the server-side record.
Every field it renders is the server's choice, so every one goes through [sanitizeForError]. A field added here needs the same treatment.
It renders no part of Body. When the response carried no structured message — the proxy-interstitial case — it says how much body there was and what type it claimed, which is enough to tell "the API rejected this" from "something else answered" without copying bytes of unknown provenance into a log.
func (*APIError) Is ¶
Is reports whether this error matches one of the package sentinels, so that errors.Is(err, ErrNotFound) works on a wrapped *APIError. A 503 matches both ErrUnavailable and the broader ErrServer.
func (*APIError) Status ¶ added in v0.2.0
Status returns the HTTP status, so an *APIError satisfies Error.
func (*APIError) ValidationErrors ¶
func (e *APIError) ValidationErrors() ([]ValidationError, error)
ValidationErrors decodes a 422's per-field detail. It returns nil, nil when the error carries no detail list, so a caller can ask unconditionally.
type AgentObject ¶
type AgentObject = api.AgentObject
AgentObject is the API representation of a registered agent.
type Block ¶ added in v0.2.0
type Block interface {
// Context reports which agent produced this block, how deep that agent sits, and
// which tool-loop iteration the block came from.
//
// A single-agent caller ignores it. One rendering a sub-agent tree routes on it,
// which is why it is on the interface rather than on each variant.
Context() BlockContext
// contains filtered or unexported methods
}
Block is one rendered piece of a turn.
Where an Event is what the server sent, a Block is what a caller draws. The stream carries deltas, duplicate reports and lifecycle noise that no renderer wants; BlockStream folds those into this smaller set, so a caller writes a switch over what it displays rather than over the wire protocol.
Sealed like Event, by a different mechanism: Event's variants each declare their own methods, while these embed one unexported struct that supplies both the context and the marker. The effect is the same — an unexported method keeps the variants this package's. That stops an independent implementation, not a type embedding an exported variant, which promotes the marker with everything else — so the seal is a strong convention rather than a proof. Give a switch over the variants a default arm regardless: the set grows when the rendering does.
type BlockContext ¶ added in v0.2.0
type BlockContext struct {
// Agent names the agent that produced the block, e.g. "coder.researcher". It is
// the response's model, which the description defines as the agent that produced
// it — so a root agent reports its own name rather than an empty one.
//
// Empty only before any response has been announced, which is where an
// [ErrorBlock] or [RetryBlock] can arrive.
Agent string
// Depth is how deep that agent sits in the sub-agent tree, counted from the dots
// in [BlockContext.Agent]: upstream derives it the same way.
//
// A convention rather than a declared field. The description does not say a dot
// in an agent name means nesting, so a name that carries one for another reason
// reports a depth it does not have. Route on Agent when that matters.
Depth int
// Iteration counts tool-loop passes within the turn. A loop that runs three
// times produces blocks at iterations 0, 1 and 2.
//
// Named for the pass rather than the turn, because [Turn] is one prompt and
// everything it produces — a different thing, and one word cannot be both.
Iteration int
// At is when the block was made, from a monotonic clock, so a renderer can
// measure elapsed time without a wall-clock jump changing the answer.
At time.Time
}
BlockContext is the metadata every Block carries.
type BlockStream ¶ added in v0.2.0
type BlockStream struct {
// TextFlushThreshold is the minimum buffered characters before a [TextChunk]
// is emitted on a word boundary. Zero means [defaultTextFlushThreshold].
//
// Raise it for a renderer that redraws expensively; set it to 1 to forward
// every delta.
TextFlushThreshold int
}
BlockStream folds a session's events into the blocks a caller renders.
The wire is not a rendering model. Text arrives as deltas, reasoning as a second delta channel, a tool call and its result as two heterogeneous items, and a tool loop's passes all report under one response. This turns that into Block values a switch is worth writing over.
A BlockStream holds configuration only. Every call to BlockStream.Blocks keeps its own state, so one value serves any number of concurrent turns.
func (*BlockStream) Blocks ¶ added in v0.2.0
Blocks folds one event sequence into blocks.
The sequence is the caller's: pair it with Client.Stream to render a live turn, or with a replayed slice to render a recorded one. An error passes through in place, so a caller reading blocks still sees a stream that failed.
type BrowserActionRequestEvent ¶
type BrowserActionRequestEvent api.BrowserActionRequestEvent
BrowserActionRequestEvent is the wire event "browser.action_request".
Request that the desktop renderer perform one browser action.
func (BrowserActionRequestEvent) EventType ¶ added in v0.2.0
func (e BrowserActionRequestEvent) EventType() string
type Chat ¶ added in v0.2.0
type Chat struct {
// contains filtered or unexported fields
}
Chat drives one session's turns.
Where Sessions is the session's state and Client.Stream is its raw events, Chat is the loop between them: it posts a prompt, reads the stream until the turn ends, runs the tools the agent asks for, and answers the approvals the server raises. A caller that wants the loop writes Chat.Send; a caller that wants the parts still has them.
func (*Chat) Prompt ¶ added in v0.2.0
Prompt prepares one turn without sending it.
Nothing is posted until the returned turn is read, so a caller that builds a turn and abandons it has sent nothing.
func (*Chat) Query ¶ added in v0.2.0
Query drives one turn and folds it into its answer.
The composition this performs is the one a caller would otherwise write: BlockStream over Chat.Send, then SkipIntermediateEnds so a tool loop reports one ending, then MergeTextAcrossIterations so it reports one answer.
Returns what it gathered alongside any error, because a turn that fails partway still produced whatever it produced, and discarding that would leave a caller with less than the stream gave.
func (*Chat) QueryStream ¶ added in v0.2.0
func (c *Chat) QueryStream(text string) *QueryStream
QueryStream prepares one turn whose text is read as it arrives.
Nothing is posted until QueryStream.Text is read.
func (*Chat) Send ¶ added in v0.2.0
Send drives one turn and yields its events until the turn ends.
The loop: subscribe, post the prompt, then read. Subscribing first is what makes the read complete — a prompt posted before the subscription exists can be answered before anyone is listening, and the turn's own events are then missed.
Tools run inline, before the turn's end is checked, because the server parks a turn on a client tool call: the terminal event only arrives after the parked call is answered. Approvals are answered the same way and for the same reason.
Ends when the turn ends, and the subscription closes with it.
Two classes of error arrive here, and they are not read the same way. A transport failure, a prompt that could not be posted, ErrTurnFailed, ErrTurnSuperseded or ErrToolCallBudget end the sequence. A tool that failed, an approval this package could not resolve, a duplicated call, an output the server refused (ErrInputDenied) and a hook that panicked are reported and the read continues — the turn is still running, and still parked on whatever comes next. Each of those has its own sentinel, so a caller deciding whether to stop matches rather than assuming.
type ChatOptions ¶ added in v0.2.0
type ChatOptions struct {
// Turn decides where a turn ends. Its zero value is the stricter rule; see
// [TurnEndsOnIdleStatus].
Turn TurnOptions
// Tools are the tools this client will run when the agent calls them. Nil
// means the client runs none, and a call the agent makes is answered with
// [ErrToolNotRegistered] rather than left parked.
Tools *ToolRegistry
// MaxToolCalls caps how many tool calls one turn will run. Zero means the
// default, [DefaultMaxToolCalls]; a negative value means no cap.
//
// The cap exists because the call id is the server's to choose, so nothing
// else bounds how many times a turn asks this client to run privileged code.
// Reaching it ends the turn: the server is parked on a call this package
// declined, so its terminal never arrives.
MaxToolCalls int
// Hooks observe the turn. Optional.
Hooks StreamHooks
// Stream configures the underlying event subscription.
Stream StreamOptions
}
ChatOptions configures how a chat drives its turns.
type ChildSessionList ¶ added in v0.2.0
type ChildSessionList = api.ChildSessionList
ChildSessionList is the paginated list of child sessions; data is a page of ChildSessionSummary.
type ChildSessionSummary ¶ added in v0.2.0
type ChildSessionSummary = api.ChildSessionSummary
ChildSessionSummary is the summary of a sub-agent (child) session under a parent session.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client talks to one omnigent server. It is safe for concurrent use, and callers should share a single Client so its connections are pooled.
func New ¶
New returns a Client for the server at baseURL, which may be empty to mean DefaultBaseURL.
The returned Client sends no credentials unless an auth option is supplied. That is deliberate: the server's identity mode is a deployment choice, so there is no scheme to guess. Pick the one matching the deployment with WithAuthHeader, WithBearerToken, or WithSessionCookie.
baseURL must carry no userinfo, and — once an auth option is supplied — must be https unless its host is loopback. Both are rejected here rather than at the first request; see the package doc's security notes for why, and for WithInsecureCredentialTransport, the explicit opt-out of the second.
Errors from here never quote baseURL back verbatim. A base URL is a place a password can hide, and an error message is the least controlled place a credential can end up.
func (*Client) Chat ¶ added in v0.2.0
func (c *Client) Chat(sessionID string, opts ChatOptions) (*Chat, error)
Chat returns a chat bound to one session.
Mirrors upstream's client.sessions_chat, and takes the session id rather than creating a session, so the session's lifecycle stays with Sessions.
func (*Client) Close ¶ added in v0.2.0
Close releases the connections this client holds open.
New gives each client its own connection pool, so a program that constructs one client per tenant or per credential refresh accumulates idle connections and the goroutines that serve them — on both ends, until the server's own idle timeout expires. Sharing one client is still the right shape, and this is the lever for the cases where that is not possible.
It does not cancel a stream in flight: a subscription ends when its caller stops ranging or its context is done, not when the pool is drained. Calling Close on a client still in use is safe, and later calls simply open new connections.
A client supplied through WithHTTPClient is the caller's to manage, so its transport is left alone. Close returns nil in that case, and always: it is an error return so that a future release can report one without breaking callers.
func (*Client) Sessions ¶ added in v0.2.0
Sessions returns the session surface, bound to this client.
func (*Client) Stream ¶
func (c *Client) Stream(ctx context.Context, sessionID string, opts StreamOptions) iter.Seq2[Event, error]
Stream subscribes to a session's event stream.
Range over the result; each step is one decoded Event or a terminal error:
for event, err := range client.Stream(ctx, sessionID, omnigent.StreamOptions{}) {
if err != nil {
return err
}
// handle event
}
Errors are terminal by construction — an error step is always the last — so the loop needs no break after one. In-stream failures are not errors here: ErrorEvent, RetryEvent and a failed turn all arrive as ordinary events, because none of them ends the subscription. Nor is a frame this build cannot decode: it is skipped, and StreamOptions.OnSkippedFrame is how to see that. Skipping every frame a stream carried is the one exception — that ends in ErrStreamProtocol, so a total decode failure cannot read as a turn that produced nothing.
No I/O happens until the first iteration, and none continues past the last: this spawns no goroutine, so abandoning the loop early cannot leak one. Breaking out of it, or cancelling ctx, closes the response body.
Validation is the one place this package reports an argument error late. Every other entry point returns an error and so rejects a bad argument before doing any work; Stream returns a sequence, which has nowhere to put an error except the sequence itself, so an empty sessionID surfaces as the first and only step — matchable with errors.Is against ErrInvalidArgument.
Ending without an error means the server closed the subscription cleanly. In practice that means the server is shutting down rather than that the turn finished — turn completion is a ResponseCompletedEvent and its siblings. Every other ending is an error: ErrStreamInterrupted for a body that stopped without the terminal sentinel, ErrStreamIdle for silence past the timeout, ctx's error for cancellation, or an APIError if the subscription was refused outright. Recovery from the first two is snapshot, resubscribe, dedupe — see the package doc.
type ClientTaskCancelEvent ¶
type ClientTaskCancelEvent api.ClientTaskCancelEvent
ClientTaskCancelEvent is the wire event "response.client_task.cancel".
Server-side request that the client cancel a tunneled tool call.
func (ClientTaskCancelEvent) EventType ¶ added in v0.2.0
func (e ClientTaskCancelEvent) EventType() string
type CompactionBlock ¶ added in v0.2.0
type CompactionBlock struct {
// contains filtered or unexported fields
}
CompactionBlock reports that the conversation is being compacted.
func (CompactionBlock) Context ¶ added in v0.2.0
func (b CompactionBlock) Context() BlockContext
type CompactionCompletedEvent ¶
type CompactionCompletedEvent api.CompactionCompletedEvent
CompactionCompletedEvent is the wire event "response.compaction.completed".
Conversation history compaction has finished.
func (CompactionCompletedEvent) EventType ¶ added in v0.2.0
func (e CompactionCompletedEvent) EventType() string
type CompactionData ¶
type CompactionData = api.CompactionData
CompactionData is the data payload for a compaction summary item.
type CompactionFailedEvent ¶
type CompactionFailedEvent api.CompactionFailedEvent
CompactionFailedEvent is the wire event "response.compaction.failed".
Conversation history compaction failed.
func (CompactionFailedEvent) EventType ¶ added in v0.2.0
func (e CompactionFailedEvent) EventType() string
type CompactionInProgressEvent ¶
type CompactionInProgressEvent api.CompactionInProgressEvent
CompactionInProgressEvent is the wire event "response.compaction.in_progress".
Conversation history is being compacted.
func (CompactionInProgressEvent) EventType ¶ added in v0.2.0
func (e CompactionInProgressEvent) EventType() string
type ConversationDeleted ¶
type ConversationDeleted = api.ConversationDeleted
ConversationDeleted is the confirmation payload returned after deleting a conversation.
type ConversationItem ¶
type ConversationItem = api.ConversationItem
ConversationItem is a persisted item with a store-assigned ID.
type ConversationRef ¶
type ConversationRef = api.ConversationRef
ConversationRef is the lightweight reference to a conversation, used in request and response bodies where only the conversation ID is needed.
type DeleteSessionOptions ¶
type DeleteSessionOptions struct {
// DeleteBranch also removes the server-created git worktree and its branch.
// Ignored for sessions without one, and best-effort: a cleanup failure does
// not fail the delete.
DeleteBranch bool
}
DeleteSessionOptions tunes a delete. The zero value leaves any git worktree alone.
type ElicitationAction ¶
type ElicitationAction string
ElicitationAction is a verdict on an outstanding elicitation. The values mirror the Model Context Protocol's ElicitResult.action.
const ( // ElicitationAccept approves: the confirmation was given, or the form was // submitted. Accepting authorises the pending tool to run with the session // owner's execution identity, so the server requires approval access for it // specifically and answers [ErrForbidden] to a caller that only has edit // access. Declining and cancelling need only edit access, so a caller able // to stop an action is not necessarily able to permit one. ElicitationAccept ElicitationAction = "accept" // ElicitationDecline refuses explicitly. ElicitationDecline ElicitationAction = "decline" // ElicitationCancel dismisses without choosing. This is also the verdict the // server synthesises when an elicitation times out, so receiving it does not // prove a client sent it. ElicitationCancel ElicitationAction = "cancel" )
type ElicitationCtx ¶ added in v0.2.0
type ElicitationCtx struct {
SessionID string
ElicitationID string
// Message is what the server is asking, e.g. a command awaiting approval.
Message string
// Phase and PolicyName are the server's own classification of the request,
// empty when it sent none. A caller with a policy keys on these. Unlike
// [ElicitationCtx.Extra] their type is enforced by the decoder, so a server
// that sends the wrong one loses the whole event rather than the field.
Phase string
PolicyName string
// Extra carries the request's undeclared parameters, nil when it had none.
//
// The schema allows them and the server uses them. tool_name — the gated
// tool's registered name, e.g. "Bash" — arrives here and nowhere else, and it
// is the finest-grained thing the server attests about an approval, which
// makes it what a policy allowlist keys on: a policy name covers every tool
// that policy gates, so allowing one tool by policy name allows the rest.
//
// Read a value in two steps, because absent and present-but-not-a-string are
// different answers and only one of them means the server said nothing:
//
// raw, present := ctx.Extra["tool_name"]
// name, valid := raw.(string)
//
// A single assertion reports both as false and cannot tell them apart, which
// is the whole reason this is a map rather than a named field.
//
// Treat either miss as unknown and fail closed. A declared field's type is
// enforced by the decoder and a violation rejects the whole event; nothing
// enforces a type here, so a policy that cannot see the tool it is gating
// should decline rather than fall through to a broader rule. Present but not
// a string is worth logging on its own: the server does not send that shape,
// so something else did.
//
// A map rather than named fields because the set is the server's to change.
// Naming one here would make the SDK's opinion about which extra matters
// permanent, and would collapse those three answers into an empty string.
Extra map[string]any
// ContentPreview is a preview of what would run, empty when the server sent
// none.
ContentPreview string
// Mode is how the server expects this to be answered: "form" for a schema the
// caller fills in, "url" for an out-of-band flow at [ElicitationCtx.URL].
// Empty when the server does not say.
//
// A decision made without it is a decision made blind, which is why upstream's
// own client passes it. Note that a "form" answer needs values this package
// cannot yet send: see [StreamHooks.OnElicitation].
Mode string
// URL is where a "url" mode flow happens, for example an OAuth authorize
// endpoint. Empty in any other mode.
//
// Show it before approving. This package refuses an off-host redirect on the
// unary path for the same reason a destination matters, and an approval that
// hides the destination gives that up.
URL string
// RequestedSchema is the shape a "form" mode answer should take. Nil when the
// server does not send one.
RequestedSchema map[string]any
// ResponseID is the response in flight when the server raised this, or empty
// when none is. It is what correlates an approval with the work it gates.
ResponseID string
}
ElicitationCtx describes an approval the server is waiting on.
SessionID is the session the request names, not the session the caller is reading. They are normally the same; when a sub-agent raises the request they are not, and the verdict has to go to the one that asked.
type ElicitationRequestEvent ¶
type ElicitationRequestEvent api.ElicitationRequestEvent
ElicitationRequestEvent is the wire event "response.elicitation_request".
Synchronous request for a decision from upstream.
func (ElicitationRequestEvent) EventType ¶ added in v0.2.0
func (e ElicitationRequestEvent) EventType() string
type ElicitationRequestParams ¶
type ElicitationRequestParams = api.ElicitationRequestParams
ElicitationRequestParams is the inner params block of a ElicitationRequestEvent.
It retains the properties the document does not declare, which is where the server sends tool_name. Reading them is supported; ElicitationCtx.Extra is the same map, handed to an approval hook.
type ElicitationResolvedEvent ¶
type ElicitationResolvedEvent api.ElicitationResolvedEvent
ElicitationResolvedEvent is the wire event "response.elicitation_resolved".
Signal that a previously-published elicitation is no longer outstanding, even though no UI approval verdict was delivered through POST /v1/sessions/{id}/events.
func (ElicitationResolvedEvent) EventType ¶ added in v0.2.0
func (e ElicitationResolvedEvent) EventType() string
type ElicitationResult ¶
type ElicitationResult struct {
// Action is the verdict. Required.
Action ElicitationAction `json:"action"`
// Content carries form values when Action is [ElicitationAccept] and the
// request asked for a schema. Nil for a plain approve/reject, and for
// decline and cancel.
//
// MCP restricts the values to JSON scalars and lists of strings, so a nested
// object or a list of anything else is rejected. The Go type is wider than
// the wire contract because map[string]any is what encoding/json gives a
// caller building one; the server is the enforcement point.
Content map[string]any `json:"content,omitempty"`
}
ElicitationResult is a verdict on one elicitation: the payload half of an InputTypeApproval input.
Hand-written for the same reason as SessionEventInput and EventAccepted: it travels on that same include_in_schema=False route, so openapi.json carries no schema for it and no drift gate covers it. Its field names and semantics mirror MCP's ElicitResult verbatim, which is deliberate on the server's part so an MCP client can bridge without translating.
type Error ¶ added in v0.2.0
type Error interface {
error
// Status returns the HTTP status the server answered with.
Status() int
}
Error is the interface every server-response error in this package satisfies.
It exists so a caller can branch on the status without naming a concrete type:
var apiErr omnigent.Error
if errors.As(err, &apiErr) && apiErr.Status() == http.StatusTooManyRequests {
// back off
}
The sentinels in this file remain the better match for a class of failure this API actually distinguishes. Reach for Error when the status itself is what matters, and for APIError when the server's own code, title or remediation is.
type ErrorBlock ¶ added in v0.2.0
type ErrorBlock struct {
// Message is the server's free-form reason. Empty when the server sent
// response.error without one, which is why Code is carried separately.
Message string
// Source is where the error came from, e.g. "llm".
Source string
// Code is the machine-readable classification, e.g. "llm_auth_failed". A
// renderer falls back to it so a blank Message still shows the caller
// something.
Code string
// contains filtered or unexported fields
}
ErrorBlock is an error the server reported during the response.
func (ErrorBlock) Context ¶ added in v0.2.0
func (b ErrorBlock) Context() BlockContext
type ErrorDetail ¶
type ErrorDetail = api.ErrorDetail
ErrorDetail is machine-readable error information attached to a failed response.
type ErrorEvent ¶
type ErrorEvent api.ErrorEvent
ErrorEvent is the wire event "response.error".
Non-recoverable error reported during the turn.
func (ErrorEvent) EventType ¶ added in v0.2.0
func (e ErrorEvent) EventType() string
type Event ¶
type Event interface {
// EventType returns the frame's wire discriminator, e.g. "session.status".
//
// Logging, metrics and routing want the type and nothing else. Without this
// each of them needs a switch over every variant to reach a field they all
// share.
//
// The value is the one the frame carried, not a constant per Go type, so a
// frame decoded by this package reports what the server actually sent. On an
// [UnknownEvent] it is the discriminator this build did not recognise.
EventType() string
// contains filtered or unexported methods
}
Event is one decoded frame from a session's event stream.
The implementations in this file are one per member of the server's discriminated union, plus UnknownEvent. Consume one with a type switch:
switch ev := event.(type) {
case OutputTextDeltaEvent:
fmt.Print(ev.Delta)
case ResponseCompletedEvent:
return nil
default:
log.Printf("ignoring %s", ev.EventType())
}
Give that switch a default arm. The set grows when the server's does, and an UnknownEvent arrives for a discriminator this build predates, so a switch without one silently drops frames it was never written to expect.
Event.EventType reaches the discriminator without a switch at all, which is what logging, metrics and routing want.
A minimal correct consumer needs relatively few of the variants. An in-process agent's turn opens at InProgressEvent and closes at exactly one of ResponseCompletedEvent, ResponseFailedEvent, IncompleteEvent or ResponseCancelledEvent. Assistant text arrives as OutputTextDeltaEvent; finished items as OutputItemDoneEvent; session-level state as SessionStatusEvent; and the echo of accepted input as SessionInputConsumedEvent. The rest — elicitation and approval flows, compaction, files, and session-metadata nudges — can be ignored without loss for that scope. That response.* lifecycle is not the whole terminal story; see the terminal-edge note below in this comment.
Five things about the stream shape are easy to get wrong:
ResponseCreatedEvent never arrives on a live turn. The harness emits it paired with "response.in_progress", and the server drops the created half before the bus every subscriber reads. Take in_progress as an in-process turn's opening event.
The generated description on InProgressEvent says the opposite — "always follows response.created". It describes the per-response harness stream, where the pair is emitted; this package reads the session stream, downstream of the drop.
Every stream opens with a fixed prologue, on every connect. First a SessionHeartbeatEvent, which is the subscription acknowledgement — but note that it is indistinguishable from the keepalive of the same name that the server emits every 15 seconds on an idle stream, so it is a position in the stream and not a payload that marks "ready". Do not act on it; act on StreamOptions.OnSubscribed, which fires once. Then, if a turn is already in flight, a REPLAY of the assistant text so far — already-emitted content, which double-renders if a snapshot was also fetched. Its shape depends on the harness: a message-scoped one (claude-native) replays OutputTextDeltaEvent only, one per in-flight message, while a response-scoped in-process agent's replay is prefixed with a synthesized ResponseCreatedEvent carrying the turn's response object. That prologue is the only place the type is observable. Then a resource snapshot of session.* events. Only then does the live tail begin.
Nothing that arrives in-stream ends the stream. ErrorEvent is non-terminal — the turn may still complete — and RetryEvent is purely informational. A turn ending is not a transport failure, and a transport failure says nothing about the turn, which keeps running server-side.
A turn can end with no response.* event at all, and for some harnesses always does. Two cases, both reached through SessionStatusEvent:
A setup-phase failure — resolving the agent spec, or building the spawn environment — kills the turn before the model stream opens, so no ResponseFailedEvent is ever emitted and the only terminal edge is a Status of "failed". It carries the failure in Error, which the server populates on that status and no other, so Error.Message is the only place the reason appears.
A terminal-backed harness (claude-native) emits no response.completed at all; its turn boundaries are session.* only. The server reads a Status of "idle" or "failed" as "no turn is active", but neither half of that edge resolves a turn on its own. ResponseID names which turn an edge describes and is set on a running edge too, so a turn ends on a terminal Status that also carries one. A running edge carrying a ResponseID is not an end: with BlockedOn set it is parked, and that field names what on. Resolving on ResponseID alone reports a session parked at a permission prompt as a finished turn, and publishes whatever partial reply it had written by then.
In both cases nothing about the transport goes quiet while a consumer waits for a terminal that is not coming: the keepalive SessionHeartbeatEvent holds off ErrStreamIdle, so watching only the response.* terminals turns a fail-fast carrying the server's own message into a hang to the consumer's own deadline. An unattended consumer wants SessionStatusEvent in its terminal switch, and wants Error.Message out of it.
SequenceNumber is not a stream cursor. It is nil on every session.* event and at best restarts from zero each turn on the others. Order by arrival.
Naming ¶
Each variant's doc states its wire type verbatim, and where two namespaces publish the same trailing name both are prefixed with theirs, so no bare name can stand for one of a pair. ResponseHeartbeatEvent is "response.heartbeat" and SessionHeartbeatEvent is "session.heartbeat"; likewise ResponseCreatedEvent against SessionCreatedEvent, and ResponseCompletedEvent, ResponseFailedEvent and ResponseCancelledEvent against their turn.* counterparts. The unprefixed name is nobody's: reaching for the wrong one of a pair now takes a deliberate act rather than a guess.
func DecodeEvent ¶ added in v0.2.0
DecodeEvent decodes one frame into its typed variant.
A frame whose discriminator this build does not know becomes an UnknownEvent rather than an error, so an older client keeps streaming against a newer server. A frame that carries a known discriminator but a malformed body is an error, because that is the server contradicting its own schema rather than extending it.
type EventAccepted ¶
type EventAccepted struct {
// Queued is true when the input became a conversation item, false for a
// control input that queues nothing.
Queued bool `json:"queued"`
// ItemID is the queued item's identifier, present when Queued. It is the
// same id that comes back on the stream as [SessionInputConsumedEvent],
// which makes it the correlation key between a send and its echo.
ItemID string `json:"item_id,omitempty"`
// PendingID identifies a native-terminal message's optimistic placeholder,
// which the consume event later clears. Usually empty.
PendingID string `json:"pending_id,omitempty"`
// Denied reports that the server handled this input synchronously by
// refusing it, rather than queueing a turn for it. It is the difference
// between the two reasons Queued can be false: a control input queues
// nothing by design, while a denied one was rejected and says why in
// [EventAccepted.Reason].
Denied bool `json:"denied,omitempty"`
// Reason is the server's explanation for a denial, e.g.
// "Denied by policy". Empty unless Denied.
//
// Worth reading rather than discarding: without it a refused send is
// indistinguishable from a control input, and a caller that treats every
// unqueued send the same way reports "the server accepted this without
// queueing it" when the server had already said exactly what was wrong.
Reason string `json:"reason,omitempty"`
}
EventAccepted is the acknowledgement a send returns. The server accepts input asynchronously: the turn has been queued, not run.
Hand-written for the same reason as SessionEventInput: it is the response body of the same include_in_schema=False route, so openapi.json documents neither and the drift gate covers neither.
type FileBlock ¶ added in v0.2.0
type FileBlock struct {
FileID string
// Filename is the name the server recorded, or nil when it recorded none.
Filename *string
// contains filtered or unexported fields
}
FileBlock is a file the agent produced.
func (FileBlock) Context ¶ added in v0.2.0
func (b FileBlock) Context() BlockContext
type FileOutputCtx ¶ added in v0.2.0
FileOutputCtx describes a file the agent produced.
type Files ¶ added in v0.2.0
type Files struct {
// contains filtered or unexported fields
}
Files reaches a session's files.
Every file route the server publishes is session-scoped, so this type's only job is to name the session. Upstream keeps a flat half — files.get(file_id) and friends — but every one of those raises: /v1/files was removed, and they survive as signposts to the session-scoped call. This package does not port a method that cannot work.
func (*Files) ForSession ¶ added in v0.2.0
func (f *Files) ForSession(sessionID string) *SessionFiles
ForSession binds the file surface to one session.
type FunctionCallData ¶
type FunctionCallData = api.FunctionCallData
FunctionCallData is the data for a function_call item.
type FunctionCallOutputData ¶
type FunctionCallOutputData = api.FunctionCallOutputData
FunctionCallOutputData is the data for a function_call_output item.
type GetSessionOptions ¶
type GetSessionOptions struct {
// IncludeItems, when false, skips the committed-items read and returns no
// items. It is the most expensive part of building a snapshot, so set it
// false when hydrating the transcript separately.
//
// Leaving it alone does not fetch the whole transcript. The server reads the
// newest 100 items and returns them chronologically, and nothing on the
// response separates a complete transcript from a truncated one, so a
// session that keeps working past 100 items silently loses its oldest.
// [Sessions.ListItems] is the paged read that reaches them.
IncludeItems *bool
// IncludeLiveness, when false, skips the runner and host liveness lookup.
IncludeLiveness *bool
// RefreshState asks the server to re-derive the session's state before
// answering rather than serving what it has cached.
RefreshState *bool
}
GetSessionOptions tunes what a snapshot includes. A nil field lets the server choose; Ptr makes one. The zero value asks for the server's defaults, which is what reconciling after a dropped stream wants.
type InProgressEvent ¶
type InProgressEvent api.InProgressEvent
InProgressEvent is the wire event "response.in_progress".
Event emitted once the task transitions to in-progress.
func (InProgressEvent) EventType ¶ added in v0.2.0
func (e InProgressEvent) EventType() string
type IncompleteDetails ¶
type IncompleteDetails = api.IncompleteDetails
IncompleteDetails is the details explaining why a response is incomplete.
type IncompleteEvent ¶
type IncompleteEvent api.IncompleteEvent
IncompleteEvent is the wire event "response.incomplete".
Terminal event for a turn that ended without completing (e.g. hit the iteration cap or token budget).
func (IncompleteEvent) EventType ¶ added in v0.2.0
func (e IncompleteEvent) EventType() string
type ListAgentsOptions ¶
type ListAgentsOptions struct {
// Limit caps the page size. The server accepts 1 to 1000 and defaults to
// 20; zero here sends nothing and takes that default.
Limit int
// After returns the agents following this cursor. Use a previous page's
// [Page.LastID].
After string
// Before returns the agents preceding this cursor. Use a previous page's
// [Page.FirstID].
Before string
// Order is the sort direction. Empty takes the server's default,
// [SortDescending].
Order SortOrder
}
ListAgentsOptions tunes an agent listing. The zero value asks for the server's defaults: the 20 most recently created agents.
type ListFilesOptions ¶ added in v0.2.0
type ListFilesOptions struct {
// Limit caps one page. Zero leaves the server's default.
Limit int
// Order sets the direction. Empty leaves the server's default.
Order SortOrder
}
ListFilesOptions tunes a file listing. The zero value asks for the server's default page.
type ListSessionsOptions ¶
type ListSessionsOptions struct {
// Limit caps the page size. The server accepts 1 to 1000 and defaults to
// 20; zero here sends nothing and takes that default.
Limit int
// After returns the sessions following this cursor. Use a previous page's
// [Page.LastID].
After string
// Before returns the sessions preceding this cursor. Use a previous page's
// [Page.FirstID].
Before string
// AgentID restricts the listing to one agent by id. This is the filter to
// reach for when reconciling against sessions a program created earlier: it
// is exact, where AgentName resolves through a mutable label.
AgentID string
// AgentName restricts the listing to one agent by name. An agent can be
// renamed, so a program that stores a name rather than an id can start
// matching a different agent, or none.
AgentName string
// Order is the sort direction. Empty takes the server's default,
// [SortDescending].
Order SortOrder
// SortBy is the timestamp to order on. Empty takes the server's default,
// [SessionSortByCreatedAt].
SortBy SessionSortBy
// SearchQuery restricts the listing to sessions matching a free-text
// search. A match populates [SessionListItem.SearchSnippet].
//
// It travels as a query parameter, so it lands in the access log of the
// server and of anything in front of it. Search for a title, not for
// something that would be a problem to find written down.
SearchQuery string
// IncludeArchived also returns archived sessions, which are otherwise
// omitted. [SessionListItem.Archived] tells them apart.
IncludeArchived bool
// Kind selects top-level, sub-agent, or all sessions. Empty takes the
// server's default, [SessionKindDefault].
Kind SessionKind
// Project restricts the listing to one project by id.
Project string
// Pinned restricts the listing to pinned sessions.
Pinned bool
}
ListSessionsOptions tunes a session listing. The zero value asks for the server's defaults: the 20 most recently created top-level sessions the caller can see, excluding archived ones.
The filters combine with AND. AgentID and AgentName both select by agent and are not alternatives to each other: setting both narrows to sessions matching both, which is empty unless they name the same agent.
type MCPServerStartup ¶ added in v0.2.0
type MCPServerStartup = api.MCPServerStartup
MCPServerStartup is one MCP server's startup state within a session.mcp_startup event.
type MCPServerSummary ¶
type MCPServerSummary = api.MCPServerSummary
MCPServerSummary is the safe subset of an MCP server's configuration for API exposure.
type MessageData ¶
type MessageData = api.MessageData
MessageData is the data for a message item (user or assistant).
type ModelUsage ¶
type ModelUsage = api.ModelUsage
ModelUsage is the cumulative token/cost usage attributed to a single LLM model.
type NativeModelOption ¶
type NativeModelOption = api.NativeModelOption
NativeModelOption is one runner-owned native model-picker row.
type NativeReasoningEffortOption ¶
type NativeReasoningEffortOption = api.NativeReasoningEffortOption
NativeReasoningEffortOption is the reasoning-effort metadata advertised by a native model catalog.
type NativeToolBlock ¶ added in v0.2.0
type NativeToolBlock struct {
// ToolType is the provider's own type, e.g. "web_search_call".
ToolType string
// Label is a short display name, e.g. "search".
Label string
// Data is the provider's payload, undecoded, because its shape is the
// provider's and this package does not declare it.
Data map[string]any
// contains filtered or unexported fields
}
NativeToolBlock is output from a provider-native tool, such as a web search or an MCP call, which the server runs and reports whole.
func (NativeToolBlock) Context ¶ added in v0.2.0
func (b NativeToolBlock) Context() BlockContext
type NativeToolData ¶
type NativeToolData = api.NativeToolData
NativeToolData is a provider-native tool output item (e.g. web_search_call).
type Option ¶
type Option interface {
// contains filtered or unexported methods
}
Option customises a Client during New.
The interface is sealed: its only implementations are this package's With* functions. That keeps the option set something this package can reason about — every option's effect is visible here — and leaves room to change what an option configures without changing what an option is.
func WithAuthHeader ¶
WithAuthHeader sends name: value on every request.
Use it for the trusted-proxy identity header, whose name is a deployment setting rather than a constant — X-Forwarded-Email is only the default.
Note that net/http does not strip a custom header across a redirect, the way it strips Authorization and Cookie. This package's redirect policy is what keeps that from mattering; see ErrUnsafeRedirect.
func WithBearerToken ¶
WithBearerToken sends token as an Authorization: Bearer header, the fallback the server accepts from non-browser clients in its OIDC and accounts modes.
func WithHTTPClient ¶
WithHTTPClient makes the Client issue its requests through httpClient.
The Client copies httpClient rather than holding it, and derives its streaming client from that copy by clearing Timeout — so a timeout set here applies to unary calls only and never truncates a stream. Set Transport.ResponseHeaderTimeout if header latency should stay bounded; the transport is the caller's here, so this package does not touch it.
A Timeout on httpClient stands unless WithUnaryTimeout is also given, which outranks it. Note what that bound has to clear either way: the server awaits a runner before it answers a session create, and again when it forwards an event to one, so a half-minute Timeout carried over from another API client aborts calls this server is still legitimately serving.
A nil CheckRedirect is replaced with this package's policy, which refuses to carry credentials off the base URL's host or down to plain http; see ErrUnsafeRedirect. Setting CheckRedirect yourself keeps yours, including nothing at all, and with it the responsibility for what the credentials do.
func WithInsecureCredentialTransport ¶
func WithInsecureCredentialTransport() Option
WithInsecureCredentialTransport permits sending a credential over plain http to a host that is not loopback, which New otherwise refuses.
It exists because "refuse" is the only fail-closed answer available to a library: a warning has nowhere to go — there is no logger here, and writing to stderr from a package is not this package's call — and silence is how a token ends up on a shared network. Refusing by default puts the decision where the deployment knowledge is, and this option is how a caller records having made it.
The legitimate cases are ones where the plaintext hop is not really a network: a sidecar on the same pod, a port-forward, a mesh that terminates TLS for you. On anything reachable by a third party it is not a trade-off, it is a leak.
func WithInternalClientOrigin ¶ added in v0.2.0
WithInternalClientOrigin sets the Origin header a deployment checks to tell an internal caller from a browser one.
A deployment that gates on Origin rejects a request carrying none, and a Go client sends none by default because it is not a browser. Announce the caller explicitly rather than have the package guess: guessing means every consumer inherits an origin claim it never made.
func WithSessionCookie ¶
WithSessionCookie sends value as the server's session cookie, which an interactive login mints. name is the cookie's name: ap_session over plain HTTP, __Host-ap_session under HTTPS.
Applying it more than once appends to the single Cookie header rather than emitting a second one, which is what RFC 6265 requires of a client and what the server's ASGI framework reads.
func WithStreamIdleTimeout ¶
WithStreamIdleTimeout sets how long Client.Stream tolerates silence before treating the transport as dead. Zero restores the default of three heartbeat intervals. A per-call StreamOptions.IdleTimeout overrides it.
func WithTransferTimeout ¶ added in v0.2.0
WithUnaryTimeout sets how long one whole non-streaming exchange may take — connect, request, response headers and body — on every call except Client.Stream, which carries no whole-exchange deadline at all: a stream's liveness is WithStreamIdleTimeout and its deadline is the caller's context. Zero restores the default.
The response-header bound on the transport this package builds follows this value, because it covers the same wait and a tighter one would decide the deadline instead. It does not follow the value below the floor a stream open needs: a Client's streaming and unary calls share one transport, and the server withholds a stream's first byte until the event relay has subscribed.
Under WithHTTPClient the transport belongs to the caller, so this sets the whole-exchange timeout alone — outranking any Timeout on the supplied client, whichever order the two options are written in — and leaves Transport.ResponseHeaderTimeout as the caller set it.
Prefer a context deadline for anything call-specific. This is one value for every unary call; its job is to stop a wedged connection hanging forever rather than to express a latency policy. WithTransferTimeout bounds one whole file transfer: SessionFiles.Upload and SessionFiles.Download.
Separate from WithUnaryTimeout because the two bound different things. A unary call's duration is the server's thinking time, which a client can put a number on. A transfer's duration is the file's size over the network's rate, which it cannot: the bound that stops a wedged RPC is the bound that makes a large upload impossible, and one value cannot be both.
Zero, the default, sets no whole-transfer bound. The context passed to the call is then the only limit on how long it may run, which is the same arrangement the streaming calls use. Set this when a caller wants one ceiling for every transfer instead of a deadline per call.
Transport.ResponseHeaderTimeout still applies, so a server that accepts a connection and then says nothing fails without waiting for this.
func WithUnaryTimeout ¶
func WithUserAgent ¶
WithUserAgent sets the User-Agent header on every request.
type OutputFileDoneEvent ¶
type OutputFileDoneEvent api.OutputFileDoneEvent
OutputFileDoneEvent is the wire event "response.output_file.done".
A streamed file output completed materializing.
func (OutputFileDoneEvent) EventType ¶ added in v0.2.0
func (e OutputFileDoneEvent) EventType() string
type OutputItemDoneEvent ¶
type OutputItemDoneEvent api.OutputItemDoneEvent
OutputItemDoneEvent is the wire event "response.output_item.done".
A conversation output item completed during the turn.
func (OutputItemDoneEvent) EventType ¶ added in v0.2.0
func (e OutputItemDoneEvent) EventType() string
type OutputTextDeltaEvent ¶
type OutputTextDeltaEvent api.OutputTextDeltaEvent
OutputTextDeltaEvent is the wire event "response.output_text.delta".
Incremental assistant-text token emitted during streaming.
func (OutputTextDeltaEvent) EventType ¶ added in v0.2.0
func (e OutputTextDeltaEvent) EventType() string
type Page ¶
type Page[T any] struct { // Data is this page's items, in the requested order. Empty on the last page // of an exhausted listing, and empty rather than nil when the server sends // an empty array. Data []T `json:"data"` // FirstID is the id of Data's first item, the cursor for paging backwards. // Empty when Data is. FirstID string `json:"first_id,omitempty"` // LastID is the id of Data's last item, the cursor for paging forwards. // Empty when Data is. LastID string `json:"last_id,omitempty"` // HasMore reports whether more items exist beyond this page in the requested // direction — the direction is baked in, because the server applies the // cursor comparison and the ordering together. // // A false value is the only finished listing. A full page does not imply // another one: the server asks for one row more than the limit and reports // whether it got it, so a true value beside an empty Data is not something this // server produces. // // Do not treat that shape as an end anyway. It means the listing was cut short, // and a caller that breaks quietly on it cannot tell a truncated answer from a // complete one — while a caller that trusts this field alone re-reads the first // page for as long as a proxy or a future server keeps saying yes. [pageSeq] // yields [ErrListingUnbounded] instead, and so should a hand-rolled loop. HasMore bool `json:"has_more"` }
Page is one page of a cursor-paginated listing.
Every listing shares this envelope because the server does: GET /v1/agents, GET /v1/sessions and GET /v1/sessions/{id}/items return the same four fields around a differently-typed Page.Data. The server also sends a constant "object": "list" alongside them, which the Go type already conveys, so it is not carried here.
Paging is by opaque cursor, not offset: the next request carries the previous page's Page.LastID as its After while Page.HasMore is true.
No exported call returns a single Page, so this is not a loop a caller writes. Every listing on this client is an iter.Seq2 that runs it — Sessions().List and its siblings — and the shape is here because the stop conditions are the part that is easy to get wrong, and worth reading before trusting a listing:
for pages := 0; ; pages++ {
page, err := fetch(ctx, cursor) // one request, one Page
if err != nil {
return err
}
for _, item := range page.Data {
// ...
}
// Only the server saying there is no more is a finished listing. A page
// that claims more while returning nothing, or with no cursor to follow,
// is a listing cut short, and breaking quietly on either makes a
// truncated answer look like the whole set.
if !page.HasMore {
break
}
if len(page.Data) == 0 || page.LastID == "" {
return fmt.Errorf("listing cut short after %d pages", pages+1)
}
cursor = page.LastID
}
Reversing that walk means Before and Page.FirstID instead. Do not derive a cursor from an item yourself: the ids are the server's and only the ones it hands back are guaranteed to page correctly under a given Order.
type PaginatedList ¶ added in v0.2.0
type PaginatedList = api.PaginatedList
PaginatedList is a paginated list response following cursor-based pagination.
type PolicyDeniedEvent ¶
type PolicyDeniedEvent api.PolicyDeniedEvent
PolicyDeniedEvent is the wire event "response.policy_denied".
Signal that a policy DENY was enforced on a native harness turn.
func (PolicyDeniedEvent) EventType ¶ added in v0.2.0
func (e PolicyDeniedEvent) EventType() string
type PolicySummary ¶
type PolicySummary = api.PolicySummary
PolicySummary is the safe subset of a policy's spec for API exposure.
type PresenceViewer ¶
type PresenceViewer = api.PresenceViewer
PresenceViewer is one user currently viewing a session (holding its SSE stream open).
type QueryResult ¶ added in v0.2.0
type QueryResult struct {
// Text is the assistant's answer, joined across the turn's tool-loop passes.
// Empty when the agent produced no text.
Text string
// Files are the artifacts the turn produced, in the order they arrived. Empty
// when it produced none.
//
// Each carries the id and, when the server reported one, the name. The bytes
// are fetched with [SessionFiles.Download]: an artifact can be any size, and a
// fold that pulled every file's content would decide that for the caller.
Files []FileBlock
}
QueryResult is one turn folded into what a caller usually wants: the answer, and the files the agent produced.
The whole turn in two fields, for the caller who does not want a sequence at all. A caller who does wants Chat.Send with BlockStream.
type QueryStream ¶ added in v0.2.0
type QueryStream struct {
// contains filtered or unexported fields
}
QueryStream is one turn's text, streamed.
Single-use, like the Turn beneath it: reading twice would post the prompt twice and the server would answer both.
func (*QueryStream) Files ¶ added in v0.2.0
func (q *QueryStream) Files() []FileBlock
Files reports the artifacts the turn produced.
Complete once QueryStream.Text has ended. Safe to call while it runs, where it reports what has arrived.
func (*QueryStream) Text ¶ added in v0.2.0
Text yields the answer in the chunks the agent produced, so a caller can render as the turn runs.
The context bounds the turn, as it does for Chat.Send. QueryStream.Files is complete once this sequence ends; reading it during the sequence reports the artifacts that have arrived so far.
type QueuedEvent ¶
type QueuedEvent api.QueuedEvent
QueuedEvent is the wire event "response.queued".
Optional event emitted between created and in_progress for background tasks that are queued before they start.
func (QueuedEvent) EventType ¶ added in v0.2.0
func (e QueuedEvent) EventType() string
type ReasoningBlock ¶ added in v0.2.0
type ReasoningBlock struct {
ReasoningText string
SummaryText string
// contains filtered or unexported fields
}
ReasoningBlock is one completed reasoning section.
Emitted only when no ReasoningChunk was sent for that section, so a caller that renders both does not draw the same reasoning twice — once as progress and again as a summary card.
func (ReasoningBlock) Context ¶ added in v0.2.0
func (b ReasoningBlock) Context() BlockContext
type ReasoningChunk ¶ added in v0.2.0
type ReasoningChunk struct {
Text string
// contains filtered or unexported fields
}
ReasoningChunk is an incremental piece of reasoning text.
Emitted while reasoning is still running, so a caller can show progress during a long tool-call window.
func (ReasoningChunk) Context ¶ added in v0.2.0
func (b ReasoningChunk) Context() BlockContext
type ReasoningData ¶
type ReasoningData = api.ReasoningData
ReasoningData is the data for a reasoning item.
type ReasoningStartBlock ¶ added in v0.2.0
type ReasoningStartBlock struct {
// contains filtered or unexported fields
}
ReasoningStartBlock reports that reasoning has begun, which is a caller's cue to show a thinking indicator.
func (ReasoningStartBlock) Context ¶ added in v0.2.0
func (b ReasoningStartBlock) Context() BlockContext
type ReasoningStartCtx ¶ added in v0.2.0
type ReasoningStartCtx struct {
ResponseID string
}
ReasoningStartCtx describes a reasoning section beginning.
type ReasoningStartedEvent ¶
type ReasoningStartedEvent api.ReasoningStartedEvent
ReasoningStartedEvent is the wire event "response.reasoning.started".
Marker emitted once when a reasoning block begins.
func (ReasoningStartedEvent) EventType ¶ added in v0.2.0
func (e ReasoningStartedEvent) EventType() string
type ReasoningSummaryTextDeltaEvent ¶
type ReasoningSummaryTextDeltaEvent api.ReasoningSummaryTextDeltaEvent
ReasoningSummaryTextDeltaEvent is the wire event "response.reasoning_summary_text.delta".
Incremental reasoning-summary token.
func (ReasoningSummaryTextDeltaEvent) EventType ¶ added in v0.2.0
func (e ReasoningSummaryTextDeltaEvent) EventType() string
type ReasoningTextDeltaEvent ¶
type ReasoningTextDeltaEvent api.ReasoningTextDeltaEvent
ReasoningTextDeltaEvent is the wire event "response.reasoning_text.delta".
Incremental reasoning-text token (full chain-of-thought).
func (ReasoningTextDeltaEvent) EventType ¶ added in v0.2.0
func (e ReasoningTextDeltaEvent) EventType() string
type ResolveOnlineRunnerOptions ¶ added in v0.2.0
type ResolveOnlineRunnerOptions struct {
// Harness is the harness the session needs, e.g. "openai-agents". Empty
// accepts any online runner.
Harness string
// Canonicalize normalises a harness name on both sides of the comparison, so
// a spec spelling that is an alias still matches a runner advertising the
// canonical name. Nil compares the names as given.
//
// It is a function rather than a table because the aliases are the server's,
// and a table here would be a second copy of them going stale.
Canonicalize func(string) string
}
ResolveOnlineRunnerOptions narrows which runner will do.
type ResourceEventData ¶
type ResourceEventData = api.ResourceEventData
ResourceEventData is the data payload for a persisted resource lifecycle event.
type ResponseCancelledEvent ¶
type ResponseCancelledEvent api.CancelledEvent
ResponseCancelledEvent is the wire event "response.cancelled".
Terminal event for a turn cancelled before completion.
func (ResponseCancelledEvent) EventType ¶ added in v0.2.0
func (e ResponseCancelledEvent) EventType() string
type ResponseCompletedEvent ¶
type ResponseCompletedEvent api.CompletedEvent
ResponseCompletedEvent is the wire event "response.completed".
Terminal event for a successfully completed turn.
func (ResponseCompletedEvent) EventType ¶ added in v0.2.0
func (e ResponseCompletedEvent) EventType() string
type ResponseCreatedEvent ¶
type ResponseCreatedEvent api.CreatedEvent
ResponseCreatedEvent is the wire event "response.created".
Initial event emitted at the start of every streaming response.
func (ResponseCreatedEvent) EventType ¶ added in v0.2.0
func (e ResponseCreatedEvent) EventType() string
type ResponseEndBlock ¶ added in v0.2.0
type ResponseEndBlock struct {
// Status is the terminal status, e.g. "completed" or "failed".
Status string
// Response is the response snapshot the terminal event carried.
//
// A pointer because the struct is large, not because it is optional: the four
// terminal events declare it as a required value, so a block built from one
// always has it. Nil only on a block a caller constructed itself.
Response *ResponseObject
// contains filtered or unexported fields
}
ResponseEndBlock reports that the response reached a terminal state.
One arrives per response. A sequence spanning several turns therefore carries several, and a caller that wants the last one alone applies SkipIntermediateEnds.
func (ResponseEndBlock) Context ¶ added in v0.2.0
func (b ResponseEndBlock) Context() BlockContext
type ResponseEndCtx ¶ added in v0.2.0
ResponseEndCtx describes a response reaching a terminal state.
type ResponseFailedEvent ¶
type ResponseFailedEvent api.FailedEvent
ResponseFailedEvent is the wire event "response.failed".
Terminal event for a turn that ended with an error.
func (ResponseFailedEvent) EventType ¶ added in v0.2.0
func (e ResponseFailedEvent) EventType() string
type ResponseHeartbeatEvent ¶
type ResponseHeartbeatEvent api.HeartbeatEvent
ResponseHeartbeatEvent is the wire event "response.heartbeat".
Keepalive event emitted on a fixed cadence during streaming.
func (ResponseHeartbeatEvent) EventType ¶ added in v0.2.0
func (e ResponseHeartbeatEvent) EventType() string
type ResponseObject ¶
type ResponseObject = api.ResponseObject
ResponseObject is the API representation of a response, meaning a task execution result.
type ResponseStartBlock ¶ added in v0.2.0
type ResponseStartBlock struct {
// Model is the agent model answering, e.g. "coder".
Model string
// ResponseID identifies the response this turn is reading.
ResponseID string
// contains filtered or unexported fields
}
ResponseStartBlock reports that a response has begun.
func (ResponseStartBlock) Context ¶ added in v0.2.0
func (b ResponseStartBlock) Context() BlockContext
type ResponseStartCtx ¶ added in v0.2.0
ResponseStartCtx describes a response beginning.
type RetryBlock ¶ added in v0.2.0
type RetryBlock struct {
// Source is what is being retried, e.g. "tool".
Source string
Attempt int
MaxAttempts int
Delay time.Duration
// contains filtered or unexported fields
}
RetryBlock reports that the server is retrying.
func (RetryBlock) Context ¶ added in v0.2.0
func (b RetryBlock) Context() BlockContext
type RetryErrorDetail ¶
type RetryErrorDetail = api.RetryErrorDetail
RetryErrorDetail is the error block carried by RetryEvent and ErrorEvent.
type RetryEvent ¶
type RetryEvent api.RetryEvent
RetryEvent is the wire event "response.retry".
A retryable failure was caught and a retry is scheduled.
func (RetryEvent) EventType ¶ added in v0.2.0
func (e RetryEvent) EventType() string
type RoutingDecisionData ¶
type RoutingDecisionData = api.RoutingDecisionData
RoutingDecisionData is the data payload for an intelligent model-router decision item.
type RunnerInfo ¶ added in v0.2.0
type RunnerInfo struct {
// RunnerID identifies the runner, e.g. "runner_abc123".
RunnerID string `json:"runner_id"`
// Online reports whether the runner is reachable now.
Online bool `json:"online"`
// Harnesses are the harnesses this runner advertises. Nil when the runner
// reported no list, which [Sessions.ResolveOnlineRunner] treats as a fallback
// rather than a refusal.
Harnesses []string `json:"harnesses"`
}
RunnerInfo is one runner the server knows about.
Hand-written: GET /v1/runners publishes a map of arrays of untyped objects, so nothing in the description pins these fields. A server-side rename breaks Sessions.ResolveOnlineRunner silently.
type SandboxStatus ¶
type SandboxStatus = api.SandboxStatus
SandboxStatus is the managed-sandbox launch progress for a host_type="managed" session.
type ServerErrorCtx ¶ added in v0.2.0
ServerErrorCtx describes an error the server reported mid-response.
type SessionAgentChangedEvent ¶
type SessionAgentChangedEvent api.SessionAgentChangedEvent
SessionAgentChangedEvent is the wire event "session.agent_changed".
Bound-agent change on a live session.
func (SessionAgentChangedEvent) EventType ¶ added in v0.2.0
func (e SessionAgentChangedEvent) EventType() string
type SessionChangedFilesInvalidatedEvent ¶
type SessionChangedFilesInvalidatedEvent api.SessionChangedFilesInvalidatedEvent
SessionChangedFilesInvalidatedEvent is the wire event "session.changed_files.invalidated".
The session's changed-files list may have changed — refetch it.
func (SessionChangedFilesInvalidatedEvent) EventType ¶ added in v0.2.0
func (e SessionChangedFilesInvalidatedEvent) EventType() string
type SessionChildSessionUpdatedEvent ¶
type SessionChildSessionUpdatedEvent api.SessionChildSessionUpdatedEvent
SessionChildSessionUpdatedEvent is the wire event "session.child_session.updated".
A child (sub-agent) session's status changed — pushed to the PARENT.
func (SessionChildSessionUpdatedEvent) EventType ¶ added in v0.2.0
func (e SessionChildSessionUpdatedEvent) EventType() string
type SessionCollaborationModeEvent ¶
type SessionCollaborationModeEvent api.SessionCollaborationModeEvent
SessionCollaborationModeEvent is the wire event "session.collaboration_mode".
Active collaboration-mode update from a Codex-native session.
func (SessionCollaborationModeEvent) EventType ¶ added in v0.2.0
func (e SessionCollaborationModeEvent) EventType() string
type SessionCreateRequest ¶
type SessionCreateRequest struct {
// AgentID is the durable identifier of the agent to bind, e.g. "ag_abc123".
// Required, and matched by ID rather than by name.
AgentID string `json:"agent_id"`
// InitialItems seeds the session's input queue, typically with a single
// user message. Seeding the first turn here avoids a follow-up send — and
// with it the race against opening the stream.
InitialItems []SessionEventInput `json:"initial_items,omitempty"`
// Title is a human-readable session title.
Title string `json:"title,omitempty"`
// Labels are initial guardrail labels for the session.
Labels map[string]string `json:"labels,omitempty"`
// ParentSessionID makes this a sub-agent session of that parent, inheriting
// its runner affinity.
ParentSessionID string `json:"parent_session_id,omitempty"`
// SubAgentName selects a sub-agent type within the parent's spec tree.
SubAgentName string `json:"sub_agent_name,omitempty"`
// HostType is "external" (the default: the caller manages the host) or
// "managed" (the server provisions a sandbox, in the background — HostID
// and Workspace must be empty, and appear on the snapshot once it
// registers).
HostType string `json:"host_type,omitempty"`
// HostID launches the runner on a registered host. Requires Workspace.
HostID string `json:"host_id,omitempty"`
// Workspace is where the session works: an absolute path on the host when
// HostID is set, or for a managed host optionally a repository URL to clone.
// Tilde and relative paths are rejected server-side.
Workspace string `json:"workspace,omitempty"`
// Git asks the server to run the session in a git worktree it creates on the
// host, with Workspace read as the source repository. Requires HostID.
Git *SessionGitOptions `json:"git,omitempty"`
// TerminalLaunchArgs are pass-through CLI arguments for a native terminal
// harness. Count and length are bounded server-side.
TerminalLaunchArgs []string `json:"terminal_launch_args,omitempty"`
// ModelOverride persists a per-session model override at create time, so it
// is on the session row before the harness launches.
ModelOverride string `json:"model_override,omitempty"`
// ReasoningEffort persists a per-session reasoning-effort override.
// Provider support is enforced later, at launch.
ReasoningEffort string `json:"reasoning_effort,omitempty"`
// CostControlModeOverride is "on" or "off"; empty defers to the spec.
CostControlModeOverride string `json:"cost_control_mode_override,omitempty"`
// HarnessOverride selects a harness other than the agent spec's. Create-time
// only — the harness process spawns on the first turn.
HarnessOverride string `json:"harness_override,omitempty"`
}
SessionCreateRequest is the JSON body of session create.
Hand-written: the create route takes a raw request and dispatches on Content-Type, so FastAPI documents no requestBody for it and openapi.json carries no schema. It mirrors the server's SessionCreateRequest and is not covered by the openapi.json drift gate.
This is the JSON create path, which binds an already-registered agent and returns the full session snapshot. The same endpoint also accepts a multipart bundled create that uploads an agent — a different contract returning a different, smaller body — which this package does not implement.
type SessionCreatedEvent ¶
type SessionCreatedEvent api.SessionCreatedEvent
SessionCreatedEvent is the wire event "session.created".
A child (sub-agent) session was spawned from this session.
func (SessionCreatedEvent) EventType ¶ added in v0.2.0
func (e SessionCreatedEvent) EventType() string
type SessionEventInput ¶
type SessionEventInput struct {
// Type is the input's discriminator; see the InputType constants. Required.
Type string `json:"type"`
// Data is the type-specific payload. The server validates its shape against
// Type and rejects a mismatch with 422. Control inputs take an empty Data.
Data map[string]any `json:"data,omitempty"`
// ModelOverride substitutes for the agent spec's model on the turn this
// input starts. Ignored when the input steers an already-running turn.
ModelOverride string `json:"model_override,omitempty"`
// Tools registers function tools for the turn this input starts, in
// OpenAI's function-tool shape. Ignored when steering a running turn, whose
// tool set is fixed at start.
Tools []map[string]any `json:"tools,omitempty"`
}
SessionEventInput is one client-submitted input for a session: the body of a send, and the element type of SessionCreateRequest.InitialItems.
Hand-written, and unusually exposed: its route is registered with include_in_schema=False, so neither the path nor this schema appears in openapi.json and no drift gate covers either. It mirrors the server's SessionEventInput. A server-side change to it breaks this client silently.
type SessionFile ¶ added in v0.2.0
type SessionFile struct {
// ID identifies the file, e.g. "file_abc123".
ID string `json:"id"`
// Filename is the name the file was uploaded under. nil when the server did
// not report one.
Filename *string `json:"filename,omitempty"`
// Bytes is the file's size. nil when the server did not report it, which a
// zero-length file does not.
Bytes *int64 `json:"bytes,omitempty"`
// CreatedAt is the Unix epoch second the file was created. nil when the server
// did not report it.
CreatedAt *int64 `json:"created_at,omitempty"`
// MimeType is the content type the server recorded, when it recorded one.
MimeType *string `json:"mime_type,omitempty"`
// Raw is the response body this file decoded from, so a caller can reach a
// field this package does not name. The routes publish no schema, so the set
// above is what has been observed rather than what is guaranteed.
//
// [SessionFile.UnmarshalJSON] fills it. Empty only when the body was empty.
Raw map[string]any `json:"-"`
}
SessionFile is one file a session owns.
Hand-written: the file routes declare an empty response schema, so nothing in the description pins these fields. A server-side rename breaks this silently.
func (*SessionFile) UnmarshalJSON ¶ added in v0.2.0
func (f *SessionFile) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes a file, keeping the body it decoded from.
The named fields above are what this package has observed, not what the server guarantees: every file route publishes an empty response schema, so nothing pins them and nothing warns when the server adds a field. Keeping the body is what lets a caller reach one without waiting for this package to name it.
The alias sheds this method, so the first decode does not recurse.
type SessionFiles ¶ added in v0.2.0
type SessionFiles struct {
// contains filtered or unexported fields
}
SessionFiles reaches the files one session owns.
The routes behind this type publish no response schema, so SessionFile is a contract this package states rather than one the description declares. The conformance tests cannot cover it in either direction.
func (*SessionFiles) Delete ¶ added in v0.2.0
func (s *SessionFiles) Delete(ctx context.Context, fileID string) error
Delete removes one file.
func (*SessionFiles) Download ¶ added in v0.2.0
func (s *SessionFiles) Download(ctx context.Context, fileID string, w io.Writer, maxBytes int64) (int64, error)
Download writes one file's content to w, refusing to write more than maxBytes.
The bound is required rather than optional. The server decides the length, and an unbounded copy from a remote server into a caller's memory or disk is a fault waiting for a large file. Pass a bound the caller can afford.
w receives at most maxBytes. A larger body returns ErrTruncated with the count actually written, so a caller can tell a prefix from the whole file.
func (*SessionFiles) Get ¶ added in v0.2.0
func (s *SessionFiles) Get(ctx context.Context, fileID string) (*SessionFile, error)
Get returns one file's metadata.
func (*SessionFiles) List ¶ added in v0.2.0
func (s *SessionFiles) List(ctx context.Context, opts ListFilesOptions) iter.Seq2[SessionFile, error]
List walks the session's files.
func (*SessionFiles) SessionID ¶ added in v0.2.0
func (s *SessionFiles) SessionID() string
SessionID returns the session these files belong to.
func (*SessionFiles) Upload ¶ added in v0.2.0
func (s *SessionFiles) Upload(ctx context.Context, filename string, content io.Reader) (*SessionFile, error)
Upload streams one file into the session.
It takes a reader rather than a path, so a caller uploads from wherever the bytes are and this package holds none of them in memory: the multipart body is written to a pipe as the request drains it. Upstream takes a path, which is the one place its shape does not transfer.
type SessionForkRequest ¶ added in v0.2.0
type SessionForkRequest = api.SessionForkRequest
SessionForkRequest is the request body for POST /v1/sessions/{source_id}/fork.
type SessionGitOptions ¶
type SessionGitOptions = api.SessionGitOptions
SessionGitOptions is the git worktree options for POST /v1/sessions.
type SessionHeartbeatEvent ¶
type SessionHeartbeatEvent api.SessionHeartbeatEvent
SessionHeartbeatEvent is the wire event "session.heartbeat".
Idle-stream keepalive on GET /v1/sessions/{id}/stream.
func (SessionHeartbeatEvent) EventType ¶ added in v0.2.0
func (e SessionHeartbeatEvent) EventType() string
type SessionInputConsumedEvent ¶
type SessionInputConsumedEvent api.SessionInputConsumedEvent
SessionInputConsumedEvent is the wire event "session.input.consumed".
A queued input item was materialized into conversation history.
func (SessionInputConsumedEvent) EventType ¶ added in v0.2.0
func (e SessionInputConsumedEvent) EventType() string
type SessionInputConsumedPayload ¶
type SessionInputConsumedPayload = api.SessionInputConsumedPayload
SessionInputConsumedPayload is the inner payload of a SessionInputConsumedEvent.
type SessionInterruptedEvent ¶
type SessionInterruptedEvent api.SessionInterruptedEvent
SessionInterruptedEvent is the wire event "session.interrupted".
User-triggered cancel reached the loop.
func (SessionInterruptedEvent) EventType ¶ added in v0.2.0
func (e SessionInterruptedEvent) EventType() string
type SessionInterruptedPayload ¶
type SessionInterruptedPayload = api.SessionInterruptedPayload
SessionInterruptedPayload is the inner payload of a SessionInterruptedEvent.
type SessionItem ¶
SessionItem is one item from Sessions.ListItems.
It is untyped because the route sends the server's flatten-for-API shape rather than the ConversationItem a snapshot carries. Both carry id, response_id, type, status and created_at; the difference is the payload. A snapshot nests it under data, while this route spreads the payload's own fields — role and content on a message, name and arguments on a function call — onto the same object, leaving absent optional fields out. openapi.json declares this page's elements untyped, so there is nothing generated to decode them into either.
An alias rather than a defined type, so it interchanges with the same flat shape on the stream, [OutputItemDoneEvent.Item].
Nothing returns this yet — Sessions.ListItems returns ConversationItem instead, and says there what that costs a caller.
type SessionItemsOptions ¶
type SessionItemsOptions struct {
// Limit caps the page size. The server accepts 1 to 1000 and defaults to
// 100; zero here sends nothing and takes that default.
Limit int
// After returns the items following this cursor. Use a previous page's
// [Page.LastID].
After string
// Before returns the items preceding this cursor. Use a previous page's
// [Page.FirstID]. Set beside After it narrows to the window between the two
// rather than replacing it, because the server applies both comparisons.
Before string
// Order is the sort direction, and both cursors are read relative to it.
// Empty takes this route's default, [SortAscending] — chronological, and
// not the [SortDescending] the agent and session listings default to.
Order SortOrder
}
SessionItemsOptions tunes a session-items listing. The zero value asks for the server's defaults: the session's oldest 100 items, chronologically.
type SessionKind ¶
type SessionKind string
SessionKind selects which sessions a listing includes.
const ( // SessionKindDefault includes only top-level sessions. The server's // default: sub-agent sessions are an implementation detail of the parent // turn, so they stay out unless asked for. SessionKindDefault SessionKind = "default" // SessionKindSubAgent includes only sub-agent sessions. SessionKindSubAgent SessionKind = "sub_agent" // SessionKindAny includes both. SessionKindAny SessionKind = "any" )
type SessionList ¶ added in v0.2.0
type SessionList = api.SessionList
SessionList is the paginated list of sessions; data is a page of SessionListItem.
type SessionListItem ¶
type SessionListItem = api.SessionListItem
SessionListItem is the lightweight session summary for GET /v1/sessions list responses.
type SessionMCPStartupEvent ¶ added in v0.2.0
type SessionMCPStartupEvent api.SessionMCPStartupEvent
SessionMCPStartupEvent is the wire event "session.mcp_startup".
Per-MCP-server startup progress for a native harness session.
func (SessionMCPStartupEvent) EventType ¶ added in v0.2.0
func (e SessionMCPStartupEvent) EventType() string
type SessionModelEvent ¶
type SessionModelEvent api.SessionModelEvent
SessionModelEvent is the wire event "session.model".
Active-model update from a terminal-backed integration.
func (SessionModelEvent) EventType ¶ added in v0.2.0
func (e SessionModelEvent) EventType() string
type SessionModelOptionsEvent ¶
type SessionModelOptionsEvent api.SessionModelOptionsEvent
SessionModelOptionsEvent is the wire event "session.model_options".
Signal that a native session's model catalog has resolved.
func (SessionModelOptionsEvent) EventType ¶ added in v0.2.0
func (e SessionModelOptionsEvent) EventType() string
type SessionPresenceEvent ¶
type SessionPresenceEvent api.SessionPresenceEvent
SessionPresenceEvent is the wire event "session.presence".
The session's viewer list changed — full state, not a delta.
func (SessionPresenceEvent) EventType ¶ added in v0.2.0
func (e SessionPresenceEvent) EventType() string
type SessionReasoningEffortEvent ¶
type SessionReasoningEffortEvent api.SessionReasoningEffortEvent
SessionReasoningEffortEvent is the wire event "session.reasoning_effort".
Active reasoning-effort update from a terminal-backed integration.
func (SessionReasoningEffortEvent) EventType ¶ added in v0.2.0
func (e SessionReasoningEffortEvent) EventType() string
type SessionResourceCreatedEvent ¶
type SessionResourceCreatedEvent api.SessionResourceCreatedEvent
SessionResourceCreatedEvent is the wire event "session.resource.created".
A session resource was created.
func (SessionResourceCreatedEvent) EventType ¶ added in v0.2.0
func (e SessionResourceCreatedEvent) EventType() string
type SessionResourceDeletedEvent ¶
type SessionResourceDeletedEvent api.SessionResourceDeletedEvent
SessionResourceDeletedEvent is the wire event "session.resource.deleted".
A session resource was deleted.
func (SessionResourceDeletedEvent) EventType ¶ added in v0.2.0
func (e SessionResourceDeletedEvent) EventType() string
type SessionResponse ¶
type SessionResponse = api.SessionResponse
SessionResponse is the API representation of a session.
type SessionSandboxStatusEvent ¶
type SessionSandboxStatusEvent api.SessionSandboxStatusEvent
SessionSandboxStatusEvent is the wire event "session.sandbox_status".
Managed-sandbox launch progress for a host_type="managed" session.
func (SessionSandboxStatusEvent) EventType ¶ added in v0.2.0
func (e SessionSandboxStatusEvent) EventType() string
type SessionSkillsEvent ¶
type SessionSkillsEvent api.SessionSkillsEvent
SessionSkillsEvent is the wire event "session.skills".
Signal that a session's runner-owned skills have resolved.
func (SessionSkillsEvent) EventType ¶ added in v0.2.0
func (e SessionSkillsEvent) EventType() string
type SessionSortBy ¶
type SessionSortBy string
SessionSortBy is the timestamp a session listing orders on.
const ( // SessionSortByCreatedAt orders on creation time. The server's default, and // the stable choice for paging: an update mid-walk cannot reorder items // underneath the cursor. SessionSortByCreatedAt SessionSortBy = "created_at" // SessionSortByUpdatedAt orders on last-activity time, which is what a // recency-ordered list wants. Because activity reorders items, a long walk // under this can revisit or skip one. SessionSortByUpdatedAt SessionSortBy = "updated_at" )
type SessionStatusEvent ¶
type SessionStatusEvent api.SessionStatusEvent
SessionStatusEvent is the wire event "session.status".
Session lifecycle status transition.
func (SessionStatusEvent) EventType ¶ added in v0.2.0
func (e SessionStatusEvent) EventType() string
type SessionSupersededEvent ¶
type SessionSupersededEvent api.SessionSupersededEvent
SessionSupersededEvent is the wire event "session.superseded".
This conversation was superseded by another and clients should follow to it.
func (SessionSupersededEvent) EventType ¶ added in v0.2.0
func (e SessionSupersededEvent) EventType() string
type SessionTerminalActivityEvent ¶
type SessionTerminalActivityEvent api.SessionTerminalActivityEvent
SessionTerminalActivityEvent is the wire event "session.terminal.activity".
A terminal's pane produced output (runner-determined activity pulse).
func (SessionTerminalActivityEvent) EventType ¶ added in v0.2.0
func (e SessionTerminalActivityEvent) EventType() string
type SessionTerminalPendingEvent ¶
type SessionTerminalPendingEvent api.SessionTerminalPendingEvent
SessionTerminalPendingEvent is the wire event "session.terminal_pending".
Terminal spin-up status for a terminal-first session.
func (SessionTerminalPendingEvent) EventType ¶ added in v0.2.0
func (e SessionTerminalPendingEvent) EventType() string
type SessionTodosEvent ¶
type SessionTodosEvent api.SessionTodosEvent
SessionTodosEvent is the wire event "session.todos".
Todo-list update from a Claude Code terminal-backed session.
func (SessionTodosEvent) EventType ¶ added in v0.2.0
func (e SessionTodosEvent) EventType() string
type SessionUsageEvent ¶
type SessionUsageEvent api.SessionUsageEvent
SessionUsageEvent is the wire event "session.usage".
Token-usage update from a terminal-backed integration.
func (SessionUsageEvent) EventType ¶ added in v0.2.0
func (e SessionUsageEvent) EventType() string
type Sessions ¶ added in v0.2.0
type Sessions struct {
// contains filtered or unexported fields
}
Sessions reaches the session surface.
It exists as its own type rather than as methods on Client because the surface is twenty-odd calls, and upstream's Python client draws the same line with its sessions namespace. Reach it through Client.Sessions.
func (*Sessions) BindRunner ¶ added in v0.2.0
func (s *Sessions) BindRunner(ctx context.Context, sessionID, runnerID string) (*SessionResponse, error)
BindRunner binds the session to a runner.
There is no UnbindRunner to pair with it. The description says only that a nil runner_id leaves the binding unchanged; it defines no value that releases one. Add the method when the description names the release, not before.
func (*Sessions) Children ¶ added in v0.2.0
func (s *Sessions) Children(ctx context.Context, sessionID string) iter.Seq2[ChildSessionSummary, error]
Children walks a session's immediate child sessions.
Immediate, not recursive: Sessions.ChildrenTree walks the subtree, and it bounds itself because a tree can carry a cycle.
func (*Sessions) ChildrenTree ¶ added in v0.2.0
func (s *Sessions) ChildrenTree(ctx context.Context, sessionID string, opts TreeOptions) (*TreeNode, error)
ChildrenTree walks a session's subtree, bounded.
Three bounds, each for a failure this walk would otherwise hit. Depth, because a caller cannot know a server-shaped tree's depth in advance. Concurrency, because one listing per child at every level is a request storm — it caps the requests in flight, so one node's paging does not hold a slot. And a per-path set, because a tree that carries a cycle would otherwise never end — the walk descends into a session once per path and records the repeat as a non-descent rather than following it. A session reachable two ways appears twice, which is what the tree says; MaxNodes bounds that expansion.
Every child pages. A parent with more children than one page holds still reports all of them, which is where upstream's own walk stops short.
On error it returns the partial tree alongside it. An operator inspecting a broken tree needs the part that resolved.
func (*Sessions) ClearModelOverride ¶ added in v0.2.0
func (s *Sessions) ClearModelOverride(ctx context.Context, sessionID string) (*SessionResponse, error)
ClearModelOverride returns the session to the model its agent spec names.
The server clears on an alias rather than on an empty value, matching its own /model semantics. This sends one of those aliases.
func (*Sessions) ClearReasoningEffort ¶ added in v0.2.0
func (s *Sessions) ClearReasoningEffort(ctx context.Context, sessionID string) (*SessionResponse, error)
ClearReasoningEffort returns the session to its spec's reasoning effort.
func (*Sessions) Compact ¶ added in v0.2.0
Compact asks the session to compact its context.
A server that refuses the compaction returns ErrInputDenied.
func (*Sessions) Create ¶ added in v0.2.0
func (s *Sessions) Create(ctx context.Context, req SessionCreateRequest) (*SessionResponse, error)
Create creates a session bound to an existing agent and returns its snapshot.
func (*Sessions) Delete ¶ added in v0.2.0
func (s *Sessions) Delete(ctx context.Context, sessionID string, opts DeleteSessionOptions) (*ConversationDeleted, error)
Delete deletes a session.
func (*Sessions) Fork ¶ added in v0.2.0
func (s *Sessions) Fork(ctx context.Context, sessionID string, req SessionForkRequest) (*SessionResponse, error)
Fork branches a session at its current state and returns the new one.
func (*Sessions) Get ¶ added in v0.2.0
func (s *Sessions) Get(ctx context.Context, sessionID string, opts GetSessionOptions) (*SessionResponse, error)
Get returns a session's snapshot.
A 404 here does not prove the session is absent: the server answers 404 for a session the caller cannot see, so as not to leak its existence. See ErrNotFound.
func (*Sessions) Interrupt ¶ added in v0.2.0
Interrupt stops the turn in flight, leaving the session usable.
A server that refuses the interrupt returns ErrInputDenied. This is the stop control, so a caller that reads nil has to be able to believe the turn stopped.
func (*Sessions) List ¶ added in v0.2.0
func (s *Sessions) List(ctx context.Context, opts ListSessionsOptions) iter.Seq2[SessionListItem, error]
List walks every session the caller can see.
The walk pages internally, so a caller ranges once. Order on creation time unless recency is what matters: activity reorders an updated-at listing underneath the cursor, so a long walk under it can revisit or skip an item. See SessionSortBy.
func (*Sessions) ListAgents ¶ added in v0.2.0
func (s *Sessions) ListAgents(ctx context.Context, opts ListAgentsOptions) iter.Seq2[AgentObject, error]
ListAgents walks the registered agents, newest first unless ListAgentsOptions.Order says otherwise.
Not only the built-in agents, despite the route's name: it is every agent not scoped to a single session, so an operator's installed agents arrive alongside the ones that ship with the server. [AgentObject.Builtin] tells them apart, and an agent created for one session is never listed.
To turn a name into the id SessionCreateRequest.AgentID wants, use Sessions.ResolveAgent — there is no lookup-by-name route, so it pages until the name matches.
The description types this listing's payload as heterogeneous, so the agent shape here is a contract this package states rather than one the document declares. See AgentObject.
func (*Sessions) ListItems ¶ added in v0.2.0
func (s *Sessions) ListItems(ctx context.Context, sessionID string, opts SessionItemsOptions) iter.Seq2[ConversationItem, error]
ListItems walks a session's conversation items in transcript order.
Read only the common fields — id, response_id, type, status, created_at, created_by. [ConversationItem.Data] is always nil here, so switching on the type and unmarshalling it yields the zero value rather than the payload.
This route sends the server's flatten-for-API shape, which spreads the payload's own fields onto the item beside the common ones instead of nesting them under data. SessionItem is that shape. The snapshot route, Sessions.Get with GetSessionOptions.IncludeItems, is the one that nests, and a ConversationItem from there does carry Data.
Returning ConversationItem for this route is a mismatch, and correcting it is breaking. Until then the compiler cannot tell a caller that Data is empty, so this comment has to.
func (*Sessions) PostEvent ¶ added in v0.2.0
func (s *Sessions) PostEvent(ctx context.Context, sessionID string, input SessionEventInput) (*EventAccepted, error)
PostEvent sends one input to a session.
This route is registered include_in_schema=False, so neither its body nor its responses appear in the vendored description. SessionEventInput and EventAccepted are contracts this package states.
Read the result rather than discarding it: an input the server refuses comes back with Queued false and Denied true, and a caller that treats every unqueued send alike reports "accepted without queueing" when the server had already said what was wrong.
func (*Sessions) ResolveAgent ¶ added in v0.2.0
ResolveAgent finds a registered agent by the name a person picked.
A picker lists names; session creation binds by id. This closes that gap.
It follows the listing cursor, so an agent past the first page still resolves. Only server-registered agents are listed, so a session-scoped agent is not resolvable this way.
A miss wraps ErrNotFound, though no 404 was involved: the listing succeeded and carried no such name. The error names some of the names it did see, capped, because a large deployment should not build thousands of them to render one message.
func (*Sessions) ResolveElicitation ¶ added in v0.2.0
func (s *Sessions) ResolveElicitation(ctx context.Context, sessionID, elicitationID string, result ElicitationResult) error
ResolveElicitation answers an approval request the agent raised.
The request names the session its resolve endpoint belongs to, which is not always the session whose stream carried it: a child's prompt is mirrored into an ancestor's stream. Post to the session the request names, or the resolve lands nowhere and the agent stays parked. See [ElicitationRequestParams.TargetSessionID].
func (*Sessions) ResolveOnlineRunner ¶ added in v0.2.0
func (s *Sessions) ResolveOnlineRunner(ctx context.Context, opts ResolveOnlineRunnerOptions) (string, error)
ResolveOnlineRunner finds an online runner that can drive a harness.
A client with no runner of its own still needs one bound before a turn can dispatch, and the server knows which of its runners are online and what each advertises.
Returns an empty id and a nil error when the server has no match. That is a normal state, not a failure: the caller waits or asks for a different harness.
A runner that reports no harness list at all is the fallback, taken only when nothing advertises the harness outright. The server matches the same way, and the alternative — treating silence as a refusal — leaves a usable runner idle.
func (*Sessions) SendMessage ¶ added in v0.2.0
SendMessage posts a user message and starts a turn.
One text block, carrying a role and a content list. A prompt with files or pre-built content blocks is built by hand and posted with Sessions.PostEvent.
func (*Sessions) SetArchived ¶ added in v0.2.0
func (s *Sessions) SetArchived(ctx context.Context, sessionID string, archived bool) (*SessionResponse, error)
SetArchived archives or restores the session.
func (*Sessions) SetExternalID ¶ added in v0.2.0
func (s *Sessions) SetExternalID(ctx context.Context, sessionID, externalID string) (*SessionResponse, error)
SetExternalID records a caller's own identifier on the session.
Idempotent on the same value. A different value is rejected, because the mapping is meant to be stable once made.
func (*Sessions) SetModelOverride ¶ added in v0.2.0
func (s *Sessions) SetModelOverride(ctx context.Context, sessionID, model string) (*SessionResponse, error)
SetModelOverride substitutes a model for the agent spec's own.
The value is forwarded to the executor as-is; the server does not enumerate valid models, so a bad one fails at turn start rather than here. Use Sessions.ClearModelOverride to remove it — the empty string is not the clear.
func (*Sessions) SetReasoningEffort ¶ added in v0.2.0
func (s *Sessions) SetReasoningEffort(ctx context.Context, sessionID, effort string) (*SessionResponse, error)
SetReasoningEffort sets the reasoning effort for later turns.
Provider support is validated when a turn executes, not here. Use Sessions.ClearReasoningEffort to remove the override.
func (*Sessions) SubtreeBusy ¶ added in v0.2.0
func (s *Sessions) SubtreeBusy(ctx context.Context, sessionID string, opts TreeOptions) (busy, complete bool, err error)
SubtreeBusy reports whether any session *below* the named one has work outstanding, and whether the walk saw the whole subtree.
It does not read the named session's own state: no listing describes it, so there is no summary to read. Use Sessions.Get for that.
complete is false when the walk did not see the whole subtree — it stopped at TreeOptions.MaxDepth or the node ceiling, or a listing failed. That makes a false busy inconclusive rather than wrong. A true busy is sound either way, so complete is true whenever busy is.
A cycle does not make the answer incomplete: a repeated session's subtree was already walked where it first appeared.
Truncation is deliberately not an error. It is a property of the answer, and the caller decides what to do about it: raise TreeOptions.MaxDepth and ask again, or accept the bound. Reporting it as an error would either force a caller to string-match, or make this package invent a sentinel for a case that is not a failure. An error here means the walk itself failed.
busy, complete, err := sessions.SubtreeBusy(ctx, id, omnigent.TreeOptions{})
switch {
case err != nil:
return err
case busy:
// work outstanding, whatever the walk missed
case !complete:
// nothing found, but the walk did not reach the whole subtree
}
func (*Sessions) Update ¶ added in v0.2.0
func (s *Sessions) Update(ctx context.Context, sessionID string, req UpdateSessionRequest) (*SessionResponse, error)
Update applies an arbitrary patch and returns the new snapshot.
Prefer one of the named wrappers below. This is here for a field they do not cover yet.
Six of the session's state changes are one PATCH each, and every field already exists on the update request, so the wrappers add no capability. Naming them is the point: Sessions.SetModelOverride states an intent that a generic patch call does not, and the difference between clearing an override and unbinding a runner is legible at the call site rather than in the payload.
type SkillSummary ¶
type SkillSummary = api.SkillSummary
SkillSummary is the safe subset of a discovered skill for API exposure.
type SkippedFrame ¶
type SkippedFrame struct {
// SessionID is the session whose stream carried the frame.
SessionID string
// Name is the frame's event: line, empty when it carried none. Note that
// dispatch prefers the payload's own type field, so this is not necessarily
// what decoding was attempted against.
Name string
// Payload is the frame's data: lines, rejoined. It is bounded only by
// maxFrameBytes, so bound it again before logging it.
Payload string
// Err is why the frame could not be decoded: [ErrStreamProtocol] for a
// payload that is not JSON or carries no discriminator, otherwise a
// field-level decode failure naming the event type it was decoded as.
Err error
}
SkippedFrame describes a frame Client.Stream dropped because it could not be decoded, reported to StreamOptions.OnSkippedFrame.
Fields will be added to it. Construct one with field names — go vet's composites check enforces that for a struct from another package — and it stays source-compatible as it grows.
type SlashCommandData ¶
type SlashCommandData = api.SlashCommandData
SlashCommandData is the data payload for a slash-command invocation observed in a harness transcript (today: Claude Code's embedded TUI).
type SortOrder ¶
type SortOrder string
SortOrder is the direction a listing returns its items in.
const ( // SortDescending returns newest first. The server's default on the agent and // session listings. SortDescending SortOrder = "desc" // SortAscending returns oldest first. The server's default on the items // listing, whose chronological order is the transcript's own. SortAscending SortOrder = "asc" )
type StreamHooks ¶ added in v0.2.0
type StreamHooks struct {
// OnResponseStart fires once per response, when the server announces it.
OnResponseStart func(ResponseStartCtx)
// OnResponseEnd fires when a response reaches a terminal state, which is once
// per turn: a tool loop's passes all report under the one response.
OnResponseEnd func(ResponseEndCtx)
// OnToolCallStart fires when the agent calls a tool, server-run or client-run.
OnToolCallStart func(ToolCallStartCtx)
// OnToolCallEnd fires when that call's output arrives.
OnToolCallEnd func(ToolCallEndCtx)
// OnReasoningStart fires when the server reports reasoning beginning.
//
// There is deliberately no matching end hook. A section closes when text starts
// or the response ends, and its accumulated text lives in [BlockStream]'s fold;
// a second copy of that state here would drift from the first. Reasoning
// completion arrives on the block sequence as [ReasoningBlock] or the last
// [ReasoningChunk], which is where a caller that wants the text reads it.
//
// A caller driving a spinner keys the stop on the next block of another kind
// rather than on a hook that would have to guess the same thing.
OnReasoningStart func(ReasoningStartCtx)
// OnRetry fires when the server reports it is retrying.
OnRetry func(RetryCtx)
// OnServerError fires when the server reports an error mid-response. It is not
// the end of the turn; a terminal response is.
OnServerError func(ServerErrorCtx)
// OnFileOutput fires when the agent produces a file.
OnFileOutput func(FileOutputCtx)
// OnElicitation decides an approval request the server raised.
//
// Return true to accept and false to decline. A nil hook declines, and so does
// a hook that panics. That is this package's own behaviour and not a policy: it
// cannot know what a caller would approve, and accepting authorises the pending
// tool to run with the session owner's execution identity — which is not
// necessarily the approver's. See [ElicitationAccept] for what the server
// requires of an accept, and [ElicitationCtx] for what the request carries.
OnElicitation func(ElicitationCtx) bool
}
StreamHooks observes a turn's lifecycle without reading raw events.
Every field is optional; a nil hook is skipped. Hooks run on the caller's own goroutine, in event order — this package starts none — so a hook that blocks holds the turn up. That is what makes them suitable for a progress display and not for work of their own.
Every hook but one only observes. StreamHooks.OnElicitation is the exception: its answer authorises a pending tool to run. The rest cannot change the turn — blocks are what a caller renders and ToolRegistry is what a caller runs, so these are for the side effects that belong to neither, such as a spinner or a metric.
type StreamOptions ¶
type StreamOptions struct {
// Idle marks this subscriber as present but inattentive, which co-viewers
// see on their own streams as a presence edge. Flipping it means
// reconnecting with the new value; there is no update path.
Idle bool
// IdleTimeout overrides the client's default tolerance for silence, which
// means time with no bytes arriving. The server heartbeats every 15 seconds,
// so this bounds transport death detection — not agent latency, and not how
// long one large frame may take to arrive. Zero uses the client's setting.
IdleTimeout time.Duration
// OnSubscribed runs once the subscription is live, before the first event
// reaches the caller, and is the supported way to post the input that
// starts a turn. It runs on the caller's goroutine, so it must return
// promptly; the idle monitor is suspended while it does.
//
// It exists because the acknowledgement cannot be recognised by inspecting
// events. The server sends the identical {"type": "session.heartbeat"}
// payload for two different things — the subscription acknowledgement, and
// the keepalive it emits every 15 seconds while a stream sits idle — so
// "send when I see a heartbeat" sends again on every keepalive, forever.
// This hook is called exactly once per stream, on the first frame of any kind —
// a keepalive comment, an empty frame or one this build cannot decode all show
// the subscription is live. Not at all only if the stream ends before delivering
// any frame, which a caller depending on this hook should give a deadline.
// Returning an error ends the stream with that error wrapped.
//
// Seeding the first turn through a session's initial items avoids
// needing this at all; it is the second and later turns that do.
//
// The second parameter is a struct rather than a growing parameter list
// because this is the package's headline hook: its signature is the one thing
// here that cannot be extended later without breaking every caller. What a
// subscription is worth telling a caller about will grow; see [Subscription].
OnSubscribed func(ctx context.Context, sub Subscription) error
// OnSkippedFrame runs for a frame this build could not decode. The frame is
// dropped and the subscription continues, so one unreadable frame degrades a
// turn rather than ending it — which is also what the Python client does with
// one. Leaving this nil still skips the frame; the hook is how a caller sees
// that it happened.
//
// Nothing this server sends reaches it today. The stream route validates every
// event against its event union before serializing it, and these types are
// generated from that union's published schema, so a frame that made it onto
// the wire has already been proven to fit. What reaches it is spec drift: a
// field whose type changed under a client generated from an older schema. An
// unrecognised event *type* is not that case — that surfaces as
// [UnknownEvent] and is not an error at all.
//
// Like [StreamOptions.OnSubscribed] it runs on the caller's goroutine with
// the idle monitor suspended, so it must return promptly. Returning an error
// ends the stream with that error wrapped, which is how a caller opts back
// into treating an undecodable frame as fatal.
//
// A transport failure is a different thing and stays terminal: this hook sees
// frames that arrived and could not be read, never a stream that stopped
// arriving.
OnSkippedFrame func(ctx context.Context, frame SkippedFrame) error
}
StreamOptions configures one call to Client.Stream.
type Subscription ¶
type Subscription struct {
// SessionID is the session this stream is subscribed to, so a hook shared
// between streams does not need to close over it.
SessionID string
// Idle mirrors [StreamOptions.Idle]: whether this subscriber declared itself
// present but inattentive.
Idle bool
}
Subscription describes the live subscription an StreamOptions.OnSubscribed hook was called for.
Fields will be added to it. Construct one with field names — go vet's composites check enforces that for a struct from another package — and it stays source-compatible as it grows.
type TerminalCommandData ¶
type TerminalCommandData = api.TerminalCommandData
TerminalCommandData is the data payload for a runner-side terminal command (!cmd) observed in a harness transcript (today: Claude Code's embedded TUI).
type TextChunk ¶ added in v0.2.0
type TextChunk struct {
Text string
// contains filtered or unexported fields
}
TextChunk is a flushed piece of streamed assistant text.
func (TextChunk) Context ¶ added in v0.2.0
func (b TextChunk) Context() BlockContext
type TextDone ¶ added in v0.2.0
type TextDone struct {
// FullText is the accumulated text of the section.
FullText string
// HasCodeBlocks reports a fenced code block in FullText, so a renderer can
// choose a monospace treatment without scanning again.
HasCodeBlocks bool
// contains filtered or unexported fields
}
TextDone is the complete text of one text section.
A caller rendering live output reads TextChunk and ignores this; one rendering a finished transcript reads this and ignores the chunks. Both arrive, so neither has to accumulate.
func (TextDone) Context ¶ added in v0.2.0
func (b TextDone) Context() BlockContext
type ToolCallEndCtx ¶ added in v0.2.0
type ToolCallEndCtx struct {
Name string
CallID string
Output string
// Err is set when this client ran the tool and it failed. Nil for a
// server-executed call, whose failure arrives as its output.
Err error
}
ToolCallEndCtx describes a tool call's output arriving.
type ToolCallInfo ¶ added in v0.2.0
type ToolCallInfo struct {
// Name is the tool the agent called.
Name string
// Arguments are the call's decoded arguments, or nil when the agent sent none
// or they did not decode.
Arguments map[string]any
// CallID identifies the call, and is what the result is posted against.
CallID string
// ItemID is the conversation item the call arrived on, or empty when the
// server sent none.
ItemID string
}
ToolCallInfo describes one tool call the agent asked the client to run.
It carries what the streamed item carries and nothing more. A response id and an agent name would both be useful and neither is on this wire: the call's payload declares call_id, name, arguments and model. Upstream draws the same line — its sessions-API tool info carries only what an item does.
StreamHooks.OnToolCallStart still reports the agent name, because an observer wants everything the item offers. A tool does not need it to answer, and it is a field the server chooses, so it is not identity.
type ToolCallStartCtx ¶ added in v0.2.0
type ToolCallStartCtx struct {
Name string
CallID string
AgentName string
Arguments map[string]any
// ExecutedBy is "server" or "client".
ExecutedBy string
}
ToolCallStartCtx describes a tool call the agent made.
type ToolExecution ¶ added in v0.2.0
type ToolExecution struct {
// Name is the tool called, e.g. "Read".
Name string
// Arguments are the call's decoded arguments. Empty when the server sent none
// or when they did not decode.
Arguments map[string]any
// ArgsSummary is a one-line rendering of Arguments for a compact display, e.g.
// "test.py". See [FormatToolArgsBrief].
ArgsSummary string
// CallID identifies the call, and is what pairs a result to it.
CallID string
// AgentName is the agent that invoked the tool.
AgentName string
// ExecutedBy is "server" or "client". A client-executed call is one this SDK
// ran on the caller's behalf; see [ToolRegistry].
ExecutedBy string
// Output is the tool's output, or nil while the call is still outstanding. A
// pointer rather than an empty string, because a tool that legitimately returns
// nothing is not a tool still running.
Output *string
}
ToolExecution is one tool call paired with its result.
Not a Block: it is what a ToolGroup holds, one per group today.
type ToolFunc ¶ added in v0.2.0
type ToolFunc func(context.Context, ToolCallInfo) (string, error)
ToolFunc runs one client-side tool call.
The returned string is posted to the server as the call's output. An error is posted too, as the output, because the server is parked waiting for one: a tool that fails still has to answer, or the turn never resumes. The error also reaches the caller, so a failure is visible rather than only recorded in a transcript.
type ToolGroup ¶ added in v0.2.0
type ToolGroup struct {
// Executions are the calls in this group, in the order the server reported them.
Executions []ToolExecution
// contains filtered or unexported fields
}
ToolGroup is a batch of tool calls, which today always holds one.
The server delivers one item per call and nothing in the item says which calls were issued together, so the fold cannot group them. Range Executions rather than indexing it: a renderer that assumes one draws nothing on the day the wire starts grouping. Upstream's client carries the same list and fills it the same way, so the shape is theirs rather than a guess.
func (ToolGroup) Context ¶ added in v0.2.0
func (b ToolGroup) Context() BlockContext
type ToolOutputDeltaEvent ¶
type ToolOutputDeltaEvent api.ToolOutputDeltaEvent
ToolOutputDeltaEvent is the wire event "response.function_call_output.delta".
Incremental output from an in-progress function call.
func (ToolOutputDeltaEvent) EventType ¶ added in v0.2.0
func (e ToolOutputDeltaEvent) EventType() string
type ToolRegistry ¶ added in v0.2.0
type ToolRegistry struct {
// contains filtered or unexported fields
}
ToolRegistry holds the tools a client will run on the agent's behalf.
A registry is built before a turn and read during it. Register every tool first; a registry is not safe to modify while a turn is reading it.
func NewToolRegistry ¶ added in v0.2.0
func NewToolRegistry() *ToolRegistry
NewToolRegistry returns an empty registry.
func (*ToolRegistry) Names ¶ added in v0.2.0
func (r *ToolRegistry) Names() []string
Names lists the registered tools, sorted, for a diagnostic that has to say what was available.
func (*ToolRegistry) Register ¶ added in v0.2.0
Register adds a tool under a name, with the schema the server advertises it by.
The schema is the caller's: this package does not derive one from a Go signature, because a derived schema is a second statement of the tool's contract that drifts from the first. Pass the same object the session was created with.
Registering a name twice replaces it, so a caller can override a default.
func (*ToolRegistry) Schemas ¶ added in v0.2.0
func (r *ToolRegistry) Schemas() []map[string]any
Schemas returns the registered schemas, for passing to session creation.
type ToolResultBlock ¶ added in v0.2.0
type ToolResultBlock struct {
Name string
CallID string
AgentName string
Output string
Arguments map[string]any
ArgsSummary string
// contains filtered or unexported fields
}
ToolResultBlock is one tool's result, emitted when the tool finishes.
Carries the call's arguments as well as its output, so a caller rendering results alone still has the call's metadata and does not have to correlate against an earlier ToolGroup.
func (ToolResultBlock) Context ¶ added in v0.2.0
func (b ToolResultBlock) Context() BlockContext
type Transform ¶ added in v0.2.0
Transform wraps a block sequence, so a caller changes what it renders without changing how it reads.
A transform is a function rather than an interface because every one of them is one operation over a sequence, and a caller composes them with Pipe.
func MergeTextAcrossIterations ¶ added in v0.2.0
func MergeTextAcrossIterations() Transform
MergeTextAcrossIterations joins the TextDone blocks of one response into one.
A response finishes a text section per message item, and a tool loop produces several, so a caller rendering a finished transcript otherwise draws the answer in as many pieces as the agent spoke. This reports it once.
It flushes at each ResponseEndBlock, which is one per turn. On a sequence spanning several turns that is one answer per turn, which is usually what a caller wants; SkipIntermediateEnds first would join them into one.
TextChunk passes through untouched, so live rendering is unaffected. A sequence that ends with no terminal response still reports what it gathered, using the context of the text rather than of an end that never came.
func OnlyAgent ¶ added in v0.2.0
OnlyAgent keeps the blocks one agent produced.
An empty name keeps every block, so a caller can pass a configured value through without branching on whether it was set.
func OnlyBlocks ¶ added in v0.2.0
OnlyBlocks keeps the blocks a predicate selects and drops the rest.
The complement of SkipBlocks, for the case where the wanted set is smaller than the unwanted one. Errors pass through for the same reason.
func SkipBlocks ¶ added in v0.2.0
SkipBlocks drops the blocks a predicate selects.
A predicate rather than a list of types, because Go has no variadic type parameter and a list of reflect.Type would move the mistake from compile time to run time. IsBlock builds the common case:
Pipe(seq, SkipBlocks(IsBlock[ReasoningBlock], IsBlock[ReasoningChunk]))
An error is never dropped. A transform that swallowed one would leave a caller reading a truncated turn as a complete one.
func SkipIntermediateEnds ¶ added in v0.2.0
func SkipIntermediateEnds() Transform
SkipIntermediateEnds keeps only the last ResponseEndBlock of a sequence.
One turn reaches one terminal response, so this is a no-op on a single turn. It is for a sequence that carries several — a Client.Stream read across more than one turn, or a replayed transcript — where a caller rendering every end draws the work as finished several times.
Each end is held back until something follows it: a block after an end proves that end was intermediate, and the end still held when the sequence stops is the real one.
The cost is that the final end arrives only when the sequence ends, which for a live stream is the same moment.
type TreeNode ¶ added in v0.2.0
type TreeNode struct {
// Session is the summary the parent's listing carried. Zero for the root,
// which no listing describes.
Session ChildSessionSummary
// ID is the session this node describes.
ID string
// Depth is how far below the root this node sits. Zero for the root.
Depth int
// Children are the nodes below this one.
Children []*TreeNode
// Truncated reports that this node has children the walk did not fetch,
// because it reached [TreeOptions.MaxDepth] or the node ceiling. Raise the
// bound and walk again to see them.
Truncated bool
// Repeated reports that this session already appears between here and the
// root, so the walk stopped rather than following a cycle. Its children are
// whatever they are at the shallower position.
Repeated bool
// Err is this node's own listing failure, so a caller can tell a subtree that
// is empty from one that could not be read. The walk continues past it, and
// [Sessions.ChildrenTree] also returns the first such error.
Err error
}
TreeNode is one session in a subtree walk, with the children the walk reached.
A node with no children has one of four reasons, and a caller usually needs to know which: it has none, the walk stopped at the depth cap, the session already appears on the path above it, or its listing failed. The three fields below name the last three; all false with no children means it genuinely has none.
type TreeOptions ¶ added in v0.2.0
type TreeOptions struct {
// MaxDepth caps how far the walk descends. Zero means three levels.
//
// A cap rather than an option to omit: a session tree is server-shaped, so a
// caller cannot know its depth before walking it, and an unbounded walk on a
// deep tree is a request storm the caller did not ask for.
MaxDepth int
// Concurrency caps how many child-listing requests are in flight at once.
// Requests rather than listings, so one node paging many times cannot hold a
// slot for its whole drain. Zero means four.
Concurrency int
// MaxNodes caps how many sessions the walk will visit. Zero means one
// thousand.
//
// The walk spawns one goroutine per node, so without this the tree's shape
// decides the process's memory. It also bounds the one cost of per-path cycle
// detection: a session reachable through two parents is expanded under both,
// which is true to the topology and doubles the work for that subtree.
MaxNodes int
}
TreeOptions bounds a subtree walk. The zero value uses the defaults below.
type Turn ¶ added in v0.2.0
type Turn struct {
// contains filtered or unexported fields
}
Turn is one prompt and the turn it produces. Single-use.
Obtained from Chat.Prompt and read exactly once. A second read is refused rather than posting the prompt again, because posting twice is a second turn the caller did not ask for and the server would answer both.
type TurnCancelledEvent ¶
type TurnCancelledEvent api.TurnCancelledEvent
TurnCancelledEvent is the wire event "turn.cancelled".
Emitted when a turn is interrupted by the user or system.
func (TurnCancelledEvent) EventType ¶ added in v0.2.0
func (e TurnCancelledEvent) EventType() string
type TurnCompletedEvent ¶
type TurnCompletedEvent api.TurnCompletedEvent
TurnCompletedEvent is the wire event "turn.completed".
Emitted when a turn finishes successfully with no pending work.
func (TurnCompletedEvent) EventType ¶ added in v0.2.0
func (e TurnCompletedEvent) EventType() string
type TurnEnd ¶ added in v0.2.0
type TurnEnd int
TurnEnd selects which signal ends a turn.
Two harness families put the end in different places, and reading the wrong one is not a stylistic difference: it decides whether a caller reads a complete answer or a half-written one.
const ( // TurnEndsOnIdleStatus ends a turn on an idle status edge that names a response. // // For the harness family that drives a real terminal, where a response terminal // means only that the prompt reached the harness, so a terminal there is not the // answer. // // This is the zero value on purpose. A harness this package does not recognise // gets the stricter rule, because the two mistakes do not cost the same: taking // a lifecycle terminal early hands the caller a partial answer it believes is // whole, while waiting for an edge that never comes blocks the read. // // Blocks, not times out. The server's heartbeat keeps the stream's idle monitor // fed, so the wrong choice here produces no error of its own while the stream // stays up. Give [Chat.Send] a context with a deadline, sized to the longest // turn this agent may legitimately take rather than to an RPC. // // Without one the read blocks until the stream itself ends, and a deployment // that caps stream duration then reports [ErrTurnIncomplete] — which says // nothing about this option being the wrong one. // // The symptom is a turn that never ends while the session's status events carry // no response id. That means the harness is in-process, and // [TurnEndsOnResponseLifecycle] is the rule it wants. TurnEndsOnIdleStatus TurnEnd = iota // TurnEndsOnResponseLifecycle ends a turn on response.completed, // response.failed, response.incomplete or response.cancelled. // // For an in-process harness, where the status edges carry no response id and // the idle edge therefore never arrives. TurnEndsOnResponseLifecycle )
type TurnFailedEvent ¶
type TurnFailedEvent api.TurnFailedEvent
TurnFailedEvent is the wire event "turn.failed".
Emitted when a turn fails due to an LLM error, timeout, or crash.
func (TurnFailedEvent) EventType ¶ added in v0.2.0
func (e TurnFailedEvent) EventType() string
type TurnOptions ¶ added in v0.2.0
type TurnOptions struct {
// End selects the signal that ends the turn. The zero value is the stricter
// rule; see [TurnEndsOnIdleStatus].
End TurnEnd
// PriorResponseIDs are the responses already on the session before this turn's
// prompt was posted.
//
// Supplying them is what separates this turn's end from an older one's. A
// response that was live before the prompt goes on running server-side and can
// reach a terminal inside this turn's window; its event is otherwise
// indistinguishable from this turn's, and taken as this turn's it ends the read
// early against another turn's reply.
//
// Read once, at the start. Build it from [Sessions.Get]:
// [SessionResponse.ActiveResponseID] names the response in flight, which is the
// one that can reach a terminal inside this turn's window. A session with
// nothing in flight needs no entry.
//
// Only an entry whose value is true counts, so a map built with false values
// gives no protection.
PriorResponseIDs map[string]bool
}
TurnOptions configures how the SDK decides one turn has ended.
Which harness a session runs is the caller's knowledge, not this package's, so TurnOptions.End is a value the caller supplies rather than a name this package maps. The rules it selects are here.
type TurnStartedEvent ¶
type TurnStartedEvent api.TurnStartedEvent
TurnStartedEvent is the wire event "turn.started".
Emitted when the runner starts a new turn for a session.
func (TurnStartedEvent) EventType ¶ added in v0.2.0
func (e TurnStartedEvent) EventType() string
type UnknownEvent ¶
type UnknownEvent struct {
// Type is the frame's discriminator, e.g. "session.something.new".
Type string
// Raw is the frame's JSON payload, owned by the caller.
Raw []byte
}
UnknownEvent carries a frame whose discriminator this build does not know.
It is not an error. The server's event schemas ignore unknown fields by contract so a new field cannot break an older parser, and this is the same guarantee one level up: a client built against an older openapi.json surfaces a newly added event type here and keeps streaming.
func (UnknownEvent) EventType ¶ added in v0.2.0
func (e UnknownEvent) EventType() string
type UpdateSessionRequest ¶ added in v0.1.2
type UpdateSessionRequest = api.UpdateSessionRequest
UpdateSessionRequest is the request body for PATCH /v1/sessions/{id}.
type UsageDetails ¶
type UsageDetails = api.UsageDetails
UsageDetails is the breakdown of output token usage.
type ValidationError ¶
type ValidationError struct {
// Loc is the path to the offending field. Its elements are strings and
// integers mixed, because a path crosses object keys and array indices, so
// it is []any rather than a generated wrapper type per position.
Loc []any `json:"loc"`
// Msg is the human-readable problem with that field.
Msg string `json:"msg"`
// Type is the validator's own name for the failure, e.g. "string_type".
Type string `json:"type"`
// Ctx carries validator-specific context. Nil when absent.
Ctx map[string]any `json:"ctx,omitempty"`
// Input is the value that failed validation. Nil when absent.
Input any `json:"input,omitempty"`
}
ValidationError is one entry from a 422's per-field detail list.