runner

package
v0.1.0 Latest Latest
Warning

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

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

Documentation

Overview

Package runner contains provider adapters. Every adapter exposes the same durable-session boundary so workflow code can be tested entirely with fakes.

Index

Constants

View Source
const CopilotSDKVersion = "v1.0.13-preview.4"

Variables

View Source
var (
	ErrUnsupportedProvider = errors.New("unsupported provider operation")
	ErrOutputLimit         = errors.New("provider output exceeds configured limit")
	ErrMissingSession      = errors.New("provider did not emit a durable session ID")
	ErrInvalidEnvelope     = errors.New("provider returned an invalid JSON envelope")
)

Functions

func BuildAntigravityArgs

func BuildAntigravityArgs(req Request) ([]string, error)

func BuildClaudeArgs

func BuildClaudeArgs(req Request) ([]string, error)

func BuildCodexArgs

func BuildCodexArgs(req Request, schemaPath string) ([]string, error)

func ClaudeFirstPartyRouteSupported

func ClaudeFirstPartyRouteSupported(runtime task.RuntimeSnapshot, environ []string) bool

ClaudeFirstPartyRouteSupported prevents pxpipe from silently replacing a configured gateway, Bedrock, Vertex, or Foundry transport.

func ClaudePXPipeServerEnvironment

func ClaudePXPipeServerEnvironment(providerEnv []string) []string

ClaudePXPipeServerEnvironment builds a private Claude pxpipe-server environment. Provider credentials stay in the Claude child and are carried by the intercepted request; they are not copied into the helper process.

func CodexChatGPTRouteSupported

func CodexChatGPTRouteSupported(runtime task.RuntimeSnapshot, environ []string) bool

CodexChatGPTRouteSupported reports whether the current routing snapshot and environment positively identify the supported first-party ChatGPT route. An empty snapshot is the default route only when authentication evidence has already established ChatGPT login; it is never authentication evidence by itself.

func CodexChatGPTRuntimeOverlay

func CodexChatGPTRuntimeOverlay(runtime task.RuntimeSnapshot) task.RuntimeSnapshot

CodexChatGPTRuntimeOverlay returns an immutable, ephemeral routing copy for the verified ChatGPT transport. It intentionally discards custom auth and gateway fields while retaining the selected CLI path and credential refs.

func CodexConfigOverrides

func CodexConfigOverrides(runtime task.RuntimeSnapshot) ([]string, error)

CodexConfigOverrides returns deterministic, non-secret `-c key=value` arguments for the exact safe routing snapshot. Runtime.SDKSettings is a typed-at-the-workflow boundary represented as JSON-compatible values so it can survive a restart without retaining credentials.

func CopilotEnvelopePrompt

func CopilotEnvelopePrompt(role Role) string

func CopilotPermissionHandler

func CopilotPermissionHandler(req Request) copilot.PermissionHandlerFunc

func CopilotReadOnlyTools

func CopilotReadOnlyTools() []string

func CopilotTools

func CopilotTools(role Role) []string

func DetectPXPipePath

func DetectPXPipePath(environ []string) string

DetectPXPipePath discovers only an executable. Discovery is not evidence of provider authentication, a supported route, or model eligibility.

func NativeSchema

func NativeSchema(role Role) string

NativeSchema returns the inline schema supplied to Codex/Claude. The schema is deliberately generated locally and contains no provider data.

func PXPipeServerEnvironment

func PXPipeServerEnvironment(providerEnv []string) []string

PXPipeServerEnvironment builds a private Codex pxpipe-server environment.

func ParseCodexLoginStatus

func ParseCodexLoginStatus(data []byte) (bool, string, error)

ParseCodexLoginStatus answers the broader question used by doctor and configure: can this CLI run authenticated turns? It intentionally accepts Codex auth modes that are not safe for the ChatGPT-only pxpipe overlay. ParseCodexAuthStatus remains the stricter routing gate.

func PathWithinRepo

func PathWithinRepo(repoRoot, candidate string) (string, error)

