Help us shape the future of Go! Take the 2026 Go Developer Survey

agentwire

package module
v0.12.1 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 31 Imported by: 3

README

agentwire

Drive vendor coding-agent CLIs as child processes over their structured protocols, behind one Go event model.

agentwire spawns the vendor binary that is already on the machine and speaks the vendor's own wire protocol to it. It normalizes the conversation into one event stream: text, tool calls, permission requests, usage, results and exit. Five protocols are covered:

  • Claude stream-json over stdin and stdout.
  • Codex app-server JSON-RPC over stdin and stdout.
  • OpenCode serve REST plus an SSE event stream, for 1.x and for the 2.x beta.
  • ACP over stdin and stdout, for Copilot, Cursor, Gemini, omp and grok.
  • pi RPC (NDJSON) over stdin and stdout.

The wire is Go-native. The official vendor SDKs are the reference implementation and the fixture oracle at build and test time only. They are never a runtime dependency. There is no Node, no sidecar and no vendored SDK.

The library knows vendors. It does not know consumers. Vendor flags, environment variables, config formats, protocol quirks and version floors live here. Homes such as ~/.trebi/harness, lifecycle hooks, memory, secret environment, job semantics and UI text belong to the caller.

No global state

Every cache, server pool, version record and crash ledger hangs off one Runtime value that the caller creates. Two runtimes in one process share nothing. No package-level mutable state exists.

Harnesses

Harness Constant Binary Protocol
Claude Code agentwire.Claude claude stream-json (NDJSON) over stdin and stdout
Codex agentwire.Codex codex app-server JSON-RPC over stdin and stdout
OpenCode agentwire.OpenCode opencode serve REST plus SSE; a 2.x binary uses the /api wire
OpenCode 2 (beta) agentwire.OpenCode2 opencode2 /api REST plus SSE
pi agentwire.Pi pi pi RPC (NDJSON) over stdin and stdout
Copilot agentwire.Copilot copilot ACP over stdin and stdout
Cursor agentwire.Cursor cursor-agent ACP over stdin and stdout
Gemini agentwire.Gemini gemini ACP over stdin and stdout
omp agentwire.OMP omp ACP over stdin and stdout (omp acp)
Grok agentwire.Grok grok ACP over stdin and stdout (grok agent --no-leader stdio)
fake agentwire.Fake shell script agentwire NDJSON, for tests
native agentwire.Native none (in process) the native module: one provider stream per turn, registered with Runtime.SetDriver

Runtime.Detect reports whether a binary is installed, its version, whether the version floor is met, and a best-effort login state. native has no binary: its driver implements agentwire.Detector, and the detection reports the credential state.

The native module

native is a separate Go module (github.com/trebi-ai/agent-wire/native). It holds an in-process loop: it folds one provider stream per turn into message parts, runs tools, approvals, skills, an MCP session, and a summary compactor, and keeps one message log per session in a FileStore. Two provider modules give it a model: native/provider/anthropic and native/provider/openai (OpenAI-compatible endpoints, so Ollama, OpenRouter, vLLM, Groq, and LM Studio work through base_url). The root module stays provider-free.

A consumer builds a native.Config, gets a driver with native.NewDriver, and registers it with Runtime.SetDriver(agentwire.Native, drv). Config.Credentials resolves the key per session, Config.ToolSetFuncs opens consumer tools per session, and Config.BeforeCall/AfterCall carry quota bookkeeping.

Getting started

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/trebi-ai/agent-wire"
)

func main() {
	ctx := context.Background()

	rt := agentwire.New(agentwire.Options{
		ClientName:    "myapp",
		ClientVersion: "1.0.0",
	})
	defer rt.Close(ctx)

	sess, err := rt.Start(ctx, agentwire.StartRequest{
		Harness:      agentwire.Claude,
		WorkingDir:   "/path/to/project",
		Instructions: "Read AGENTS.md before you edit anything.",
		Permissions:  agentwire.PermissionPolicy{Mode: agentwire.PermissionAsk},
		MCPServers: []agentwire.MCPServer{{
			Name:    "myapp",
			Command: "myapp",
			Args:    []string{"mcp"},
		}},
	})
	if err != nil {
		log.Fatalf("start: %v", err)
	}
	defer sess.Close(ctx)

	if err := sess.Prompt(ctx, agentwire.Prompt{Text: "Summarize this project."}); err != nil {
		log.Fatalf("prompt: %v", err)
	}

	for ev := range sess.Events() {
		switch ev.Type {
		case agentwire.EventAssistant:
			fmt.Print(ev.Text)
		case agentwire.EventPermission:
			// The policy answered everything it could. A human answers the rest.
			if err := sess.AnswerPermission(ctx, ev.Permission.ID, agentwire.Decision{Allow: true}); err != nil {
				log.Printf("answer: %v", err)
			}
		case agentwire.EventExit:
			fmt.Printf("\nsession ended: %s\n", ev.Error)
		}
	}
}

Runtime.Resume reopens a known session. Runtime.Reconcile kills children that a previous process left behind. Runtime.Close stops every session and shared server.

Session rule

A consumer MUST drain Session.Events() until the channel closes, or call Session.Close. EventExit is always the last event, and it is delivered even after Close. A consumer that stops reading without closing leaks the framing goroutine.

Plan limits

A session emits EventLimits when the harness reports the plan limits of the account. Event.Limits is a *Limits: Plan, Rejected, and Windows. Each LimitWindow has Key (five_hour, daily, weekly, monthly, or custom), Scope (for example opus or a Codex limit id), Minutes, UsedPercent (0–100), and ResetsAt (zero when not known). WindowKey(minutes) maps a window length to its key. A window is identified by its length, not by its position in the vendor frame.

  • Claude: each rate_limit_event frame on a stream-json run. A rejected frame also emits EventError with a limit end reason.
  • Codex: account/rateLimits/read after the handshake, and each account/rateLimits/updated notification. An error reply (an API-key login) emits nothing.

Runtime.Limits(ctx, harness, ModelQuery) reads the limits without a live session. It starts a short-lived child, never caches, and times out after 15 seconds. Codex implements it through the optional driver interface LimitsReader. Other harnesses return ErrUnsupported.

Models

Runtime.Models(ctx, harness, ModelQuery) lists the models of one harness as []ModelInfo (ID, Name, Description, Default). ID is the value that StartRequest.Model and SetModel take. The first call can start a short-lived vendor child (Claude initialize, Codex model/list, the OpenCode server, ACP session/new, pi get_available_models). The child sends no prompt and uses no model turn.

The runtime caches each list by harness, binary, version and home. Options.ModelCacheTTL sets the lifetime: zero means 10 minutes, and a negative value turns the cache off. A failed probe stays in the cache for 30 seconds only. Call Runtime.InvalidateModels(harness) after a login or a config change. A harness without a lister returns ErrUnsupported. A driver from Runtime.SetDriver that implements ModelLister is used the same way.

