agent

package
v0.15.9 Latest Latest
Warning

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

Go to latest
Published: Aug 16, 2026 License: AGPL-3.0 Imports: 8 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrV2PromptConflict  = stderrors.New("agent V2: prompt conflict (id collision)")
	ErrV2SessionNotFound = stderrors.New("agent V2: session not found")
)

V2 error sentinels. Re-exported from pkg/agent/opencode; canonical location is here so callers don't import the opencode package.

View Source
var ErrNoRunningPod = &pkgerrors.StatusError{
	Status:  http.StatusNotFound,
	Code:    "no_running_pod",
	Message: "workspace pod not running",
}

ErrNoRunningPod is the canonical sentinel for "workspace pod is not running (empty podIP)". The opencode adapter wraps this via fmt.Errorf("workspace %s: %w", workspaceID, ErrNoRunningPod) so callers use errors.Is(err, agent.ErrNoRunningPod) without importing the opencode package. Previously defined in pkg/agent/opencode/agent_client.go — moved here (US-65.6-followup) to break the import cycle that forced handlers to import the opencode package for the sentinel.

Functions

func IsSessionNotFound added in v0.14.2

func IsSessionNotFound(err error) bool

IsSessionNotFound returns true if err is or wraps ErrV2SessionNotFound. Convenience for handlers that need to map to HTTP 404.

func Register

func Register(agentType AgentType, a AgentRuntime)

func Unregister

func Unregister(agentType AgentType)

Types

type Adapter added in v0.14.0

type Adapter interface {

	// CreateSession creates a new session in the agent and returns the
	// platform view. The adapter generates or translates the ID.
	CreateSession(ctx context.Context, userID, workspaceID, title string) (*session.Session, error)

	// GetSession returns the current state of one session.
	GetSession(ctx context.Context, userID, workspaceID, sessionID string) (*session.Session, error)

	// ListSessions returns the platform view of all sessions on the
	// workspace, ordered by recency.
	ListSessions(ctx context.Context, userID, workspaceID string) ([]session.Session, error)

	// RenameSession updates a session's title.
	RenameSession(ctx context.Context, userID, workspaceID, sessionID, title string) error

	// DeleteSession removes a session from the agent. Subsequent Get
	// calls return a not-found error.
	DeleteSession(ctx context.Context, userID, workspaceID, sessionID string) error

	// Send delivers a user message synchronously and returns the
	// assistant's completed response. Streaming callers use Stream
	// instead; Send is for request-response callers (MCP, SDK).
	Send(ctx context.Context, userID, workspaceID, sessionID, text string, opts session.SendOpts) (*session.Message, error)

	// SendAsync delivers a user message without waiting for the
	// assistant response. The returned message ID lets the caller
	// correlate via Stream or GetHistory. Use this for long-running
	// tasks where the caller will poll.
	SendAsync(ctx context.Context, userID, workspaceID, sessionID, text string, opts session.SendOpts) (messageID string, err error)

	// Abort stops any in-flight work on the session. The abort is
	// non-destructive: queued user input survives and runs on the
	// next wake (design 0049 §3 — loss of revoke is accepted).
	Abort(ctx context.Context, userID, workspaceID, sessionID string) error

	// GetHistory returns the session transcript in platform shape.
	// The adapter drops agent-specific parts (e.g. opencode's patch
	// part) and produces FileChange parts from git diff where the
	// agent reported file changes.
	GetHistory(ctx context.Context, userID, workspaceID, sessionID string) ([]session.Message, error)

	// Stream subscribes to the session's event stream, translating
	// agent-specific events to session.Event values. The channel
	// closes when the context is canceled or the agent ends the
	// stream. Unknown/malformed events are dropped silently.
	// Connection-level errors (scanner failure) are emitted as
	// session.EventError events before the channel closes.
	Stream(ctx context.Context, userID, workspaceID, sessionID string) (<-chan session.Event, error)

	// ListPending returns the currently-blocking InputRequests on a
	// session (questions or permissions). The adapter unifies the
	// agent's question/permission shapes into session.InputRequest.
	//
	// Contract: a non-nil error means the pending set is UNKNOWN
	// (agent unreachable, endpoint error) — never treat it as an
	// authoritative empty. A 404-not-implemented endpoint yields an
	// authoritative empty with a nil error. Callers that broadcast
	// snapshots (the SSE input snapshot) must mark failed fetches as
	// non-authoritative so they cannot wipe live pending prompts.
	ListPending(ctx context.Context, userID, workspaceID, sessionID string) ([]session.InputRequest, error)

	// Resolve settles a pending InputRequest with the user's reply.
	// For questions, reply carries the selected option(s) or custom
	// text. For permissions, reply carries "allow" / "deny" (the
	// adapter translates to the agent's accept/reject endpoints).
	Resolve(ctx context.Context, userID, workspaceID, requestID, reply string) error

	// ListAvailableModels returns the catalog of models the agent can
	// select, including context/output limits for "context: X% used"
	// display. The adapter converts agent-side cost/relay/provider
	// data into the platform ModelInfo shape.
	ListAvailableModels(ctx context.Context, userID, workspaceID string) ([]session.ModelInfo, error)

	// SetModel changes the session's active model. Subsequent Send
	// calls use the new model.
	SetModel(ctx context.Context, userID, workspaceID, sessionID string, model session.ModelRef) error

	// Capabilities reports the optional agent behaviors the client
	// may render affordances for (steer, queue, rewind, fork, stash,
	// diff, reasoning). The set is per-adapter, not per-session.
	Capabilities() []session.Capability

	// FormatProviderConfig renders the supplied providers in the
	// agent's native config format. Returns nil if the agent has no
	// provider config concept for these credentials.
	FormatProviderConfig(providers []LLMProviderData) ([]byte, error)

	// ValidateCredentials checks whether the supplied raw config
	// (already agent-formatted) is structurally valid. Returns nil
	// on success, an error describing the validation failure
	// otherwise. Used at bind time to reject malformed credentials
	// before they reach the workspace.
	ValidateCredentials(rawConfig []byte) (*CredentialCheckResult, error)
}

