adapterruntime

package
v0.9.0 Latest Latest
Warning

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

Go to latest
Published: May 29, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package adapterruntime is a Go SDK for ox adapter authors.

It handles protocol framing, serve-mode dispatch, graceful shutdown, and unknown-method responses so adapter authors can focus on agent-specific logic (session file discovery, transcript parsing, hook installation).

Non-Go adapters implement the same protocol directly against the spec. This package is a convenience layer, not a requirement.

Index

Constants

View Source
const DefaultSilenceWindow = 20 * time.Second

DefaultSilenceWindow is the time the detector waits after a pattern match before confirming the session is terminal. If new entries arrive in this window — and they are not themselves matches — the pending match is revoked. Defeats the failure mode where a rendered prior transcript quotes a rate-limit line.

View Source
const MaxRawMessageBytes = 512

MaxRawMessageBytes caps how much of the matched line is forwarded to the daemon over the wire. Long lines are truncated to this length to keep telemetry / audit records compact and to avoid leaking secrets that may sit on the same line (the boundary guard in upstream code strips before write, but defense-in-depth).

Variables

View Source
var ErrSessionNotFound = fmt.Errorf("session not found")

ErrSessionNotFound is returned when an agent_id has no stored session state.

Functions

func AdapterDisabledByEnv added in v0.9.0

func AdapterDisabledByEnv(adapterName string) bool

AdapterDisabledByEnv reports whether OX_DISABLE_TERMINAL_DETECTION names the given adapter. Adapter binaries call this BEFORE constructing a TerminalDetector — when true, skip detector instantiation. Comma-separated, case-insensitive, whitespace-tolerant. Empty env var (the common case) returns false for any adapter.

func AssistantEntry

func AssistantEntry(ts time.Time, content string) adapterprotocol.RawEntry

AssistantEntry creates an assistant message entry.

func LogConfirmedMatch added in v0.9.0

func LogConfirmedMatch(logger *slog.Logger, adapter string, c ConfirmedMatch)

LogConfirmedMatch is a debug helper for adapter authors who want to see what fired without standing up a metrics implementation.

func Run

func Run(cfg Config)

Run dispatches to the appropriate handler based on os.Args[1]. It reads the subcommand from the CLI arguments, calls the handler, serializes the result as compact JSON to stdout, and exits. For --serve, it enters serve mode (blocking).

func RunWithArgs

func RunWithArgs(cfg Config, args []string, stdin io.Reader, stdout io.Writer) error

RunWithArgs is like Run but accepts explicit args and IO for testing. Returns an error instead of calling os.Exit, making it safe for tests and embedding.

func SystemEntry

func SystemEntry(ts time.Time, content string) adapterprotocol.RawEntry

SystemEntry creates a system/context injection entry.

func ToolResultEntry

func ToolResultEntry(ts time.Time, toolOutput string, isError bool) adapterprotocol.RawEntry

ToolResultEntry creates a tool result entry (error output from a tool call).

func ToolResultWithID

func ToolResultWithID(ts time.Time, toolOutput string, isError bool, callID string) adapterprotocol.RawEntry

ToolResultWithID creates a tool result entry with a correlation ID.

func ToolUseEntry

func ToolUseEntry(ts time.Time, toolName, toolInput string) adapterprotocol.RawEntry

ToolUseEntry creates a tool invocation entry (tool call, no result yet).

func ToolUseWithID

func ToolUseWithID(ts time.Time, toolName, toolInput, callID string) adapterprotocol.RawEntry

ToolUseWithID creates a tool invocation entry with a correlation ID. Use the same callID in ToolResultWithID to correlate call and result.

func UserEntry

func UserEntry(ts time.Time, content string) adapterprotocol.RawEntry

UserEntry creates a user message entry.

func ValidateRepoRoot

func ValidateRepoRoot(root string) error

ValidateRepoRoot checks that a repo root path is non-empty, absolute, and contains a .sageox/ directory. All adapters using this SDK get this validation for free when called via the find-session dispatch path. The .sageox stat uses a 500ms timeout to prevent NFS hangs in hook paths.