ModelArgs(harness, model, effort) returns the CLI flags that set the model and the effort, for a consumer that starts the interactive CLI itself (for example in a terminal). An empty value adds no flag. max becomes xhigh for Codex and grok. OpenCode has no flag.

A live session can do more through two optional interfaces. Use a type assertion to find them:

if sm, ok := sess.(agentwire.SessionModels); ok {
	models := sm.Models() // the list the handshake offered, or nil
}
if ms, ok := sess.(agentwire.ModelSetter); ok {
	err := ms.SetModel(ctx, "sonnet") // the next model call uses it
}

SetModel never interrupts the running turn. Claude applies the model from its next API call. Codex and OpenCode apply it from the next prompt. Features.Models and Features.SetModel tell a picker what to show before a session exists.

Mid-turn input

Send delivers a prompt in one call, whatever the turn state:

d, err := agentwire.Send(ctx, sess, agentwire.Prompt{Text: "Also run the tests."})
switch d {
case agentwire.DeliverySteered:
	// The input joined the running turn.
case agentwire.DeliveryPrompted:
	// A new turn starts now, or the vendor queues it.
case agentwire.DeliveryHeld:
	// The session sends it as a new turn when the running turn ends.
}

Send tries Steer first. When no turn runs, it calls Prompt. When the vendor refuses the steer, or the session cannot steer, it holds the prompt. At the result of the turn, the session joins all held prompts with a blank line and sends them as one turn with Kind: "followup". When the process exits first, the session emits one EventError with code held_prompt_dropped, and Text holds the dropped prompts.

Prompt during a turn returns ErrTurnActive, except on Claude, which queues the second prompt itself.

Steerer is the low-level call behind Send. It returns ErrNoActiveTurn when no turn runs, and ErrUnsupported when the vendor refuses the input. omp steers through _session/steering; grok advertises no steering, so its input is held.

Extension points

StartRequest carries everything the caller wants for one session. The library builds the command.

Field Effect
MCPServers Injects MCP servers for this session only. stdio (Command, Args, Env) or HTTP (URL, Headers). Injection adds to the user's own servers.
Skills Makes extra skill directories visible: natively, through an overlay config dir, or as an index in the instructions.
Instructions Adds per-session system instructions. The rendering is per harness.
Permissions One policy (ask, auto_edit, auto) mapped onto each vendor's own mechanism. Unanswered requests arrive as EventPermission.
Home Isolates the harness config home. Empty uses the user's own configuration and login.
Raw Per-harness overrides the typed fields do not cover, for example strict_mcp (Claude) or approvalPolicy and sandbox (Codex).

Other escape hatches: ExtraArgs appends consumer flags verbatim, BaseCommand skips the launch builder, LogPath writes the frames in and out as NDJSON (see "Frame log" below), and FS routes ACP file reads and writes to the caller so it can see every edit.

Frame log

StartRequest.LogPath keeps a debug trail of the frames in and out. Claude, Codex, and pi stream each message in small fragments and then send the complete message again. The log leaves out these fragments. It holds the fragments of the open message in memory. When an error event or a failed exit occurs, the log writes an error line ("dir":"error"). When the stream fails or ends inside a message, the library also writes the held fragments to FragmentLogPath. Set LogFragments to write every fragment to the log. ACP sends no complete message, so its chunks stay in the log.

Vendor binaries and logins

The library spawns the vendor binary that is on the machine and uses the machine's own login. It never reads, copies or stores vendor tokens. It does not install or update vendor binaries, and it does not require vendor SDKs at run time.

Development

go test ./...
scripts/test.live.sh claude          # real binaries and real logins

The live tier needs the build tag live, a named harness in AGENTWIRE_LIVE, and a shared login. See docs/compat.md for the vendor versions this library was verified against.

Documentation

Overview

Package agentwire drives vendor coding-agent CLIs as child processes over their structured protocols: Claude stream-json, Codex app-server, OpenCode serve, ACP (Copilot, Cursor, Gemini, omp, grok) and pi RPC.

One event model covers every harness. The library knows vendors; it does not know consumers. Vendor flags, env vars, config formats, protocol quirks and version floors live here. Product homes, hooks, memory, secret env, job semantics and UI text belong to the caller.

There is no global state: every cache, pool and ledger hangs off one Runtime, and two runtimes in one process share nothing.

The wire is Go-native. Official vendor SDKs are the reference implementation and the fixture oracle at build and test time only; they are never a runtime dependency.

Index

Constants

View Source
const (
	EventInit       = wire.EventInit
	EventAssistant  = wire.EventAssistant
	EventThought    = wire.EventThought
	EventUser       = wire.EventUser
	EventTool       = wire.EventTool
	EventPermission = wire.EventPermission
	EventResult     = wire.EventResult
	EventError      = wire.EventError
	EventUsage      = wire.EventUsage
	EventStatus     = wire.EventStatus
	EventExit       = wire.EventExit
	EventLimits     = wire.EventLimits
)

Event types.

View Source
const (
	WindowFiveHour = wire.WindowFiveHour
	WindowDaily    = wire.WindowDaily
	WindowWeekly   = wire.WindowWeekly
	WindowMonthly  = wire.WindowMonthly
	WindowCustom   = wire.WindowCustom
)

Window keys.

View Source
const (
	StatusUnknown = wire.StatusUnknown
	StatusRunning = wire.StatusRunning
	StatusWaiting = wire.StatusWaiting
	StatusIdle    = wire.StatusIdle
)

Session statuses.

View Source
const (
	ToolRead   = wire.ToolRead
	ToolEdit   = wire.ToolEdit
	ToolExec   = wire.ToolExec
	ToolMCP    = wire.ToolMCP
	ToolSearch = wire.ToolSearch
	ToolOther  = wire.ToolOther
)

Tool kinds.

View Source
const (
	// DeliverySteered means the prompt joined the running turn.
	DeliverySteered = wire.DeliverySteered
	// DeliveryPrompted means a new turn starts now, or the vendor queues it.
	DeliveryPrompted = wire.DeliveryPrompted
	// DeliveryHeld means the prompt starts a new turn when the running turn
	// ends.
	DeliveryHeld = wire.DeliveryHeld
)
View Source
const CodeHeldPromptDropped = wire.CodeHeldPromptDropped

CodeHeldPromptDropped is the EventError code of held prompts that never reached the vendor, because the session ended first. Text holds them.

View Source
const DefaultFakeScript = `` /* 126-byte string literal not displayed */

DefaultFakeScript emits a tiny assistant turn and a success result.

The session writes one NDJSON line to the script's stdin per call: {"type":"user","text":…} for Prompt, {"type":"steer","text":…} for Steer while a turn runs, and {"type":"set_model","model":…} for SetModel. This script echoes a steer line that arrives during its turn:

read -r line
echo '{"type":"assistant","text":"working"}'
if read -r next; then echo '{"type":"user","text":"got: '"$next"'"}'; fi
echo '{"type":"result","subtype":"success"}'
View Source
const FakeModelsEnv = "AGENTWIRE_FAKE_MODELS"

