libacp

package module
v0.0.0-...-c4fad48 Latest Latest
Warning

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

Go to latest
Published: Jul 30, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

README

libacp

libacp is a Go implementation of the Agent Client Protocol (ACP) — the JSON-RPC-over-NDJSON protocol editors and coding agents use to talk to each other. It implements ACP v1 (ProtocolVersion = 1).

It was extracted from contenox/contenox, where it is the one deliberately public library in an otherwise internal codebase.

What it provides

libacp implements both roles of the protocol:

  • Agent side — implement the Agent interface (or embed UnimplementedAgent and override only what you need) and serve it over a transport with NewAgentSideConnection.
  • Client side — implement the Client interface (or embed UnimplementedClient) and drive an agent over a transport with NewClientSideConnection.

Both roles share the same wire machinery:

  • NDJSON framing over any io.ReadWriteCloser
  • Request-id correlation
  • Per-request cancelable contexts honoring $/cancel_request
  • Panic-safe handler dispatch
  • Extension-method passthrough

The acpexec subpackage (github.com/contenox/libacp/acpexec) spawns an agent (or client) subprocess over stdio and wires its stdin/stdout together into the io.ReadWriteCloser a connection expects, plus a Supervisor for restart/backoff around a long-lived subprocess.

Install

go get github.com/contenox/libacp

Usage

Client-role usage — spawn an agent subprocess, connect, initialize, open a session, and send a prompt:

proc, err := acpexec.Spawn(ctx, exec.Command("contenox", "acp"))
if err != nil {
	return err
}

conn := libacp.NewClientSideConnection(proc, func(*libacp.ClientSideConnection) libacp.Client {
	return myClient{} // embeds libacp.UnimplementedClient
})
go conn.Run(ctx)

if _, err := conn.Initialize(ctx, libacp.InitializeRequest{
	ProtocolVersion: libacp.ProtocolVersion,
	ClientInfo:      &libacp.Implementation{Name: "my-editor", Version: "1.0"},
}); err != nil {
	return err
}

sess, err := conn.NewSession(ctx, libacp.NewSessionRequest{Cwd: "/abs/path/to/project"})
if err != nil {
	return err
}

resp, err := conn.Prompt(ctx, libacp.PromptRequest{
	SessionID: sess.SessionID,
	Prompt:    []libacp.ContentBlock{libacp.NewTextContent("hello")},
})
_ = conn.CancelPrompt(sess.SessionID) // cancel the in-flight turn from another goroutine

Agent-role usage mirrors this: implement Agent (or embed UnimplementedAgent), and serve it over a transport with NewAgentSideConnection(rw, factory).

See the package doc and the *_test.go files for further detail on individual methods, session updates, permissions, terminals, and MCP wiring.

License

Apache License 2.0 — see LICENSE.

Documentation

Overview

Package libacp implements the Agent Client Protocol (ACP) v1, the JSON-RPC-over-NDJSON protocol editors and agents use to talk to each other. It supports both roles: the agent side, implementing Agent (or embedding UnimplementedAgent) and serving it via NewAgentSideConnection; and the client side, implementing Client (or embedding UnimplementedClient) and driving an agent via NewClientSideConnection. Both share the same wire machinery: NDJSON framing, request-id correlation, per-request cancelable contexts honoring "$/cancel_request", panic-safe handler dispatch, and extension-method passthrough.

A connection reads from any io.ReadWriteCloser; the subpackage github.com/contenox/libacp/acpexec spawns an agent subprocess over stdio and hands back the transport.

Client-role usage — spawn, connect, initialize, open a session, prompt:

proc, err := acpexec.Spawn(ctx, exec.Command("contenox", "acp"))
if err != nil {
	return err
}
conn := libacp.NewClientSideConnection(proc, func(*libacp.ClientSideConnection) libacp.Client {
	return myClient{} // embeds libacp.UnimplementedClient
})
go conn.Run(ctx)

if _, err := conn.Initialize(ctx, libacp.InitializeRequest{
	ProtocolVersion: libacp.ProtocolVersion,
	ClientInfo:      &libacp.Implementation{Name: "my-editor", Version: "1.0"},
}); err != nil {
	return err
}

sess, err := conn.NewSession(ctx, libacp.NewSessionRequest{Cwd: "/abs/path/to/project"})
if err != nil {
	return err
}

resp, err := conn.Prompt(ctx, libacp.PromptRequest{
	SessionID: sess.SessionID,
	Prompt:    []libacp.ContentBlock{libacp.NewTextContent("hello")},
})
_ = conn.CancelPrompt(sess.SessionID) // cancel the in-flight turn from another goroutine

Index

Examples

Constants

View Source
const (
	ErrParseError     = -32700
	ErrInvalidRequest = -32600
	ErrMethodNotFound = -32601
	ErrInvalidParams  = -32602
	ErrInternalError  = -32603

	ErrAuthRequired = -32000
	// ErrRequestTimeout is the wire signal that a peer's handler ran out of
	// time, since a Go sentinel cannot cross the JSON-RPC boundary. Matches
	// the code MCP implementations use for the same condition.
	ErrRequestTimeout   = -32001
	ErrResourceNotFound = -32002
)
View Source
const (
	AuthMethodTypeTerminal = "terminal"
	AuthMethodTypeEnvVar   = "env_var"
)
View Source
const (
	MethodInitialize   = "initialize"
	MethodAuthenticate = "authenticate"
	MethodLogout       = "logout"

	MethodSessionNew             = "session/new"
	MethodSessionLoad            = "session/load"
	MethodSessionResume          = "session/resume"
	MethodSessionClose           = "session/close"
	MethodSessionDelete          = "session/delete"
	MethodSessionList            = "session/list"
	MethodSessionPrompt          = "session/prompt"
	MethodSessionCancel          = "session/cancel"
	MethodSessionUpdate          = "session/update"
	MethodSessionSetMode         = "session/set_mode"
	MethodSessionSetConfigOption = "session/set_config_option"
	// MethodSessionSetModel is the UNSTABLE model-picker method: switch a
	// session's active model (see SetSessionModelRequest / SessionModelState).
	// Experimental, not part of the stable ACP spec; may change or be removed.
	MethodSessionSetModel = "session/set_model"

	MethodSessionRequestPermission = "session/request_permission"

	// MethodCancelRequest is the protocol-level "$/cancel_request"
	// notification: either side may signal it no longer awaits the response to
	// an in-flight request. "$/"-prefixed methods are always safe to ignore.
	MethodCancelRequest = "$/cancel_request"

	MethodFSReadTextFile  = "fs/read_text_file"
	MethodFSWriteTextFile = "fs/write_text_file"

	MethodTerminalCreate      = "terminal/create"
	MethodTerminalOutput      = "terminal/output"
	MethodTerminalWaitForExit = "terminal/wait_for_exit"
	MethodTerminalKill        = "terminal/kill"
	MethodTerminalRelease     = "terminal/release"
)
View Source
const (
	SessionConfigOptionTypeSelect  = "select"
	SessionConfigOptionTypeBoolean = "boolean"
)

Session configuration option type discriminators (SessionConfigOption.Type).

View Source
const ExtensionMethodPrefix = "_"

ExtensionMethodPrefix is the reserved namespace for custom "extension" methods and notifications: any method name starting with underscore. "$/"-prefixed methods (MethodCancelRequest) are a separate, protocol-owned namespace and are never extension-eligible.

View Source
const HandlerDrainTimeout = 10 * time.Second

HandlerDrainTimeout bounds how long Run waits, after shutdown cancels everything, for in-flight handler goroutines to return. A backstop for a handler that ignores its cancelled context; should never fire normally.

View Source
const ProtocolVersion = 1

Variables

View Source
var (
	// ErrAgentStartFailed marks a failure to launch or initialize the agent
	// subprocess. Not retryable (see IsStartupError).
	ErrAgentStartFailed = errors.New("libacp: agent start failed")

	// ErrIdleTimeout marks a turn that produced no session/update or result
	// past a driver's idle deadline, distinct from an overall context deadline.
	ErrIdleTimeout = errors.New("libacp: agent idle timeout")

	// ErrNoDisplayableOutput marks a turn that stopped with a normal reason
	// but never produced a renderable agent message. Detect via TurnTracker.
	ErrNoDisplayableOutput = errors.New("libacp: prompt turn produced no displayable output")
)

Client-side failure sentinels for a driver of a ClientSideConnection (typically over an acpexec subprocess). libacp never returns these itself; they are the vocabulary a driver wraps its own transport/lifecycle failures in. Classify with IsStartupError / IsTimeoutError / IsRetryableError rather than string-matching.

