Documentation
¶
Overview ¶
Package common is the memcode PROTOCOL contract: the wire types shared by the CLI (the client) and the api gateway (the server), the sdk/agent ↔ CLI stream-json envelope, the abstract Intent, shared error sentinels, and the model/pricing metadata both ledgers price against.
It is the single source of truth for everything that crosses a memcode wire. Both the cli and api modules redeclared these types independently once, and the drift caused real outages (tool input_schema, lane bypass) — this package exists so that can never happen again.
Invariant: common holds the PROTOCOL only and is stdlib-only (zero third-party dependencies). It contains no prompt doctrine, no provider keys, no routing logic, and no capability interfaces — those live at their consuming boundary (the cli and api each declare the structural interface they need; a client implementation merely satisfies them). A guard test enforces the stdlib-only rule.
Index ¶
- Constants
- Variables
- type AskRequestData
- type AskResponseData
- type AssistantDeltaData
- type Block
- type CacheControl
- type Effort
- type Envelope
- type ErrorData
- type InitializeData
- type InitializedData
- type Intent
- type MediaSource
- type Message
- type PermissionRequestData
- type PermissionResponseData
- type Request
- type Response
- type ResultData
- type RoutingHint
- type SessionStateData
- type StreamHandler
- type ToolCallData
- type ToolDef
- type ToolResultData
- type UsageData
- type UserTurnData
Constants ¶
const ( // client → CLI (control) MsgInitialize = "initialize" // first message; configure the session MsgUserTurn = "user_turn" // submit a user prompt (starts a turn) MsgPermissionResponse = "permission_response" // answer a permission_request MsgAskResponse = "ask_response" // answer an ask_request MsgCancel = "cancel" // interrupt the running turn // CLI → client (events) MsgInitialized = "initialized" // session ready; carries session id MsgAssistantDelta = "assistant_delta" // a chunk of assistant text MsgToolCall = "tool_call" // a tool is running MsgToolResult = "tool_result" // a tool finished (summary) MsgPermissionRequest = "permission_request" // the turn is blocked awaiting approval MsgAskRequest = "ask_request" // the turn is blocked awaiting a user choice MsgSessionState = "session_state" // busy/idle + room/mode telemetry MsgUsage = "usage" // running token counts MsgResult = "result" // a turn finished MsgError = "error" // a turn errored )
Message families. The direction comments are from the wrapper's point of view.
const CodeAccountLocked = "account_locked"
CodeAccountLocked is the gateway's machine-readable error code for an org locked by a negative balance (HTTP 402), mapped to ErrAccountLocked.
const CodeByokKeyFailed = "byok_key_failed"
CodeByokKeyFailed is the gateway's machine-readable error code for a turn killed by the user's own provider key (HTTP 422 body and the SSE error event), mapped to ErrByokKeyFailed.
const CodeContextOverflow = "context_overflow"
CodeContextOverflow is the gateway's machine-readable error code for a context-window overflow — carried on the HTTP 413 body and the SSE error event, mapped to ErrContextOverflow by the client.
const CodeInsufficientCredit = "insufficient_credits"
CodeInsufficientCredit is the gateway's machine-readable error code for an exhausted credit wallet — carried on the HTTP 402 body and the SSE error event, mapped to ErrInsufficientCredit by the client.
const CodeSubscriptionRequired = "subscription_required"
CodeSubscriptionRequired is the gateway's machine-readable error code for an org with no active subscription (HTTP 402), mapped to ErrSubscriptionRequired.
const StreamJSONVersion = "1"
StreamJSONVersion is the protocol version stamped on every Envelope. Bump on a breaking change to the message families or payloads.
Variables ¶
var ErrAccountLocked = errors.New("account locked — your balance is negative; add credits at memcode.ai/account/billing")
ErrAccountLocked is returned when the org's balance went negative from a post-hoc debit overrun — the gateway returns 402 with code "account_locked". Everything is refused, BYOK included, until credits are added. Never retried.
var ErrByokKeyFailed = errors.New("your API key was rejected — fix or remove it with /apikeys")
ErrByokKeyFailed is returned when the turn died on the USER's own provider key (fail-the-turn doctrine: a failing BYOK key is never absorbed onto memcode's keys) — the gateway returns 422 with code "byok_key_failed". Never retried: the fix is /apikeys, not another attempt.
var ErrContextOverflow = errors.New("context window overflow")
ErrContextOverflow is returned when a turn's prompt exceeds the served context window on EVERY available backend (the gateway already absorbs vLLM overflow up to Anthropic; this fires only when even Anthropic's window is exceeded). The CLI runtime watches for it with errors.Is and compacts-then-retries the turn rather than surfacing a raw error — the reactive end of the routing ladder. On the wire it is signalled by HTTP 413 with code "context_overflow" and by an SSE error event carrying the same code.
var ErrInsufficientCredit = errors.New("credits exhausted — add credits at memcode.ai/account/billing")
ErrInsufficientCredit is returned when the org's prepaid credit balance is exhausted — the gateway returns 402 with code "insufficient_credits". The CLI surfaces a friendly "add credits" message and stops the turn (no retry: buying credits is a user action, not a transient failure).
var ErrStreamIncomplete = errors.New("gateway stream ended without a response")
ErrStreamIncomplete is returned when an SSE stream ends WITHOUT a terminal response event (a mid-stream read error, or the connection closing early — e.g. a Cloud Run request-timeout cutting off a long model call). It is a transient TRANSPORT failure, not a model/logic error: the failed call appended nothing, so the runtime can retry it from the same history. Watched with errors.Is and retried (bounded) rather than dying mid-turn with work half-done.
var ErrSubscriptionRequired = errors.New("subscription required — choose a plan at memcode.ai/account/billing")
ErrSubscriptionRequired is returned when the org has no active subscription — the gateway returns 402 with code "subscription_required". Subscription is mandatory for every LLM call, BYOK included (2026-07-26). Never retried: choosing a plan is a user action.
ErrUnauthorized is returned when the gateway rejects the bearer token (HTTP 401): expired, revoked, or the retired legacy static token. It means the SESSION is signed out, not that the turn transiently failed — hosts watch with errors.Is, flip to their signed-out state, and prompt for /login instead of surfacing a raw HTTP error. Never retried.
Functions ¶
This section is empty.
Types ¶
type AskRequestData ¶
type AskRequestData struct {
Question string `json:"question"`
Options []string `json:"options,omitempty"`
}
AskRequestData / AskResponseData are the ask_user human-in-the-loop round-trip.
type AskResponseData ¶
type AskResponseData struct {
Answer string `json:"answer"`
}
type AssistantDeltaData ¶
type AssistantDeltaData struct {
Text string `json:"text"`
}
AssistantDeltaData is a chunk of streamed assistant text (CLI → client).
type Block ¶
type Block struct {
Type string `json:"type"` // "text" | "image" | "document" | "tool_use" | "tool_result"
Text string `json:"text,omitempty"`
// image / document
Source *MediaSource `json:"source,omitempty"`
// tool_use
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Input json.RawMessage `json:"input,omitempty"`
// tool_result
ToolUseID string `json:"tool_use_id,omitempty"`
Content string `json:"content,omitempty"`
IsError bool `json:"is_error,omitempty"`
// ContentBlocks carries STRUCTURED content for a tool_result — one or more
// content blocks (text and/or image) instead of the flat Content string. This
// is the path for tool results that include vision (e.g. a browser screenshot
// returned to the model as an image block). When non-empty, providers emit the
// structured content union (Anthropic: text+image parts in the tool_result;
// OpenAI: multi-part tool message). When empty, Content (flat string) is used
// — backwards compatible with every text-only tool result.
ContentBlocks []Block `json:"content_blocks,omitempty"`
// thinking (extended/adaptive reasoning). These MUST round-trip UNMODIFIED: the
// API requires the assistant's thinking blocks to be passed back verbatim on the
// next tool-use turn (it verifies them via Signature) or it rejects the request.
// So we parse them off responses and re-send them; we just don't display them.
// Data carries a redacted_thinking block's opaque payload, if one ever appears.
Thinking string `json:"thinking,omitempty"`
Signature string `json:"signature,omitempty"`
Data string `json:"data,omitempty"`
// prompt caching (set on the wire only, never on shared history — see the gateway)
CacheControl *CacheControl `json:"cache_control,omitempty"`
}
Block is one content block in a message (text, image/document, a tool call, or a tool result, or a thinking block).
func DocumentBlock ¶
DocumentBlock builds a document block from raw file bytes (application/pdf is the interoperable case: Anthropic document source, OpenAI Responses input_file, Gemini inline blob). Models without native document input absorb the turn to a capable tier — the catalog's pdf flag gates that in the router.
func ImageBlock ¶
ImageBlock builds a vision block from raw image bytes.
func ToolResultBlocks ¶
ToolResultBlocks builds a tool_result block carrying structured content blocks (text and/or image) instead of a flat string. This is the path for tool results that include vision — e.g. a browser screenshot returned as an image block. When blocks is empty, falls back to an empty text result.
func (Block) MarshalJSON ¶
MarshalJSON serializes a Block for the wire. Thinking blocks MUST carry the "thinking" field even when its text is EMPTY: adaptive thinking frequently returns a block that is just a signature (the reasoning text omitted), and on the next tool-use turn Anthropic rejects a thinking block without the field — "thinking.thinking: Field required". The struct tag is `omitempty` (correct for every OTHER block type, which must NOT carry a stray empty "thinking"), so we special-case thinking / redacted_ thinking here and let the alias handle the rest with default tags.
type CacheControl ¶
type CacheControl struct {
Type string `json:"type"` // "ephemeral"
// TTL is the cache lifetime: "" (default 5m) or "1h". The long TTL is used for the
// STABLE doctrine+tools prefix, which is byte-identical across turns even as the
// volatile facts (room/personality) change — so it survives interactive gaps >5min.
TTL string `json:"ttl,omitempty"`
}
CacheControl marks a prompt-cache breakpoint. Everything from the start of the prompt up to and including the marked block is cached (5-min ephemeral TTL); later requests sharing that prefix read it instead of re-paying input tokens, and cache reads are excluded from the input-tokens/min rate limit.
type Effort ¶
type Effort string
Effort is an abstract reasoning-depth setting. It exists so call sites express INTENT ("this is a judgment call, think harder") without hardcoding a provider shape that differs by model — e.g. Opus 5 rejects manual budget_tokens and requires adaptive thinking + an effort hint, while Opus 4.5 wants budget_tokens.
type Envelope ¶
type Envelope struct {
Version string `json:"version"`
Type string `json:"type"`
ID string `json:"id,omitempty"` // correlation id (e.g. a permission/ask request↔response)
TurnID string `json:"turn_id,omitempty"` // the turn this message belongs to
Data json.RawMessage `json:"data,omitempty"`
}
Envelope is the single wire frame. Data is a type-specific payload (below).
type ErrorData ¶
type ErrorData struct {
Message string `json:"message"`
}
ErrorData reports a turn-level error (CLI → client).
type InitializeData ¶
type InitializeData struct {
Cwd string `json:"cwd,omitempty"` // working directory / repo root
Mode string `json:"mode,omitempty"` // permission mode: ask | auto | allow-all
Pin string `json:"pin,omitempty"` // pinned model label ("" = Automatic)
}
InitializeData configures the session (client → CLI).
type InitializedData ¶
type InitializedData struct {
SessionID string `json:"session_id"`
Protocol string `json:"protocol"` // echoes the version for handshake validation
}
InitializedData announces a ready session (CLI → client).
type Intent ¶
type Intent struct {
// Purpose is what this turn is FOR (ledger attribution + lane selection):
// "main_loop" | "explore" | "classify" | "compact" | …
Purpose string `json:"purpose,omitempty"`
// Mode names the doctrine the turn runs under ("chat" | "exec" | "plan" |
// …) — plan mode has its own lane branch.
Mode string `json:"mode,omitempty"`
// Reasoning is the abstract reasoning depth the turn warrants (maps to Effort).
Reasoning Effort `json:"reasoning,omitempty"`
// Difficulty is the TIER demand the turn was judged to have — a separate axis
// from Reasoning (thinking depth): a tricky single-file bug can be
// {standard, high-thinking}. Judged by the turn_intent classifier on the
// classify lane: "lookup" (short read-only retrieval → cheap tier) |
// "standard" (ordinary work) | "deep" (repo-scale/architectural/root-cause →
// heavy tier). Empty = unjudged (judge unavailable) — the ladder falls back
// to Reasoning-based escalation.
Difficulty string `json:"difficulty,omitempty"`
// Risk is the session layer's escalation signal — what only it observes:
// "self_heal" | "user_friction_high" | … (folded from RoutingHint.Reason).
Risk string `json:"risk,omitempty"`
// Interactive is true when a live user is present (vs a batch/background
// job) — available to the policy for latency-vs-cost trade-offs.
Interactive bool `json:"interactive,omitempty"`
// Vendor is the per-session strong-tier override: "" = the configured
// default, or one of "openai" | "anthropic" | "gemini" | "grok". It names a
// VENDOR, not a model — the ladder still resolves the tier (frontier/
// balanced/cheap) within that vendor from the catalog's tier triples. Set by
// the CLI's /model selector; BYOK steering may prefer a keyed vendor when
// none is configured.
Vendor string `json:"vendor,omitempty"`
// Pin is the user's explicit model choice from /model: a catalog LABEL
// ("sol", "sonnet", "glm-5p2", …), never a raw provider id — raw ids stay at
// the serving edge. "" = Automatic (the routing doctrine decides). When set
// and valid, every real request serves this model; invisible plumbing
// (classify/compact/shrinkwrap) stays on the utility lanes, and an
// unknown/un-pinnable label falls through to Automatic so a stale pin never
// breaks a session. Pins are never coerced — capability gaps fail typed, not
// silently rerouted.
Pin string `json:"pin,omitempty"`
}
Intent is the abstract selection contract: the session layer expresses WHAT a turn is, and the CLI's selection policy (cli/internal/llm: laneFor + the resolver) maps intent → concrete catalog label. It is a client-internal type now — it never rides a wire. The abstraction survives the policy's move from the gateway because it is what keeps call sites from naming models: purposes and modes are stable vocabulary; labels and tiers are catalog data.
type MediaSource ¶
type MediaSource struct {
Type string `json:"type"` // "base64"
MediaType string `json:"media_type"` // e.g. image/png, application/pdf
Data string `json:"data"`
}
MediaSource carries base64 image/document data for vision blocks.
type Message ¶
type Message struct {
Role string `json:"role"` // "user" | "assistant"
Blocks []Block `json:"content"`
}
Message is one turn (a role plus its content blocks).
type PermissionRequestData ¶
type PermissionRequestData struct {
Title string `json:"title"`
Label string `json:"label,omitempty"`
Detail string `json:"detail,omitempty"`
Command string `json:"command,omitempty"`
Cwd string `json:"cwd,omitempty"`
Risk string `json:"risk,omitempty"`
Editable bool `json:"editable,omitempty"`
}
PermissionRequestData asks the client to approve an action (CLI → client). The client answers with a PermissionResponseData carrying the same Envelope.ID.
type PermissionResponseData ¶
type Request ¶
type Request struct {
Model string `json:"model,omitempty"` // resolved id at the serving edge; clients carry their choice in Pin
System string `json:"system,omitempty"`
Messages []Message `json:"messages"`
// SystemVolatile is the per-turn-variable doctrine suffix (room/personality/
// extra-mile/nudge + the turn-scoped extra) split OUT of System so it sits
// OUTSIDE the cached prefix. The CLI's doctrine composer fills it (directly,
// or via the transport's compose hook from Mode+Facts); adapters place it
// cache-safely — Anthropic as a second, uncached system block, chat/
// completions as a trailing system message, so the stable prefix still
// auto-caches.
SystemVolatile string `json:"system_volatile,omitempty"`
Tools []ToolDef `json:"tools,omitempty"`
MaxTokens int `json:"max_tokens,omitempty"`
// ToolChoice, when set to a tool name, FORCES the model to call that tool — the
// reliable cross-provider path to structured output (Anthropic tool_choice / OpenAI
// tool_choice). Used by the plan reviewer's verdict so the JSON comes back in a tool_use
// block instead of best-effort prose-JSON. "" = the model chooses (the common case).
ToolChoice string `json:"tool_choice,omitempty"`
// Mode + Facts select a doctrine prompt: the transport's compose hook
// (cli/internal/doctrine) renders the mode's doctrine with these gathered
// facts (root, platform, shell, overview/pack, room, nudge) into System/
// SystemVolatile just before encoding. Mode "" = raw transport (no
// composition; System passes through as-is).
Mode string `json:"mode,omitempty"`
Facts map[string]string `json:"facts,omitempty"`
// Effort is the ABSTRACT reasoning-depth knob. The provider maps it to whatever
// thinking control the target model actually supports (adaptive+effort, manual
// budget_tokens, or nothing). Zero value (EffortOff) means no extended thinking.
Effort Effort `json:"effort,omitempty"`
// RoutingHint is the session layer's escalation signal (ROUTING.md): user
// friction/mood, a self-healing retry after the agent's own edit broke, a
// high-risk surface. The Runner folds its Reason into Intent.Risk for the
// selection ladder. nil = no opinion (the common case).
RoutingHint *RoutingHint `json:"routing_hint,omitempty"`
// Purpose + Session are transport-local labels (json:"-": never marshaled on
// this type). Purpose labels the call for the client ledger and the lane
// leak-log. Session rides the compat wire as the standard `user` field for
// serving affinity (prefix-cache locality) and gateway telemetry. Stamped by
// the CLI's llm.Runner; never set by hand.
Purpose string `json:"-"`
Session string `json:"-"`
// Difficulty is the turn_intent judge's tier verdict ("lookup" | "standard"
// | "deep"), a CLI-side selection input (json:"-", read by the CLI's own
// resolution policy — it never rides the wire).
Difficulty string `json:"-"`
// Pin is the CLI-side carrier for the session's model choice: the resolved
// catalog LABEL the transport puts in the wire `model` field. Under
// all-policy-client-side the CLI's selection policy stamps it on EVERY
// call (Automatic is client behavior); "" is only ever seen by test fakes.
Pin string `json:"-"`
// BillingLane is the requested billing lane: "" / "byok_preferred" |
// "byok_only" | "credits". Client-side it is set by policy (the consented
// credits retry after a BYOK key failure) and rides the wire as the
// memcode_billing extension; server-side the compat handler re-stamps it
// from that extension and the gateway ENFORCES it — it never silently
// reroutes between the user's keys and credits. NOT marshaled on this type.
BillingLane string `json:"-"`
}
Request is a model completion request — the provider-neutral shape every adapter encodes from. Model selection is the CLI's job (llm/lane.go + llm/resolve.go): the Runner's policy resolves a catalog label into Pin, and the transport writes it into the wire `model` field. Server-side, Model carries the gateway's raw provider id after the label gate resolves it.
type Response ¶
type Response struct {
StopReason string `json:"stop_reason"` // "end_turn" | "tool_use" | "max_tokens"
Blocks []Block `json:"blocks"` // assistant content (text and/or tool_use)
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
// prompt-cache telemetry (tokens written to / read from the cache this call)
CacheWriteTokens int `json:"cache_write_tokens,omitempty"`
CacheReadTokens int `json:"cache_read_tokens,omitempty"`
// SearchCount is the number of native server-side web searches the serving
// vendor billed on this call (Anthropic usage.server_tool_use.web_search_requests;
// OpenAI/Grok Responses web_search_call output items). Searches carry a
// PER-REQUEST fee on top of tokens — the gateway prices it via
// common.SearchFeeUSD(Backend, SearchCount) when it emits cost_usd.
SearchCount int `json:"search_count,omitempty"`
// ToolOrigin records how a tool call was obtained: "structured" (the model's native
// tool_calls — the intended path) or "salvaged_*" (the gateway recovered it from
// text a brittle parser missed). Surfaced so salvage stays OBSERVABLE, never silently
// the main contract.
ToolOrigin string `json:"tool_origin,omitempty"`
// Serving telemetry — the ledger's ground truth for WHO served this call.
// Model is the model that actually ran (a fallback-chain hop can differ from
// the requested label). Backend is the serving vendor: "cheap" (the hosted
// cheap lane, vendor-neutral — the inference vendor never rides the wire) |
// "anthropic" | "openai" | "gemini" | "grok" | "unknown" (label missing from
// the client catalog); "vllm" is the legacy cheap-lane tag an un-redeployed
// gateway sends (clients accept both). FallbackReason names why the served
// model differs from the primary choice: a client-side absorb ("vision" |
// "pdf" | "window") or a recovery hop ("model_error: …").
Model string `json:"model,omitempty"`
Backend string `json:"backend,omitempty"`
FallbackReason string `json:"fallback_reason,omitempty"`
// RequestedModel is the label the selection policy chose BEFORE any absorb/
// fallback — the basis for the ledger's counterfactual "what the primary
// would have cost". The CLI's finalize stamps it (the gateway echoes the
// requested id on hosted turns); it equals Model when nothing intervened.
RequestedModel string `json:"requested_model,omitempty"`
// ContextWindow is the serving backend's real input window in tokens, when
// the backend reports one (0 when the client's catalog already knows it).
ContextWindow int `json:"context_window,omitempty"`
// InputBudget is the serving lane's usable INPUT budget (window − output reserve −
// safety margin) — the size a prompt must fit under to land on THIS lane. The CLI
// learns the cheap lane's budget from this and aims compaction at it. 0 when not
// lane-served.
InputBudget int `json:"input_budget,omitempty"`
// Pool is the cheap lane's served-model short label ("glm-5p2" | "kimi-k3"),
// the CLI's ⇄ ServedBy tag for lane serves. EstimatedPromptTokens is the
// server's pre-call size estimate (stamped per serve so the usage log's
// EstimateRatio can calibrate the estimator against real InputTokens).
Pool string `json:"pool,omitempty"`
EstimatedPromptTokens int `json:"estimated_prompt_tokens,omitempty"`
// BYOK telemetry. BYOK (whether this call ran on the user's OWN provider
// key) rides the wire — it is the key owner's own information, shown by the
// CLI footer strictly per-turn. BYOKVendor stays SERVER-INTERNAL (json:"-"):
// the cheap lane's inference vendor never leaves the server (SanitizeResponse
// zeroes it defensively). The gateway reads both for metering (usage line +
// zero-debit policy) before sanitizing.
BYOK bool `json:"byok,omitempty"`
BYOKVendor string `json:"-"`
}
Response is a model completion result, plus the serving telemetry the footer/ ledger read. On the hosted backend the gateway stamps the serving facts (via the memcode response extension); in direct-endpoint mode the CLI's recovery policy stamps them itself (finalize) — same fields, one reader.
type ResultData ¶
type ResultData struct {
Text string `json:"text,omitempty"` // final assistant text, if captured
Completed bool `json:"completed"`
}
ResultData ends a turn (CLI → client).
type RoutingHint ¶
type RoutingHint struct {
Reason string `json:"reason,omitempty"`
}
RoutingHint carries the escalation signal only the CLI can observe — the Risk input to the CLI's OWN semantic ladder (cli/internal/llm/lane.go): "self_heal" | "agent_frontier" | "agent_strong" | "user_friction_high" | "high_risk_surface" | the plan escalation reasons. It never rides any wire; it is a request-local field the Runner's selection policy reads. (The old PreferredPath/Force fields were read by nothing — deleted.)
type SessionStateData ¶
type SessionStateData struct {
Busy bool `json:"busy"`
Mode string `json:"mode,omitempty"` // room mode (normal/repair/replan), when known
}
SessionStateData reports busy/idle + light room telemetry (CLI → client).
type StreamHandler ¶
StreamHandler receives incremental events during a streamed completion. Both callbacks are optional. Text fires for each chunk of assistant text as it arrives; Usage fires when the API reports token counts (input at the start, authoritative output near the end — NOT per delta, so a live "tokens so far" display must estimate from streamed text between Usage calls and reconcile when Usage fires).
This is a data type (a struct of callbacks), part of the wire contract's shape. The capability INTERFACES (ModelProvider, Streamer, WebSearcher, WebFetcher, Advisor) are intentionally NOT declared here — each consumer declares the structural interface it needs, referencing these common types; an implementation merely satisfies it.
type ToolCallData ¶
type ToolCallData struct {
Name string `json:"name"`
Target string `json:"target,omitempty"`
Detail string `json:"detail,omitempty"`
}
ToolCallData / ToolResultData report tool activity (CLI → client).
type ToolDef ¶
type ToolDef struct {
Name string `json:"name"`
Description string `json:"description"`
InputSchema map[string]any `json:"input_schema"`
// prompt caching — set on the LAST tool only, on the wire (see the gateway)
CacheControl *CacheControl `json:"cache_control,omitempty"`
}
ToolDef describes a tool the model may call.
type ToolResultData ¶
type UsageData ¶
type UsageData struct {
OutputTokens int `json:"output_tokens"`
}
UsageData reports running token counts (CLI → client).
type UserTurnData ¶
type UserTurnData struct {
Text string `json:"text"`
}
UserTurnData submits a prompt (client → CLI).