FakeModelsEnv holds the fake model list as a JSON array of ModelInfo, used when FakeDriver.ModelList is empty.

View Source
const FakeScriptEnv = "AGENTWIRE_FAKE_SCRIPT"

FakeScriptEnv overrides the fake driver script from the environment.

View Source
const StallThreshold = 5 * time.Minute

StallThreshold is the "running but no events and no in-flight tools" window that raises a stall. It is observability only; the consumer's max duration is the killer.

Variables

View Source
var ErrNoActiveTurn = wire.ErrNoActiveTurn

ErrNoActiveTurn reports a Steer with no running turn.

View Source
var ErrResumeUnsupported = errors.New("agentwire: session resume not supported")

ErrResumeUnsupported reports a driver that cannot resume a session (the vendor no longer knows it, or the protocol has no resume).

View Source
var ErrTurnActive = wire.ErrTurnActive

ErrTurnActive reports a Prompt while a turn runs on a harness that cannot take a second prompt then. The caller waits for the result, or steers.

View Source
var ErrUnsupported = wire.ErrUnsupported

ErrUnsupported reports a capability the harness does not have.

Functions

func ClaudeMCPConfig added in v0.3.1

func ClaudeMCPConfig(servers []MCPServer) map[string]any

ClaudeMCPConfig renders the Claude/pi JSON shape for injected servers: the value of the top-level "mcpServers" key.

func CodexMCPArgs added in v0.3.1

func CodexMCPArgs(servers []MCPServer) []string

CodexMCPArgs renders the `-c mcp_servers.<name>...` overrides for injected servers. It never renders a secret value: a stdio server lists its env keys in env_vars, and a url server maps each header to an env var in env_http_headers. The process must carry CodexMCPEnv.

func CodexMCPEnv added in v0.3.2

func CodexMCPEnv(servers []MCPServer) (map[string]string, error)

CodexMCPEnv returns the env that the codex process must carry for the servers of CodexMCPArgs. Codex has one process env, so two servers that set one key to different values return an error.

func CredentialFiles added in v0.8.0

func CredentialFiles(h Harness) []string

CredentialFiles are the vendor credential files, relative to the login home. DefaultHome gives the home of the user's own login.

func DefaultEffort added in v0.8.0

func DefaultEffort(h Harness) string

DefaultEffort returns the effort that harness h uses when no effort is set, or "" when the vendor does not document one. A user config can change it.

func DefaultHome added in v0.8.0

func DefaultHome(h Harness) string

DefaultHome is the login home of the user's own login, such as ~/.claude. It is "" for a harness with no login facts.

func DropKeys added in v0.9.0

func DropKeys(format string, data []byte, keys []string) ([]byte, error)

DropKeys removes keys from a home file and gives the new content.

func Efforts added in v0.8.0

func Efforts(h Harness, model string) []string

Efforts returns the effort levels of model on harness h, from low to high. The levels are the same for each model of a harness, except OpenCode: there the levels are the variants of each model, and Efforts returns nil (use the live model list). An empty, non-nil result means that the harness has no effort setting.

func HomeMCPFile added in v0.9.0

func HomeMCPFile(h Harness) string

HomeMCPFile is the home file that holds the MCP servers of a harness, or "" when the harness reads them from elsewhere.

func HomeTrustFile added in v0.12.0

func HomeTrustFile(h Harness) string

HomeTrustFile is the home file that holds the trusted folders of a harness, or "" when the harness keeps no folder trust in its home.

func LoginCommand added in v0.8.0

func LoginCommand(h Harness) []string

LoginCommand is the command line that starts an interactive vendor login, with the binary first. Nil means that the user logs in inside the TUI with /login. Run it with LoginEnv.

func LoginCommandFor added in v0.9.0

func LoginCommandFor(h Harness, o LoginOptions) ([]string, error)

LoginCommandFor is LoginCommand for one login choice. A separate Claude login makes a long-lived token, because the keychain entry belongs to the user's own login. OpenCode needs a provider with a browser login.

func LoginDir added in v0.9.0

func LoginDir(h Harness) string

LoginDir is the login home inside a harness home, relative to it. Pass it to LoginEnv, AuthStatus, and the credential files. OpenCode keeps its login in an XDG data folder, apart from its config folder.

func LoginEnv added in v0.8.0

func LoginEnv(h Harness, home string) map[string]string

LoginEnv is the env for LoginCommand and LogoutCommand. A home that is not "" points the vendor at that login home. For OpenCode the home is the XDG data folder, which is not the StartRequest.Home config folder.

func LoginTokenEnv added in v0.9.0

func LoginTokenEnv(h Harness) string

LoginTokenEnv is the env key that holds a token login of h, such as the token that a separate Claude login prints. It is "" when h has none.

func LogoutCommand added in v0.8.0

func LogoutCommand(h Harness) []string

LogoutCommand is the vendor logout command line, with the binary first. Nil means that the vendor has no logout command.

func MergeKeys added in v0.9.0

func MergeKeys(format string, own, user []byte, keys, skip []string) ([]byte, error)

MergeKeys copies keys of user into own and gives the new own content. A nil keys copies each key not in skip. A key that user does not hold leaves own as it is. format is json or toml.

func ModelArgs added in v0.7.2

func ModelArgs(h Harness, model, effort string) []string

ModelArgs returns the flags that set the model and the reasoning effort on the interactive CLI of a harness, for example to start it in a terminal. An empty model or effort adds no flag. A harness without the flag gets none: OpenCode 2 has no model flag, and only some CLIs take an effort.

func OpenCodeConfigContent added in v0.3.1

func OpenCodeConfigContent(servers []MCPServer) (string, error)

OpenCodeConfigContent renders the OpenCode config fragment that carries the injected servers: the value of OPENCODE_CONFIG_CONTENT.

func SessionFiles added in v0.11.0

func SessionFiles(h Harness, home, id string) []string

SessionFiles lists the files and folders that harness h keeps in home for the vendor session id. Remove them to forget the session. The list is empty when the harness keeps no such files or id is not a plain name.

func SetHomeMCP added in v0.9.0

func SetHomeMCP(h Harness, data []byte, servers []MCPServer) ([]byte, error)

SetHomeMCP writes servers into the MCP file of a harness home and gives the new content. It replaces a server of the same name.

func SetHomeTrust added in v0.12.0

func SetHomeTrust(h Harness, data []byte, dirs []string) ([]byte, error)

SetHomeTrust marks dirs as trusted in the trust file of a harness home and gives the new content. The vendor CLI then shows no folder trust dialog for them. It keeps the other fields of each folder entry.

func SetKeys added in v0.9.0

func SetKeys(path string, data []byte, keys []HomeKey) ([]byte, error)

SetKeys writes values into a home file and gives the new content. The format comes from the file extension.

func SharedLoginEnv added in v0.9.0

func SharedLoginEnv(h Harness) map[string]string