Adapter is the single seam between platform code (proxy handlers, MCP server, services) and one agent runtime. It folds the existing AgentRuntime + Dialect + AgentClient interface shapes into one surface and adds the session/messaging methods the proxy migrates to under US-65.4.

Per design 0049 §4.6, the full Adapter has ~18 methods. This file defines the interface shape; the opencode implementation lives in pkg/agent/opencode/adapter.go. Platform code holds an Adapter (interface) and never imports an implementation.

AgentConfigWriter is NOT embedded here. The two seams run in different processes: AgentConfigWriter is held by agentd (the in-pod supervisor that writes agent-config.json); Adapter is held by the API server (proxy handlers that translate HTTP calls). The API pod has no filesystem access to the workspace PVC and cannot write agent config. Composing the two into one interface would force every Adapter implementation to provide panic-stubs for Apply/HasRelay — code that lies about its capabilities. design 0049 §4.6's "folds AgentConfigWriter" described the long-term intent (one unified seam per agent); the as-built architecture has two seams because the two processes have different capabilities.

Rule 12 (containment before abstraction): pass-through operations (Rewind, Fork) are NOT included until a second adapter validates their shape or a forcing UX need lands one.

type AdminPromptChange added in v0.15.7

type AdminPromptChange struct {
	Text string
}

AdminPromptChange is the admin-prompt-source update payload. See AgentConfigInput.AdminPrompt for semantics.

type AgentConfigInput added in v0.14.0

