llm

package
v0.2.0-alpha.2 Latest Latest
Warning

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

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

Documentation

Overview

Package llm holds LLM wire types and the Tool contract used across core services and agent execution. Keep this package at the bottom of the dependency graph to avoid import cycles.

Index

Constants

View Source
const (
	ContentPartText  = "text"
	ContentPartImage = "image"
)

Content part kinds.

View Source
const (
	MessageSourceCommandResult  = "command_result"
	MessageSourceSubagentResult = "subagent_result"
	MessageSourceMonitorEvent   = "monitor_event"
)

Message sources for background-event provenance. See Message.Source; the set mirrors docs/design/local-background-jobs.md.

View Source
const (
	// ProviderOpenAICompatible is OpenAI Chat Completions, spoken by OpenRouter,
	// LiteLLM, vLLM, and local inference servers. It is the default.
	ProviderOpenAICompatible = "openai_compatible"
	// ProviderOpenAI is OpenAI's own Responses API.
	ProviderOpenAI = "openai"
	// ProviderAnthropic is the Anthropic Messages API.
	ProviderAnthropic = "anthropic"
	// ProviderOllama is Ollama's own /api/chat, spoken by a local daemon. Its
	// compatibility endpoint would answer ProviderOpenAICompatible, but that
	// path cannot set the context window the runtime otherwise defaults and
	// silently truncates to.
	ProviderOllama = "ollama"
)

Provider types name the wire protocol an upstream speaks.

A provider type selects a client implementation; it is not a vendor name. Claude reached through an OpenAI-compatible gateway is ProviderOpenAICompatible, and Claude reached at Anthropic's own endpoint is ProviderAnthropic.

This is the one definition. A configured model entry, a catalog target, and a recorded call all name a protocol, and an operator must read the same word in settings.yaml, in the catalog, and in the call ledger. It lives here because a protocol name is a contract, not something config loads or the gateway resolves.

View Source
const NanoUnitsPerUnit = 1_000_000_000

NanoUnitsPerUnit is the fixed-point scale rates and costs are held at: one currency unit is 1e9 of them.

Money is not held in a float here. A rate like $0.30 per million tokens has no exact binary representation, and a run of a few hundred calls accumulates the error into a figure someone will compare against an invoice. Integer nano-units multiply and add exactly, and nine decimal places is finer than any provider prices to.

Variables

This section is empty.

Functions

func FormatAmount

func FormatAmount(nano int64) string

FormatAmount renders nano-units as a decimal string with six places, which is enough to show a single cheap call without reading as zero.

func KnownProvider

func KnownProvider(name string) bool

KnownProvider reports whether name is a provider type BuildMax implements. An empty name is not known: a caller that has not stated a protocol has not finished describing its upstream. Defaulting an unset one is the configuration boundary's job, not this package's.

func ParseRate

func ParseRate(s string) (int64, error)

ParseRate reads a decimal price string — "3", "0.30", "3.75" — into nano-units.

Rates are written as decimal strings rather than numbers because that is how every provider publishes them, so a configured value can be compared against a price page without arithmetic, and because a JSON or YAML float would round the value before this package ever saw it.

func ProviderNeedsCredential

func ProviderNeedsCredential(name string) bool

ProviderNeedsCredential reports whether an upstream speaking this protocol must carry a secret.

Every hosted protocol does, and one without a credential is a misconfiguration that must fail at the first call rather than send an unauthenticated request. A local runtime has none: what authorizes the call is being able to reach the daemon at all, which is a property of the deployment's network. Demanding a placeholder for it would turn a working setup into a diagnostic failure.

func Providers

func Providers() []string

Providers returns every implemented wire protocol, for help text and error messages that must not drift from the list above.

func WithCallOrigin

func WithCallOrigin(ctx context.Context, origin CallOrigin) context.Context

WithCallOrigin attaches runtime-owned origin metadata to one model call.

Types

type Access

type Access uint8

Access describes what a tool call does to the world. The zero value is AccessWrite so an undeclared tool is treated conservatively. Read by the permission layer and the tool scheduler; see docs/design/tool-permissions.md.

const (
	// AccessWrite means the call changes durable or process state.
	AccessWrite Access = iota
	// AccessReadOnly means the call observes and returns.
	AccessReadOnly
)

func (Access) String

func (a Access) String() string

type AccessDeclarer

type AccessDeclarer interface {
	Access(args map[string]any) Access
}

AccessDeclarer is an optional interface a Tool can implement to classify its own calls. Args are passed so the answer can depend on the call.

AccessReadOnly is a claim about effect only. It does not promise that Execute is safe on several goroutines at once — CallMcpTool reports what a third party says about itself, which is not something this runtime can underwrite. A scheduler must require concurrency safety separately.

type ArgChecker

type ArgChecker interface {
	CheckArgs(args map[string]any) ToolAction
}