SharedLoginEnv is the env that makes a harness home use the user's own login. On macOS, Claude keeps the login in the keychain; an empty CLAUDE_SECURESTORAGE_CONFIG_DIR keeps the default keychain entry while CLAUDE_CONFIG_DIR points at the home. OpenCode reads the user data folder.

func SharesUserHome added in v0.9.0

func SharesUserHome(h Harness) bool

SharesUserHome reports that a harness runs in the user's own home when it shares the user login. Codex cold-starts its plugin sync in a new home, omp keeps its login in a database, and OpenCode 2 reads only the user config folders.

func SplitArgs

func SplitArgs(command string) []string

SplitArgs splits a prepared command line into arguments.

The rules are the POSIX shell's word splitting, minus expansion: unquoted whitespace separates words, single quotes are literal, double quotes keep whitespace and honor a backslash before " \ $ ` and newline, and a backslash outside quotes escapes the next character. A consumer that pins a path with spaces or a literal backslash therefore gets the argument it wrote.

func TokenEnvKeys added in v0.8.0

func TokenEnvKeys(h Harness) []string

TokenEnvKeys are the env keys whose value is a usable login for h, such as an API key.

func WindowKey added in v0.6.0

func WindowKey(minutes int) string

WindowKey maps a window length in minutes to its key.

Types

type Attachment

type Attachment = wire.Attachment

Attachment is one out-of-band prompt payload (images first).

type AuthState

type AuthState string

AuthState is the best-effort login view of a harness.

const (
	AuthUnknown AuthState = "unknown"
	AuthOK      AuthState = "ok"
	AuthMissing AuthState = "missing"
	// AuthExpired means that a login is on file, but the vendor rejects it.
	AuthExpired AuthState = "expired"
)

func AuthStatus added in v0.8.0

func AuthStatus(ctx context.Context, h Harness, home string) (AuthState, error)

AuthStatus tells the login state of h in a login home. A home of "" means the user's own login, where a token env key also counts. It asks the vendor first, then reads the credential files. A login on file that the vendor rejects is AuthExpired.

type Classification

type Classification struct {
	Class   FailureClass
	Code    string
	Message string
}

Classification is the family plus a stable machine code.

func ClassifyError

func ClassifyError(text string, status int) Classification

ClassifyError inspects error-shaped text and returns the family and code.

text MUST be error-shaped: a result error field, a stderr tail, an HTTP status field. Free-form assistant prose or tool output yields false positives, because vendors quote ordinary words such as "capacity" and "credentials" in normal text. status is an optional HTTP status from the wire (0 = unknown).

func ClassifyFor

func ClassifyFor(h Harness, text string, status int) Classification

ClassifyFor is ClassifyError with the harness recorded in the code, so a consumer maps "claude.limit.rate_limited" and "codex.limit.rate_limited" to different text.

func ClassifyResult

func ClassifyResult(r *Result) Classification

ClassifyResult classifies an error result frame.

func ClassifyResultFor

func ClassifyResultFor(h Harness, r *Result) Classification

ClassifyResultFor is ClassifyResult with the harness recorded in the code.

func (Classification) EndReason

func (c Classification) EndReason() string

EndReason maps a class to the run end-reason vocabulary (auth|limit|failed|launch).

type Credentials added in v0.3.0

type Credentials struct {
	// Format names the provider wire: "anthropic" or "openai".
	Format string
	// BaseURL overrides the provider endpoint. Empty uses the provider
	// default; an http(s) URL turns the SDK into a gateway client.
	BaseURL string
	// APIKey is the bearer credential.
	APIKey string
	// Headers are extra HTTP headers for every provider call.
	Headers map[string]string
}

Credentials are the provider credentials a native session runs with.

type Decision

type Decision = wire.Decision

Decision answers a Permission.

type Delivery added in v0.5.0

type Delivery = wire.Delivery

Delivery tells how Send delivered a prompt.

func Send added in v0.5.0

func Send(ctx context.Context, s Session, p Prompt) (Delivery, error)

Send delivers p in one call: it steers the running turn, else prompts, else holds p until the turn ends. A session with no Sender gets Prompt.

type Detection

type Detection struct {
	Harness Harness
	// Path is the resolved binary, empty when the harness is not installed.
	Path string
	// Version is the first line of `<bin> --version`, empty when unknown.
	Version string
	// Supported reports that the binary exists and meets the version floor.
	Supported bool
	// Ready reports Supported && Auth == AuthOK. Runtime.Detect sets it for
	// every harness, so a picker reads one field.
	Ready bool
	// Auth is the best-effort login state.
	Auth AuthState
	// AuthDetail is a short note from the auth probe, such as an account
	// email. It never contains a token.
	AuthDetail string
	// Features is the static capability table for the harness.
	Features Features
	// Detail explains an unsupported verdict.
	Detail string
}

Detection is the result of probing one harness on this machine.

type Detector added in v0.3.0

type Detector interface {
	Detect(ctx context.Context) (Detection, error)
}

Detector lets a driver answer Detect without a binary lookup. The native driver implements it: it is in-process and has no binary to find.

type Driver

type Driver interface {
	Name() string
	Start(ctx context.Context, req StartRequest) (Session, error)
	Resume(ctx context.Context, req StartRequest) (Session, error)
}

Driver starts sessions for one vendor protocol. The built-in drivers are unexported and reached through Runtime.Driver.

type Event

type Event = wire.Event

Event is one normalized wire event from a vendor session.

type EventType

type EventType = wire.EventType

EventType is the normalized wire vocabulary.

type FailureClass

type FailureClass string

FailureClass is the entitlement/quota classification family. No pricing logic lives here, and no operator-facing text: a consumer maps the class and the code to its own wording.

const (
	FailNone        FailureClass = ""
	FailAuth        FailureClass = "auth"
	FailLimit       FailureClass = "limit"
	FailEntitlement FailureClass = "entitlement"
	FailOverloaded  FailureClass = "overloaded"
	FailProtocol    FailureClass = "protocol"
	FailUnknown     FailureClass = "unknown"
)

type FakeDriver

type FakeDriver struct {
	// Script is a shell program. Empty uses FakeScriptEnv, then
	// DefaultFakeScript.
	Script string
	// Dir overrides the working directory.
	Dir string
	// ModelList is the list Runtime.Models returns. Empty reads
	// FakeModelsEnv.
	ModelList []ModelInfo
	// NoSteer makes every steer fail with ErrUnsupported, so Send holds a
	// prompt during a turn and sends it after the result.
	NoSteer bool
}

FakeDriver is the scripted driver for tests: it spawns a shell that emits the same NDJSON vocabulary the vendors do, so a consumer's event pump is exercised without a vendor binary.

It implements Driver on its own, so a consumer injects it with Runtime.SetDriver.

func (FakeDriver) Models added in v0.4.0

Models implements ModelLister: ModelList, else the list in FakeModelsEnv.

func (FakeDriver) Name

