stdio

package
v0.1.6 Latest Latest
Warning

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

Go to latest
Published: May 15, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package stdio provides a unified adapter framework for CLI tools that communicate via stdin/stdout using structured JSON protocols.

It supports two communication patterns:

  • Persistent mode: Long-lived process with bidirectional JSON (e.g., Claude Code)
  • Per-turn mode: New process per message (e.g., Codex exec, Gemini, OpenCode)

Adding a new CLI tool requires only implementing the CLISpec interface.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func IsStdioCLIType

func IsStdioCLIType(cliType string) bool

IsStdioCLIType checks if a cli_type string refers to a stdio-mode adapter.

Types

type CLISpec

type CLISpec interface {
	// Name returns the CLI tool identifier (e.g., "claude", "codex").
	Name() string

	// Mode returns the communication pattern for this CLI.
	Mode() StdioMode

	// Binary returns the command name or path to execute.
	Binary() string

	// BuildArgs returns command-line arguments for starting the process.
	BuildArgs(opts StartOptions) []string

	// ParseLine parses a single JSON line from stdout into events.
	// May return zero or more events per line.
	ParseLine(line string) ([]Event, error)

	// FormatInput formats a user message for writing to stdin.
	FormatInput(message string) ([]byte, error)

	// FormatPermissionResponse formats a permission response for stdin.
	// optionID is the ID from the selected PermissionOption.
	FormatPermissionResponse(requestID string, optionID string) ([]byte, error)
}

CLISpec defines how to interact with a specific CLI tool. Implementing this interface is all that's needed to add a new CLI.

type ClaudeSpec

type ClaudeSpec struct{}

ClaudeSpec implements CLISpec for Claude Code. Claude Code uses a persistent process with bidirectional JSON over stdio.

Launch flags:

--output-format stream-json      → stdout emits NDJSON events
--input-format stream-json       → stdin accepts NDJSON messages
--permission-prompt-tool stdio   → permission requests as JSON on stdout

func (ClaudeSpec) Binary

func (ClaudeSpec) Binary() string

func (ClaudeSpec) BuildArgs

func (ClaudeSpec) BuildArgs(opts StartOptions) []string

func (ClaudeSpec) FormatInput

func (ClaudeSpec) FormatInput(message string) ([]byte, error)

func (ClaudeSpec) FormatPermissionResponse

func (ClaudeSpec) FormatPermissionResponse(requestID string, optionID string) ([]byte, error)

FormatPermissionResponse writes a control_response for allow or deny. optionID "allow" → allow, anything else → deny.

func (ClaudeSpec) Mode

func (ClaudeSpec) Mode() StdioMode

func (ClaudeSpec) Name

func (ClaudeSpec) Name() string

func (ClaudeSpec) ParseLine

func (ClaudeSpec) ParseLine(line string) ([]Event, error)

ParseLine parses a single JSON line from Claude Code's stdout.

type CodexSpec

type CodexSpec struct{}

CodexSpec implements CLISpec for OpenAI Codex CLI. Codex uses per-turn mode: each invocation is a separate process. Session continuity is achieved via `codex exec resume --last`.

Launch pattern:

First turn:  codex exec --json -          (reads prompt from stdin)
Resume:      codex exec resume --last --json -

func (CodexSpec) Binary

func (CodexSpec) Binary() string

func (CodexSpec) BuildArgs

func (CodexSpec) BuildArgs(opts StartOptions) []string

func (CodexSpec) FormatInput

func (CodexSpec) FormatInput(message string) ([]byte, error)

func (CodexSpec) FormatPermissionResponse

func (CodexSpec) FormatPermissionResponse(requestID, optionID string) ([]byte, error)

FormatPermissionResponse is not applicable for per-turn mode.

func (CodexSpec) Mode

func (CodexSpec) Mode() StdioMode

func (CodexSpec) Name

func (CodexSpec) Name() string

func (CodexSpec) ParseLine

func (CodexSpec) ParseLine(line string) ([]Event, error)

ParseLine parses Codex JSONL output events. Event types: thread.started, turn.started, item.completed, turn.completed, turn.failed, error.

type Engine

type Engine interface {
	SendToBot(platform, channel, message string)
	SendResponseToSession(sessionName, message string)
	SendPermissionPrompt(sessionName, message string)
}

Engine interface matches cli.Engine — duplicated here to avoid circular import (this package is under internal/cli/).

type Event

type Event struct {
	Type       EventType
	Text       string             // For EventText and EventResult
	ToolUse    *ToolUseInfo       // For EventToolUse
	Permission *PermissionRequest // For EventPermission
	Error      error              // For EventError
	Done       bool               // True when the turn is fully complete
	SessionID  string             // Populated when a new session is detected
}