ArgChecker is an optional interface a Tool can implement to make arg-level policy decisions before Execute is called. Return ToolActionAllow to proceed normally.

type CallOrigin

type CallOrigin struct {
	// Surface is where the call began, such as cli, desktop, server, or worker.
	Surface string
	// ViaGateway says a BuildMax gateway forwarded the call to its provider.
	ViaGateway bool
}

CallOrigin is runtime-owned correlation metadata for one model call. It is not sent as prompt content and never carries user, workspace, or session identifiers.

func CallOriginFromContext

func CallOriginFromContext(ctx context.Context) (CallOrigin, bool)

CallOriginFromContext returns the origin metadata for one model call.

type CallProfile

type CallProfile string

CallProfile is what a call is for. It is the caller's statement of intent, not a provider setting: a title and a tool-calling turn send the same shape of request and have nothing in common in how they will be reused.

It exists because prompt caching is charged. A cache write costs more than ordinary input and only repays itself if a later call reads it, so whether to ask for one cannot be decided from the request alone — a one-shot utility call and the first turn of a long run look identical on the wire. Carrying the answer in an untyped context value, or guessing it from prompt text, would hide a billed behavior from the callers and tests that have to reason about it.

const (
	// ProfileAgentTurn is one iteration of the agent loop: a large stable
	// prefix that the next iteration will send again.
	ProfileAgentTurn CallProfile = "agent_turn"
	// ProfileTitle is one-shot title generation.
	ProfileTitle CallProfile = "title"
	// ProfileCompaction is summarizing history the run is about to discard.
	ProfileCompaction CallProfile = "compaction"
	// ProfileEvaluation is a harness call made about a run rather than by one.
	ProfileEvaluation CallProfile = "evaluation"
	// ProfileProbe is a single question with no expectation of reuse: a
	// connectivity check, a tool's own model call.
	ProfileProbe CallProfile = "probe"
)

func (CallProfile) Valid

func (p CallProfile) Valid() bool

Valid reports whether p is a profile this build knows. An unknown profile is refused rather than defaulted, because the default it would fall to is the one that spends money.

type Completion

type Completion struct {
	Content   string
	ToolCalls []ToolCall
	Usage     Usage
	// ProviderState is set only by a protocol that carries reasoning state and
	// only when the model produced some.
	ProviderState *ProviderState
}

Completion is one model turn: what the assistant said, what it asked to run, what it cost, and any reasoning state the protocol needs back.

It is a struct rather than a longer return list because every capability the contract has gained wanted another slot, and a fifth positional value is where that stops being readable.

func (Completion) AssistantMessage

func (c Completion) AssistantMessage() Message

AssistantMessage is the history entry this completion becomes. The agent loop appends it verbatim, so reasoning state reaches the next request without any layer between here and there having to know it exists.

type ContentPart

type ContentPart struct {
	Type string `json:"type"`
	// Text is set on a text part.
	Text string `json:"text,omitempty"`
	// MediaType and Data are set on an image part. Data is base64 with no
	// data: prefix, which is the form every protocol here wants.
	MediaType string `json:"media_type,omitempty"`
	Data      string `json:"data,omitempty"`
}

ContentPart is one piece of a message's content.

Parts sit beside Content rather than replacing it. A message with an image still carries text saying what the image is, so token estimation, trimming, compaction, traces, and the terminal renderer keep working unchanged and a protocol that cannot take images still receives a sensible turn.

type Cost

type Cost struct {
	Currency string `json:"currency"`
	// Uncached is fresh prompt input — the prompt minus what was cached.
	Uncached int64 `json:"uncached"`
	// CacheRead and CacheWrite are the cached parts of the prompt.
	CacheRead  int64 `json:"cache_read"`
	CacheWrite int64 `json:"cache_write"`
	// Output is the generated tokens.
	Output int64 `json:"output"`
	// Total is the sum of the four above.
	Total int64 `json:"total"`
	// Baseline is what the same call would have cost with no caching at all:
	// every prompt token billed at the fresh input rate. It is the only honest
	// way to say whether caching helped, because the alternative — comparing
	// against zero — would report a saving on a call that only ever wrote.
	Baseline int64 `json:"baseline"`
}

Cost is what one call, or a run of them, is estimated to have cost.

Every field is nano-units of Currency. The breakdown is kept rather than summed away because the parts answer different questions: whether caching paid for itself is Uncached + CacheRead + CacheWrite against Baseline, and what the call cost is Total.

func EstimateCost

func EstimateCost(usage Usage, pricing Pricing) (Cost, bool)

EstimateCost prices one usage report, or (Cost{}, false) when it cannot.

It refuses rather than guesses in two cases. Unconfigured rates give no basis for a number at all. Usage a provider never reported gives nothing to multiply: a call with no counts is an unmeasured call, not a free one, and returning a zero cost for it would turn an unknown into a claim.