func (FakeDriver) Name() string

Name implements Driver.

func (FakeDriver) Resume

func (f FakeDriver) Resume(ctx context.Context, req StartRequest) (Session, error)

Resume implements Driver.

func (FakeDriver) Start

func (f FakeDriver) Start(ctx context.Context, req StartRequest) (Session, error)

Start implements Driver.

type Features

type Features struct {
	// MCPInject says how MCPServers reach the harness.
	MCPInject MCPCapability
	// SkillsInject says how Skills reach the harness.
	SkillsInject SkillSupport
	// Instructions reports per-session instruction support.
	Instructions bool
	// Attachments reports prompt image support.
	Attachments bool
	// Resume reports session resume support.
	Resume bool
	// Permissions reports that the harness asks before a tool runs and the
	// library can answer.
	Permissions bool
	// FS reports that the harness can route file reads and writes to
	// StartRequest.FS.
	FS bool
	// Models reports that Runtime.Models can list the harness models.
	Models bool
	// SetModel reports that a live session implements ModelSetter.
	SetModel bool
	// Steer reports that a live session implements Steerer and the vendor
	// takes input during a running turn.
	Steer bool
}

Features reports what a harness can take per session. It is static per harness: a feature that needs StartRequest.Home degrades at run time rather than appearing and disappearing here.

type FileSystem

type FileSystem interface {
	ReadTextFile(ctx context.Context, path string) (string, error)
	WriteTextFile(ctx context.Context, path, content string) error
}

FileSystem is the optional file access an ACP agent may request. When it is set, the library advertises fs.readTextFile and fs.writeTextFile and routes those calls here, so a consumer can observe or approve every file write.

type Harness

type Harness string

Harness identifies one vendor coding agent.

const (
	Claude    Harness = "claude"
	Codex     Harness = "codex"
	OpenCode  Harness = "opencode"
	OpenCode2 Harness = "opencode2"
	Pi        Harness = "pi"
	Copilot   Harness = "copilot"
	Cursor    Harness = "cursor"
	Gemini    Harness = "gemini"
	OMP       Harness = "omp"
	Grok      Harness = "grok"
	Fake      Harness = "fake"
	// Native is the in-process harness the native module serves. It is not
	// in New's built-in driver table: a consumer registers it with
	// SetDriver(Native, nativedriver) or leaves it out entirely.
	Native Harness = "native"
)

func Harnesses

func Harnesses() []Harness

Harnesses lists every harness the library can drive a wire for.

type HomeFile added in v0.9.0

type HomeFile struct {
	Key  string       `json:"key"`
	Kind HomeFileKind `json:"kind"`
	// User is the path relative to the user's home folder.
	User string `json:"user"`
	// Home is the path relative to the harness home.
	Home string `json:"home"`
	// Format is json, toml, or markdown for a file, and empty for a folder.
	Format string `json:"format,omitempty"`
	// Keys are the keys that a HomeKeys entry copies. A TOML key can be
	// "table.key". Nil copies each key that no other entry of the same
	// home file names.
	Keys []string `json:"keys,omitempty"`
	// Share is the default: true shares the user file.
	Share bool `json:"share"`
	// Login marks a login file. The auth mode decides its share.
	Login bool `json:"login,omitempty"`
	// Hooks marks a file or a key that holds user hooks.
	Hooks bool `json:"hooks,omitempty"`
	// OwnEnv is the env key that points the vendor at the own copy.
	OwnEnv string `json:"own_env,omitempty"`
}

HomeFile is one vendor file or folder that a harness home can share with the user's own setup, or keep as an own copy.

func HomeFiles added in v0.9.0

func HomeFiles(h Harness) []HomeFile

HomeFiles lists the files and folders of a harness home that can share the user's own setup. Nil means that the harness has no home layout.

type HomeFileKind added in v0.9.0

type HomeFileKind string

HomeFileKind tells how a user file reaches a harness home.

const (
	// HomeLink links the home path to the user file or folder.
	HomeLink HomeFileKind = "link"
	// HomeEach links each entry of the user folder into the home folder.
	HomeEach HomeFileKind = "each"
	// HomeKeys copies top-level keys of the user file into the home file.
	HomeKeys HomeFileKind = "keys"
	// HomeRead marks a user file that the vendor reads itself. An own copy
	// lives at Home, and OwnEnv points the vendor at it.
	HomeRead HomeFileKind = "read"
)

type HomeKey added in v0.9.0

type HomeKey struct {
	// File is the path relative to the harness home.
	File string `json:"file"`
	// Key is a top-level JSON key, or a TOML key with its table:
	// "table.key".
	Key   string `json:"key"`
	Value any    `json:"value"`
	// Keep keeps a value that the file holds already.
	Keep bool `json:"keep,omitempty"`
}

HomeKey is one value that a home file must hold.

func HomeDefaults added in v0.9.0

func HomeDefaults(h Harness) []HomeKey

HomeDefaults are the values that a harness home needs to start with no first-run dialog and no self-update.

type InstallMethod added in v0.8.0

type InstallMethod string

InstallMethod is how a harness binary gets on the machine.

const (
	// InstallNPM installs a global npm package.
	InstallNPM InstallMethod = "npm"
	// InstallBrew installs a Homebrew formula.
	InstallBrew InstallMethod = "brew"
	// InstallScript pipes a vendor install script into a shell.
	InstallScript InstallMethod = "script"
	// InstallBinary downloads a binary. The caller does the download.
	InstallBinary InstallMethod = "binary"
)

type InstallRecipe added in v0.8.0

type InstallRecipe struct {
	Harness Harness
	Method  InstallMethod
	// Package is the npm package, the brew formula, the script URL, or the
	// download URL.
	Package string
	// Bin is the binary that the install puts on PATH.
	Bin string
	// Requires lists the programs that must be on PATH before the install.
	Requires []string
	// Update is the vendor self-update command after Bin. Nil means install
	// again.
	Update []string
	// UpdateTo is the command after Bin that installs one version. The
	// version goes last. Nil means install again.
	UpdateTo []string
	// UpdateOffEnv is the env that stops the vendor auto-update.
	UpdateOffEnv map[string]string
	// UpdateOffArgs are launch args that stop the vendor auto-update.
	UpdateOffArgs []string
}

InstallRecipe holds the vendor facts to install and update one harness. The library runs none of these commands.

func Recipe added in v0.8.0

func Recipe(h Harness) (InstallRecipe, bool)

Recipe returns the install facts of h. ok=false means that the harness has no vendor install, such as the native and fake harnesses.

func (InstallRecipe) Command added in v0.8.0

func (r InstallRecipe) Command(version string) []string

Command is the install command line. An empty version means the latest. The script method ignores the version. The binary method gives nil.

func (InstallRecipe) UpdateCommand added in v0.8.0

func (r InstallRecipe) UpdateCommand(version string) []string

UpdateCommand is the command line that updates Bin. An empty version means the latest. It falls back to Command when the vendor has no matching self-update.

