Documentation
¶
Overview ¶
Package core holds AgentKit's canonical vocabulary and every interface seam. Machinery lives elsewhere; core holds declarations, the small pure functions that are part of the contract, and the EventStream.
core imports only schema. Nothing in AgentKit below the root package may import the root package, which is what keeps anything that needs an *Agent out of core.
Index ¶
- Constants
- Variables
- func BatchTerminates(votes []bool) bool
- func MarshalEvent(e Event, enc MessageEncoder) ([]byte, error)
- func ReportUsage(ctx context.Context, u Usage)
- func ShouldIterate(m *AssistantMessage) bool
- func StreamChannel(s *EventStream) <-chan StreamEvent
- func WithNestedCaller(ctx context.Context, c NestedCaller) context.Context
- func WithUsageReporter(ctx context.Context, report func(Usage)) context.Context
- type API
- type AfterToolCall
- type AfterToolCallContext
- type AfterToolCallDecision
- type AgentDoneEvent
- type AgentStartEvent
- type AssistantMessage
- type BeforeToolCall
- type BeforeToolCallContext
- type BeforeToolCallDecision
- type BlockType
- type CacheRetention
- type Content
- type ContentBlock
- type Effort
- type ErrorEvent
- type Event
- type EventClass
- type EventStream
- func (s *EventStream) End(res StreamResult)
- func (s *EventStream) Err() error
- func (s *EventStream) Events() iter.Seq[Event]
- func (s *EventStream) Next() (Event, bool)
- func (s *EventStream) Push(e Event)
- func (s *EventStream) Result() *AssistantMessage
- func (s *EventStream) RunResult() (RunResult, error)
- func (s *EventStream) Wait() StreamResult
- type EventType
- type ExecutionMode
- type Message
- type MessageEncoder
- type MessageEndEvent
- type MessageStartEvent
- type MessageUpdateEvent
- type Messages
- type Model
- type NestedCaller
- type NoToolsMode
- type PreparedArguments
- type ProviderClient
- type Request
- type ResultHandler
- type Role
- type RunResult
- type RunStopReason
- type StopReason
- type StreamEvent
- type StreamOptions
- type StreamResult
- type TextBlock
- type TextDeltaEvent
- type TextEndEvent
- type TextStartEvent
- type ThinkingBlock
- type ThinkingDeltaEvent
- type ThinkingEndEvent
- type ThinkingKind
- type ThinkingStartEvent
- type Tool
- type ToolCallEndEvent
- type ToolCallStartEvent
- type ToolChoice
- type ToolExecutionEndEvent
- type ToolExecutionStartEvent
- type ToolExecutionUpdateEvent
- type ToolHandler
- type ToolInputDeltaEvent
- type ToolMetadata
- type ToolPolicy
- type ToolResult
- type ToolResultEvent
- type ToolResultMessage
- type ToolUseBlock
- type ToolWire
- type TurnEndEvent
- type TurnStartEvent
- type Usage
- type UsageField
- type UsageJSON
- type UsageWire
- type UserMessage
Constants ¶
const BlockErrorCode = "blocked_by_policy"
const MinMaxPendingBytes int64 = 1 << 20
MinMaxPendingBytes is the floor for StreamOptions.MaxPendingBytes.
A floor of "the largest single event AgentKit can emit" is not computable: the largest event is a ToolResultEvent bounded only by the tool output cap, which is configured elsewhere and can be raised after the stream is created. A documented constant is a promise something can keep.
Variables ¶
var ( ErrMaxTurns = errors.New("agentkit: maximum turns reached") ErrBudgetExceeded = errors.New("agentkit: budget exceeded") ErrToolRejected = errors.New("agentkit: tool call rejected") ErrRefusal = errors.New("agentkit: model refused the request") // ErrBusy: a conflicting operation was attempted while a turn was in // flight. The caller may retry; it is never queued. ErrBusy = errors.New("agentkit: agent is busy") // ErrAborted: the run stopped on a cancelled context, or a provider ended // its stream as aborted. On a cancellation the run's error wraps it // beside the context's own error, so errors.Is matches either. ErrAborted = errors.New("agentkit: run aborted") ErrStreamOverrun = errors.New("agentkit: stream consumer overrun") // ErrNoNestedCaller: CallNested was used where no NestedCaller is // attached — a tool that declares no ReachableTools, or one invoked by // something that is neither an agent run nor a Runner. ErrNoNestedCaller = errors.New( "agentkit: no nested caller on the context; only a tool that declares ReachableTools can call other tools") // ErrTerminated: an interceptor voted to end the run during a nested // call. The wrapper should stop and return; its result carries the vote // to the batch whatever it returns. ErrTerminated = errors.New("agentkit: run terminated by an interceptor during a nested call") // Raised by the loop / session layer. ErrNotContinuable = errors.New("agentkit: transcript is not continuable") ErrSessionExists = errors.New("agentkit: session log already exists at path") // ErrSessionNotEmpty makes the forbidden "construct, then patch the model // onto it" resume a runtime failure on the first line rather than a style // violation. ErrSessionNotEmpty = errors.New( "agentkit: session store is not empty; resume with LoadSession + NewAgentFromSession") // ErrUnguardedExecute: a toolset that reaches a shell tool (execute, // run_command) with no Guard is refused by agentkit.NewRunner, and so by // agentkit.New, before any request. A headless embedder with no policy // would otherwise hand the model an unrestricted shell by omission. // Supply an interceptor — guard.Restricted is the shipped starting point // — or pass guard.AllowAll to say so explicitly. ErrUnguardedExecute = errors.New( "agentkit: a shell tool is reachable but Toolset.Guard is nil; " + "supply an interceptor (guard.Restricted is the shipped starting point) " + "or guard.AllowAll to opt out explicitly") )
Sentinel errors, all comparable with errors.Is.
Functions ¶
func BatchTerminates ¶
BatchTerminates is a batch's vote to end the run: an AND over finalized results. An empty batch never terminates. OR semantics are the obvious-but-wrong default: the model emits N parallel calls, and a unilateral finish would compute N-1 results it never sees.
func MarshalEvent ¶
func MarshalEvent(e Event, enc MessageEncoder) ([]byte, error)
MarshalEvent encodes e as its discriminated JSON form.
func ReportUsage ¶
ReportUsage hands u to the context's usage reporter, if there is one. Code that spends on the agent's behalf outside the loop — a custom Summarizer, a transform that calls a model — reports what each call cost through it. Without a reporter it does nothing.
func ShouldIterate ¶
func ShouldIterate(m *AssistantMessage) bool
ShouldIterate is ExtractToolUse's boolean form. The loop calls this; nothing in the loop compares a StopReason to decide whether to continue.
func StreamChannel ¶
func StreamChannel(s *EventStream) <-chan StreamEvent
StreamChannel adapts an EventStream to a provider's channel: every event, then a last item carrying the stream's error, and its result message too when that was not the last event. The consumer must drain the channel.
func WithNestedCaller ¶
func WithNestedCaller(ctx context.Context, c NestedCaller) context.Context
WithNestedCaller attaches c to ctx. An agent run or a Runner does this for every tool that declares ReachableTools before calling it, so a tool implementation never constructs a NestedCaller itself.
func WithUsageReporter ¶
WithUsageReporter returns a context whose ReportUsage calls go to report. The agent installs one around its context transform, so a model call made off the loop — a compaction summary — is counted in Agent.Usage, and so seen by StopPolicy budgets, like the turns it makes room for.
Types ¶
type API ¶
type API string
API identifies a wire protocol. Providers are keyed by this, never by vendor: Model.Provider is a vendor id used only for credential resolution and catalog lookup.
type AfterToolCall ¶
type AfterToolCall func(ctx context.Context, in AfterToolCallContext) AfterToolCallDecision
type AfterToolCallContext ¶
type AfterToolCallContext struct {
ToolName string
ToolUseID string
// ParentToolUseID and ParentToolName are as in BeforeToolCallContext.
ParentToolUseID string
ParentToolName string
Arguments map[string]any
Result *ToolResultMessage // mutable in place
Elapsed time.Duration
// ToolResult is the handler's result as returned, after panic and
// Handler-error conversion, passed by value. It is read-only: changing
// it (or anything reached through its Metadata pointer) has no effect
// on the emitted, persisted or stop-policy message. Result remains the
// one mutable record and already carries Metadata.
ToolResult ToolResult
}
type AfterToolCallDecision ¶
type AfterToolCallDecision struct {
// Terminate is *bool because nil means "no opinion".
Terminate *bool
}
type AgentDoneEvent ¶
func (AgentDoneEvent) Class ¶
func (AgentDoneEvent) Class() EventClass
func (AgentDoneEvent) EventType ¶
func (AgentDoneEvent) EventType() EventType
type AgentStartEvent ¶
func (AgentStartEvent) Class ¶
func (AgentStartEvent) Class() EventClass
func (AgentStartEvent) EventType ¶
func (AgentStartEvent) EventType() EventType
type AssistantMessage ¶
type AssistantMessage struct {
Content Content
// StopReason is the canonical enum; RawStopReason is the provider's own
// finish string, verbatim. Neither may be read by the continuation check
// — use ExtractToolUse.
StopReason StopReason
RawStopReason string
// StopDetail is the provider's own account of why it stopped, when it
// gives one beyond the reason — a refusal's category and explanation
// ("cyber: …"). Like RawStopReason it never drives control flow.
StopDetail string
ErrorMessage string
Usage Usage
Timestamp time.Time
// Model is the REQUESTED model, never ResponseModel: transcript repair
// replays a turn's thinking signatures only to the model that issued
// them, and a fallback-served turn judged against ResponseModel would
// have its own valid signatures stripped.
Model string
ResponseModel string
ResponseID string
// Effort is the thinking effort the turn was requested with.
Effort Effort
}
func (AssistantMessage) Clone ¶
func (m AssistantMessage) Clone() Message
func (AssistantMessage) Role ¶
func (AssistantMessage) Role() Role
type BeforeToolCall ¶
type BeforeToolCall func(ctx context.Context, in BeforeToolCallContext) BeforeToolCallDecision
type BeforeToolCallContext ¶
type BeforeToolCallContext struct {
ToolName string
ToolUseID string
// ParentToolUseID and ParentToolName identify the wrapper call that made
// this call through CallNested. Both are empty for a call the model made
// directly.
ParentToolUseID string
ParentToolName string
Tool Tool
Arguments map[string]any
// RawInput is the model's own bytes, key order intact.
RawInput json.RawMessage
Assistant *AssistantMessage
Batch []ToolUseBlock
Index int
TurnCount int
}
BeforeToolCallContext is handed to the single tool-authorization boundary. Arguments are already validated and coerced.
type BeforeToolCallDecision ¶
type CacheRetention ¶
type CacheRetention string
CacheRetention is a provider-neutral tri-state: how long a provider keeps the prompt cache.
const ( CacheRetentionNone CacheRetention = "none" CacheRetentionShort CacheRetention = "short" CacheRetentionLong CacheRetention = "long" )
type Content ¶
type Content []ContentBlock
type ContentBlock ¶
type ContentBlock interface {
BlockType() BlockType
CloneBlock() ContentBlock
// contains filtered or unexported methods
}
ContentBlock is a sealed union: TextBlock | ThinkingBlock | ToolUseBlock. Sealed because every provider adapter switches exhaustively over it and a third-party variant would make that switch silently incomplete.
type Effort ¶
type Effort string
Effort is how much thinking a model puts into a turn: on an adaptive model it is sent as output_config.effort, on a budget model it picks the row's budget_tokens.
type ErrorEvent ¶
type ErrorEvent struct {
Message string
// Err is not serialized; the wire form carries Message.
Err error
Terminal bool
}
func (ErrorEvent) Class ¶
func (ErrorEvent) Class() EventClass
func (ErrorEvent) EventType ¶
func (ErrorEvent) EventType() EventType
type Event ¶
type Event interface {
EventType() EventType
Class() EventClass
// contains filtered or unexported methods
}
Event is a sealed union. clone and size are unexported so the union cannot be extended from outside and so EventStream.Push can deep-copy every event centrally, rather than trusting every producer to remember.
type EventClass ¶
type EventClass uint8
EventClass splits events into deltas, snapshots and authoritative events.
A split into deltas and authoritative events alone ("complete and final for the item it names, exactly one per item, always emitted") would place everything that is not a delta into the authoritative class. But MessageUpdateEvent is by construction repeated and non-final, and a stream carries many of them. A consumer treating it as authoritative would treat the first MessageUpdateEvent as final and stop rendering.
const ( // ClassIncremental is an optimization. It may be coalesced, may be absent // entirely (a non-streaming provider emits none), and is never truth. ClassIncremental EventClass = iota // ClassSnapshot is complete as of now, repeatable, never final. A consumer // may render from it and must not treat it as closing anything. ClassSnapshot // ClassAuthoritative is complete and final for the item it names, exactly // one per item, always emitted. Receiving one means DISCARD the deltas // accumulated for that item and take this payload whole. ClassAuthoritative )
type EventStream ¶
type EventStream struct {
// contains filtered or unexported fields
}
EventStream is an unbounded mutex + condition-variable queue. It is not a channel: the producer is a paid model call holding an HTTP connection open, and blocking it on a slow consumer stalls the SSE body read, trips the provider's stream idle timeout and kills the request. A slow consumer costs memory and nothing else.
There is no Close, no channel and no BufferSize.
func ErrorStream ¶
func ErrorStream(msg *AssistantMessage, err error) *EventStream
ErrorStream returns an already-ended stream carrying one ErrorEvent. This is the pre-closed error stream, for failures raised before any bytes are sent — an unregistered Api, an unusable option, an OnPayload abort.
The original error is retrievable unwrapped through Err, so errors.Is and errors.As still work on the caller's own sentinel. That is how a function whose only return is *EventStream both encodes failures in the stream and propagates an OnPayload error to the caller unmodified.
func EventStreamOf ¶
func EventStreamOf(ch <-chan StreamEvent, err error) *EventStream
EventStreamOf adapts a provider's channel to an EventStream, so a consumer reads events and the result the same way whatever produced them. err is Stream's own error: it ends the stream at once.
func NewEventStream ¶
func NewEventStream(opts StreamOptions) *EventStream
func (*EventStream) End ¶
func (s *EventStream) End(res StreamResult)
End records the terminal result and wakes everyone. It is idempotent; the first call wins. The producer pushes the terminal EVENT immediately before calling End, so a consumer sees that event and then ok=false.
Every producer MUST `defer stream.End(...)`: Result blocks until it lands.
func (*EventStream) Err ¶
func (s *EventStream) Err() error
Err does not block. It reports ErrStreamOverrun for a dropped consumer even when the run itself succeeded.
func (*EventStream) Events ¶
func (s *EventStream) Events() iter.Seq[Event]
Events is a range-over-func iterator over the stream. Breaking out early is safe: the producer is never blocked on this consumer.
func (*EventStream) Next ¶
func (s *EventStream) Next() (Event, bool)
Next blocks until an event is available or the stream is done. Events queued before End are drained before it reports done, so End never loses events.
Single-consumer: two goroutines calling Next split the stream. Fan-out is the caller's concern.
func (*EventStream) Push ¶
func (s *EventStream) Push(e Event)
Push never blocks. It deep-copies e before enqueueing — centralizing the copy here is what makes the guarantee hold for producers that forget. Pushes after End, and after an overrun, are dropped silently.
func (*EventStream) Result ¶
func (s *EventStream) Result() *AssistantMessage
Result blocks until End and returns the assistant message. It works even if no event was ever read — the result is fed by End, not by consumption, and that is what makes abandoning a stream safe.
func (*EventStream) RunResult ¶
func (s *EventStream) RunResult() (RunResult, error)
RunResult blocks until End and returns the agent-level result. It is the run-facing accessor; on a provider-level stream the RunResult is zero and the error is the stream's own. It is separate from Result because one accessor cannot yield both an AssistantMessage and a RunResult.
func (*EventStream) Wait ¶
func (s *EventStream) Wait() StreamResult
Wait blocks until End and returns the terminal result.
type EventType ¶
type EventType string
const ( EvAgentStart EventType = "agent_start" EvAgentDone EventType = "agent_done" EvTurnStart EventType = "turn_start" EvTurnEnd EventType = "turn_end" EvMessageStart EventType = "message_start" EvMessageUpdate EventType = "message_update" EvMessageEnd EventType = "message_end" EvTextStart EventType = "text_start" EvTextDelta EventType = "text_delta" EvTextEnd EventType = "text_end" EvThinkingStart EventType = "thinking_start" EvThinkingDelta EventType = "thinking_delta" EvThinkingEnd EventType = "thinking_end" // Model-stream decode events: the model writing a tool call. Distinct // from the execution triple below, which reports the tool RUNNING; one // name for both would produce two events of that name per call with // different payloads. The loop emits the execution triple. EvToolCallStart EventType = "tool_call_start" EvToolInputDelta EventType = "tool_input_delta" EvToolCallEnd EventType = "tool_call_end" EvToolExecutionStart EventType = "tool_execution_start" EvToolExecutionUpdate EventType = "tool_execution_update" EvToolExecutionEnd EventType = "tool_execution_end" EvToolResult EventType = "tool_result" EvError EventType = "error" )
type ExecutionMode ¶
type ExecutionMode uint8
const ( // Parallel is the zero value, so every custom tool defaults correctly with // no field set. Parallel ExecutionMode = iota Sequential )
type Message ¶
Message is a sealed union: UserMessage | AssistantMessage | ToolResultMessage.
An interface, not a Role-discriminated fat struct. ToolResultMessage is a first-class role whose ToolUseID, IsError, ToolName and Usage exist on no other role. A fat struct puts those fields on every message and makes "flatten results into a shared user message" a one-line accident instead of a type error. Sealed by isMessage() so provider adapters' role switches stay total.
type MessageEncoder ¶
type MessageEncoder func(Message) (json.RawMessage, error)
MessageEncoder renders one canonical message as JSON.
type MessageEndEvent ¶
type MessageEndEvent struct{ Message AssistantMessage }
func (MessageEndEvent) Class ¶
func (MessageEndEvent) Class() EventClass
func (MessageEndEvent) EventType ¶
func (MessageEndEvent) EventType() EventType
type MessageStartEvent ¶
type MessageStartEvent struct{ Message AssistantMessage }
func (MessageStartEvent) Class ¶
func (MessageStartEvent) Class() EventClass
func (MessageStartEvent) EventType ¶
func (MessageStartEvent) EventType() EventType
type MessageUpdateEvent ¶
type MessageUpdateEvent struct{ Message AssistantMessage }
func (MessageUpdateEvent) Class ¶
func (MessageUpdateEvent) Class() EventClass
func (MessageUpdateEvent) EventType ¶
func (MessageUpdateEvent) EventType() EventType
type Model ¶
type Model struct {
ID string
ContextWindow int
MaxOutputTokens int
InputCostPerMillion float64
OutputCostPerMillion float64
CacheReadCostPerMillion float64
CacheWriteCostPerMillion float64
// ThinkingKind is how the model takes extended thinking.
ThinkingKind ThinkingKind
// Efforts is the wire value of each effort the model takes: a token
// budget on a budget model, an effort name on an adaptive one. With a map,
// an effort absent from it (or nil) is not sent; with no map at all, the
// effort is sent by its own name.
Efforts map[Effort]*string
// Compat is the catalog row's capability object, as JSON (for example
// {"supports_sampling": false}).
Compat json.RawMessage
}
Model is a catalog row: what a request to the model may ask for and what it costs. Prices are USD per million tokens.
type NestedCaller ¶
type NestedCaller interface {
Call(ctx context.Context, calls ...ToolUseBlock) ([]ToolResult, error)
}
NestedCaller runs tool calls on behalf of a wrapper tool — one that declares ReachableTools — through the same pipeline as a call the model makes: argument preparation and validation, BeforeToolCall, the handler, AfterToolCall and the execution events.
Call returns one result per call, in the order of calls. A call that fails — unknown to the wrapper, invalid, blocked, aborted, a handler error — is a result with OK false, never a Go error. The error is for the wrapper as a whole: ErrTerminated when an interceptor voted to end the run.
type NoToolsMode ¶
type NoToolsMode string
const ( NoToolsNone NoToolsMode = "" NoToolsBuiltin NoToolsMode = "builtin" NoToolsAll NoToolsMode = "all" )
type PreparedArguments ¶
type PreparedArguments struct {
Raw json.RawMessage
Args map[string]any
// Coercions records what step 3 changed, for diagnostics.
Coercions []schema.Coercion
}
PreparedArguments is the output of the argument pipeline.
Raw the bytes handed to the handler Args the decoded map, for interceptors and policies to read
Raw is the model's own bytes whenever the pipeline changed nothing, so the handler — and any replay — sees exactly what the model wrote. When a step did change the value, Raw is rebuilt from the original bytes: every key the change left alone keeps its position and its bytes, and only changed or new keys are re-encoded. A map has no order, and re-encoding it whole would sort the keys.
func PrepareArguments ¶
func PrepareArguments(t Tool, c ToolUseBlock) (PreparedArguments, error)
PrepareArguments runs the fixed argument pipeline before the handler. Every stage runs inside the caller's panic-recover boundary.
- Tool.PrepareArguments — per-tool shape repair, runs FIRST, on a copy.
- Delete explicit nulls for OPTIONAL properties.
- Coerce primitives against the declared schema.
- Validate; a failure echoes the model's own arguments back in the order it wrote them, so the error is self-correcting.
Step 2 is the non-obvious one: constrained sampling forces the model to emit every declared property, so optional fields arrive as explicit nulls. Treating them as present is a validation failure on well-formed output.
func (PreparedArguments) TryWithArgs ¶
func (p PreparedArguments) TryWithArgs(args map[string]any) (PreparedArguments, error)
TryWithArgs is WithArgs returning an error for a value that is not representable in JSON. An interceptor's replacement arguments are the embedder's code: one that produces NaN fails that call, not the run.
func (PreparedArguments) WithArgs ¶
func (p PreparedArguments) WithArgs(args map[string]any) PreparedArguments
WithArgs replaces the arguments, as an interceptor may do, rebuilding Raw so the keys the interceptor kept keep their positions.
It panics if a value is not representable in JSON (NaN, a channel); TryWithArgs reports that as an error instead.
type ProviderClient ¶
type ProviderClient interface {
Stream(ctx context.Context, req Request) (<-chan StreamEvent, error)
}
ProviderClient is a model behind a wire API. Stream sends one request and returns its events on a channel that is closed when the turn ends. It returns an error, and no channel, only when the request cannot be made at all — its context is already done, say; a failure once streaming has begun is the last item's Err.
type Request ¶
type Request struct {
System []ContentBlock
// Prefix is sent after the system prompt and before Messages on every
// request, with a cache breakpoint on its last block.
Prefix Messages
Messages Messages
// Tools is []ToolWire, not []Tool, so no provider can reach a handler or
// any other field a request body must not carry.
Tools []ToolWire
ToolChoice ToolChoice
MaxTokens *int // upper bound; capped at the model's output cap
Temperature *float64 // nil is absent; an explicit 0 is sent
TopP *float64
StopSequences []string
// Effort is the thinking effort; empty sends none.
Effort Effort
}
Request is the canonical, provider-independent model call. Messages are the RAW canonical history: transcript repair happens inside the provider, never here, because the loop is not running when a transcript is loaded from disk and no caller may be able to skip it. The model is the provider's: a ProviderClient is built for one.
type ResultHandler ¶
type ResultHandler func(ctx context.Context, input json.RawMessage) ToolResult
ResultHandler is the richer form. It exists because ToolHandler's signature cannot express a full ToolResult: ToolResult.Terminate is `json:"-"` and Metadata is stripped by ToLLMMap, so there is no channel through json.RawMessage for either. Without it, the tools that end a run (finish, submit_answer, exit_plan_mode) are unimplementable by a consumer, and every built-in tool that truncates cannot report metadata.
type RunResult ¶
type RunResult struct {
Messages Messages
StopReason RunStopReason
// LastReason is the final assistant message's canonical reason, kept
// because RunStopReason and StopReason are different vocabularies.
LastReason StopReason
Usage Usage
TurnCount int
Error error
}
RunResult is the flattened ACTIVE BRANCH — a view, not the durable representation, and not what a caller should persist.
type RunStopReason ¶
type RunStopReason string
RunStopReason is why a RUN ended. It is a distinct type from StopReason because "max_turns" is not something any model can say, and because RunResult.StopReason == StopReasonLength would otherwise be ambiguous between "the model hit its output cap" and "the run hit its turn cap".
const ( RunStopEndTurn RunStopReason = "end_turn" RunStopMaxTurns RunStopReason = "max_turns" RunStopBudgetExceeded RunStopReason = "budget_exceeded" RunStopPolicy RunStopReason = "stop_policy" RunStopToolTerminate RunStopReason = "tool_terminate" RunStopError RunStopReason = "error" RunStopAborted RunStopReason = "aborted" // RunStopTimeout: the run outlived its configured timeout // (agentkit.Config.Timeout). RunStopTimeout RunStopReason = "timeout" // RunStopRefusal: the run ended on a model's refusal (StopReasonRefusal // with no tool calls). Run also returns an error wrapping ErrRefusal. RunStopRefusal RunStopReason = "refusal" )
type StopReason ¶
type StopReason string
StopReason is the canonical, lossy normalization of a provider finish reason. RawStopReason on AssistantMessage keeps the provider's own string.
NAMING: the Go identifier of an output-cap stop is StopReasonLength and its wire string is "max_tokens". They are the same thing.
const ( StopReasonStop StopReason = "stop" StopReasonLength StopReason = "max_tokens" StopReasonToolUse StopReason = "tool_use" StopReasonStopSequence StopReason = "stop_sequence" StopReasonRefusal StopReason = "refusal" StopReasonError StopReason = "error" StopReasonAborted StopReason = "aborted" )
func (StopReason) ShortCircuits ¶
func (r StopReason) ShortCircuits() bool
ShortCircuits reports the two reasons that terminate a turn BEFORE tool extraction. Every other reason, recognized or not, is control-flow-identical.
type StreamEvent ¶
StreamEvent is one item of a provider stream: an event, and on the last item of a failed stream the error. The provider's final assistant message is the Message of the last MessageEndEvent, a failed turn included: its StopReason says it failed and ErrorMessage says why.
type StreamOptions ¶
type StreamOptions struct {
// MaxPendingBytes bounds queued-but-unread payload. Zero is unbounded (the
// default). A non-zero value below MinMaxPendingBytes is raised to it.
// Exceeding it drops the CONSUMER (Next returns false, Err returns
// ErrStreamOverrun) and lets the run finish with the result still
// available — it never drops events.
MaxPendingBytes int64
}
type StreamResult ¶
type StreamResult struct {
// Message is non-nil once the stream has ended, always — including on a
// pre-flight failure: partial content plus the failure must be one value.
Message *AssistantMessage
// Result is set on agent-level streams and nil on provider-level ones.
Result *RunResult
Err error
}
StreamResult is what End records.
type TextBlock ¶
type TextBlock struct{ Text string }
TextBlock holds a Go string, not []byte, deliberately: strings are immutable, so the deep copy EventStream takes at every Push is a header copy rather than a byte copy. Cloning a partial assistant message is therefore O(blocks), not O(streamed characters).
func (TextBlock) CloneBlock ¶
func (b TextBlock) CloneBlock() ContentBlock
type TextDeltaEvent ¶
func (TextDeltaEvent) Class ¶
func (TextDeltaEvent) Class() EventClass
func (TextDeltaEvent) EventType ¶
func (TextDeltaEvent) EventType() EventType
type TextEndEvent ¶
TextEndEvent carries the WHOLE text, not just a final delta. It is authoritative: a consumer discards the deltas it accumulated and takes the payload whole, which is impossible if the end event carries only a delta.
func (TextEndEvent) Class ¶
func (TextEndEvent) Class() EventClass
func (TextEndEvent) EventType ¶
func (TextEndEvent) EventType() EventType
type TextStartEvent ¶
type TextStartEvent struct{ BlockIndex int }
func (TextStartEvent) Class ¶
func (TextStartEvent) Class() EventClass
func (TextStartEvent) EventType ¶
func (TextStartEvent) EventType() EventType
type ThinkingBlock ¶
type ThinkingBlock struct {
Thinking string
// Signature is provider-issued and opaque; it may be empty on an aborted
// stream, and transcript repair downgrades an unsigned block to text.
// Never inspected.
Signature string
Redacted bool
}
func (ThinkingBlock) BlockType ¶
func (ThinkingBlock) BlockType() BlockType
func (ThinkingBlock) CloneBlock ¶
func (b ThinkingBlock) CloneBlock() ContentBlock
type ThinkingDeltaEvent ¶
func (ThinkingDeltaEvent) Class ¶
func (ThinkingDeltaEvent) Class() EventClass
func (ThinkingDeltaEvent) EventType ¶
func (ThinkingDeltaEvent) EventType() EventType
type ThinkingEndEvent ¶
func (ThinkingEndEvent) Class ¶
func (ThinkingEndEvent) Class() EventClass
func (ThinkingEndEvent) EventType ¶
func (ThinkingEndEvent) EventType() EventType
type ThinkingKind ¶
type ThinkingKind string
ThinkingKind is how a model takes extended thinking.
const ( // ThinkingKindNone is a model without extended thinking. ThinkingKindNone ThinkingKind = "none" // ThinkingKindAdaptive is thinking {"type":"adaptive"} with an effort. ThinkingKindAdaptive ThinkingKind = "adaptive" // ThinkingKindBudget is thinking {"type":"enabled"} with budget_tokens. ThinkingKindBudget ThinkingKind = "budget" )
type ThinkingStartEvent ¶
type ThinkingStartEvent struct{ BlockIndex int }
func (ThinkingStartEvent) Class ¶
func (ThinkingStartEvent) Class() EventClass
func (ThinkingStartEvent) EventType ¶
func (ThinkingStartEvent) EventType() EventType
type Tool ¶
type Tool struct {
Name string
Description string
InputSchema *schema.Schema
// OutputSchema describes ToolResult.Data on success, for programmatic
// callers (a script runner binding results, a wrapper tool, generated
// documentation). It is optional and never enforced at runtime: a nil
// schema means the tool makes no promise. It is NOT part of ToolWire, so
// no provider request body carries it.
OutputSchema *schema.Schema
// ReachableTools are the tools this tool can call on the model's behalf
// through CallNested — a wrapper such as a script runner. A tool that
// declares none reaches none. Every call it makes goes through the same
// interceptor, audit and event pipeline as a direct call, and only to a
// tool listed here (after ToolPolicy filtering). Not part of ToolWire.
ReachableTools []Tool
// Terminating marks a tool whose purpose is to end the run (finish,
// submit_answer). No wrapper may reach one: a run ends on a decision the
// model or the embedder made, never on one a wrapper made for it. Not
// part of ToolWire.
Terminating bool
Handler ToolHandler
Execute ResultHandler
Label string // display name, never sent
// Builtin marks a first-party tool. It exists so ToolPolicy.NoTools can
// mean "builtin" without the resolver keeping a hardcoded name list that
// silently stops matching when a tool is renamed.
Builtin bool
ExecutionMode ExecutionMode
PrepareArguments func(map[string]any) map[string]any
PromptGuidelines []string
}
Tool is the registry + loop type. Exactly one of Handler and Execute must be set.
func Named ¶
Named returns the tools of ts called names, in the order of names, each a value copy: the result shares nothing with ts. It looks only at the top-level entries of ts, never at ReachableTools, so a wrapper's tools are reached through the wrapper and the policy that prunes them stays in force; when ts holds a name twice, the first wins. With no names the result is empty and non-nil.
A name that ts does not hold, or one that names repeats, is an error and the result is nil. The error for a missing name quotes every missing name, in the order of names, and lists the names ts does hold, in ts order, because the usual mistake is a typo or a qualified MCP name (server__tool) and the set as given fixes it in one step.
func ReachableTools ¶
ReachableTools returns tools and everything they reach through ReachableTools, transitively, each name once, in order of first appearance (depth first). It is the one answer to "what can this tool set call", however deeply wrappers nest.
type ToolCallEndEvent ¶
type ToolCallEndEvent struct {
BlockIndex int
Block ToolUseBlock
}
ToolCallEndEvent carries the finalized block, Input bytes included, so the consumer needs no salvage parser of its own. It AUGMENTS the accumulated call and never replaces state later events read.
func (ToolCallEndEvent) Class ¶
func (ToolCallEndEvent) Class() EventClass
func (ToolCallEndEvent) EventType ¶
func (ToolCallEndEvent) EventType() EventType
type ToolCallStartEvent ¶
func (ToolCallStartEvent) Class ¶
func (ToolCallStartEvent) Class() EventClass
func (ToolCallStartEvent) EventType ¶
func (ToolCallStartEvent) EventType() EventType
type ToolChoice ¶
type ToolChoice string
ToolChoice is a provider-neutral tri-state. The zero value is Unset, so absent cannot be confused with auto and needs no pointer.
const ( ToolChoiceUnset ToolChoice = "" ToolChoiceAuto ToolChoice = "auto" ToolChoiceNone ToolChoice = "none" )
func (ToolChoice) IsSet ¶
func (c ToolChoice) IsSet() bool
type ToolExecutionEndEvent ¶
type ToolExecutionEndEvent struct {
ToolUseID string
Name string
Result Content
IsError bool
ElapsedMS int64
ParentToolUseID string
}
func (ToolExecutionEndEvent) Class ¶
func (ToolExecutionEndEvent) Class() EventClass
func (ToolExecutionEndEvent) EventType ¶
func (ToolExecutionEndEvent) EventType() EventType
type ToolExecutionStartEvent ¶
ParentToolUseID, on the three execution events, is the wrapper call a nested call was made through, empty for a call the model made directly. A nested call emits these events and no ToolResultEvent: its result goes to the wrapper, not into the transcript.
func (ToolExecutionStartEvent) Class ¶
func (ToolExecutionStartEvent) Class() EventClass
func (ToolExecutionStartEvent) EventType ¶
func (ToolExecutionStartEvent) EventType() EventType
type ToolExecutionUpdateEvent ¶
type ToolExecutionUpdateEvent struct {
ToolUseID string
Name string
Chunk string
ParentToolUseID string
}
func (ToolExecutionUpdateEvent) Class ¶
func (ToolExecutionUpdateEvent) Class() EventClass
func (ToolExecutionUpdateEvent) EventType ¶
func (ToolExecutionUpdateEvent) EventType() EventType
type ToolHandler ¶
type ToolHandler func(ctx context.Context, input json.RawMessage) (json.RawMessage, error)
ToolHandler is the plain form of a tool's handler: JSON arguments in, JSON result out.
type ToolInputDeltaEvent ¶
type ToolInputDeltaEvent struct {
BlockIndex int
ToolUseID string
Delta string // partial argument JSON, as received
}
func (ToolInputDeltaEvent) Class ¶
func (ToolInputDeltaEvent) Class() EventClass
func (ToolInputDeltaEvent) EventType ¶
func (ToolInputDeltaEvent) EventType() EventType
type ToolMetadata ¶
type ToolMetadata struct {
Truncated bool `json:"truncated,omitzero"`
TruncatedBy string `json:"truncated_by,omitzero"` // "lines" | "bytes"
TotalBytes int64 `json:"total_bytes,omitzero"`
TotalLines int64 `json:"total_lines,omitzero"`
SpillPath string `json:"spill_path,omitzero"`
DurationMS int64 `json:"duration_ms,omitzero"`
ExitCode *int `json:"exit_code,omitzero"`
Outcome string `json:"outcome,omitzero"` // ok|exit|signal|timeout|abort
LineEnding string `json:"line_ending,omitzero"`
}
func (*ToolMetadata) Clone ¶
func (m *ToolMetadata) Clone() *ToolMetadata
Clone returns a deep copy of m, including a fresh ExitCode pointer. Nil returns nil.
func (*ToolMetadata) IsEmpty ¶
func (m *ToolMetadata) IsEmpty() bool
IsEmpty reports whether m is nil or has every field at its zero value (including a nil ExitCode pointer). An empty metadata carries no information and is treated as nil on the message.
type ToolPolicy ¶
type ToolPolicy struct {
// Tools, when non-nil (INCLUDING empty), is used verbatim and bypasses
// everything below. Non-nil-but-empty is distinct from nil.
Tools []Tool
ToolNames []string // nil means the default built-in set
ExcludeTools []string
NoTools NoToolsMode
CustomTools []Tool
}
ToolPolicy is the five-field tool exposure policy Resolve applies, uniformly to built-in and caller-supplied tools.
func (ToolPolicy) Resolve ¶
func (p ToolPolicy) Resolve(registered []Tool) []Tool
Resolve is the five-field resolution over a registered set. It applies UNIFORMLY to built-in and caller-supplied tools — that uniformity is the point, and it is what makes four otherwise surprising consequences true. Each is intended and each has a test:
NoTools "all" disables CUSTOM tools too, not just built-ins. NoTools "builtin" leaves the allowlist unset, so custom tools SURVIVE. ToolNames constrains custom tools, not only built-ins. ExcludeTools applies to custom tools, not only built-ins.
Resolution order is Tools → NoTools → ToolNames → ExcludeTools, with CustomTools merged in before name-based selection so the selectors see one undifferentiated set.
This is what a per-agent tool allowlist resolves to, and it is what lets an agent be scoped to read-and-search-only without rebuilding the tool set by hand.
type ToolResult ¶
type ToolResult struct {
OK bool `json:"ok"`
Data map[string]any `json:"data"`
Error string `json:"error,omitzero"`
Detail string `json:"detail,omitzero"`
// Terminate never reaches the model; it is this result's vote to end the
// run (see BatchTerminates).
Terminate bool `json:"-"`
Metadata *ToolMetadata `json:"metadata,omitzero"`
// Blocks are extra content blocks appended after the text the model
// reads, each its own block in the result.
Blocks []ContentBlock `json:"-"`
// Text is the MODEL-FACING rendering of the result. When non-empty it is
// the text block the model reads, verbatim, in place of the JSON envelope;
// Data, Error and Detail stay populated for programmatic consumers
// (interceptors, the audit trail, an MCP server bridging the tool) and are
// unaffected.
//
// It exists because the envelope is the wrong shape for the results that
// dominate an agent's context. A source file inside a JSON string pays for
// every newline, tab and quote twice (`\n`, `\t`, `\"`) — 9–13% more
// bytes and, because escape sequences tokenize badly, an estimated 15–30%
// more tokens on indented code — and the model reads code through a layer
// of escaping it then has to undo when it writes an edit. Every built-in
// tool sets Text on success; an error result leaves it empty and the
// envelope is used.
Text string `json:"-"`
}
ToolResult is a tool call's output envelope.
func CallNested ¶
func CallNested(ctx context.Context, calls ...ToolUseBlock) ([]ToolResult, error)
CallNested runs calls through the NestedCaller on ctx. With none attached — the tool declares no ReachableTools, or is invoked by something that is neither an agent run nor a Runner — it runs nothing and returns ErrNoNestedCaller.
func ErrResult ¶
func ErrResult(code, detail string) ToolResult
func OKResult ¶
func OKResult(data map[string]any) ToolResult
func (ToolResult) LLMText ¶
func (r ToolResult) LLMText() string
LLMText is the text the model sees for this result: Text when the tool rendered one, otherwise the envelope (ToLLMMap) as JSON. A marshal failure — a Data value that is not JSON-representable — yields a fixed error envelope rather than an empty result, so the transcript never carries a tool_result with no content.
func (ToolResult) ToLLMMap ¶
func (r ToolResult) ToLLMMap() map[string]any
ToLLMMap strips Metadata and Terminate. Providers receive only the payload.
type ToolResultEvent ¶
type ToolResultEvent struct{ Message ToolResultMessage }
func (ToolResultEvent) Class ¶
func (ToolResultEvent) Class() EventClass
func (ToolResultEvent) EventType ¶
func (ToolResultEvent) EventType() EventType
type ToolResultMessage ¶
type ToolResultMessage struct {
ToolUseID string
ToolName string
Content Content
IsError bool
Timestamp time.Time
// AddedToolNames marks tools that became available at this point in the
// transcript. Opaque to the loop.
AddedToolNames []string
Usage *Usage // optional; a pointer, never omitempty
// Metadata is the producing handler's structured metadata (exit code,
// outcome, truncation, spill path, totals, duration, line ending).
// Persisted by the session codec, never sent to a provider. Nil for
// handler-less results (unknown tool, invalid arguments, blocked,
// aborted, max_tokens synthesis, repair synthesis).
Metadata *ToolMetadata
}
func (ToolResultMessage) Clone ¶
func (m ToolResultMessage) Clone() Message
func (ToolResultMessage) Role ¶
func (ToolResultMessage) Role() Role
type ToolUseBlock ¶
type ToolUseBlock struct {
ID string
Name string
// Input is the provider's own argument bytes, verbatim and AUTHORITATIVE
// for every replay, fingerprint and serialization path.
// Invariant: always syntactically valid JSON object bytes; NewToolUse
// normalizes nil/empty to {}.
Input json.RawMessage
// ThoughtSignature is opaque and provider-issued; transcript repair strips
// it on cross-model replay. Never inspected.
ThoughtSignature string
}
func ExtractToolUse ¶
func ExtractToolUse(m *AssistantMessage) []ToolUseBlock
ExtractToolUse is the loop's continuation predicate, by name. It scans Content for ToolUseBlock and reads no stop reason.
func NewToolUse ¶
func NewToolUse(id, name string, raw json.RawMessage) (ToolUseBlock, error)
NewToolUse checks raw is a JSON object and keeps its bytes. A nil, empty or whitespace-only raw yields {}.
func (ToolUseBlock) BlockType ¶
func (ToolUseBlock) BlockType() BlockType
func (ToolUseBlock) CloneBlock ¶
func (b ToolUseBlock) CloneBlock() ContentBlock
func (ToolUseBlock) InputMap ¶
func (b ToolUseBlock) InputMap() map[string]any
InputMap decodes lazily for handler and interceptor convenience. The result is NEVER re-encoded back into a request.
Numbers come back as json.Number, not float64, because numeric literals must survive verbatim. Handlers doing args["limit"].(float64) will fail their type assertion; assert json.Number and call its Int64 or Float64.
type ToolWire ¶
ToolWire is the projection a provider is allowed to see — the name, description and input schema, and nothing else.
It is exported because providers live in their own packages, where an unexported projection would be out of reach. Exported, the invariant is STRONGER than unexported-ness: Request.Tools is []ToolWire, so no provider can name a Handler, PromptGuidelines or PrepareArguments at all, and a new Tool field cannot widen a request body without a compile error at the boundary.
ToolWire carries no json tags on purpose: each provider encodes it into its own dialect (Anthropic input_schema, OpenAI parameters).
type TurnEndEvent ¶
type TurnEndEvent struct {
TurnIndex int
Message AssistantMessage
// ToolResults is always non-nil; [] for a no-tool turn.
ToolResults []ToolResultMessage
Usage Usage
}
func (TurnEndEvent) Class ¶
func (TurnEndEvent) Class() EventClass
func (TurnEndEvent) EventType ¶
func (TurnEndEvent) EventType() EventType
type TurnStartEvent ¶
type TurnStartEvent struct{ TurnIndex int }
func (TurnStartEvent) Class ¶
func (TurnStartEvent) Class() EventClass
func (TurnStartEvent) EventType ¶
func (TurnStartEvent) EventType() EventType
type Usage ¶
type Usage struct {
// InputTokens is NET of cache reads and writes.
InputTokens int64
OutputTokens int64
// ReasoningTokens is a SUBSET of OutputTokens, not an addend.
ReasoningTokens int64
CacheReadTokens int64
CacheWriteTokens int64
// CacheWrite1hTokens is a SUBSET of CacheWriteTokens.
CacheWrite1hTokens int64
TotalTokens int64
// Requests is how many model requests the usage covers.
Requests int64
CostUSD float64
// BilledModel is the model that actually served the request.
BilledModel string
// Unpriced is true when at least one request the usage covers had a cost
// that neither the vendor reported nor the catalog priced, so CostUSD
// counts that request as zero. The cost bit cannot say this: Add ORs it.
Unpriced bool
// Set records which fields the provider actually reported, so an explicit
// 0 beats an SDK fallback and a wholly unreported usage is
// distinguishable from a reported all-zero one.
Set UsageField
}
func (Usage) Add ¶
Add sums two usages field-wise and ORs their presence masks and their Unpriced flags. The session layer only sums; it never recomputes cost.
func (Usage) ContextTokens ¶
ContextTokens is TotalTokens when non-zero, else Input+Output+CacheRead+CacheWrite.
func (Usage) Has ¶
func (u Usage) Has(f UsageField) bool
func (Usage) IsZero ¶
IsZero is true when nothing was reported OR everything reported was zero — an "all-zero-usage response", which must never serve as an anchor.
func (*Usage) SetField ¶
func (u *Usage) SetField(f UsageField, v int64)
type UsageField ¶
type UsageField uint32
UsageField is a presence bit. Presence could be a pointer or an explicit flag; a mask is chosen over eight pointers because Usage is summed on every turn, and pointer arithmetic across a summation is where double-counting actually happens.
const ( UsageInputTokens UsageField = 1 << iota UsageOutputTokens UsageReasoningTokens UsageCacheReadTokens UsageCacheWriteTokens UsageCacheWrite1hTokens UsageTotalTokens UsageCostUSD )
type UsageJSON ¶
type UsageJSON struct {
InputTokens int64 `json:"input_tokens"`
OutputTokens int64 `json:"output_tokens"`
ReasoningTokens int64 `json:"reasoning_tokens"`
CacheReadTokens int64 `json:"cache_read_tokens"`
CacheWriteTokens int64 `json:"cache_write_tokens"`
CacheWrite1hTokens int64 `json:"cache_write_1h_tokens"`
TotalTokens int64 `json:"total_tokens"`
CostUSD float64 `json:"cost_usd"`
BilledModel string `json:"billed_model"`
Reported bool `json:"reported"`
Unpriced bool `json:"unpriced"`
}
UsageJSON is Usage's wire shape for events. Every field is present — a consumer reads the same keys whether or not the provider reported them — and `reported` says which ones the provider actually set. `unpriced` says the cost counts at least one request as zero because no one could price it.
type UsageWire ¶
type UsageWire struct {
InputTokens *int64
OutputTokens *int64
ReasoningTokens *int64
CacheReadTokens *int64
CacheWriteTokens *int64
CacheWrite1hTokens *int64
TotalTokens *int64
}
UsageWire is the decode-side helper providers use: every numeric is a pointer so an explicit 0 is distinguishable from absent. Into folds it onto a Usage, setting presence bits for exactly the non-nil fields.
type UserMessage ¶
func (UserMessage) Clone ¶
func (m UserMessage) Clone() Message
func (UserMessage) Role ¶
func (UserMessage) Role() Role