func (Cost) Add

func (c Cost) Add(other Cost) (Cost, bool)

Add accumulates another call's cost.

Two costs in different currencies are not added, because BuildMax holds no exchange rate and inventing one would produce a total that is wrong in both. The mismatch is reported so a caller can say "unavailable" rather than show a figure that silently dropped half the run.

func (Cost) Saved

func (c Cost) Saved() int64

Saved is Baseline minus Total, and zero when caching cost more than it saved.

A negative saving is not reported as one. A run that wrote a cache entry nothing went on to read genuinely paid more than it would have without caching, and dressing that up as a small saving is the kind of claim this package exists not to make. Callers that want the loss compare the two fields themselves.

type GrantScoper

type GrantScoper interface {
	GrantScope(args map[string]any) string
}

GrantScoper is an optional interface a Tool can implement to narrow what a session grant covers. A tool that dispatches to something else — an MCP server, another agent — would otherwise have one approval cover every target it can reach.

type LLMClient

type LLMClient interface {
	ChatCompletionBlocking(ctx context.Context, req Request) (Completion, error)
	ChatCompletionStreaming(ctx context.Context, req Request, onDelta func(string)) (Completion, error)
	ContextWindow() int // 0 = no windowing configured
}

LLMClient can perform chat completions with tools and exposes its configuration.

type Message

type Message struct {
	Role       string     `json:"role"`                   // "user", "assistant", "system", or "tool"
	Content    string     `json:"content,omitempty"`      // message content
	ToolCallID string     `json:"tool_call_id,omitempty"` // for role "tool": the ID of the tool call this result answers
	ToolCalls  []ToolCall `json:"tool_calls,omitempty"`   // for role "assistant": tool calls made by the model
	// Source records non-user provenance for a user-role message. Model
	// providers share no portable mid-history event role, so a background
	// event travels on the wire as a user message — but the persisted history
	// and the trace must not claim the user said it. Empty means genuinely
	// user-authored. Wire adapters map fields explicitly and never send it.
	Source string `json:"source,omitempty"`
	// ProviderState is opaque reasoning state the producing protocol requires
	// back on later turns. For role "assistant" only. See ProviderState.
	ProviderState *ProviderState `json:"provider_state,omitempty"`
	// Parts is non-text content this message carries. Content stays the text
	// projection of the same message, so nothing that reads it has to know
	// parts exist. See ContentPart.
	Parts []ContentPart `json:"parts,omitempty"`
}

Message represents a chat message for the API (user, assistant, or tool).

func (Message) Images

func (m Message) Images() []ContentPart

Images returns the image parts of a message, or nil when it has none.

type MultimodalTool

type MultimodalTool interface {
	Tool
	ExecuteMultimodal(ctx context.Context, args map[string]any) (ToolResult, error)
}

MultimodalTool is a Tool whose result can carry non-text content.

It is an optional upgrade rather than a change to Execute because exactly one tool needs it — the MCP gateway, which forwards whatever a server returns — and widening the contract would make sixteen text-only tools carry a field they never set. The agent loop asks for it and falls back to Execute.

type PolicyProvider

type PolicyProvider interface {
	DefaultAction() ToolAction
}

PolicyProvider is an optional interface a Tool can implement to declare its default action. The agent uses this as a fallback when no arg-level check applies.

type Pricing

type Pricing struct {
	// Currency is the ISO 4217 code the rates are quoted in. Rates in different
	// currencies are never added: BuildMax does not convert.
	Currency string
	// InputPerMTok is fresh prompt input, excluding anything cached.
	InputPerMTok int64
	// CacheReadPerMTok is prompt served from the provider's cache.
	CacheReadPerMTok int64
	// CacheWritePerMTok is prompt written into it.
	CacheWritePerMTok int64
	// OutputPerMTok is generated tokens.
	OutputPerMTok int64
}

Pricing is what one model charges, as nano-currency-units per million tokens.

The four rates are separate because prompt caching prices them differently: a cache read is cheaper than fresh input and a cache write is dearer, which is the whole reason caching is a decision rather than a free win. Collapsing them into one input rate would make every cached call look mispriced.

A zero rate is a real price — some models genuinely do not charge for cache reads — so "not configured" is the zero Pricing as a whole, reported by Configured, rather than a zero in any single field.

func (Pricing) Configured

func (p Pricing) Configured() bool

Configured reports whether these rates can price a call. A currency alone is not enough, and neither is a rate without one: a cost shown from half a price list is a guess wearing a number.

type ProviderState

type ProviderState struct {
	Protocol string          `json:"protocol"`
	Data     json.RawMessage `json:"data"`
}

ProviderState is provider-owned content that a protocol produces and then requires unchanged on subsequent requests: Anthropic thinking blocks, OpenAI Responses reasoning items. Nothing outside the adapter that produced it may interpret it, and nothing rewrites it — a signature over edited content is worse than no state at all.