PathWithinRepo canonicalizes both the repository and candidate path and rejects missing paths, symlink escapes, and arbitrary host reads.

func ResolveExecutable

func ResolveExecutable(envName, fallback string, environ []string) (string, error)

func RunInteractiveProcess

func RunInteractiveProcess(ctx context.Context, path string, args []string, dir string, env []string, stdin io.Reader, stdout, stderr io.Writer) error

func SafeURL

func SafeURL(raw string) bool

func SanitizedEnv

func SanitizedEnv(environ []string) []string

SanitizedEnv removes known provider bypass/auto-approval controls. It does not log or otherwise expose the remaining values.

func UsageFromMap

func UsageFromMap(values map[string]any, inputIncludesCache bool) task.TokenUsage

UsageFromMap normalizes snake_case and camelCase usage payloads. For OpenAI and Copilot, cached tokens are a subset of input tokens; Anthropic reports cache read/write tokens separately, so callers select the correct total.

func ValidateEnvelope

func ValidateEnvelope(env Envelope, expected Role) error

func ValidateSelection

func ValidateSelection(role Role, model, effort, speed string, models []ModelInfo, adapter Adapter) error

ValidateSelection checks a provider-advertised model/effort/speed tuple. Empty effort and standard speed are valid defaults; every non-default value must be explicitly advertised by the selected model.

func VerifyReportedSelection

func VerifyReportedSelection(provider string, req Request, response Response) error

func WritePathWithinRepo

func WritePathWithinRepo(repoRoot, candidate string) (string, string, error)

WritePathWithinRepo validates a file-write target, including a not-yet-created file. Every existing path component is resolved so a symlink cannot redirect the write outside the repository. The returned relative path uses slashes for matching against RoleMux's repository-relative scope syntax.

Types

type Adapter

type Adapter interface {
	Run(context.Context, Request, Callbacks) (Response, error)
	ListModels(context.Context, ModelListRequest) (ModelPage, error)
	Version(context.Context) (string, error)
	Auth(context.Context) (AuthStatus, error)
}

type Antigravity

type Antigravity struct {
	Path               string
	Process            ProcessFunc
	InteractiveProcess InteractiveProcessFunc
	Env                []string
}

Antigravity adapts Google's `agy` CLI. The CLI owns authentication, conversations, model discovery, sandboxing, and effort validation.

func NewAntigravity

func NewAntigravity(path string) (*Antigravity, error)

func (*Antigravity) Auth

func (a *Antigravity) Auth(ctx context.Context) (AuthStatus, error)

func (*Antigravity) ListModels

func (a *Antigravity) ListModels(ctx context.Context, req ModelListRequest) (ModelPage, error)

func (*Antigravity) LocalAuthHint

func (a *Antigravity) LocalAuthHint() AuthStatus

func (*Antigravity) Login

func (a *Antigravity) Login(ctx context.Context, req LoginRequest) error

func (*Antigravity) Probe

func (a *Antigravity) Probe(ctx context.Context) error

func (*Antigravity) Run

func (a *Antigravity) Run(ctx context.Context, req Request, callbacks Callbacks) (Response, error)

func (*Antigravity) SupportsRole

func (a *Antigravity) SupportsRole(Role) error

func (*Antigravity) Version

func (a *Antigravity) Version(ctx context.Context) (string, error)

type AuthStatus

type AuthStatus struct {
	Authenticated bool   `json:"authenticated"`
	Version       string `json:"version,omitempty"`
	Message       string `json:"message,omitempty"`
	// Account is used only as an in-memory cache discriminator. Catalog cache
	// files persist its hash, never the raw account identifier.
	Account string `json:"-"`
}

type Authenticator

type Authenticator interface {
	Login(context.Context, LoginRequest) error
}

Authenticator is optional so third-party adapters can remain read-only. Built-in CLI adapters implement it with their official interactive login.

type Callbacks