type LedgerEntry

type LedgerEntry struct {
	Key        string    `json:"key"`
	Harness    string    `json:"harness"`
	PID        int       `json:"pid"`
	PGID       int       `json:"pgid,omitempty"`
	StartToken string    `json:"start_token,omitempty"`
	StartedAt  time.Time `json:"started_at"`
	Argv       []string  `json:"argv,omitempty"`
}

LedgerEntry is one spawned child, recorded so a crashed process can reconcile leftovers on boot. StartToken is the positive-match guard against pid reuse.

type LimitWindow added in v0.6.0

type LimitWindow = wire.LimitWindow

LimitWindow is the use of one plan limit window.

type Limits added in v0.6.0

type Limits = wire.Limits

Limits is the plan limit state of the account behind a session.

type LimitsReader added in v0.6.0

type LimitsReader interface {
	ReadLimits(ctx context.Context, q ModelQuery) (Limits, error)
}

LimitsReader is the optional driver capability that reads the plan limits of the account without a live session.

type LoginOptions added in v0.9.0

type LoginOptions struct {
	// Provider is the model provider of a harness that has many, such as
	// OpenCode.
	Provider string
	// Separate is a login apart from the user's own login.
	Separate bool
}

LoginOptions picks a vendor login.

type MCPCapability

type MCPCapability string

MCPCapability reports how a harness accepts per-session MCP servers.

const (
	// MCPNone means the harness cannot take an injected server.
	MCPNone MCPCapability = "none"
	// MCPFlags means the library renders the servers into the launch.
	MCPFlags MCPCapability = "flags"
	// MCPSession means the servers travel in the session-open request.
	MCPSession MCPCapability = "session"
)

type MCPServer

type MCPServer struct {
	Name    string
	Command string
	Args    []string
	Env     map[string]string
	URL     string
	Headers map[string]string
}

MCPServer is one Model Context Protocol server injected into a session. Command selects a stdio server; URL selects an HTTP server.

type ModelInfo added in v0.4.0

type ModelInfo struct {
	// ID is the value StartRequest.Model and SetModel take.
	ID string `json:"id"`
	// Name is the display name. Empty means use ID.
	Name string `json:"name,omitempty"`
	// Description is a short vendor note, such as the context size.
	Description string `json:"description,omitempty"`
	// Default marks the model the harness uses when Model is empty.
	Default bool `json:"default,omitempty"`
}

ModelInfo is one model a harness offers.

func StaticModels added in v0.8.0

func StaticModels(h Harness) []ModelInfo

StaticModels returns a fixed model list for a harness whose CLI cannot list models without a session, or nil. It needs no install and no login. Runtime.Models gives the live list.

type ModelLister added in v0.4.0

type ModelLister interface {
	Models(ctx context.Context, q ModelQuery) ([]ModelInfo, error)
}

ModelLister is the optional driver capability that lists models without a live session.

type ModelQuery added in v0.4.0

type ModelQuery struct {
	Binary     string            // empty searches PATH
	Home       string            // same meaning as StartRequest.Home
	Env        map[string]string // same meaning as StartRequest.Env
	WorkingDir string            // some vendors read project config
}

ModelQuery scopes a model list to one install and one config home.

type ModelSetter added in v0.4.0

type ModelSetter interface {
	SetModel(ctx context.Context, model string) error
}

ModelSetter is the optional session capability that changes the model of a live session. It never interrupts the running turn.

type Options

type Options struct {
	// ClientName is the product name the harness sees in clientInfo and in
	// vendor client markers, for example "trebi" or "sling".
	ClientName string
	// ClientVersion is the product version reported next to ClientName.
	ClientVersion string
	// LedgerPath is the crash ledger file. Empty disables the ledger.
	LedgerPath string
	// Logger receives structured lifecycle lines. Nil discards them.
	Logger *slog.Logger
	// FingerprintIgnoreEnv lists environment keys left out of the OpenCode
	// server fingerprint, so per-run values do not split a shared server.
	FingerprintIgnoreEnv []string
	// MaxFrameBytes bounds one wire frame. Zero uses 16 MiB.
	MaxFrameBytes int
	// OnStderr receives live stderr lines from every child.
	OnStderr func(Harness, string)
	// AuthCacheTTL is how long a harness login probe answers from cache.
	// Zero uses 5 min. A negative value disables the cache.
	AuthCacheTTL time.Duration
	// ModelCacheTTL is how long a model list answers from cache. Zero uses
	// 10 min. A negative value disables the cache.
	ModelCacheTTL time.Duration
	// HTTPClient makes the release lookups of LatestVersion. Nil uses a
	// client with a 10 s timeout.
	HTTPClient *http.Client
}

Options configures a Runtime.

type Permission

type Permission = wire.Permission

Permission is a vendor permission request awaiting a decision.

type PermissionMode

type PermissionMode string

PermissionMode is the one policy that every harness maps onto its own mechanism.

const (
	// PermissionAsk routes every request to the consumer as an
	// EventPermission. It is the default.
	PermissionAsk PermissionMode = "ask"
	// PermissionAutoEdit answers edit requests in the library and routes the
	// rest to the consumer.
	PermissionAutoEdit PermissionMode = "auto_edit"
	// PermissionAuto answers every request in the library.
	PermissionAuto PermissionMode = "auto"
	// PermissionInherit adds no permission-mode flag, so the operator's own
	// harness configuration decides. It is the mode for a consumer that must
	// not override a setting the operator made in the harness itself. The
	// host prompt callback is still installed, and requests the harness sends
	// are routed to the consumer, like ask.
	PermissionInherit PermissionMode = "inherit"
)

type PermissionPolicy

type PermissionPolicy struct {
	Mode PermissionMode
	// AllowTools lists extra tools that are allowed without asking, in "ask"
	// and "auto_edit" mode. Patterns may end in "*", for example
	// "mcp__sling__*".
	AllowTools []string
}

PermissionPolicy is the normalized permission policy.

func (PermissionPolicy) Answer

func (p PermissionPolicy) Answer(kind ToolKind, tool string) (Decision, bool)

Answer returns the decision the policy makes without the consumer. ok=false means the request becomes an EventPermission.

func (PermissionPolicy) Normalized

func (p PermissionPolicy) Normalized() PermissionPolicy

Normalized returns the policy with a concrete mode.

type Prompt

type Prompt = wire.Prompt

Prompt is one user turn written to a live session.

type Registry

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

Registry owns the live sessions and the pid ledger. A Runtime builds one when Options.LedgerPath is set.

func NewRegistry

func NewRegistry(path string) *Registry

NewRegistry builds a registry whose ledger lives at path. An empty path disables persistence, which is what tests want.

func (*Registry) Add

func (r *Registry) Add(key string, s Session)

Add registers a live session under key and records its pid.

func (*Registry) CloseAll

func (r *Registry) CloseAll(ctx context.Context)

CloseAll gracefully closes every live session and empties the registry.

func (*Registry) Get