func ValidateSessionID

func ValidateSessionID(id string) error

ValidateSessionID checks that a session ID is safe to use in file path construction. It rejects path traversal attempts (../, absolute paths) and path separators that could escape the intended directory.

Types

type CancelSubagentHandler

CancelSubagentHandler handles cancel-subagent requests.

type Config

type Config struct {
	Info              func() (*adapterprotocol.InfoResponse, error)
	Detect            func() (*adapterprotocol.DetectResponse, error)
	InstallHooks      func(adapterprotocol.HookParams) (*adapterprotocol.InstallHooksResponse, error)
	CheckHooks        func(adapterprotocol.HookParams) (*adapterprotocol.CheckHooksResponse, error)
	UninstallHooks    func(adapterprotocol.HookParams) (*adapterprotocol.UninstallHooksResponse, error)
	Read              func(adapterprotocol.ReadParams) (*adapterprotocol.ReadResult, error)
	ReadMetadata      func(adapterprotocol.ReadParams) (*adapterprotocol.ReadMetadataResult, error)
	Diagnose          func(adapterprotocol.DiagnoseParams) (*adapterprotocol.DiagnoseResult, error)
	FindSession       func(adapterprotocol.FindSessionParams) (*adapterprotocol.FindSessionResult, error)
	ReadFromOffset    func(adapterprotocol.ReadFromOffsetParams) (*adapterprotocol.ReadFromOffsetResult, error)
	ImportSession     func(adapterprotocol.ImportSessionParams) (*adapterprotocol.ImportSessionResult, error)
	CapturePrior      func(adapterprotocol.CapturePriorParams) (*adapterprotocol.CapturePriorResult, error)
	InstallRules      func(adapterprotocol.RulesParams) (*adapterprotocol.InstallRulesResponse, error)
	CheckRules        func(adapterprotocol.RulesParams) (*adapterprotocol.CheckRulesResponse, error)
	UninstallRules    func(adapterprotocol.RulesParams) (*adapterprotocol.UninstallRulesResponse, error)
	InstallCommands   func(adapterprotocol.CommandsParams) (*adapterprotocol.InstallCommandsResponse, error)
	CheckCommands     func(adapterprotocol.CommandsParams) (*adapterprotocol.CheckCommandsResponse, error)
	UninstallCommands func(adapterprotocol.CommandsParams) (*adapterprotocol.UninstallCommandsResponse, error)
	Serve             func(*Server)
}

Config holds the handler functions for each adapter subcommand. Nil handlers cause the subcommand to return an error.

type ConfirmedMatch added in v0.9.0

type ConfirmedMatch struct {
	SessionID   string
	Pattern     TerminalPattern
	Match       Match
	EntrySeq    int64
	DetectedAt  time.Time
	ConfirmedAt time.Time
}

ConfirmedMatch is what Tick returns when a pending match's silence window has elapsed. Translates 1:1 to a terminal_error event.

func (ConfirmedMatch) ToTerminalErrorData added in v0.9.0

func (c ConfirmedMatch) ToTerminalErrorData() adapterprotocol.TerminalErrorData

ToTerminalErrorData converts a ConfirmedMatch to the wire payload.

type EndSessionHandler

type EndSessionHandler func(ctx context.Context, p adapterprotocol.EndSessionParams) error

EndSessionHandler handles end-session requests.

type FileWatcher

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

FileWatcher watches session files for changes and pushes entry events. Thread-safe: multiple sessions can be watched concurrently.

func NewFileWatcher

func NewFileWatcher(writer *Writer, readFn ReadFunc) (*FileWatcher, error)

NewFileWatcher creates a watcher that pushes entry events via the writer. readFn is called to read new entries from the session file at a given offset.

func NewFileWatcherWithDetector added in v0.9.0

func NewFileWatcherWithDetector(writer *Writer, readFn ReadFunc, detector *TerminalDetector) (*FileWatcher, error)

NewFileWatcherWithDetector creates a watcher with an optional terminal-error detector. Passing a nil or empty detector is equivalent to NewFileWatcher.