type AgentConfigInput struct {
	// Providers updates the LLM provider map. Formatted is the
	// agent-rendered provider JSON — the agent's formatter is the
	// only thing that knows the shape (e.g. opencode's
	// {"provider": {...}} struct). The writer passes the bytes
	// through verbatim.
	//
	// A non-nil pointer with nil/empty Formatted bytes clears the
	// provider source.
	Providers *AgentProvidersChange

	// Model updates the default model selection. Empty string
	// (ModelSelection("")) clears the model source.
	Model *ModelSelection

	// Relay updates the relay state. A non-nil pointer with empty URL
	// clears the relay source (distinct from nil = leave unchanged).
	Relay *RelayState

	// MCPServers updates the MCP server list. A non-nil pointer with
	// an empty Servers slice clears the MCP source.
	MCPServers *MCPServerChange

	// AdminPrompt updates the platform-level system prompt rendered into
	// the agent's build prompt (replace semantics: the new Text fully
	// supersedes the prior source, side-car-loaded or rendered). A
	// non-nil pointer with empty Text clears the prompt source
	// (distinct from nil = leave unchanged).
	//
	// At bootstrap the writer loads this source from the side-car file
	// (WithAdminPromptPath — the materialize staging contract); this
	// field is the runtime update path so a caller can revise the
	// prompt without recreating the writer. It does NOT watch files.
	AdminPrompt *AdminPromptChange

	// AllowedDirs updates the glob patterns auto-approved as the
	// agent's external-directory allow rules. A non-nil pointer with an
	// empty Dirs slice clears the source. Same bootstrap/runtime split
	// as AdminPrompt: construction may load from the side-car file
	// (WithAllowedDirsPath); this field updates thereafter.
	AllowedDirs *AllowedDirsChange
}

AgentConfigInput is the partial-update payload for Apply. Each pointer field is one source; nil means "leave the writer's existing state for this source unchanged". A non-nil pointer to a zero-value struct means "this source is now empty/disabled" — e.g. `Relay: &RelayState{}` clears the relay source.

Fields are pointers (not values + a "set" bool) because:

  • Pointer-omission composes cleanly with JSON encoding semantics callers already understand.
  • The zero value of the input struct is a valid no-op Apply.

type AgentConfigWriter added in v0.14.0

type AgentConfigWriter interface {
	Apply(in AgentConfigInput) (restartRequired bool, err error)
	HasRelay() bool
}

AgentConfigWriter is the seam between platform code and the agent-specific config writer. The interface is the ONLY surface platform code holds after construction; it does not import or reference the concrete type.

Methods:

  • Apply merges the supplied input onto the writer's existing state and writes the result atomically. Returns restartRequired=true when the running agent process must be restarted to pick up the change (the opencode implementation always returns true — opencode does not hot-reload its config file). A future agent that hot-reloads returns false and the platform's restart machinery no-ops.

  • HasRelay reports whether the writer's current state includes a relay block. Used by the readyz handler and the relay injector short-circuit. Read-only — does not write.

Thread-safety: implementations must be safe for concurrent use. The opencode implementation guards Apply with a mutex.

type AgentProvidersChange added in v0.14.0

type AgentProvidersChange struct {
	// Formatted is the agent-rendered provider JSON. For opencode,
	// this is the output of FormatOpenCodeConfig — a struct
	// {"provider": {...}} containing the agent's exact provider map.
	Formatted []byte
}

AgentProvidersChange is the provider-source update payload. See AgentConfigInput.Providers for semantics.

type AgentRuntime

type AgentRuntime interface {
	Type() AgentType
	ValidateCredentials(rawConfig []byte) (*CredentialCheckResult, error)
	FormatProviderConfig(providers []LLMProviderData) ([]byte, error)
}

func Get

func Get(agentType AgentType) (AgentRuntime, error)

type AgentType

type AgentType string
const (
	AgentTypeOpenCode AgentType = "opencode"
)

type AllowedDirsChange added in v0.15.7

type AllowedDirsChange struct {
	Dirs []string
}

AllowedDirsChange is the allowed-dirs-source update payload. See AgentConfigInput.AllowedDirs for semantics.

type CredentialCheckResult

type CredentialCheckResult struct {
	State   CredentialState `json:"state"`
	Agent   AgentType       `json:"agent"`
	Message string          `json:"message,omitempty"`
}

type CredentialState

type CredentialState string
const (
	CredentialStatePresent CredentialState = "Present"
	CredentialStateMissing CredentialState = "Missing"
	CredentialStateInvalid CredentialState = "Invalid"
)

type Dialect