func (r *Registry) Get(key string) (Session, bool)

Get returns the live session for a key.

func (*Registry) Keys

func (r *Registry) Keys() []string

Keys lists live session keys in no particular order.

func (*Registry) List

func (r *Registry) List() []LedgerEntry

List reads the persisted ledger.

func (*Registry) Path

func (r *Registry) Path() string

Path is the ledger file, empty when the registry is memory-only.

func (*Registry) Reconcile

func (r *Registry) Reconcile() (killed []string, stale []string)

Reconcile is the boot path: for each ledger row, kill the process only when it is positively matched (alive and start token equal); every row is then dropped. It returns the run keys whose processes were killed and every key that needs a session_gone row.

func (*Registry) Remove

func (r *Registry) Remove(key string)

Remove unregisters a session and drops its ledger row.

func (*Registry) Track

func (r *Registry) Track(key, harness string, pid, pgid int, argv []string)

Track records a process without a live Session. Use it for a daemon-owned helper such as an OpenCode server.

type Result

type Result = wire.Result

Result is the terminal turn payload.

type Runtime

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

Runtime owns every piece of per-process state: the driver table, the OpenCode server pool, version caches, the crash ledger and the id counter.

func New

func New(opts Options) *Runtime

New builds a runtime.

func (*Runtime) Close

func (rt *Runtime) Close(ctx context.Context) error

Close stops every live session and shared server.

func (*Runtime) Detect

func (rt *Runtime) Detect(ctx context.Context, h Harness) (Detection, error)

Detect probes one harness: binary, version, floor and login state. A missing binary is a supported=false verdict, not an error. A driver that implements Detector answers for itself without a binary lookup.

func (*Runtime) Driver

func (rt *Runtime) Driver(h Harness) (Driver, bool)

Driver resolves the driver for a harness. ok=false means the harness has no proven wire.

func (*Runtime) HelpText

func (rt *Runtime) HelpText(ctx context.Context, bin string) string

HelpText returns the cached `<bin> --help` output, for a decision that needs to compare two flags instead of testing one.

func (*Runtime) InvalidateDetect added in v0.2.2

func (rt *Runtime) InvalidateDetect(h Harness)

InvalidateDetect drops the cached login probes of one harness. A consumer calls it after a login or a logout, so Detect sees the new state at once.

func (*Runtime) InvalidateModels added in v0.4.0

func (rt *Runtime) InvalidateModels(h Harness)

InvalidateModels drops the cached lists of one harness, for example after a login or a config change.

func (*Runtime) LatestVersion added in v0.8.0

func (rt *Runtime) LatestVersion(ctx context.Context, h Harness) (string, error)

LatestVersion asks the vendor registry for the newest release of h, such as "2.1.308". It uses Options.HTTPClient. A harness with no registry gives ErrUnsupported.

func (*Runtime) Limits added in v0.6.0

func (rt *Runtime) Limits(ctx context.Context, h Harness, q ModelQuery) (Limits, error)

Limits reads the plan limits of one harness account. It starts a short-lived child and never caches. A harness with no reader returns ErrUnsupported.

func (*Runtime) Logger

func (rt *Runtime) Logger() *slog.Logger

Logger is the runtime logger.

func (*Runtime) MaxFrameBytes

func (rt *Runtime) MaxFrameBytes() int

MaxFrameBytes is the effective frame cap.

func (*Runtime) Models added in v0.4.0

func (rt *Runtime) Models(ctx context.Context, h Harness, q ModelQuery) ([]ModelInfo, error)

Models lists the models of one harness. It answers from a cache; the first call can start a short-lived child. A harness with no lister returns ErrUnsupported.

func (*Runtime) OpenCodeMessages

func (rt *Runtime) OpenCodeMessages(ctx context.Context, sessionID string) ([]byte, bool)

OpenCodeMessages returns the persisted message list for one session from a live server. ok=false means no server currently holds the session.

func (*Runtime) Reconcile

func (rt *Runtime) Reconcile() error

Reconcile kills leftover children from a previous process and drops the ledger. Only positively matched pids die: a reused pid is left alone.

func (*Runtime) Resume

func (rt *Runtime) Resume(ctx context.Context, req StartRequest) (Session, error)

Resume reopens a known session. It returns ErrResumeUnsupported when the vendor no longer knows the session, or the protocol has no resume.

func (*Runtime) SetDriver

func (rt *Runtime) SetDriver(h Harness, d Driver)

SetDriver replaces the driver for a harness. Tests use it to inject a scripted driver. nil drops the override and restores the built-in driver, so a caller can always get back to the default table.

func (*Runtime) Start

func (rt *Runtime) Start(ctx context.Context, req StartRequest) (Session, error)

Start opens a new session and waits for the protocol handshake. A handshake failure is returned as an error; the child is killed first.

func (*Runtime) Version

func (rt *Runtime) Version(ctx context.Context, bin string) string

Version returns the cached `--version` line of a binary.

type Sender added in v0.5.0

type Sender interface {
	Send(ctx context.Context, p Prompt) (Delivery, error)
}

Sender is the session capability behind Send.

type Session

type Session interface {
	// Provider is the harness name.
	Provider() string
	// ID is the vendor session id; it may be empty until init.
	ID() string
	// PID is the child process id (0 for in-process sessions).
	PID() int
	// Prompt writes one user turn.
	Prompt(ctx context.Context, p Prompt) error
	// Interrupt asks the harness to stop the current turn.
	Interrupt(ctx context.Context) error
	// AnswerPermission routes a decision to the vendor.
	AnswerPermission(ctx context.Context, permissionID string, d Decision) error
	// InterruptTurn is the protocol-level interrupt. It is the same call as
	// Interrupt for every built-in driver, and is kept for callers that want
	// to be explicit about not falling back to a signal.
	InterruptTurn(ctx context.Context) error
	// Done is closed when the session has ended and Events is drained.
	Done() <-chan struct{}
	// Err is the first failure the session recorded, if any.
	Err() error
	// Close runs the graceful close sequence and waits for the drain.
	Close(ctx context.Context) error
	// Events is closed after the terminal events.
	Events() <-chan Event
}

Session is one vendor conversation. The caller holds it for the life of the run.

A caller MUST read Events until it closes, or call Close. A caller that stops reading without closing leaks the framing goroutine.

type SessionModels added in v0.4.0

type SessionModels interface {
	Models() []ModelInfo
}

SessionModels is the optional session capability that returns the models the live session offered in its handshake (Claude initialize, ACP session/new). It returns nil when the handshake offered none.

type SessionStatus

type SessionStatus = wire.SessionStatus

SessionStatus is the activity view of a session.

type Skill

type Skill struct {
	// Name overrides the name in the SKILL.md frontmatter.
	Name string
	// Dir is the absolute path of the directory that holds SKILL.md.
	Dir string
}

Skill is one extra skill made visible to a session.

type SkillSupport

type SkillSupport string

SkillSupport reports how a harness takes extra skills for one session.