type Callbacks struct {
	// SessionStarted must durably record an ID before a fresh call is
	// considered successful. Adapters invoke it as soon as the provider emits
	// its durable session/thread event.
	SessionStarted func(string) error
	Event          func(Event) error
	// Diagnostic receives concise, non-secret launch diagnostics. Adapters do
	// not persist these messages and callers may leave it nil.
	Diagnostic func(string)
}

type CapabilityProber

type CapabilityProber interface {
	Probe(context.Context) error
}

CapabilityProber is implemented by adapters whose security contract relies on a particular installed CLI flag surface.

type Claude

type Claude struct {
	Path               string
	Process            ProcessFunc
	InteractiveProcess InteractiveProcessFunc
	Env                []string
	Custom             []ModelInfo
	PXPipePath         string
	TaskLauncher       PXPipeLauncher
}

func NewClaude

func NewClaude(path string) (*Claude, error)

func (*Claude) Auth

func (c *Claude) Auth(ctx context.Context) (AuthStatus, error)

func (*Claude) ListModels

func (c *Claude) ListModels(ctx context.Context, req ModelListRequest) (ModelPage, error)

func (*Claude) Login

func (c *Claude) Login(ctx context.Context, req LoginRequest) error

func (*Claude) Probe

func (c *Claude) Probe(ctx context.Context) error

func (*Claude) Run

func (c *Claude) Run(ctx context.Context, req Request, callbacks Callbacks) (Response, error)

func (*Claude) Version

func (c *Claude) Version(ctx context.Context) (string, error)

type Codex

type Codex struct {
	Path               string
	Process            ProcessFunc
	InteractiveProcess InteractiveProcessFunc
	Env                []string
	// PXPipePath is discovered once for the adapter, then checked again at
	// launch time so installing/removing the optional helper between turns is
	// safe. TaskLauncher is an injectable lifecycle boundary for tests.
	PXPipePath   string
	TaskLauncher CodexTaskLauncher
	AuthProbe    CodexAuthProbe
	// ModelPages is a deterministic seam for discovery tests. Production uses
	// the app-server model/list protocol when it is nil.
	ModelPages func(context.Context, task.RuntimeSnapshot) ([]ModelInfo, string, string, error)
}

func NewCodex

func NewCodex(path string) (*Codex, error)

func (*Codex) Auth

func (c *Codex) Auth(ctx context.Context) (AuthStatus, error)

func (*Codex) ListModels

func (c *Codex) ListModels(ctx context.Context, req ModelListRequest) (ModelPage, error)

func (*Codex) Login

func (c *Codex) Login(ctx context.Context, req LoginRequest) error

func (*Codex) Probe

func (c *Codex) Probe(ctx context.Context) error

func (*Codex) Run

func (c *Codex) Run(ctx context.Context, req Request, callbacks Callbacks) (Response, error)

func (*Codex) Version

func (c *Codex) Version(ctx context.Context) (string, error)

type CodexAuthEvidence

type CodexAuthEvidence struct {
	Mode   CodexAuthMode
	Reason string
}

type CodexAuthMode

type CodexAuthMode string

CodexAuthMode is transient evidence from the selected Codex executable's own login-status command. It is deliberately not part of RuntimeSnapshot.

const (
	CodexAuthChatGPT         CodexAuthMode = "chatgpt"
	CodexAuthAPIKey          CodexAuthMode = "api_key"
	CodexAuthUnauthenticated CodexAuthMode = "unauthenticated"
	CodexAuthUnknown         CodexAuthMode = "unknown"
)

func ParseCodexAuthStatus

func ParseCodexAuthStatus(data []byte) (CodexAuthMode, error)

ParseCodexAuthStatus recognizes the supported Codex 0.153.x status output without retaining account identifiers or token material. Unknown and contradictory output is intentionally rejected so callers can fall back to the untouched provider invocation.

type CodexAuthProbe

type CodexAuthProbe func(context.Context, string, []string) (CodexAuthEvidence, error)

CodexAuthProbe is injectable so the task-launch gate can be tested without invoking a real provider CLI. Implementations must not return credentials.

type CodexTaskLauncher