View Source
var (
	ErrConnectionClosed = errors.New("libacp: connection closed")
)
View Source
var ErrHandlerDrainTimeout = errors.New("libacp: timed out waiting for handler goroutines to return")

ErrHandlerDrainTimeout reports that Run gave up waiting for handler goroutines to return (see HandlerDrainTimeout). Some handler may still be running, so the caller's teardown of shared state is unsafe.

Functions

func AfterResponse

func AfterResponse(ctx context.Context, fn func())

AfterResponse schedules fn to run once the current request's result is on the wire — use it to emit a session/update that must reach the client only after it can resolve the session (e.g. available_commands_update after session/new). Outside a request handler, fn runs immediately.

func AsNotExist

func AsNotExist(err error) error

AsNotExist normalizes a not-found failure (per IsNotFound) into an error satisfying errors.Is(err, os.ErrNotExist), so fs/* callers can branch with the same predicate as local I/O. Any other error, including nil, is returned unchanged.

func FlattenContent

func FlattenContent(blocks []ContentBlock) (text string, dropped []string)

FlattenContent projects a content block list down to a single string — the inverse of content.go's constructors — for a consumer that can only accept flat text (a prompt field, a log line, a title).

Lossy: image, audio, blob resources, and unknown block types carry no text and are dropped; a resource block contributes only its inline Resource.Text, never its Blob. Blocks join with a single newline (empty pieces contribute nothing) and a resource link renders as "name: uri" — one rendering policy, not a canonical one; a caller needing a different shape should write its own walk.

dropped is the deduplicated, first-seen-ordered list of block types that could not be represented, so a caller can report the loss instead of silently swallowing it.

func IsExtensionMethod

func IsExtensionMethod(method string) bool

IsExtensionMethod reports whether method is eligible for dispatch through an ExtRequestHandler/ExtNotificationHandler: non-empty and starting with ExtensionMethodPrefix.

func IsNotFound

func IsNotFound(err error) bool

IsNotFound reports whether err is a peer's answer of "that resource does not exist" (file/resource sense, not lifecycle). Only a typed *Error counts — a raw error's text is never classified here, so a startup failure like exec.ErrNotFound can't be misread as a missing file.

Code == ErrResourceNotFound is the canonical signal. As a fallback, some agents answer fs/read_text_file with a generic ErrInternalError whose message just says "not found", so the message is also checked — but only for codes describing the request's subject. Protocol-level codes (parse, invalid request/params, method not found, auth required) and ErrRequestTimeout describe the request itself and are excluded, since message-sniffing them would misclassify an unimplemented method or a timeout as a missing file.

func IsRetryableError

func IsRetryableError(err error) bool

IsRetryableError reports whether retrying the turn (typically after respawning the agent) might succeed: timeouts, a dropped transport (ErrConnectionClosed / EOF / closed pipe / EPIPE / ECONNRESET), and an empty turn are retryable; cancellation and startup failures are not. The trailing string match is a cross-platform safety net for transport errors that do not wrap into a recognizable sentinel.

func IsStartupError

func IsStartupError(err error) bool

IsStartupError reports whether err means the agent could not be started or is unusable as configured — not fixable by retrying.

func IsTimeoutError

func IsTimeoutError(err error) bool

IsTimeoutError reports whether err is a context deadline or idle-watchdog timeout. A remote-serialized deadline loses its Go identity crossing the wire, so it is matched by its ErrRequestTimeout code instead of sentinel.

func NegotiateProtocolVersion

func NegotiateProtocolVersion(theirs, ours int) int

NegotiateProtocolVersion returns the protocol version two peers will speak: theirs when this peer can speak it (1 <= theirs <= ours), otherwise ours (normally ProtocolVersion). Deliberately does not require exact equality — a peer answering a different, mutually supported version is spec-legal, not an interop failure.

Types

type Agent

type Agent interface {
	Initialize(ctx context.Context, req InitializeRequest) (InitializeResponse, error)
	Authenticate(ctx context.Context, req AuthenticateRequest) (AuthenticateResponse, error)
	Logout(ctx context.Context, req LogoutRequest) (LogoutResponse, error)
	NewSession(ctx context.Context, req NewSessionRequest) (NewSessionResponse, error)
	LoadSession(ctx context.Context, req LoadSessionRequest) (LoadSessionResponse, error)
	ResumeSession(ctx context.Context, req ResumeSessionRequest) (ResumeSessionResponse, error)
	CloseSession(ctx context.Context, req CloseSessionRequest) (CloseSessionResponse, error)
	DeleteSession(ctx context.Context, req DeleteSessionRequest) (DeleteSessionResponse, error)
	ListSessions(ctx context.Context, req ListSessionsRequest) (ListSessionsResponse, error)
	SetSessionMode(ctx context.Context, req SetSessionModeRequest) (SetSessionModeResponse, error)
	// SetSessionModel switches a session's active model. This is the UNSTABLE Zed
	// model-picker surface (session/set_model, see MethodSessionSetModel); an agent
	// that advertises no `models` state returns MethodNotFound, matching the
	// experimental method's optional-capability contract.
	SetSessionModel(ctx context.Context, req SetSessionModelRequest) (SetSessionModelResponse, error)
	SetSessionConfigOption(ctx context.Context, req SetSessionConfigOptionRequest) (SetSessionConfigOptionResponse, error)
	Prompt(ctx context.Context, req PromptRequest) (PromptResponse, error)
	Cancel(ctx context.Context, req CancelNotification) error
}

type AgentAuthCapabilities

type AgentAuthCapabilities struct {
	Logout *LogoutCapabilities `json:"logout,omitempty"`
	Meta   json.RawMessage     `json:"_meta,omitempty"`
}

AgentAuthCapabilities describes agent auth capabilities — currently just whether it supports the `logout` method.

type AgentCapabilities

type AgentCapabilities struct {
	LoadSession         bool                  `json:"loadSession,omitempty"`
	PromptCapabilities  PromptCapabilities    `json:"promptCapabilities,omitempty"`
	McpCapabilities     McpCapabilities       `json:"mcpCapabilities,omitempty"`
	SessionCapabilities SessionCapabilities   `json:"sessionCapabilities,omitempty"`
	Auth                AgentAuthCapabilities `json:"auth,omitempty"`
	Meta                json.RawMessage       `json:"_meta,omitempty"`
}

type AgentFactory

type AgentFactory func(conn *AgentSideConnection) Agent

type AgentSideConnection

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

func NewAgentSideConnection

func NewAgentSideConnection(rw io.ReadWriteCloser, factory AgentFactory) *AgentSideConnection

func (*AgentSideConnection) CallExtMethod

func (c *AgentSideConnection) CallExtMethod(ctx context.Context, method string, params json.RawMessage) (json.RawMessage, error)

CallExtMethod sends a custom extension request (method must satisfy IsExtensionMethod) to the client and returns its raw result. Outbound half of the extension-method seam; SetExtRequestHandler installs the inbound half. A canceled ctx aborts the wait and best-effort notifies the client with "$/cancel_request".

func (*AgentSideConnection) CloseErr

func (c *AgentSideConnection) CloseErr() error

func (*AgentSideConnection) Closed

func (c *AgentSideConnection) Closed() <-chan struct{}

func (*AgentSideConnection) CreateTerminal

func (*AgentSideConnection) KillTerminal

func (*AgentSideConnection) ReadTextFile

func (*AgentSideConnection) ReleaseTerminal

func (*AgentSideConnection) RequestPermission

func (*AgentSideConnection) Run

func (c *AgentSideConnection) Run(ctx context.Context) (err error)

func (*AgentSideConnection) SendExtNotification

func (c *AgentSideConnection) SendExtNotification(method string, params json.RawMessage) error

SendExtNotification sends a custom, fire-and-forget extension notification (method must satisfy IsExtensionMethod) to the client.

func (*AgentSideConnection) SessionUpdate

func (c *AgentSideConnection) SessionUpdate(n SessionNotification) error

func (*AgentSideConnection) SetExtNotificationHandler

func (c *AgentSideConnection) SetExtNotificationHandler(h ExtNotificationHandler)

SetExtNotificationHandler installs h to handle inbound extension notifications. Call it from the AgentFactory before Run starts reading. Nil (the default) silently ignores extension notifications.

func (*AgentSideConnection) SetExtRequestHandler

func (c *AgentSideConnection) SetExtRequestHandler(h ExtRequestHandler)

SetExtRequestHandler installs h to handle inbound extension requests (method names starting with ExtensionMethodPrefix). Call it from the AgentFactory before Run starts reading. Nil (the default) answers extension requests with MethodNotFound.

func (*AgentSideConnection) TerminalOutput

func (*AgentSideConnection) WaitForTerminalExit

func (*AgentSideConnection) WriteTextFile

type Annotations

type Annotations struct {
	Audience []string `json:"audience,omitempty"`
	// LastModified is an ISO 8601 timestamp indicating when the underlying
	// resource was last modified.
	LastModified string          `json:"lastModified,omitempty"`
	Priority     *float64        `json:"priority,omitempty"`
	Meta         json.RawMessage `json:"_meta,omitempty"`
}

type AuthCapabilities

type AuthCapabilities struct {
	Terminal bool `json:"terminal,omitempty"`
}

AuthCapabilities is the client-side auth capability object (unstable spec surface): it gates which auth method types the client can handle.

type AuthEnvVar

type AuthEnvVar struct {
	Name     string          `json:"name"`
	Label    string          `json:"label,omitempty"`
	Secret   *bool           `json:"secret,omitempty"`
	Optional bool            `json:"optional,omitempty"`
	Meta     json.RawMessage `json:"_meta,omitempty"`
}

AuthEnvVar describes one variable of an env_var auth method. Secret is a pointer because the spec default is true: nil emits nothing (client assumes secret), an explicit false must reach the wire.

type AuthMethod

type AuthMethod struct {
	ID          string            `json:"id"`
	Name        string            `json:"name"`
	Description string            `json:"description,omitempty"`
	Type        string            `json:"type,omitempty"`
	Args        []string          `json:"args,omitempty"`
	Env         map[string]string `json:"env,omitempty"`
	Vars        []AuthEnvVar      `json:"vars,omitempty"`
	Link        string            `json:"link,omitempty"`
	Meta        json.RawMessage   `json:"_meta,omitempty"`
}

AuthMethod covers the spec's auth method union. Type discriminates on the wire: "" (stable default), "terminal" (unstable; Args/Env launch the agent binary for a TUI), or "env_var" (unstable; Vars lists env vars to collect).

type AuthenticateRequest

type AuthenticateRequest struct {
	MethodID string          `json:"methodId"`
	Meta     json.RawMessage `json:"_meta,omitempty"`
}

type AuthenticateResponse

type AuthenticateResponse struct {
	Meta json.RawMessage `json:"_meta,omitempty"`
}

type AvailableCommand

type AvailableCommand struct {
	Name string `json:"name"`
	// Description is spec-required (strict clients reject commands without
	// it), so no omitempty: an empty string still reaches the wire.
	Description string                 `json:"description"`
	Input       *AvailableCommandInput `json:"input,omitempty"`
	Meta        json.RawMessage        `json:"_meta,omitempty"`
}

type AvailableCommandInput

type AvailableCommandInput struct {
	Hint string `json:"hint,omitempty"`
}

type CancelNotification

type CancelNotification struct {
	SessionID SessionID       `json:"sessionId"`
	Meta      json.RawMessage `json:"_meta,omitempty"`
}

type CancelRequestNotification

type CancelRequestNotification struct {
	RequestID RequestID       `json:"requestId"`
	Meta      json.RawMessage `json:"_meta,omitempty"`
}

CancelRequestNotification is the payload of "$/cancel_request": the JSON-RPC id of the request whose response is no longer awaited.

type Client

type Client interface {
	RequestPermission(ctx context.Context, req RequestPermissionRequest) (RequestPermissionResponse, error)
	ReadTextFile(ctx context.Context, req ReadTextFileRequest) (ReadTextFileResponse, error)
	WriteTextFile(ctx context.Context, req WriteTextFileRequest) (WriteTextFileResponse, error)
	CreateTerminal(ctx context.Context, req CreateTerminalRequest) (CreateTerminalResponse, error)
	TerminalOutput(ctx context.Context, req TerminalOutputRequest) (TerminalOutputResponse, error)
	WaitForTerminalExit(ctx context.Context, req WaitForTerminalExitRequest) (WaitForTerminalExitResponse, error)
	KillTerminal(ctx context.Context, req KillTerminalRequest) (KillTerminalResponse, error)
	ReleaseTerminal(ctx context.Context, req ReleaseTerminalRequest) (ReleaseTerminalResponse, error)
	// SessionUpdate handles an inbound "session/update" notification. It has no
	// response on the wire (JSON-RPC notifications never do); the returned
	// error is reported to the implementation only, e.g. for logging.
	SessionUpdate(ctx context.Context, n SessionNotification) error
}

Client is the editor-side counterpart to Agent (agent.go): the set of requests an agent may send to the client, plus the inbound session/update notification. ClientSideConnection (clientconn.go) is the mirror image of AgentSideConnection: it dispatches these methods for incoming JSON-RPC requests/notifications, and exposes the agent-bound methods (Initialize, session/new, session/prompt, ...) as outbound calls.

func FilterSessionUpdates

func FilterSessionUpdates(live SessionID, inner Client) Client

FilterSessionUpdates wraps a Client so session/update notifications for any session other than live are dropped before reaching inner.SessionUpdate; every other method passes through. ClientSideConnection forwards updates regardless of session id (session bookkeeping is the app's job), so a driver that reconnects or swaps sessions needs this guard or a just-abandoned session's chunks leak into the new turn's UI.

Wrap the Client the ClientFactory returns; construct a new wrapper with the updated live id whenever the driver's active session changes.

type ClientCapabilities

type ClientCapabilities struct {
	FS       FileSystemCapabilities     `json:"fs,omitempty"`
	Terminal bool                       `json:"terminal,omitempty"`
	Session  *ClientSessionCapabilities `json:"session,omitempty"`
	Auth     AuthCapabilities           `json:"auth,omitempty"`
	Meta     json.RawMessage            `json:"_meta,omitempty"`
}

func (ClientCapabilities) SupportsBooleanConfigOptions

func (c ClientCapabilities) SupportsBooleanConfigOptions() bool

SupportsBooleanConfigOptions reports whether the client advertised clientCapabilities.session.configOptions.boolean.

type ClientFactory

type ClientFactory func(conn *ClientSideConnection) Client

type ClientSessionCapabilities

type ClientSessionCapabilities struct {
	ConfigOptions *SessionConfigOptionsCapabilities `json:"configOptions,omitempty"`
	Meta          json.RawMessage                   `json:"_meta,omitempty"`
}

ClientSessionCapabilities mirrors the spec's clientCapabilities.session.

type ClientSideConnection

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

ClientSideConnection is the editor-side mirror of AgentSideConnection (conn.go): it dispatches incoming agent->client requests (session/request_ permission, fs/*, terminal/*) and the session/update notification to a Client, and exposes the client->agent methods (initialize, session/new, session/prompt, ...) as outbound calls. Wire framing, id correlation, and shutdown behavior follow the same design as AgentSideConnection.

func NewClientSideConnection

func NewClientSideConnection(rw io.ReadWriteCloser, factory ClientFactory) *ClientSideConnection

func (*ClientSideConnection) Authenticate

func (*ClientSideConnection) CallExtMethod

func (c *ClientSideConnection) CallExtMethod(ctx context.Context, method string, params json.RawMessage) (json.RawMessage, error)

CallExtMethod sends a custom extension request (method must satisfy IsExtensionMethod) to the agent and returns its raw result. Outbound half of the extension-method seam; SetExtRequestHandler installs the inbound half.

func (*ClientSideConnection) CancelPrompt

func (c *ClientSideConnection) CancelPrompt(sessionID SessionID) error

CancelPrompt cancels sessionID's in-flight prompt turn: sends "session/cancel" and, for as long as this session's Prompt call remains outstanding, auto-resolves every session/request_permission request for sessionID (new or in-flight) with the "cancelled" outcome, instead of invoking or waiting on Client.RequestPermission — the client-side half of the spec's prompt-turn cancellation contract. The auto-resolve mark clears the moment the Prompt call for sessionID returns. With no outstanding Prompt call for sessionID, behaves exactly like CancelSession.

func (*ClientSideConnection) CancelSession

func (c *ClientSideConnection) CancelSession(req CancelNotification) error

CancelSession sends "session/cancel" — a notification, not a request, per spec: the agent must resolve the in-flight session/prompt call with stop reason "cancelled" rather than answering this call itself. Does not apply the pending-permission auto-cancel rule; use CancelPrompt for that.

func (*ClientSideConnection) CloseErr

func (c *ClientSideConnection) CloseErr() error

func (*ClientSideConnection) CloseSession

func (*ClientSideConnection) Closed

func (c *ClientSideConnection) Closed() <-chan struct{}

func (*ClientSideConnection) DeleteSession

func (*ClientSideConnection) Initialize

func (*ClientSideConnection) ListSessions

func (*ClientSideConnection) LoadSession

func (*ClientSideConnection) Logout

Logout is only meaningful when the agent advertised AgentCapabilities.Auth.Logout during initialize.

func (*ClientSideConnection) NewSession

func (*ClientSideConnection) Prompt

Prompt registers req.SessionID's turn in promptTurns for the call's duration, so CancelPrompt can mark it and promptCancelling can check session/request_permission requests against it. Removed on return, only if still this call's own entry, so it can't clobber a later overlapping call.

func (*ClientSideConnection) ResumeSession

func (*ClientSideConnection) Run

func (c *ClientSideConnection) Run(ctx context.Context) (err error)

func (*ClientSideConnection) SendExtNotification

func (c *ClientSideConnection) SendExtNotification(method string, params json.RawMessage) error

SendExtNotification sends a custom, fire-and-forget extension notification (method must satisfy IsExtensionMethod) to the agent.

func (*ClientSideConnection) SetExtNotificationHandler

func (c *ClientSideConnection) SetExtNotificationHandler(h ExtNotificationHandler)

SetExtNotificationHandler installs h to handle inbound extension notifications. Call it from the ClientFactory before Run starts reading. Nil (the default) silently ignores extension notifications.

func (*ClientSideConnection) SetExtRequestHandler

func (c *ClientSideConnection) SetExtRequestHandler(h ExtRequestHandler)

SetExtRequestHandler installs h to handle inbound extension requests (method names starting with ExtensionMethodPrefix). Call it from the ClientFactory before Run starts reading. Nil (the default) answers extension requests with MethodNotFound.

func (*ClientSideConnection) SetSessionConfigOption

func (*ClientSideConnection) SetSessionMode

SetSessionMode switches a session to a different SessionMode.ID, one of the ids the session's SessionModeState.AvailableModes advertised.

func (*ClientSideConnection) SetSessionModel

SetSessionModel switches a session to a different ModelInfo.ID, one of the ids the session's SessionModelState.AvailableModels advertised. This is the unstable Zed model-picker method (session/set_model), not part of the stable ACP spec and subject to change. On success the requested model is authoritative: no session/update kind reconfirms it.

type CloseSessionRequest

type CloseSessionRequest struct {
	SessionID SessionID       `json:"sessionId"`
	Meta      json.RawMessage `json:"_meta,omitempty"`
}

type CloseSessionResponse

type CloseSessionResponse struct {
	Meta json.RawMessage `json:"_meta,omitempty"`
}

type ContentBlock

type ContentBlock struct {
	Type        string            `json:"type"`
	Text        string            `json:"text,omitempty"`
	Data        string            `json:"data,omitempty"`
	MimeType    string            `json:"mimeType,omitempty"`
	URI         string            `json:"uri,omitempty"`
	Name        string            `json:"name,omitempty"`
	Title       string            `json:"title,omitempty"`
	Description string            `json:"description,omitempty"`
	Size        *int64            `json:"size,omitempty"`
	Resource    *EmbeddedResource `json:"resource,omitempty"`
	Annotations *Annotations      `json:"annotations,omitempty"`
	Meta        json.RawMessage   `json:"_meta,omitempty"`
}

func NewImageContent

func NewImageContent(data, mimeType string) ContentBlock

func NewResourceContent

func NewResourceContent(resource EmbeddedResource) ContentBlock
func NewResourceLink(uri, name string) ContentBlock

func NewTextContent

func NewTextContent(text string) ContentBlock

type ContentKind

type ContentKind string
const (
	ContentKindText         ContentKind = "text"
	ContentKindImage        ContentKind = "image"
	ContentKindAudio        ContentKind = "audio"
	ContentKindResource     ContentKind = "resource"
	ContentKindResourceLink ContentKind = "resource_link"
)

type CreateTerminalRequest

type CreateTerminalRequest struct {
	SessionID       SessionID       `json:"sessionId"`
	Command         string          `json:"command"`
	Args            []string        `json:"args,omitempty"`
	Env             []EnvVariable   `json:"env,omitempty"`
	Cwd             string          `json:"cwd,omitempty"`
	OutputByteLimit *int64          `json:"outputByteLimit,omitempty"`
	Meta            json.RawMessage `json:"_meta,omitempty"`
}

type CreateTerminalResponse

type CreateTerminalResponse struct {
	TerminalID string          `json:"terminalId"`
	Meta       json.RawMessage `json:"_meta,omitempty"`
}

type DeleteSessionRequest

type DeleteSessionRequest struct {
	SessionID SessionID       `json:"sessionId"`
	Meta      json.RawMessage `json:"_meta,omitempty"`
}

type DeleteSessionResponse

type DeleteSessionResponse struct {
	Meta json.RawMessage `json:"_meta,omitempty"`
}

type EmbeddedResource

type EmbeddedResource struct {
	URI      string          `json:"uri"`
	MimeType string          `json:"mimeType,omitempty"`
	Text     string          `json:"text,omitempty"`
	Blob     string          `json:"blob,omitempty"`
	Meta     json.RawMessage `json:"_meta,omitempty"`
}

type EnvVariable

type EnvVariable struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

type Error

type Error struct {
	Code    int             `json:"code"`
	Message string          `json:"message"`
	Data    json.RawMessage `json:"data,omitempty"`
	// contains filtered or unexported fields
}

Error is a JSON-RPC error object. The exported fields are the entire wire contract; cause is process-local and never serialized.

func AsError

func AsError(err error) *Error

AsError converts a handler error into the JSON-RPC error that goes on the wire. It retains err as cause for same-process sentinel matching, and promotes a deadline to ErrRequestTimeout so a remote caller can tell "too slow, retry" from "broken, give up". Everything else becomes ErrInternalError.

func InternalError

func InternalError(msg string) *Error

func InvalidParams

func InvalidParams(msg string) *Error

func InvalidRequest

func InvalidRequest(msg string) *Error

func MethodNotFound

func MethodNotFound(method string) *Error

func NewError

func NewError(code int, message string) *Error

func NewErrorf

func NewErrorf(code int, format string, args ...any) *Error

func ParseError

func ParseError(msg string) *Error

func (*Error) Error

func (e *Error) Error() string

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap exposes the originating handler error; an Error decoded from the wire has no cause and returns nil. Classify a remote failure via Code instead (see IsTimeoutError).

type ExtNotificationHandler

type ExtNotificationHandler func(ctx context.Context, method string, params json.RawMessage)

ExtNotificationHandler handles an inbound extension notification, fire-and-forget.

See ExtNotification: https://agentclientprotocol.com/protocol/extensibility

type ExtRequestHandler

type ExtRequestHandler func(ctx context.Context, method string, params json.RawMessage) (json.RawMessage, *Error)

ExtRequestHandler handles an inbound extension request (method not in the core ACP set but IsExtensionMethod). params is the raw, unparsed request params (nil if omitted) — extension methods define their own wire schema. Returns a raw JSON result or an *Error, written back through the same JSON-RPC machinery as a core handler; participates in "$/cancel_request" cancellation via ctx like any other inbound request.

See ExtRequest/ExtResponse: https://agentclientprotocol.com/protocol/extensibility

type FileSystemCapabilities

type FileSystemCapabilities struct {
	ReadTextFile  bool            `json:"readTextFile,omitempty"`
	WriteTextFile bool            `json:"writeTextFile,omitempty"`
	Meta          json.RawMessage `json:"_meta,omitempty"`
}

type HttpHeader

type HttpHeader struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

type Implementation

type Implementation struct {
	Name    string `json:"name"`
	Title   string `json:"title,omitempty"`
	Version string `json:"version,omitempty"`
}

type Incoming

type Incoming struct {
	Kind         IncomingKind
	Request      Request
	Notification Notification
	Response     Response
}

func ParseIncoming

func ParseIncoming(data []byte) (Incoming, error)

type IncomingKind

type IncomingKind uint8
const (
	IncomingKindUnknown IncomingKind = iota
	IncomingKindRequest
	IncomingKindNotification
	IncomingKindResponse
)

type InitializeRequest

type InitializeRequest struct {
	ProtocolVersion    int                `json:"protocolVersion"`
	ClientCapabilities ClientCapabilities `json:"clientCapabilities,omitempty"`
	ClientInfo         *Implementation    `json:"clientInfo,omitempty"`
	Meta               json.RawMessage    `json:"_meta,omitempty"`
}

type InitializeResponse

type InitializeResponse struct {
	ProtocolVersion   int               `json:"protocolVersion"`
	AgentCapabilities AgentCapabilities `json:"agentCapabilities,omitempty"`
	AgentInfo         *Implementation   `json:"agentInfo,omitempty"`
	AuthMethods       []AuthMethod      `json:"authMethods,omitempty"`
	Meta              json.RawMessage   `json:"_meta,omitempty"`
}

type KillTerminalRequest

type KillTerminalRequest struct {
	SessionID  SessionID       `json:"sessionId"`
	TerminalID string          `json:"terminalId"`
	Meta       json.RawMessage `json:"_meta,omitempty"`
}

type KillTerminalResponse

type KillTerminalResponse struct {
	Meta json.RawMessage `json:"_meta,omitempty"`
}

type ListSessionsRequest

type ListSessionsRequest struct {
	Cwd    string          `json:"cwd,omitempty"`
	Cursor string          `json:"cursor,omitempty"`
	Meta   json.RawMessage `json:"_meta,omitempty"`
}

type ListSessionsResponse

type ListSessionsResponse struct {
	Sessions   []SessionInfo   `json:"sessions"`
	NextCursor string          `json:"nextCursor,omitempty"`
	Meta       json.RawMessage `json:"_meta,omitempty"`
}

type LoadSessionRequest

type LoadSessionRequest struct {
	SessionID SessionID `json:"sessionId"`
	Cwd       string    `json:"cwd"`
	// AdditionalDirectories are extra workspace roots on top of Cwd; each path
	// must be absolute.
	AdditionalDirectories []string        `json:"additionalDirectories,omitempty"`
	McpServers            []McpServer     `json:"mcpServers"`
	Meta                  json.RawMessage `json:"_meta,omitempty"`
}

type LoadSessionResponse

type LoadSessionResponse struct {
	Modes         *SessionModeState     `json:"modes,omitempty"`
	Models        *SessionModelState    `json:"models,omitempty"`
	ConfigOptions []SessionConfigOption `json:"configOptions,omitempty"`
	Meta          json.RawMessage       `json:"_meta,omitempty"`
}

type LogoutCapabilities

type LogoutCapabilities struct {
	Meta json.RawMessage `json:"_meta,omitempty"`
}

LogoutCapabilities is present ({}) when the agent supports the `logout` method.

type LogoutRequest

type LogoutRequest struct {
	Meta json.RawMessage `json:"_meta,omitempty"`
}

LogoutRequest terminates the current authenticated session. Only meaningful when the agent advertises AgentCapabilities.Auth.Logout.

type LogoutResponse

type LogoutResponse struct {
	Meta json.RawMessage `json:"_meta,omitempty"`
}

type McpCapabilities

type McpCapabilities struct {
	HTTP bool            `json:"http,omitempty"`
	SSE  bool            `json:"sse,omitempty"`
	Meta json.RawMessage `json:"_meta,omitempty"`
}

type McpServer

type McpServer struct {
	Type    string          `json:"type,omitempty"`
	Name    string          `json:"name"`
	Command string          `json:"command,omitempty"`
	Args    []string        `json:"args,omitempty"`
	Env     []EnvVariable   `json:"env,omitempty"`
	URL     string          `json:"url,omitempty"`
	Headers []HttpHeader    `json:"headers,omitempty"`
	Meta    json.RawMessage `json:"_meta,omitempty"`
}

func (McpServer) Kind

func (m McpServer) Kind() McpServerKind

func (McpServer) MarshalJSON

func (m McpServer) MarshalJSON() ([]byte, error)

MarshalJSON forces args/env (stdio) and headers (http/sse) onto the wire as `[]` rather than omitting them when empty, since the spec declares them always-serialized with no default and omitempty can't express that on the flattened McpServer struct — hence the two per-transport wire shapes above.

func (McpServer) Validate

func (m McpServer) Validate() error

type McpServerKind

type McpServerKind string
const (
	McpServerKindStdio McpServerKind = ""
	McpServerKindHTTP  McpServerKind = "http"
	McpServerKindSSE   McpServerKind = "sse"
)

type ModelInfo

type ModelInfo struct {
	ID          string          `json:"modelId"`
	Name        string          `json:"name"`
	Description string          `json:"description,omitempty"`
	Meta        json.RawMessage `json:"_meta,omitempty"`
}

ModelInfo describes one selectable model in a SessionModelState. ID is the stable identifier passed back in SetSessionModelRequest. Part of the UNSTABLE model-picker surface; carries no effort/fast-mode facet.

type NewSessionRequest

type NewSessionRequest struct {
	Cwd string `json:"cwd"`
	// AdditionalDirectories are extra workspace roots on top of Cwd; each path
	// must be absolute.
	AdditionalDirectories []string        `json:"additionalDirectories,omitempty"`
	McpServers            []McpServer     `json:"mcpServers"`
	Meta                  json.RawMessage `json:"_meta,omitempty"`
}

type NewSessionResponse

type NewSessionResponse struct {
	SessionID SessionID         `json:"sessionId"`
	Modes     *SessionModeState `json:"modes,omitempty"`
	// Models is the UNSTABLE model-picker surface (see SessionModelState); nil
	// means the agent exposes no selectable model.
	Models        *SessionModelState    `json:"models,omitempty"`
	ConfigOptions []SessionConfigOption `json:"configOptions,omitempty"`
	Meta          json.RawMessage       `json:"_meta,omitempty"`
}

type Notification

type Notification struct {
	JSONRPC string          `json:"jsonrpc"`
	Method  string          `json:"method"`
	Params  json.RawMessage `json:"params,omitempty"`
}

func NewNotification

func NewNotification(method string, params json.RawMessage) Notification

type PermissionOption

type PermissionOption struct {
	OptionID string               `json:"optionId"`
	Name     string               `json:"name"`
	Kind     PermissionOptionKind `json:"kind"`
	Meta     json.RawMessage      `json:"_meta,omitempty"`
}

type PermissionOptionKind

type PermissionOptionKind string
const (
	PermissionAllowOnce    PermissionOptionKind = "allow_once"
	PermissionAllowAlways  PermissionOptionKind = "allow_always"
	PermissionRejectOnce   PermissionOptionKind = "reject_once"
	PermissionRejectAlways PermissionOptionKind = "reject_always"
)

type PermissionOutcomeKind

type PermissionOutcomeKind string
const (
	PermissionOutcomeCancelled PermissionOutcomeKind = "cancelled"
	PermissionOutcomeSelected  PermissionOutcomeKind = "selected"
)

type PermissionToolCall

type PermissionToolCall struct {
	ToolCallID string             `json:"toolCallId"`
	Title      string             `json:"title,omitempty"`
	Kind       ToolKind           `json:"kind,omitempty"`
	Status     ToolCallStatus     `json:"status,omitempty"`
	Content    []ToolCallContent  `json:"content,omitempty"`
	Locations  []ToolCallLocation `json:"locations,omitempty"`
	RawInput   json.RawMessage    `json:"rawInput,omitempty"`
	RawOutput  json.RawMessage    `json:"rawOutput,omitempty"`
	Meta       json.RawMessage    `json:"_meta,omitempty"`
}

type PlanEntry

type PlanEntry struct {
	Content  string            `json:"content"`
	Priority PlanEntryPriority `json:"priority"`
	Status   PlanEntryStatus   `json:"status"`
	Meta     json.RawMessage   `json:"_meta,omitempty"`
}

type PlanEntryPriority

type PlanEntryPriority string
const (
	PlanPriorityHigh   PlanEntryPriority = "high"
	PlanPriorityMedium PlanEntryPriority = "medium"
	PlanPriorityLow    PlanEntryPriority = "low"
)

type PlanEntryStatus

type PlanEntryStatus string
const (
	PlanStatusPending    PlanEntryStatus = "pending"
	PlanStatusInProgress PlanEntryStatus = "in_progress"
	PlanStatusCompleted  PlanEntryStatus = "completed"
)

type PromptCapabilities

type PromptCapabilities struct {
	Image           bool            `json:"image,omitempty"`
	Audio           bool            `json:"audio,omitempty"`
	EmbeddedContext bool            `json:"embeddedContext,omitempty"`
	Meta            json.RawMessage `json:"_meta,omitempty"`
}

type PromptRequest

type PromptRequest struct {
	SessionID SessionID       `json:"sessionId"`
	Prompt    []ContentBlock  `json:"prompt"`
	Meta      json.RawMessage `json:"_meta,omitempty"`
}

type PromptResponse

type PromptResponse struct {
	StopReason StopReason      `json:"stopReason"`
	Meta       json.RawMessage `json:"_meta,omitempty"`
}

PromptResponse is the result of a "session/prompt" request. Per the ACP v1 schema it carries only stopReason and _meta; custom fields must not be added at the root of a spec type. Per-turn usage/cost belongs on the "usage_update" SessionUpdate (see SessionUpdateUsageUpdate) instead.

type ReadTextFileRequest

type ReadTextFileRequest struct {
	SessionID SessionID       `json:"sessionId"`
	Path      string          `json:"path"`
	Line      *int            `json:"line,omitempty"`
	Limit     *int            `json:"limit,omitempty"`
	Meta      json.RawMessage `json:"_meta,omitempty"`
}

type ReadTextFileResponse

type ReadTextFileResponse struct {
	Content string          `json:"content"`
	Meta    json.RawMessage `json:"_meta,omitempty"`
}

type ReleaseTerminalRequest

type ReleaseTerminalRequest struct {
	SessionID  SessionID       `json:"sessionId"`
	TerminalID string          `json:"terminalId"`
	Meta       json.RawMessage `json:"_meta,omitempty"`
}

type ReleaseTerminalResponse

type ReleaseTerminalResponse struct {
	Meta json.RawMessage `json:"_meta,omitempty"`
}

type Request

type Request struct {
	JSONRPC string          `json:"jsonrpc"`
	ID      RequestID       `json:"id"`
	Method  string          `json:"method"`
	Params  json.RawMessage `json:"params,omitempty"`
}

func NewRequest

func NewRequest(id RequestID, method string, params json.RawMessage) Request

type RequestID

type RequestID struct {
	Kind   RequestIDKind
	Number int64
	String string
}

func NewRequestIDNull

func NewRequestIDNull() RequestID

func NewRequestIDNumber

func NewRequestIDNumber(n int64) RequestID

func NewRequestIDString

func NewRequestIDString(s string) RequestID

func (RequestID) Equal

func (r RequestID) Equal(other RequestID) bool

func (RequestID) MarshalJSON

func (r RequestID) MarshalJSON() ([]byte, error)

func (RequestID) String_

func (r RequestID) String_() string

func (*RequestID) UnmarshalJSON

func (r *RequestID) UnmarshalJSON(data []byte) error

type RequestIDKind

type RequestIDKind uint8
const (
	RequestIDKindNull RequestIDKind = iota
	RequestIDKindNumber
	RequestIDKindString
)

type RequestPermissionOutcome

type RequestPermissionOutcome struct {
	Outcome  PermissionOutcomeKind `json:"outcome"`
	OptionID string                `json:"optionId,omitempty"`
}

func (*RequestPermissionOutcome) UnmarshalJSON

func (o *RequestPermissionOutcome) UnmarshalJSON(data []byte) error

type RequestPermissionRequest

type RequestPermissionRequest struct {
	SessionID SessionID          `json:"sessionId"`
	ToolCall  PermissionToolCall `json:"toolCall"`
	Options   []PermissionOption `json:"options"`
	Meta      json.RawMessage    `json:"_meta,omitempty"`
}

type RequestPermissionResponse

type RequestPermissionResponse struct {
	Outcome RequestPermissionOutcome `json:"outcome"`
	Meta    json.RawMessage          `json:"_meta,omitempty"`
}

type Response

type Response struct {
	JSONRPC string          `json:"jsonrpc"`
	ID      RequestID       `json:"id"`
	Result  json.RawMessage `json:"result,omitempty"`
	Error   *Error          `json:"error,omitempty"`
}

func NewErrorResponse

func NewErrorResponse(id RequestID, err *Error) Response

func NewResultResponse

func NewResultResponse(id RequestID, result json.RawMessage) Response

type ResumeSessionRequest

type ResumeSessionRequest struct {
	SessionID SessionID `json:"sessionId"`
	Cwd       string    `json:"cwd"`
	// AdditionalDirectories are extra workspace roots on top of Cwd; each path
	// must be absolute.
	AdditionalDirectories []string        `json:"additionalDirectories,omitempty"`
	McpServers            []McpServer     `json:"mcpServers,omitempty"`
	Meta                  json.RawMessage `json:"_meta,omitempty"`
}

ResumeSessionRequest reconnects to an existing session without history replay (the client kept its transcript). McpServers is optional here, unlike session/new and session/load.

type ResumeSessionResponse

type ResumeSessionResponse struct {
	Modes         *SessionModeState     `json:"modes,omitempty"`
	Models        *SessionModelState    `json:"models,omitempty"`
	ConfigOptions []SessionConfigOption `json:"configOptions,omitempty"`
	Meta          json.RawMessage       `json:"_meta,omitempty"`
}

type SessionCapabilities

type SessionCapabilities struct {
	List   *struct{} `json:"list,omitempty"`
	Resume *struct{} `json:"resume,omitempty"`
	Close  *struct{} `json:"close,omitempty"`
	Delete *struct{} `json:"delete,omitempty"`
	// AdditionalDirectories present ({}) means the agent honors
	// additionalDirectories on session/new, session/load, and session/resume,
	// and may report SessionInfo.AdditionalDirectories from session/list.
	AdditionalDirectories *struct{}       `json:"additionalDirectories,omitempty"`
	Meta                  json.RawMessage `json:"_meta,omitempty"`
}

type SessionConfigGroup

type SessionConfigGroup struct {
	Group   string               `json:"group"`
	Name    string               `json:"name"`
	Options []SessionConfigValue `json:"options"`
	Meta    json.RawMessage      `json:"_meta,omitempty"`
}

type SessionConfigOption

type SessionConfigOption struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
	Category    string `json:"category,omitempty"`
	Type        string `json:"type"`
	// CurrentValue is always the Go-side string form: the selected
	// SessionConfigValue.Value id for Select, "true"/"false" for Boolean.
	// MarshalJSON renders Boolean as a JSON boolean on the wire; UnmarshalJSON
	// accepts either wire shape back into this string.
	CurrentValue string              `json:"currentValue"`
	Options      SessionConfigValues `json:"options"`
	Meta         json.RawMessage     `json:"_meta,omitempty"`
}

func (SessionConfigOption) MarshalJSON

func (o SessionConfigOption) MarshalJSON() ([]byte, error)

func (*SessionConfigOption) UnmarshalJSON

func (o *SessionConfigOption) UnmarshalJSON(data []byte) error

type SessionConfigOptionValue

type SessionConfigOptionValue struct {
	IsBool bool
	Str    string
	Bool   bool
}

SessionConfigOptionValue is the value union of session/set_config_option: a plain string value id (default) or a boolean (request Type "boolean").

Example
package main

import (
	"encoding/json"
	"fmt"

	"github.com/contenox/libacp"
)

func main() {
	var req libacp.SetSessionConfigOptionRequest
	_ = json.Unmarshal([]byte(`{"sessionId":"s","configId":"c","value":"model-x"}`), &req)
	fmt.Println(req.Value.AsString(), req.Value.IsBool)

	_ = json.Unmarshal([]byte(`{"sessionId":"s","configId":"c","type":"boolean","value":true}`), &req)
	fmt.Println(req.Value.AsString(), req.Value.IsBool)
}
Output:
model-x false
true true

func BoolConfigValue

func BoolConfigValue(b bool) SessionConfigOptionValue

func StringConfigValue

func StringConfigValue(s string) SessionConfigOptionValue

func (SessionConfigOptionValue) AsString

func (v SessionConfigOptionValue) AsString() string

AsString renders the value for consumers that key handling off strings; booleans become "true"/"false".

func (SessionConfigOptionValue) MarshalJSON

func (v SessionConfigOptionValue) MarshalJSON() ([]byte, error)

func (*SessionConfigOptionValue) UnmarshalJSON

func (v *SessionConfigOptionValue) UnmarshalJSON(data []byte) error

type SessionConfigOptionsCapabilities

type SessionConfigOptionsCapabilities struct {
	// Boolean present ({}) means the client accepts type:"boolean" config
	// options and may send boolean set_config_option values.
	Boolean *struct{}       `json:"boolean,omitempty"`
	Meta    json.RawMessage `json:"_meta,omitempty"`
}

type SessionConfigValue

type SessionConfigValue struct {
	Value       string          `json:"value"`
	Name        string          `json:"name"`
	Description string          `json:"description,omitempty"`
	Meta        json.RawMessage `json:"_meta,omitempty"`
}

type SessionConfigValues

type SessionConfigValues struct {
	Values []SessionConfigValue
	Groups []SessionConfigGroup
}

func NewGroupedSessionConfigValues

func NewGroupedSessionConfigValues(groups []SessionConfigGroup) SessionConfigValues

func NewSessionConfigValues

func NewSessionConfigValues(values []SessionConfigValue) SessionConfigValues

func (SessionConfigValues) AllValues

func (v SessionConfigValues) AllValues() []SessionConfigValue

func (SessionConfigValues) MarshalJSON

func (v SessionConfigValues) MarshalJSON() ([]byte, error)

func (*SessionConfigValues) UnmarshalJSON

func (v *SessionConfigValues) UnmarshalJSON(data []byte) error

type SessionID

type SessionID string

type SessionInfo

type SessionInfo struct {
	SessionID SessionID `json:"sessionId"`
	Cwd       string    `json:"cwd,omitempty"`
	// AdditionalDirectories is the ordered additional-root list, when tracked.
	AdditionalDirectories []string        `json:"additionalDirectories,omitempty"`
	Title                 string          `json:"title,omitempty"`
	UpdatedAt             string          `json:"updatedAt,omitempty"`
	Meta                  json.RawMessage `json:"_meta,omitempty"`
}

type SessionMode

type SessionMode struct {
	ID          string          `json:"id"`
	Name        string          `json:"name"`
	Description string          `json:"description,omitempty"`
	Meta        json.RawMessage `json:"_meta,omitempty"`
}

type SessionModeState

type SessionModeState struct {
	CurrentModeID  string          `json:"currentModeId"`
	AvailableModes []SessionMode   `json:"availableModes"`
	Meta           json.RawMessage `json:"_meta,omitempty"`
}

SessionModeState is the wire shape for `modes` in session/new and session/load responses: an object, not a bare array.

type SessionModelState

type SessionModelState struct {
	CurrentModelID  string          `json:"currentModelId"`
	AvailableModels []ModelInfo     `json:"availableModels"`
	Meta            json.RawMessage `json:"_meta,omitempty"`
}

SessionModelState is the UNSTABLE model-picker surface: the wire shape of the optional `models` field in session/new, session/load, and session/resume responses, mirroring SessionModeState for modes. Not part of the stable ACP spec and may change; dispatched over session/set_model (see MethodSessionSetModel).

type SessionNotification

type SessionNotification struct {
	SessionID SessionID       `json:"sessionId"`
	Update    SessionUpdate   `json:"update"`
	Meta      json.RawMessage `json:"_meta,omitempty"`
}

type SessionUpdate

type SessionUpdate struct {
	SessionUpdate SessionUpdateKind `json:"sessionUpdate"`

	Content *ContentBlock `json:"-"`

	ToolCallID  string             `json:"toolCallId,omitempty"`
	Title       string             `json:"title,omitempty"`
	Kind        ToolKind           `json:"kind,omitempty"`
	Status      ToolCallStatus     `json:"status,omitempty"`
	ToolContent []ToolCallContent  `json:"-"`
	Locations   []ToolCallLocation `json:"locations,omitempty"`
	RawInput    json.RawMessage    `json:"rawInput,omitempty"`
	RawOutput   json.RawMessage    `json:"rawOutput,omitempty"`

	Entries []PlanEntry `json:"entries,omitempty"`

	AvailableCommands []AvailableCommand `json:"availableCommands,omitempty"`

	CurrentModeID string `json:"currentModeId,omitempty"`

	ConfigOptions []SessionConfigOption `json:"configOptions,omitempty"`

	// Used, Size, Cost: usage_update fields (session context indicator).
	Used int        `json:"used,omitempty"`
	Size int        `json:"size,omitempty"`
	Cost *UsageCost `json:"cost,omitempty"`

	// MessageID groups streamed chunks into messages: all chunks of one message
	// share an id; a change marks a new message. Optional in the spec.
	MessageID string `json:"messageId,omitempty"`

	// UpdatedAt is the session_info_update timestamp (ISO 8601); Title above is
	// shared with tool_call updates (same wire key).
	UpdatedAt string `json:"updatedAt,omitempty"`

	Meta json.RawMessage `json:"_meta,omitempty"`
}

func NewAgentMessageChunk

func NewAgentMessageChunk(text string) SessionUpdate

func NewAgentThoughtChunk

func NewAgentThoughtChunk(text string) SessionUpdate

func NewUserMessageChunk

func NewUserMessageChunk(text string) SessionUpdate

func (SessionUpdate) MarshalJSON

func (u SessionUpdate) MarshalJSON() ([]byte, error)

func (*SessionUpdate) UnmarshalJSON

func (u *SessionUpdate) UnmarshalJSON(data []byte) error

type SessionUpdateKind

type SessionUpdateKind string
const (
	SessionUpdateUserMessageChunk  SessionUpdateKind = "user_message_chunk"
	SessionUpdateAgentMessageChunk SessionUpdateKind = "agent_message_chunk"
	SessionUpdateAgentThoughtChunk SessionUpdateKind = "agent_thought_chunk"
	SessionUpdateToolCall          SessionUpdateKind = "tool_call"
	SessionUpdateToolCallUpdate    SessionUpdateKind = "tool_call_update"
	SessionUpdatePlan              SessionUpdateKind = "plan"
	SessionUpdateAvailableCommands SessionUpdateKind = "available_commands_update"
	SessionUpdateCurrentMode       SessionUpdateKind = "current_mode_update"
	SessionUpdateConfigOption      SessionUpdateKind = "config_option_update"
	SessionUpdateUsageUpdate       SessionUpdateKind = "usage_update"
	SessionUpdateSessionInfo       SessionUpdateKind = "session_info_update"
)

func AllSessionUpdateKinds

func AllSessionUpdateKinds() []SessionUpdateKind

AllSessionUpdateKinds returns every SessionUpdateKind the spec defines, in declaration order, so a consumer's translation table can be tested for completeness against the library instead of a hand-maintained copy. Adding a const above requires adding it here too. Freshly built on every call.

type SetSessionConfigOptionRequest

type SetSessionConfigOptionRequest struct {
	SessionID SessionID `json:"sessionId"`
	ConfigID  string    `json:"configId"`
	// Type discriminates the value variant: absent/unknown means Value is a
	// string value id (the default), "boolean" means Value is a bool.
	Type  string                   `json:"type,omitempty"`
	Value SessionConfigOptionValue `json:"value"`
	Meta  json.RawMessage          `json:"_meta,omitempty"`
}

type SetSessionConfigOptionResponse

type SetSessionConfigOptionResponse struct {
	ConfigOptions []SessionConfigOption `json:"configOptions"`
	Meta          json.RawMessage       `json:"_meta,omitempty"`
}

type SetSessionModeRequest

type SetSessionModeRequest struct {
	SessionID SessionID       `json:"sessionId"`
	ModeID    string          `json:"modeId"`
	Meta      json.RawMessage `json:"_meta,omitempty"`
}

SetSessionModeRequest is session/set_mode's params: switch a session to one of the ids SessionModeState.AvailableModes advertised.

type SetSessionModeResponse

type SetSessionModeResponse struct {
	Meta json.RawMessage `json:"_meta,omitempty"`
}

type SetSessionModelRequest

type SetSessionModelRequest struct {
	SessionID SessionID       `json:"sessionId"`
	ModelID   string          `json:"modelId"`
	Meta      json.RawMessage `json:"_meta,omitempty"`
}

SetSessionModelRequest is session/set_model's params: switch a session to one of the ids SessionModelState.AvailableModels advertised. Part of the UNSTABLE model-picker surface (see MethodSessionSetModel).

type SetSessionModelResponse

type SetSessionModelResponse struct {
	Meta json.RawMessage `json:"_meta,omitempty"`
}

SetSessionModelResponse is session/set_model's result: always empty — the requested modelId is authoritative on success, and no session/update kind exists to reconfirm it.

type StopReason

type StopReason string
const (
	StopReasonEndTurn         StopReason = "end_turn"
	StopReasonMaxTokens       StopReason = "max_tokens"
	StopReasonMaxTurnRequests StopReason = "max_turn_requests"
	StopReasonRefusal         StopReason = "refusal"
	StopReasonCancelled       StopReason = "cancelled"
)

type TerminalExitStatus

type TerminalExitStatus struct {
	ExitCode *int    `json:"exitCode,omitempty"`
	Signal   *string `json:"signal,omitempty"`
}

type TerminalOutputRequest

type TerminalOutputRequest struct {
	SessionID  SessionID       `json:"sessionId"`
	TerminalID string          `json:"terminalId"`
	Meta       json.RawMessage `json:"_meta,omitempty"`
}

type TerminalOutputResponse

type TerminalOutputResponse struct {
	Output     string              `json:"output"`
	Truncated  bool                `json:"truncated"`
	ExitStatus *TerminalExitStatus `json:"exitStatus,omitempty"`
	Meta       json.RawMessage     `json:"_meta,omitempty"`
}

type TerminalPeer

TerminalPeer is the subset of the ACP client side that RunTerminal drives. *AgentSideConnection satisfies it; tests and alternative transports can supply their own implementation.

type TerminalResult

type TerminalResult struct {
	Output    string
	Truncated bool
	ExitCode  int
	Signal    *string
	Cancelled bool // ctx was cancelled; the terminal was killed
	TimedOut  bool // ctx hit its deadline; the terminal was killed
}

TerminalResult is the reconciled outcome of one command run over a peer terminal. Cancelled and TimedOut are kept distinct: a deadline means the command ran out of budget, a cancellation means something (usually session/cancel) stopped the turn. Either way the terminal was killed before the result was read.

func RunTerminal

func RunTerminal(ctx context.Context, p TerminalPeer, req CreateTerminalRequest, onCreated func(terminalID string)) (TerminalResult, error)

RunTerminal creates a terminal on the peer, waits for it to exit, collects its output and releases it, returning the reconciled result.

onCreated, when non-nil, is invoked after the terminal exists but before the wait begins — the seam for callers that need to surface the live terminal (e.g. attaching it to a tool call in a UI).

ctx governs only create and wait; release, kill and the output fetch run on detached contexts since they matter most exactly when ctx is already dead. The terminal is always released before returning.

A non-nil error means the protocol exchange itself failed (create, a non-ctx wait failure, or output read); the result still carries the Cancelled/TimedOut flags established so far. Policy decisions (Truncated as a budget error, banners, exit-status mapping) belong to the caller.

type ToolCallContent

type ToolCallContent struct {
	Type       ToolCallContentKind `json:"type"`
	Content    *ContentBlock       `json:"content,omitempty"`
	Path       string              `json:"path,omitempty"`
	OldText    string              `json:"oldText,omitempty"`
	NewText    string              `json:"newText,omitempty"`
	TerminalID string              `json:"terminalId,omitempty"`
	Meta       json.RawMessage     `json:"_meta,omitempty"`
}

func (ToolCallContent) MarshalJSON

func (c ToolCallContent) MarshalJSON() ([]byte, error)

MarshalJSON forces path/newText onto the wire for the "diff" variant even when empty — newText:"" is the correct shape for a diff clearing a file's content, which plain omitempty can't distinguish from absent.

type ToolCallContentKind

type ToolCallContentKind string
const (
	ToolCallContentRegular  ToolCallContentKind = "content"
	ToolCallContentDiff     ToolCallContentKind = "diff"
	ToolCallContentTerminal ToolCallContentKind = "terminal"
)

type ToolCallLocation

type ToolCallLocation struct {
	Path string `json:"path"`
	Line *int   `json:"line,omitempty"`
}

type ToolCallStatus

type ToolCallStatus string
const (
	ToolCallStatusPending    ToolCallStatus = "pending"
	ToolCallStatusInProgress ToolCallStatus = "in_progress"
	ToolCallStatusCompleted  ToolCallStatus = "completed"
	ToolCallStatusFailed     ToolCallStatus = "failed"
)

type ToolKind

type ToolKind string
const (
	ToolKindRead       ToolKind = "read"
	ToolKindEdit       ToolKind = "edit"
	ToolKindDelete     ToolKind = "delete"
	ToolKindMove       ToolKind = "move"
	ToolKindSearch     ToolKind = "search"
	ToolKindExecute    ToolKind = "execute"
	ToolKindThink      ToolKind = "think"
	ToolKindFetch      ToolKind = "fetch"
	ToolKindSwitchMode ToolKind = "switch_mode"
	ToolKindOther      ToolKind = "other"
)

type TurnTracker

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

TurnTracker watches one prompt turn's session/update stream and tells a client-side driver whether the agent ever produced a renderable answer — guarding against an agent returning a normal session/prompt result while never emitting an agent_message_chunk. A driver feeds each notification to Observe and calls Err at turn end to convert "nothing displayable" into an explicit ErrNoDisplayableOutput. Single-turn: construct fresh (or Reset) per turn. Not safe for concurrent use; drive from the read-loop goroutine.

func (*TurnTracker) Err

func (t *TurnTracker) Err(stop StopReason) error

Err returns nil when the turn produced displayable output, otherwise an ErrNoDisplayableOutput enriched with the turn's stop reason and tool-update count. Call it once the session/prompt result is in hand, passing that result's StopReason.

func (*TurnTracker) Observe

func (t *TurnTracker) Observe(n SessionNotification)

Observe records one inbound session/update. It only inspects the update payload; session-id matching (stale-update filtering) is a separate concern — see FilterSessionUpdates.

func (*TurnTracker) Reset

func (t *TurnTracker) Reset()

Reset returns the tracker to its zero state so it can be reused for the next turn on the same session.

func (*TurnTracker) SawDisplayableOutput

func (t *TurnTracker) SawDisplayableOutput() bool

SawDisplayableOutput reports whether any agent_message_chunk carrying renderable content has been observed this turn.

func (*TurnTracker) ToolUpdateCount

func (t *TurnTracker) ToolUpdateCount() int

ToolUpdateCount reports how many tool_call / tool_call_update notifications were observed. Reported inside Err so an operator can tell "tool activity but no final text" from "literally nothing".

type UnimplementedAgent

type UnimplementedAgent struct{}

func (UnimplementedAgent) Authenticate

func (UnimplementedAgent) Cancel

func (UnimplementedAgent) CloseSession

func (UnimplementedAgent) Initialize

func (UnimplementedAgent) ListSessions

func (UnimplementedAgent) LoadSession

func (UnimplementedAgent) Logout

func (UnimplementedAgent) NewSession

func (UnimplementedAgent) Prompt

type UnimplementedClient

type UnimplementedClient struct{}

UnimplementedClient rejects every request-shaped Client method with MethodNotFound and treats SessionUpdate as a no-op, mirroring UnimplementedAgent (agent.go). Embed it to implement only the methods a particular client cares about.

func (UnimplementedClient) KillTerminal

func (UnimplementedClient) ReadTextFile

func (UnimplementedClient) SessionUpdate

type UsageCost

type UsageCost struct {
	Amount   float64 `json:"amount"`
	Currency string  `json:"currency"`
}

type WaitForTerminalExitRequest

type WaitForTerminalExitRequest struct {
	SessionID  SessionID       `json:"sessionId"`
	TerminalID string          `json:"terminalId"`
	Meta       json.RawMessage `json:"_meta,omitempty"`
}

type WaitForTerminalExitResponse

type WaitForTerminalExitResponse struct {
	ExitCode *int            `json:"exitCode,omitempty"`
	Signal   *string         `json:"signal,omitempty"`
	Meta     json.RawMessage `json:"_meta,omitempty"`
}

type WriteTextFileRequest

type WriteTextFileRequest struct {
	SessionID SessionID       `json:"sessionId"`
	Path      string          `json:"path"`
	Content   string          `json:"content"`
	Meta      json.RawMessage `json:"_meta,omitempty"`
}

type WriteTextFileResponse

type WriteTextFileResponse struct {
	Meta json.RawMessage `json:"_meta,omitempty"`
}

Directories

Path Synopsis
Package acpexec spawns a subprocess and wires its stdin/stdout together as a single io.ReadWriteCloser — the transport shape libacp.NewAgentSideConnection and libacp.NewClientSideConnection expect — so an ACP peer (an editor, or a test driving a reference binary) can be reached over stdio without hand-rolled pipe/shutdown bookkeeping.
Package acpexec spawns a subprocess and wires its stdin/stdout together as a single io.ReadWriteCloser — the transport shape libacp.NewAgentSideConnection and libacp.NewClientSideConnection expect — so an ACP peer (an editor, or a test driving a reference binary) can be reached over stdio without hand-rolled pipe/shutdown bookkeeping.
cmd
acp-stub-agent command
Command acp-stub-agent is a hermetic ACP Agent used to validate libacp's agent-side wire dispatch against ACP conformance clients, without any LLM backend.
Command acp-stub-agent is a hermetic ACP Agent used to validate libacp's agent-side wire dispatch against ACP conformance clients, without any LLM backend.

Jump to

Keyboard shortcuts

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