const (
	// SkillNone means the harness gets no extra skills.
	SkillNone SkillSupport = "none"
	// SkillNative means the harness loads the skills itself, from a directory
	// the library generates or points at.
	SkillNative SkillSupport = "native"
	// SkillOverlay means the library links the skills into a per-session
	// config directory the harness already reads.
	SkillOverlay SkillSupport = "overlay"
	// SkillInstructions means the skills are described in the session
	// instructions and the agent reads them from disk.
	SkillInstructions SkillSupport = "instructions"
)

type Snapshot

type Snapshot struct {
	State         SessionStatus
	TurnActive    bool
	Progressing   bool
	Stalled       bool
	StallSince    time.Time
	InflightTools int
	OpenApprovals int
	LastEventAt   time.Time
	LastEventKind EventType
	LastToolName  string
	IdleSince     time.Time
	WaitingSince  time.Time
	LastResult    *Result
	// Exited reports that the session's process ended.
	Exited bool
	// ExitedAt is the moment the exit event landed.
	ExitedAt time.Time
}

Snapshot is the derived state a consumer and its reaper read.

type StartRequest

type StartRequest struct {
	Harness Harness
	// Binary is the executable. Empty searches PATH.
	Binary     string
	WorkingDir string
	// Env is added to the parent environment and wins on conflict.
	Env map[string]string
	// Home is an optional isolated config home. Empty uses the user's own
	// configuration, including their login, MCP servers and skills.
	Home string

	// SessionID is a resume target, or a pre-assigned id where the harness
	// allows one.
	SessionID string
	// Model is the vendor model name. Empty uses the vendor default.
	Model string
	// Effort is one of low, medium, high, max. Empty uses the vendor default.
	Effort string

	Permissions  PermissionPolicy
	MCPServers   []MCPServer
	Skills       []Skill
	Instructions string

	// Credentials carries the provider credentials for the native harness
	// only. The library never reads env or secrets to fill it, and the CLI
	// drivers ignore it.
	Credentials *Credentials

	// OneShot writes OneShotPrompt and closes stdin, so the child exits after
	// the turn. Use it for a job with no follow-up surface.
	OneShot       bool
	OneShotPrompt string
	// HandshakeTimeout overrides the protocol handshake wait.
	HandshakeTimeout time.Duration

	// ExtraArgs is appended verbatim after the library's own arguments. Use
	// it for consumer flags such as --settings or -c notify=...
	ExtraArgs []string
	// BaseCommand is an expert override: when it is set the launch builder is
	// skipped and the command is used as-is.
	BaseCommand []string
	// Raw carries per-harness overrides the typed fields do not cover:
	// "strict_mcp" (bool, Claude), "acp_auth_method" (string), "approvalPolicy"
	// and "sandbox" (string, Codex), "auth_method" (string, ACP).
	Raw map[string]any

	// LogPath writes the frames in and out as NDJSON. Secrets are not
	// redacted, so the file is created mode 0600. Stream fragments that a
	// later complete frame repeats are left out unless LogFragments is set.
	LogPath string
	// FragmentLogPath gets the buffered fragments of the open message when
	// the stream fails or ends before the complete message. Empty drops them.
	FragmentLogPath string
	// LogFragments writes every stream fragment to LogPath (debug).
	LogFragments bool
	// FS is the optional ACP file-system adapter.
	FS FileSystem
	// OnStderr receives live stderr lines from this child.
	OnStderr func(Harness, string)
}

StartRequest describes one session. The caller describes; the library builds the command.

func (StartRequest) RawBool

func (r StartRequest) RawBool(key string) (bool, bool)

RawBool reads a boolean override.

func (StartRequest) RawString

func (r StartRequest) RawString(key string) string

RawString reads a string override.

type Steerer added in v0.4.0

type Steerer interface {
	Steer(ctx context.Context, p Prompt) error
}

Steerer is the optional session capability that adds input to the running turn. It returns ErrNoActiveTurn when no turn runs; the caller then uses Prompt. It returns ErrUnsupported when the vendor refused the steer; the caller then queues the input. It never interrupts the turn.

type ToolEvent

type ToolEvent = wire.ToolEvent

ToolEvent is one tool invocation (start or completion).

type ToolKind

type ToolKind = wire.ToolKind

ToolKind is the coarse class of a tool invocation.

func ClassifyTool

func ClassifyTool(name string) ToolKind

ClassifyTool maps a vendor tool name to a coarse kind, so a consumer can render or auto-approve without a per-vendor name table.

type Tracker

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

Tracker is the per-run activity tracker. Wire events are the only input; a consumer reads snapshots instead of scraping a terminal.

func NewTracker

func NewTracker() *Tracker

NewTracker builds an empty tracker.

func (*Tracker) InflightCount

func (t *Tracker) InflightCount() int

InflightCount reports tool invocations without a matching result.

func (*Tracker) Observe

func (t *Tracker) Observe(e Event) Snapshot

Observe folds one event into the tracker and returns the derived snapshot.

func (*Tracker) OldestApprovalID

func (t *Tracker) OldestApprovalID() string

OldestApprovalID returns the oldest open wire permission id, or "" when the pending approval has none.

func (*Tracker) PermissionAdded

func (t *Tracker) PermissionAdded(id string) Snapshot

PermissionAdded registers an approval the wire did not open.

func (*Tracker) PermissionDecided

func (t *Tracker) PermissionDecided(id string) Snapshot

PermissionDecided clears an open approval. An empty id clears the oldest one (approvals that arrived without a wire id have none).

func (*Tracker) PromptWritten

func (t *Tracker) PromptWritten()

PromptWritten marks the optimistic running state when a prompt is written: model latency is not idleness.

func (*Tracker) Snapshot

func (t *Tracker) Snapshot() Snapshot

Snapshot returns the derived state.

type Usage

type Usage = wire.Usage

Usage is token/cost accounting in vendor units.

Directories

Path Synopsis
internal
flight
Package flight collapses concurrent same-key calls into one execution.
Package flight collapses concurrent same-key calls into one execution.
jsonrpc
Package jsonrpc is the JSON-RPC 2.0 client shared by the harness drivers that speak it (Codex app-server, ACP agents).
Package jsonrpc is the JSON-RPC 2.0 client shared by the harness drivers that speak it (Codex app-server, ACP agents).
proc
Package proc spawns and reaps one child process with explicit pipes, group control, and a single Wait.
Package proc spawns and reaps one child process with explicit pipes, group control, and a single Wait.
wire
Package wire is the session plumbing shared by every harness driver: it owns the child process, the NDJSON framing, the event fan-out, and the graceful close sequence.
Package wire is the session plumbing shared by every harness driver: it owns the child process, the NDJSON framing, the event fan-out, and the graceful close sequence.
native module
Package tui reads the screen of a vendor TUI that runs in a terminal.
Package tui reads the screen of a vendor TUI that runs in a terminal.

Jump to

Keyboard shortcuts

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