type CodexTaskLauncher = PXPipeLauncher

CodexTaskLauncher remains an alias for compatibility with the Codex adapter tests while Claude and future providers share the generic lifecycle.

type Copilot

type Copilot struct {
	Path               string
	BaseDirectory      string
	InteractiveProcess InteractiveProcessFunc
	Env                []string
	// Discover is a deterministic seam for catalog tests. Production uses the
	// pinned SDK's ListModels method.
	Discover func(context.Context) ([]copilot.ModelInfo, error)
}

func NewCopilot

func NewCopilot(path string) (*Copilot, error)

func (*Copilot) Auth

func (c *Copilot) Auth(ctx context.Context) (AuthStatus, error)

func (*Copilot) ListModels

func (c *Copilot) ListModels(ctx context.Context, req ModelListRequest) (ModelPage, error)

func (*Copilot) Login

func (c *Copilot) Login(ctx context.Context, req LoginRequest) error

func (*Copilot) Run

func (c *Copilot) Run(ctx context.Context, req Request, callbacks Callbacks) (Response, error)

func (*Copilot) SupportsRole

func (c *Copilot) SupportsRole(Role) error

func (*Copilot) Version

func (c *Copilot) Version(ctx context.Context) (string, error)

type Envelope

type Envelope struct {
	Role      string          `json:"role"`
	Status    string          `json:"status,omitempty"`
	Verdict   string          `json:"verdict,omitempty"`
	Plan      string          `json:"plan,omitempty"`
	Question  string          `json:"question,omitempty"`
	WorkUnits []task.WorkUnit `json:"work_units,omitempty"`
	Findings  []task.Finding  `json:"findings,omitempty"`
}

Envelope is the only model output accepted by the workflow. Fields are intentionally small: natural-language prose cannot substitute for status or verdict, and strict decoding rejects additions that could be misinterpreted.

func DecodeEnvelope

func DecodeEnvelope(data []byte, expected Role) (Envelope, error)

type Event

type Event struct {
	Type      string          `json:"type"`
	SessionID string          `json:"session_id,omitempty"`
	Model     string          `json:"model,omitempty"`
	Effort    string          `json:"effort,omitempty"`
	Raw       json.RawMessage `json:"raw,omitempty"`
}

type Factory

type Factory func(cliPath, repoRoot string) (Adapter, string, error)

Factory constructs one provider adapter and returns its resolved executable path. repoRoot is available for adapters that keep repository-private state.

type InteractiveProcessFunc

type InteractiveProcessFunc func(context.Context, string, []string, string, []string, io.Reader, io.Writer, io.Writer) error

InteractiveProcessFunc is the terminal-attached process seam used by provider login commands.

type LocalAuthHinter

type LocalAuthHinter interface {
	LocalAuthHint() AuthStatus
}

LocalAuthHinter lets an adapter report credential presence without starting the provider. Configure may use this to stay responsive; doctor and task execution still use the adapter's authoritative live checks.

type LoginRequest

type LoginRequest struct {
	RepoRoot string
	Stdin    io.Reader
	Stdout   io.Writer
	Stderr   io.Writer
}

type ModelInfo

type ModelInfo struct {
	ID                     string        `json:"id"`
	Label                  string        `json:"label,omitempty"`
	Description            string        `json:"description,omitempty"`
	Provider               string        `json:"provider"`
	Origin                 string        `json:"origin"`
	Availability           string        `json:"availability"`
	AgeSeconds             int64         `json:"age_seconds,omitempty"`
	ContextWindowTokens    int           `json:"context_window_tokens,omitempty"`
	MaxContextWindowTokens int           `json:"max_context_window_tokens,omitempty"`
	MaxPromptTokens        int           `json:"max_prompt_tokens,omitempty"`
	MaxOutputTokens        int           `json:"max_output_tokens,omitempty"`
	IsDefault              bool          `json:"is_default,omitempty"`
	Efforts                []string      `json:"efforts,omitempty"`
	EffortOptions          []ModelOption `json:"effort_options,omitempty"`
	DefaultEffort          string        `json:"default_effort,omitempty"`
	SpeedOptions           []ModelOption `json:"speed_options,omitempty"`
	DefaultSpeed           string        `json:"default_speed,omitempty"`
	Aliases                []string      `json:"aliases,omitempty"`
	Custom                 bool          `json:"custom"`
	Account                string        `json:"-"`
}