Event represents a single event from a CLI process.

type EventType

type EventType int

EventType identifies the kind of event emitted by a CLI process.

const (
	EventText       EventType = iota // Text content from the assistant
	EventToolUse                     // Tool invocation notification
	EventPermission                  // Permission request requiring user action
	EventError                       // Error occurred
	EventResult                      // Turn completed
)

type GeminiSpec

type GeminiSpec struct{}

GeminiSpec implements CLISpec for Google Gemini CLI. Gemini uses per-turn mode: each invocation is a separate process. Session continuity is achieved via --resume latest.

Launch pattern:

First turn:  gemini --output-format stream-json -p PROMPT
Resume:      gemini --resume latest --output-format stream-json -p PROMPT

func (GeminiSpec) Binary

func (GeminiSpec) Binary() string

func (GeminiSpec) BuildArgs

func (GeminiSpec) BuildArgs(opts StartOptions) []string

func (GeminiSpec) FormatInput

func (GeminiSpec) FormatInput(message string) ([]byte, error)

FormatInput returns nil — Gemini takes prompt via -p flag, not stdin.

func (GeminiSpec) FormatPermissionResponse

func (GeminiSpec) FormatPermissionResponse(requestID, optionID string) ([]byte, error)

FormatPermissionResponse is not applicable for per-turn mode.

func (GeminiSpec) Mode

func (GeminiSpec) Mode() StdioMode

func (GeminiSpec) Name

func (GeminiSpec) Name() string

func (GeminiSpec) ParseLine

func (GeminiSpec) ParseLine(line string) ([]Event, error)

ParseLine parses Gemini stream-json NDJSON events. Event types: init, message, tool_use, tool_result, result, error.

type NeedsStdioAdapter

type NeedsStdioAdapter interface {
	GetPendingPermission(sessionName string) *PendingPermission
	RespondPermission(sessionName, requestID, optionID string) error
}

NeedsStdioAdapter is a marker interface that adapters can implement to indicate they need the stdio permission flow. The engine checks for this interface to route permission responses.

type OpenCodeSpec

type OpenCodeSpec struct{}

OpenCodeSpec implements CLISpec for OpenCode CLI. OpenCode uses per-turn mode: each invocation is a separate process. Session continuity is achieved via --continue flag.

Launch pattern:

First turn:  opencode run --format json PROMPT
Resume:      opencode run --format json --continue PROMPT

func (OpenCodeSpec) Binary

func (OpenCodeSpec) Binary() string

func (OpenCodeSpec) BuildArgs

func (OpenCodeSpec) BuildArgs(opts StartOptions) []string

func (OpenCodeSpec) FormatInput

func (OpenCodeSpec) FormatInput(message string) ([]byte, error)

FormatInput returns nil — OpenCode takes prompt as positional arg, not stdin.

func (OpenCodeSpec) FormatPermissionResponse

func (OpenCodeSpec) FormatPermissionResponse(requestID, optionID string) ([]byte, error)

FormatPermissionResponse is not applicable for per-turn mode.

func (OpenCodeSpec) Mode

func (OpenCodeSpec) Mode() StdioMode

func (OpenCodeSpec) Name

func (OpenCodeSpec) Name() string

func (OpenCodeSpec) ParseLine

func (OpenCodeSpec) ParseLine(line string) ([]Event, error)

ParseLine parses OpenCode JSON output events. Falls back to plain text if the line is not valid JSON.

type PendingPermission

type PendingPermission struct {
	Request   *PermissionRequest
	Session   string
	Timer     *time.Timer
	Responded bool
}

PendingPermission holds a pending permission request waiting for user response.

type PermissionOption

type PermissionOption struct {
	ID   string // Machine-readable identifier
	Text string // Human-readable label
}

PermissionOption represents one choice in a permission prompt.

type PermissionRequest

type PermissionRequest struct {
	RequestID string
	ToolName  string
	Input     string
	Options   []PermissionOption
}

PermissionRequest represents a permission prompt from the CLI. It contains multiple numbered options the user can choose from.

func (*PermissionRequest) FormatOptions

func (p *PermissionRequest) FormatOptions() string

FormatOptions formats the permission request as a numbered list for display.

func (*PermissionRequest) OptionByID

func (p *PermissionRequest) OptionByID(id string) *PermissionOption

OptionByID returns the option with the given ID.

func (*PermissionRequest) OptionByIndex

func (p *PermissionRequest) OptionByIndex(idx int) *PermissionOption