type Dialect interface {
	// --- Session route paths (pod-internal, relative) ---
	SessionCreatePath() string
	SessionListPath() string
	SessionMessagePath(sessionID string) string
	SessionPromptAsyncPath(sessionID string) string
	SessionAbortPath(sessionID string) string
	SessionGetPath(sessionID string) string
	EventStreamPath() string

	// --- Input request route paths ---
	QuestionListPath() string
	QuestionReplyPath(requestID string) string
	QuestionRejectPath(requestID string) string
	PermissionListPath() string
	PermissionReplyPath(requestID string) string

	// --- SSE event classification ---
	IsQuestionAsked(eventType string) bool
	IsQuestionResolved(eventType string) bool
	IsPermissionAsked(eventType string) bool
	IsPermissionResolved(eventType string) bool
	IsSessionIdle(eventType string, properties json.RawMessage) bool
	IsSessionBusy(eventType string, properties json.RawMessage) bool

	// --- Event parsing (returns nil+error if event doesn't match) ---
	ParseQuestionRequest(eventType string, properties json.RawMessage) (*QuestionRequest, error)
	ParsePermissionRequest(eventType string, properties json.RawMessage) (*PermissionRequest, error)
	ParseSessionStatus(properties json.RawMessage) (sessionID string, status string, err error)
}

Dialect encodes the HTTP API contract of the agent running inside a workspace pod. Implement this interface for each supported agent runtime.

type InputResolution

type InputResolution struct {
	RequestID string `json:"request_id"`
	SessionID string `json:"session_id"`
	Reply     string `json:"reply,omitempty"`
}

InputResolution contains the resolution data for a question or permission.

type LLMModelConfig

type LLMModelConfig struct {
	ID           string `json:"id"`
	Label        string `json:"label,omitempty"`
	ContextLimit int    `json:"contextLimit,omitempty"`
	OutputLimit  int    `json:"outputLimit,omitempty"`
}

LLMModelConfig specifies a model identifier, optional display label, and optional context/output token limits.