Protocol names the producer so a session continued under a different one drops what it cannot use, rather than sending a payload that protocol would reject. That is what lets history stay portable while carrying state that is not.

func (*ProviderState) Belongs

func (p *ProviderState) Belongs(protocol string) bool

Belongs reports whether this state was produced by the named protocol. A nil state belongs to none, so callers can test the result directly.

type Request

type Request struct {
	Messages []Message
	Tools    []ToolDef
	Profile  CallProfile
	// CacheScope is an opaque discriminator that keeps unrelated prompt
	// populations out of one another's provider cache bucket. Empty means the
	// call is not scoped beyond the credential it uses.
	//
	// It exists because a provider cache key is a routing hint, not an
	// authorization boundary: two callers sharing one credential share a bucket
	// unless something separates them. For managed inference the gateway sets
	// it from the authenticated team, so one team's prefix cannot be bucketed
	// with another's. It is never accepted from a client, never persisted, and
	// never logged — it is an input to a hash and nothing else.
	CacheScope string
}

Request is one completion request.

Profile travels with the messages rather than beside them so a caller cannot forget it at one call site and get a different charge than at another.

type StreamSink

type StreamSink interface {
	OnDelta(delta string)
}

StreamSink receives content deltas during streaming. Implementations may write to stdout, send to a TUI, or buffer for SSE.

type TitleGenerator

type TitleGenerator interface {
	GenerateTitle(ctx context.Context, input string) (title string, promptTokens, completionTokens int, err error)
}

TitleGenerator generates a short title from an input string, e.g. via LLM. Returns token usage for metering; on error or when nil, callers fall back to truncated input.

type Tool

type Tool interface {
	Name() string
	Description() string
	Parameters() any // JSON schema for arguments (e.g. map[string]any)
	Execute(ctx context.Context, args map[string]any) (result string, err error)
}

Tool is a capability the agent can invoke by name. Both the core agent loop (internal/core/agent) and runtime tools (internal/tool) use this interface.

type ToolAction

type ToolAction int

ToolAction is the outcome of a policy check before a tool call executes.

const (
	// ToolActionAllow executes the tool unconditionally.
	ToolActionAllow ToolAction = iota
	// ToolActionAsk requests interactive approval before execution.
	ToolActionAsk
	// ToolActionDeny blocks execution and returns an error result to the model.
	ToolActionDeny
)

type ToolCall

type ToolCall struct {
	ID        string `json:"id"`                  // unique id for this call
	Name      string `json:"name"`                // tool name to invoke
	Arguments string `json:"arguments,omitempty"` // JSON object of arguments
}

ToolCall is a tool invocation returned by the model.

type ToolDef

type ToolDef struct {
	Name        string // tool name
	Description string // description for the model
	Parameters  any    // JSON schema for arguments (e.g. map[string]any or jsonschema.Definition)
}

ToolDef describes a tool (function) the model can call.

type ToolRegistry

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

ToolRegistry keeps the tools available to an agent run.

func NewToolRegistry

func NewToolRegistry() ToolRegistry

NewToolRegistry builds an empty registry for the tools available to an agent run.

func (*ToolRegistry) AppendTools

func (r *ToolRegistry) AppendTools(tools ...Tool)

AppendTools adds tools to the registry.

func (ToolRegistry) GetDefs

func (r ToolRegistry) GetDefs() []ToolDef

GetDefs builds the LLM-facing tool definitions.

func (ToolRegistry) Lookup

func (r ToolRegistry) Lookup(name string) Tool

Lookup returns the executable tool matching name, or nil if none match.

func (ToolRegistry) Tools

func (r ToolRegistry) Tools() []Tool

Tools returns the registered tools in append order.

type ToolResult

type ToolResult struct {
	Text  string
	Parts []ContentPart
}

ToolResult is what a tool produces when text alone cannot say it.

Text is always set and is what hooks, traces, the terminal, and token estimation read; Parts carries whatever the text could only describe.

type Usage

type Usage struct {
	PromptTokens     int `json:"prompt_tokens"`
	CompletionTokens int `json:"completion_tokens"`
	TotalTokens      int `json:"total_tokens"`
	// CacheReadTokens is the part of the prompt served from a provider's cache,
	// and CacheWriteTokens the part written into it. Both are subsets of the
	// prompt: adding them to PromptTokens would count the same tokens twice.
	// Zero means the provider reported none, which is also what a provider
	// without caching reports.
	CacheReadTokens  int `json:"cache_read_tokens,omitempty"`
	CacheWriteTokens int `json:"cache_write_tokens,omitempty"`
}

Usage holds token counts from the API (same shape for non-stream and stream responses).

Jump to

Keyboard shortcuts

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