OptionByIndex returns the option at the given 1-based index. Returns nil if the index is out of range.

type StartOptions

type StartOptions struct {
	WorkDir   string
	Model     string
	Env       map[string]string
	Context   context.Context
	Prompt    string // User's input message (for CLIs that take prompt as arg)
	Resume    bool   // Whether to resume a previous session
	SessionID string // Explicit session ID to resume (optional)
	Yolo      bool   // Auto-approve all permission prompts
}

StartOptions contains parameters for starting a CLI process.

type StdioAdapter

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

StdioAdapter implements cli.CLIAdapter for stdio-based CLI tools. It supports multiple CLI tools via the CLISpec interface.

func NewStdioAdapter

func NewStdioAdapter(spec CLISpec, config StdioAdapterConfig) *StdioAdapter

NewStdioAdapter creates a new stdio adapter for the given CLISpec.

func (*StdioAdapter) Close

func (a *StdioAdapter) Close() error

Close shuts down all sessions and releases resources.

func (*StdioAdapter) CreateSession

func (a *StdioAdapter) CreateSession(sessionName string, opts ...cli.SessionOption) error

CreateSession creates a new session and starts the CLI process.

func (*StdioAdapter) GetPendingPermission

func (a *StdioAdapter) GetPendingPermission(sessionName string) *PendingPermission

GetPendingPermission returns the pending permission request for a session.

func (*StdioAdapter) HandleHookData

func (a *StdioAdapter) HandleHookData(data []byte) (string, string, string, error)

HandleHookData returns an error — stdio adapters don't use hooks.

func (*StdioAdapter) IsSessionAlive

func (a *StdioAdapter) IsSessionAlive(sessionName string) bool

IsSessionAlive checks if the CLI process is still running.

func (*StdioAdapter) RespondPermission

func (a *StdioAdapter) RespondPermission(sessionName, requestID, optionID string) error

RespondPermission sends a permission response to the CLI process.

func (*StdioAdapter) SendInput

func (a *StdioAdapter) SendInput(sessionName, input string) error

SendInput sends a user message to the CLI process. For PersistentMode: writes to stdin and waits for result events. For PerTurnMode: spawns a new process per message.

func (*StdioAdapter) SetEngine

func (a *StdioAdapter) SetEngine(engine Engine)

SetEngine sets the engine reference for sending responses.

func (*StdioAdapter) StopSession

func (a *StdioAdapter) StopSession(sessionName string) error

StopSession stops a single session and removes it from the adapter.

type StdioAdapterConfig

type StdioAdapterConfig struct {
	PermissionTimeout time.Duration
	IdleTimeout       time.Duration
	Env               map[string]string
}

StdioAdapterConfig holds configuration for stdio adapters.

type StdioMode

type StdioMode int

StdioMode determines how the CLI process is managed.

const (
	// PersistentMode keeps a single long-lived process.
	// Messages are sent via stdin and responses arrive via stdout.
	PersistentMode StdioMode = iota

	// PerTurnMode spawns a new process for each user message.
	// The process exits after producing a result.
	PerTurnMode
)

type StdioProcess

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

StdioProcess manages a CLI subprocess with stdin/stdout pipes.

func NewStdioProcess

func NewStdioProcess(ctx context.Context, spec CLISpec, opts StartOptions) (*StdioProcess, error)

NewStdioProcess spawns a new CLI process and starts reading stdout.

func (*StdioProcess) Close

func (p *StdioProcess) Close() error

Close shuts down the process gracefully.

func (*StdioProcess) CloseInput

func (p *StdioProcess) CloseInput() error

CloseInput closes stdin, signaling EOF to the process. Required for per-turn CLIs (e.g., codex) that read prompt from stdin. Safe to call multiple times.

func (*StdioProcess) Events

func (p *StdioProcess) Events() <-chan Event

Events returns the channel of events from the CLI process.

func (*StdioProcess) Pid

func (p *StdioProcess) Pid() int

Pid returns the process ID.

func (*StdioProcess) WriteInput

func (p *StdioProcess) WriteInput(message string) error

WriteInput writes a formatted user message to stdin. If FormatInput returns nil, the write is skipped (for CLIs that take prompt as arg).

func (*StdioProcess) WritePermissionResponse

func (p *StdioProcess) WritePermissionResponse(requestID, optionID string) error

WritePermissionResponse writes a formatted permission response to stdin. If FormatPermissionResponse returns nil, the write is skipped.

type ToolUseInfo

type ToolUseInfo struct {
	Name  string
	Input string
}

ToolUseInfo describes a tool invocation.

Jump to

Keyboard shortcuts

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