ContextLimit and OutputLimit MUST be set together (both > 0) to be emitted into opencode's agent-config.json — opencode's published JSON Schema (https://opencode.ai/config.json) requires both `context` and `output` when the `limit` object is present. See pkg/secrets/types.go LLMModelConfig for the authoritative documentation.

type LLMProviderData

type LLMProviderData struct {
	Kind       string           `json:"kind"`
	Slug       string           `json:"slug"`
	APIKey     string           `json:"apiKey"`
	BaseURL    string           `json:"baseURL,omitempty"`
	Models     []LLMModelConfig `json:"models,omitempty"`
	Default    string           `json:"default,omitempty"`
	SmallModel string           `json:"smallModel,omitempty"`
}

LLMProviderData is re-exported from pkg/secrets for use in the interface. This avoids a circular import between pkg/agent and pkg/secrets.

Epic 55: see pkg/secrets/types.go for the authoritative documentation. Briefly — Kind is the SDK-class enum; Slug is the per-owner unique identity that becomes the literal key in agent-config.json's provider map (opencode persists it as `providerID` on session records).

type MCPServerChange added in v0.14.0

type MCPServerChange struct {
	Servers []MCPServerEntry
}

MCPServerChange is the MCP-source update payload. See AgentConfigInput.MCPServers for semantics.

type MCPServerEntry added in v0.14.0

type MCPServerEntry struct {
	Name      string
	Transport string // http, sse, or stdio
	URL       string
	Command   string
	Args      []string
	TimeoutMs int
	Env       map[string]string
	Headers   map[string]string
}

MCPServerEntry is one MCP server to render into the agent's config. Agent-neutral: the opencode adapter converts to opencode's remote/local shape via pkg/agentd/secrets.RenderOpencodeMCPServerEntry.

type ModelSelection added in v0.14.0

type ModelSelection string

ModelSelection is the fully-qualified "providerID/modelID" string the agent expects in its config. Platform code resolves the providerID before calling Apply; the writer does not look it up.

Empty string = clear the model source.

type PermissionRequest

type PermissionRequest struct {
	ID            string                 `json:"id"`
	SessionID     string                 `json:"session_id"`
	RootSessionID string                 `json:"root_session_id,omitempty"`
	Permission    string                 `json:"permission"`
	Patterns      []string               `json:"patterns"`
	Metadata      map[string]interface{} `json:"metadata,omitempty"`
	Always        []string               `json:"always,omitempty"`
	Tool          *ToolRef               `json:"tool,omitempty"`
}

PermissionRequest is the normalized, agent-agnostic representation of a pending permission.

RootSessionID — see QuestionRequest for semantics.

type QuestionInfo

type QuestionInfo struct {
	Question string           `json:"question"`
	Header   string           `json:"header"`
	Options  []QuestionOption `json:"options"`
	Multiple bool             `json:"multiple"`
	Custom   bool             `json:"custom"`
}

QuestionInfo is a single question with its options.

type QuestionOption

type QuestionOption struct {
	Label       string `json:"label"`
	Description string `json:"description"`
}

QuestionOption is a single selectable choice within a question.

type QuestionRequest

type QuestionRequest struct {
	ID            string         `json:"id"`
	SessionID     string         `json:"session_id"`
	RootSessionID string         `json:"root_session_id,omitempty"`
	Questions     []QuestionInfo `json:"questions"`
	Tool          *ToolRef       `json:"tool,omitempty"`
}

QuestionRequest is the normalized, agent-agnostic representation of a pending question.

RootSessionID is the top-level session in the parent chain. For top-level sessions it equals SessionID. For subtask/subagent sessions (e.g. opencode's `task` tool spawning child sessions) it is the ancestor session that the user is actually viewing in the chat UI. The frontend uses this to bubble subtask prompts up to the user — without it, prompts would be silently dropped because the subtask's SessionID does not match the URL session.

type RelayModel added in v0.14.0

type RelayModel struct {
	ID           string
	Name         string
	ContextLimit int
	OutputLimit  int
}

RelayModel is one free-tier model discovered by the relay injector. Mirrors opencode.RelayModel (and the controller's freemodels.Model wire format) — the type is agent-neutral at this layer; the opencode adapter converts.

type RelayState added in v0.14.0

type RelayState struct {
	URL    string
	Models []RelayModel
}

RelayState describes what the relay injector discovered. A pointer-to-zero-value means "relay disabled" (empty URL); nil means "leave the existing relay source unchanged". The two are distinct and load-bearing — see AgentConfigInput.Relay.

type ToolRef

type ToolRef struct {
	MessageID string `json:"message_id"`
	CallID    string `json:"call_id"`
}

ToolRef identifies the tool call that triggered the input request.

type V2ClientFactory added in v0.14.2

type V2ClientFactory func(ctx context.Context, workspaceID string) (V2SessionClient, error)

V2ClientFactory builds a V2SessionClient for the given workspace.

type V2Delivery added in v0.14.2

type V2Delivery string

V2Delivery selects how the agent's V2 session runner admits a prompt. Generic equivalent of opencode.V2Delivery; allows proxy_v2.go to use V2 types without importing the opencode package.

const (
	V2DeliveryQueue V2Delivery = "queue"
	V2DeliverySteer V2Delivery = "steer"
)

type V2PromptResponse added in v0.14.2

type V2PromptResponse struct {
	AdmittedSeq int    `json:"admittedSeq"`
	ID          string `json:"id"`
	SessionID   string `json:"sessionID"`
}

V2PromptResponse is the response from a V2 prompt admission.

type V2SessionClient added in v0.14.2

type V2SessionClient interface {
	PromptV2(ctx context.Context, sessionID, text string, delivery V2Delivery) (*V2PromptResponse, error)
	InterruptV2(ctx context.Context, sessionID string) error
}

V2SessionClient is the subset of agent client methods the proxy's V2 session-queue paths use. Defined in pkg/agent so proxy_v2.go doesn't need to import pkg/agent/opencode.

Directories

Path Synopsis
filediff
Package filediff produces unified-diff text for files changed by an agent turn, using `git diff` against the workspace PVC's HEAD commit.
Package filediff produces unified-diff text for files changed by an agent turn, using `git diff` against the workspace PVC's HEAD commit.

Jump to

Keyboard shortcuts

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