func (*FileWatcher) Close

func (fw *FileWatcher) Close()

Close shuts down the file watcher.

func (*FileWatcher) Unwatch

func (fw *FileWatcher) Unwatch(agentID string)

Unwatch stops watching for the given agent.

func (*FileWatcher) Watch

func (fw *FileWatcher) Watch(agentID, sessionFile string, offset int64) error

Watch starts watching a session file for the given agent.

type FindSessionHandler

FindSessionHandler handles find-session requests.

type Match added in v0.9.0

type Match struct {
	PatternID   string
	Reason      string
	RawMessage  string
	ResetsAtRaw string
	ResetsAt    *time.Time
}

Match is what a TerminalPattern.Check returns when an entry matches. The detector populates additional fields (Source, ConfirmedAt) before emitting the terminal_error event.

type Metrics added in v0.9.0

type Metrics interface {
	PatternHit(adapter, patternID string, source PatternSource, reason string)
	PatternConfirmed(adapter, patternID string)
	PatternRevoked(adapter, patternID string)
	ResetsAtParseFailure(adapter, patternID string)
	SilenceWindowObservedMs(adapter, patternID string, observed time.Duration)
}

Metrics is the observability surface for the detector. The watcher (or test harness) injects an implementation. Production wires this to OTLP counters / gauges; the default NopMetrics drops everything.

PatternHit fires for every match found (before silence-window confirmation). PatternConfirmed fires when the silence window elapses without a revoking entry. PatternRevoked fires when a non-matching entry arrives before the window elapses (false positive caught). ResetsAtParseFailure fires when a pattern matched but ParseResetsAt could not produce an absolute timestamp.

type NopMetrics added in v0.9.0

type NopMetrics struct{}

NopMetrics is a Metrics that discards all events.

func (NopMetrics) PatternConfirmed added in v0.9.0

func (NopMetrics) PatternConfirmed(string, string)

func (NopMetrics) PatternHit added in v0.9.0

func (NopMetrics) PatternHit(string, string, PatternSource, string)

func (NopMetrics) PatternRevoked added in v0.9.0

func (NopMetrics) PatternRevoked(string, string)

func (NopMetrics) ResetsAtParseFailure added in v0.9.0

func (NopMetrics) ResetsAtParseFailure(string, string)

func (NopMetrics) SilenceWindowObservedMs added in v0.9.0

func (NopMetrics) SilenceWindowObservedMs(string, string, time.Duration)

type PatternSource added in v0.9.0

type PatternSource string

PatternSource categorizes how a match was found.

const (
	SourceStructured PatternSource = "structured"
	SourceRegex      PatternSource = "regex"
	SourceExitCode   PatternSource = "exit_code"
)

type ReadFromOffsetHandler

ReadFromOffsetHandler handles read-from-offset requests.

type ReadFunc

type ReadFunc func(sessionFile string, offset int64) ([]adapterprotocol.RawEntry, int64, error)

ReadFunc reads new entries from offset and returns them with the new offset.

type Server

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

Server manages the serve-mode request/response loop.

func NewServer

func NewServer(r io.Reader, w io.Writer) *Server

NewServer creates a serve-mode server reading from r, writing to w.

func (*Server) Context

func (s *Server) Context() context.Context

Context returns the server's context, canceled on shutdown.

func (*Server) OnCancelSubagent

func (s *Server) OnCancelSubagent(h CancelSubagentHandler)

OnCancelSubagent registers the handler for cancel-subagent requests.

func (*Server) OnEndSession

func (s *Server) OnEndSession(h EndSessionHandler)

OnEndSession registers the handler for end-session requests.

func (*Server) OnFindSession

func (s *Server) OnFindSession(h FindSessionHandler)

OnFindSession registers the handler for find-session requests.

func (*Server) OnReadFromOffset

func (s *Server) OnReadFromOffset(h ReadFromOffsetHandler)

OnReadFromOffset registers the handler for read-from-offset requests.

func (*Server) OnSpawnSubagent