func PaginateModelPages

func PaginateModelPages(ctx context.Context, first func(context.Context, string) (ModelPage, error)) ([]ModelInfo, string, error)

PaginateModelPages follows every non-empty cursor and detects cursor loops. The function is used by live Codex discovery and is independently testable.

type ModelListRequest

type ModelListRequest struct {
	Refresh bool
	Runtime task.RuntimeSnapshot
}

type ModelOption

type ModelOption struct {
	ID          string `json:"id"`
	Label       string `json:"label,omitempty"`
	Description string `json:"description,omitempty"`
}

ModelOption is a provider-advertised setting value. Descriptions are kept optional because not every CLI exposes them.

type ModelPage

type ModelPage struct {
	Models     []ModelInfo
	NextCursor string
	Account    string
	Endpoint   string
}

type PXPipeCapabilityCheck

type PXPipeCapabilityCheck func(context.Context, ProcessSpec) error

type PXPipeLaunchError

type PXPipeLaunchError struct {
	BeforeTask bool
	Cause      error
}

PXPipeLaunchError records whether a helper failure happened before the provider could have accepted the task. Only that phase is eligible for a direct fallback; after the boundary, replaying the prompt could duplicate work.

func (*PXPipeLaunchError) Error

func (e *PXPipeLaunchError) Error() string

func (*PXPipeLaunchError) Unwrap

func (e *PXPipeLaunchError) Unwrap() error

type PXPipeLaunchSpec

type PXPipeLaunchSpec struct {
	PXPipePath         string
	Provider           ProcessSpec
	ProviderName       string
	ServerEnv          []string
	EventsFile         string
	RoutePrefix        string
	TaskStartsOnLaunch bool
	Diagnostic         func(string)
}

PXPipeLaunchSpec is the boundary between provider transport preparation and the helper lifecycle. Provider fields are copied into the private server environment; the helper never receives RoleMux's durable runtime snapshot.

type PXPipeLauncher

type PXPipeLauncher interface {
	Launch(context.Context, PXPipeLaunchSpec) (ProcessResult, error)
}

PXPipeLauncher changes only a provider task launch. It is intentionally separate from ProcessFunc so auth, version, login, discovery, and selection probes cannot accidentally pass through pxpipe.

type PXPipePortChooser

type PXPipePortChooser func(context.Context) ([]int, error)

type PXPipeServer

type PXPipeServer interface {
	WaitReady(context.Context) error
	Done() <-chan struct{}
	Err() error
	Stop(context.Context) error
}

type PXPipeServerFactory

type PXPipeServerFactory func(context.Context, PXPipeServerSpec) (PXPipeServer, error)

type PXPipeServerSpec

type PXPipeServerSpec struct {
	Path           string
	Env            []string
	Dir            string
	Port           int
	StartupTimeout time.Duration
	Readiness      func(context.Context, string) error
}

PXPipeServerSpec and PXPipeServer are injectable seams for lifecycle tests. The production server requires its owned child to announce the configured address and then pass an HTTP readiness check before it is accepted.

type PXPipeTaskLauncher

type PXPipeTaskLauncher struct {
	Path            string
	Process         ProcessFunc
	ServerFactory   PXPipeServerFactory
	PortChooser     PXPipePortChooser
	CapabilityCheck PXPipeCapabilityCheck
	MaxAttempts     int
	StartupTimeout  time.Duration
	ShutdownTimeout time.Duration
}

PXPipeTaskLauncher owns one foreground server for one provider turn. It never talks to, reconfigures, or stops an existing user daemon.

func (*PXPipeTaskLauncher) Launch

type ProcessFunc