func (s *Server) OnSpawnSubagent(h SpawnSubagentHandler)

OnSpawnSubagent registers the handler for spawn-subagent requests.

func (*Server) OnSubagentStatus

func (s *Server) OnSubagentStatus(h SubagentStatusHandler)

OnSubagentStatus registers the handler for subagent-status requests.

func (*Server) Serve

func (s *Server) Serve()

Serve runs the serve-mode loop. It blocks until shutdown or EOF.

func (*Server) Writer

func (s *Server) Writer() *Writer

Writer returns the thread-safe writer for push events.

type SessionStore

type SessionStore[T any] struct {
	// contains filtered or unexported fields
}

SessionStore is a typed, concurrent-safe store for per-session state. Adapters use it to cache file handles, byte offsets, and other state keyed by agent_id.

func NewSessionStore

func NewSessionStore[T any]() *SessionStore[T]

NewSessionStore creates a new SessionStore.

func (*SessionStore[T]) Delete

func (s *SessionStore[T]) Delete(agentID string) (T, bool)

Delete removes and returns state for an agent.

func (*SessionStore[T]) Get

func (s *SessionStore[T]) Get(agentID string) (T, bool)

Get retrieves state for an agent. Returns false if not found.

func (*SessionStore[T]) Set

func (s *SessionStore[T]) Set(agentID string, state T)

Set stores state for an agent.

type SpawnSubagentHandler

SpawnSubagentHandler handles spawn-subagent requests.

type StructuredCheck added in v0.9.0

type StructuredCheck func(entry adapterprotocol.RawEntry, raw json.RawMessage) *Match

StructuredCheck inspects a raw line (parsed by the adapter to a RawEntry, plus the original JSON bytes for free-form path lookups). Returns a non-nil Match when the entry matches the structured signal, or nil otherwise. Structured checks are the preferred detection path: they look at vendor-supplied fields (e.g. message.stop_reason) rather than guessing at free-text wording that vendors change quietly.

type SubagentStatusHandler

SubagentStatusHandler handles subagent-status requests.

type TerminalDetector added in v0.9.0

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

TerminalDetector evaluates a list of patterns against every entry the watcher pushes, holds matches in a silence-window pending state, and emits confirmed matches via Tick.

Thread-safe: the watcher calls OnBatch from its read goroutine and Tick from a heartbeat goroutine concurrently.

func NewTerminalDetector added in v0.9.0

func NewTerminalDetector(adapterName string, patterns []TerminalPattern, silenceWindow time.Duration, metrics Metrics) *TerminalDetector

NewTerminalDetector constructs a detector with the given patterns. adapterName is used for metric labeling. Passing nil patterns yields a no-op detector that is safe to call (handy for the disabled case).

func (*TerminalDetector) Enabled added in v0.9.0

func (d *TerminalDetector) Enabled() bool

Enabled reports whether the detector has any patterns to evaluate. Callers can skip OnBatch/Tick wiring entirely when false.

func (*TerminalDetector) Forget added in v0.9.0

func (d *TerminalDetector) Forget(sessionID string)

Forget drops any pending state for the given session. The watcher calls this on Unwatch so a re-Watch under the same agentID does not inherit a pending match from a previous session.

func (*TerminalDetector) OnBatch added in v0.9.0

func (d *TerminalDetector) OnBatch(sessionID string, entries []adapterprotocol.RawEntry, seq int64, rawLines []json.RawMessage)

OnBatch evaluates entries (in order) against the registered patterns. If any entry matches, the match is stored as pending for this session. If a non-matching entry arrives while a match is pending, the pending match is revoked — vendor messages that look like a rate-limit but are followed by more conversational output were a false positive.

rawLines is parallel to entries (same length) carrying the original JSON bytes for structured-pass JSON-path checks. Pass nil for adapters that cannot supply raws; only structured patterns that look at RawEntry fields will fire in that case.

seq is the per-session monotonically increasing batch identifier from the entries event. The first session-batch should be 1, never 0 (0 reserved for "unknown").

func (*TerminalDetector) Tick added in v0.9.0

func (d *TerminalDetector) Tick(now time.Time) []ConfirmedMatch

Tick checks every pending match and returns the ones whose silence window has elapsed. Caller (typically the watcher heartbeat) emits a terminal_error event for each ConfirmedMatch.

type TerminalPattern added in v0.9.0

type TerminalPattern struct {
	ID            string
	Reason        string // empty means "log + metric, do not finalize"
	Source        PatternSource
	Structured    StructuredCheck
	Substrings    []string // cheap pre-filter for regex patterns; ANY substring must be present before Re is evaluated
	Re            *regexp.Regexp
	Roles         []string // for regex patterns: gate to these entry.Role values (default {"system"})
	ParseResetsAt func(match []string) (raw string, parsed *time.Time)
}

TerminalPattern declares one detection rule for an adapter. Patterns are evaluated in registration order; the first match wins for a given entry. Structured patterns are always checked before regex patterns on the same entry — see TerminalDetector.checkEntry.

func LoadYAMLPatterns added in v0.9.0

func LoadYAMLPatterns(
	data []byte,
	structuredRegistry map[string]StructuredCheck,
	parserRegistry map[string]func(match []string) (string, *time.Time),
) ([]TerminalPattern, error)

LoadYAMLPatterns parses a YAML catalog and returns TerminalPattern values. structuredRegistry resolves named structured checks declared via YAMLStructuredRef.Check. parserRegistry resolves named ParseResetsAt entries (e.g. "relative_duration_or_clock"). Returns an error if any regex fails to compile or any named lookup fails — adapter init must surface this rather than silently skipping bad patterns.

type Writer

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

Writer provides thread-safe JSON writing to stdout. Both serve-mode responses and push events share the same pipe. Writes are buffered to a bytes.Buffer and flushed as a single Write call to ensure atomicity on pipes (writes < PIPE_BUF are atomic on POSIX).

func NewWriter

func NewWriter(w io.Writer) *Writer

NewWriter creates a thread-safe Writer.

func (*Writer) PushEvent

func (w *Writer) PushEvent(evt adapterprotocol.Event)

PushEvent writes an unsolicited event (e.g., file_watcher entries push).

func (*Writer) WriteResponse

func (w *Writer) WriteResponse(resp adapterprotocol.Response)

WriteResponse writes a serve-mode response.

type YAMLCatalog added in v0.9.0

type YAMLCatalog struct {
	Version  int                `yaml:"version"`
	Patterns []YAMLPatternEntry `yaml:"patterns"`
}

YAMLCatalog is the on-disk representation of an adapter's pattern set. Adapters embed a YAML file via go:embed and pass the bytes to LoadYAMLPatterns to get back TerminalPattern values registered with a Go-resident StructuredCheck registry.

type YAMLPatternEntry added in v0.9.0

type YAMLPatternEntry struct {
	ID         string            `yaml:"id"`
	Source     PatternSource     `yaml:"source"`
	Reason     string            `yaml:"reason"`
	Roles      []string          `yaml:"roles,omitempty"`
	Substrings []string          `yaml:"substrings,omitempty"`
	Re         string            `yaml:"re,omitempty"`
	Structured YAMLStructuredRef `yaml:"structured,omitempty"`
	ParseHint  string            `yaml:"parse_resets_at,omitempty"` // name of a registered ParseResetsAt
}

YAMLPatternEntry is a single pattern in the catalog file. Structured patterns reference a named check function from a registry (string indirection so the YAML stays declarative); regex patterns inline their regex + parser hint.

type YAMLStructuredRef added in v0.9.0

type YAMLStructuredRef struct {
	JSONPath string `yaml:"json_path,omitempty"` // simple dotted path for the bundled equality check
	Equals   string `yaml:"equals,omitempty"`
	Check    string `yaml:"check,omitempty"` // named entry in StructuredRegistry (custom checks)
}

YAMLStructuredRef declares which named structured check to bind. Resolved against the catalog's structuredRegistry at load time.

Jump to

Keyboard shortcuts

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