type ProcessFunc func(context.Context, ProcessSpec) (ProcessResult, error)

ProcessFunc is injectable for adapter tests. Production adapters use RunProcess, which drains both pipes concurrently.

type ProcessResult

type ProcessResult struct {
	Stdout   []byte
	Stderr   []byte
	ExitCode int
	// ProcessStarted is true only after the OS accepted the child. A false
	// value is reliable evidence that no provider/helper process could run.
	// Injectable ProcessFunc implementations should set it when they start a
	// child before returning a setup error.
	ProcessStarted bool
}

func RunProcess

func RunProcess(ctx context.Context, spec ProcessSpec) (ProcessResult, error)

type ProcessSpec

type ProcessSpec struct {
	Path           string
	Args           []string
	Dir            string
	Env            []string
	Stdin          string
	MaxOutputBytes int64
	// StdoutLine is invoked as each complete stdout line arrives. It is used
	// for durable provider session events that must be saved before the child
	// exits. The complete stdout stream is still returned in ProcessResult.
	StdoutLine func([]byte) error
}

type ProviderError

type ProviderError struct {
	Code         string `json:"code"`
	Message      string `json:"message"`
	Retryable    bool   `json:"retryable"`
	KnownSession bool   `json:"known_session"`
	SessionID    string `json:"session_id,omitempty"`
	Cause        error  `json:"-"`
}

func (*ProviderError) Error

func (e *ProviderError) Error() string

func (*ProviderError) Unwrap

func (e *ProviderError) Unwrap() error

type Registry

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

Registry is the only provider-construction boundary used by the CLI. A new provider adds an Adapter implementation and registers one factory; workflow, catalog, picker, and command code remain provider-agnostic.

func BuiltinRegistry

func BuiltinRegistry() *Registry

BuiltinRegistry contains the adapters shipped in this binary.

func NewRegistry

func NewRegistry() *Registry

func (*Registry) Build

func (r *Registry) Build(name, cliPath, repoRoot string) (Adapter, string, error)

func (*Registry) Has

func (r *Registry) Has(name string) bool

func (*Registry) Names

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

func (*Registry) Register

func (r *Registry) Register(name string, factory Factory) error

type Request

type Request struct {
	Role      Role
	Operation string
	Prompt    string
	Model     string
	Effort    string
	Speed     string
	RepoRoot  string
	Scope     string
	// SkillDirectories are provider-native roots selected by RoleMux. Adapters
	// that expose an explicit skill-loading API may use them; others rely on
	// their native discovery while receiving the same bounded metadata note.
	SkillDirectories []string
	SessionID        string
	Resume           bool
	Sandbox          string
	Runtime          task.RuntimeSnapshot

	// MaxOutputBytes is a hard process-output bound. Zero uses a safe default.
	MaxOutputBytes int64
}

type Response

type Response struct {
	Text           string          `json:"text,omitempty"`
	SessionID      string          `json:"session_id"`
	ReportedModel  string          `json:"reported_model,omitempty"`
	ReportedEffort string          `json:"reported_effort,omitempty"`
	Envelope       *Envelope       `json:"envelope,omitempty"`
	Raw            json.RawMessage `json:"raw,omitempty"`
	Usage          task.TokenUsage `json:"usage,omitempty"`
	// UsageCumulative means token counters cover the whole resumed provider
	// conversation rather than only this invocation.
	UsageCumulative bool `json:"usage_cumulative,omitempty"`
}

type Role

type Role string
const (
	RolePlanner      Role = "planner"
	RolePlanReviewer Role = "plan_reviewer"
	RoleImplementer  Role = "implementer"
	RoleCodeReviewer Role = "code_reviewer"
)

type RoleSupporter

type RoleSupporter interface {
	SupportsRole(Role) error
}

RoleSupporter lets an adapter fail before model selection when it cannot safely perform a role. Adapters that omit it are assumed to support all roles.

type TokenUsage

type TokenUsage = task.TokenUsage

Jump to

Keyboard shortcuts

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