coding

package
v0.84.20 Latest Latest
Warning

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

Go to latest
Published: Aug 29, 2026 License: MIT Imports: 43 Imported by: 0

Documentation

Overview

Package coding is the Go port of pi's coding agent (@earendil-works/pi-coding-agent): the built-in tools (read/write/edit/bash/ls/find/grep), system prompt, session runner, and CLI plumbing built on the agent and ai packages.

Index

Constants

View Source
const (
	DefaultMaxLines   = 2000
	DefaultMaxBytes   = 50 * 1024 // 50KB
	GrepMaxLineLength = 500
)
View Source
const ConfigDirName = ".pi"

ConfigDirName is pi's per-project/user config directory name.

View Source
const CurrentSessionVersion = 3

CurrentSessionVersion matches pi's session file format version.

View Source
const DefaultModelSpec = "anthropic/claude-sonnet-4-5"

DefaultModelSpec is the model used when none is specified. Deliberate divergence from pi: pi's default model comes from settings.json / findInitialModel (first available provider default); the Go port has no settings manager, so an empty spec resolves to this fixed default.

View Source
const DefaultThinkingLevel = agent.ThinkMedium

DefaultThinkingLevel is pi's DEFAULT_THINKING_LEVEL (defaults.ts:3): an unset reasoning level starts at "medium" before clamping to the model's capabilities.

Variables

View Source
var DefaultCompactionSettings = CompactionSettings{
	Enabled:          true,
	ReserveTokens:    16384,
	KeepRecentTokens: 20000,
}

DefaultCompactionSettings mirrors pi's defaults.

View Source
var ErrNoRemoteSession = errors.New("No remote session is attached")

ErrNoRemoteSession reports an operation that needs an attached session when none is attached.

View Source
var ErrRemoteSessionDisposed = errors.New("Remote session is disposed")

ErrRemoteSessionDisposed reports work attempted on a closed RemoteSession, and is also what an in-flight operation returns when disposal preempts it.

View Source
var ErrShellAborted = errors.New("aborted")

ErrShellAborted is what a shell operation returns when the context ended the command. It is pi's `throw new Error("aborted")` from ops.exec (bash.ts:461), rendered as a sentinel so the tool can classify it with errors.Is.

View Source
var ToolNames = []string{"read", "bash", "powershell", "edit", "write", "grep", "find", "ls"}

ToolNames are the built-in coding tool identifiers.

View Source
var ToolSnippets = map[string]string{
	"read":       "Read file contents",
	"bash":       "Execute bash commands (ls, grep, find, etc.)",
	"powershell": "Execute PowerShell commands",
	"edit":       "Make precise file edits with exact text replacement, including multiple disjoint edits in one call",
	"write":      "Create or overwrite files",
	"grep":       "Search file contents for patterns (respects .gitignore)",
	"find":       "Find files by glob pattern (respects .gitignore)",
	"ls":         "List directory contents",
	"web_fetch":  "Fetch a web URL and return readable text",
}

ToolSnippets are the one-line prompt snippets keyed by tool name.

Functions

func AgentDir

func AgentDir() string

AgentDir returns the global agent config directory (~/.pi/agent). It returns "" when the home directory cannot be determined (no HOME — which is ordinary in containers, CI, systemd units and cron). It must NOT fall back to a relative path: a relative path resolves against the process working directory, i.e. whatever repository the agent happens to be run in, so `.pi/agent` would make a hostile repo's files look like the user's own global config. pi cannot reach that state — its getHomeDir is `process.env.HOME || homedir()` and Node's homedir() consults the passwd database — so failing closed is the faithful answer as well as the safe one. Every caller must treat "" as "there is no agent directory".

func AgentsSkillsDir added in v0.84.19

func AgentsSkillsDir() string

AgentsSkillsDir returns the user's AGENTS-convention skills directory (~/.agents/skills), the sibling of pi's own ~/.pi/agent/skills. It returns "" when the home directory cannot be determined, for the same reason AgentDir does — see there.

func AreExperimentalFeaturesEnabled added in v0.2.0

func AreExperimentalFeaturesEnabled() bool

AreExperimentalFeaturesEnabled ports pi's areExperimentalFeaturesEnabled (core/experimental.ts, upstream 66335d3a): the guard that lets users opt in to early features. It is true only when the PI_EXPERIMENTAL environment variable is exactly "1" — unset, empty, "0", "true", or any other value all leave experimental features disabled.

func BuildSystemPrompt

func BuildSystemPrompt(opts BuildSystemPromptOptions) string

BuildSystemPrompt constructs the coding agent system prompt (port of buildSystemPrompt), including tools, guidelines, project context, and footer.

func CreateAllTools

func CreateAllTools(cwd string) []agent.AgentTool

CreateAllTools returns all eight built-in tools, in pi's createAllTools order (powershell sits after bash). powershell is included even off Windows: like pi, the shell is resolved inside Execute, so constructing the tool is safe everywhere and only running it reports the platform error.

func CreateCodingTools

func CreateCodingTools(cwd string) []agent.AgentTool

CreateCodingTools returns the default coding tool set [read, bash, edit, write].

func CreateTool

func CreateTool(name, cwd string) (agent.AgentTool, error)

CreateTool builds a single built-in tool by name, rooted at cwd. The bash tool built this way exposes no PI_* session metadata: there is no session to read it from, matching pi's `exposeSessionEnvironment && ctx` guard when the tool runs without an extension context.

func DefaultSessionDir

func DefaultSessionDir(cwd string) string

DefaultSessionDir returns the per-cwd session directory under the agent dir, using pi's safe-path encoding (--<cwd with separators as dashes>--). It returns "" when AgentDir() does — with no home there is nowhere global to put sessions, and the old relative fallback wrote them into whatever repo the process was run in.

func DetectSupportedImageMimeTypeFromFile added in v0.84.19

func DetectSupportedImageMimeTypeFromFile(path string) string

DetectSupportedImageMimeTypeFromFile reads up to the sniff window from a file and identifies a supported image type (mime.ts detectSupportedImageMimeTypeFromFile), returning "" when the file is not an image pi can attach — including when it cannot be opened, matching pi's null. Upstream de82e5367 promoted it to published SDK surface; the buffer variant it delegates to is not published there and stays unexported here.

func DocsPath

func DocsPath() string

DocsPath returns the absolute path to the pi package docs directory.

func EstimateContextTokens

func EstimateContextTokens(messages []agent.AgentMessage) int

EstimateContextTokens sums estimated tokens across messages (pure heuristic).

func EstimateMessageTokens

func EstimateMessageTokens(m agent.AgentMessage) int

EstimateMessageTokens estimates the token cost of a message (port of estimateTokens: char count / 4, rounded up).

func ExamplesPath

func ExamplesPath() string

ExamplesPath returns the absolute path to the pi package examples directory.

func FormatSize

func FormatSize(bytes int) string

FormatSize renders a byte count as a human-readable size.

func FormatSkillsForPrompt

func FormatSkillsForPrompt(skills []Skill) string

FormatSkillsForPrompt renders visible skills as the Agent Skills XML block.

func GetExperimentalToolSampling added in v0.84.18

func GetExperimentalToolSampling() *ai.ConstrainedSamplingConfig

GetExperimentalToolSampling ports pi's getExperimentalToolSampling: the constrained-sampling config for the built-in read/bash/edit/write tools when experimental features are enabled, nil otherwise.

func LoadSessionMessages

func LoadSessionMessages(path string) ([]agent.AgentMessage, error)

LoadSessionMessages reconstructs the LLM message transcript from a session file for resume. It routes through SessionTree.BuildContext so compacted, branched, and custom-message sessions resume identically to pi (emitting the compaction summary in place of the pre-compaction turns) rather than naively concatenating every message entry.

func LoadSkillsWithDiagnostics

func LoadSkillsWithDiagnostics(cwd string) ([]Skill, []SkillDiagnostic)

LoadSkillsWithDiagnostics is LoadSkills but also returns validation diagnostics. Like LoadSkills it treats the project as UNTRUSTED.

func LoadSkillsWithTrust added in v0.84.20

func LoadSkillsWithTrust(cwd string, projectTrusted bool) ([]Skill, []SkillDiagnostic)

LoadSkillsWithTrust is LoadSkillsWithDiagnostics with the project-trust decision supplied by the caller. Passing true scans <cwd>/.pi/skills, which is what pi does once isProjectTrusted() holds; passing false is pi's headless answer. Only a host that has established trust — by prompting, or by an explicit operator opt-in — may pass true.

func PackageDir

func PackageDir() string

PackageDir returns the pi package root directory, mirroring pi's getPackageDir: honor PI_PACKAGE_DIR, else walk up from the executable until a package.json is found, else fall back to the executable's directory. A dist/ holding only a build's copied metadata resolves to the package root above it.

func ReadmePath

func ReadmePath() string

ReadmePath returns the absolute path to the pi package README.md.

func ResolveModel

func ResolveModel(spec string) (*ai.Model, error)

ResolveModel resolves a model spec to a Model from the catalog (an empty spec resolves to DefaultModelSpec). Kept for source compatibility; the parsed thinking level and warnings are available via ResolveModelPattern.

func TruncateLine

func TruncateLine(line string, maxChars int) (string, bool)

TruncateLine truncates a single line to maxChars, appending a marker. maxChars counts UTF-16 code units like pi's `line.length`/`line.slice` (astral characters count as 2). A slice that would split a surrogate pair yields a lone high surrogate in JS, which serializes as U+FFFD.

Types

type BashExecOptions added in v0.84.20

type BashExecOptions struct {
	// OnData receives interleaved stdout/stderr as it is produced.
	OnData func(data []byte)
	// TimeoutSeconds bounds the command; zero or less means no timeout.
	TimeoutSeconds float64
	// Env is the child environment, already assembled.
	Env []string
}

BashExecOptions are the per-command controls pi passes to BashOperations.exec.

type BashOperations added in v0.84.20

type BashOperations struct {
	// Exec runs a command, streaming output through OnData, and reports the
	// exit code. A NIL exit code with a nil error is pi's `exitCode: null` — the
	// child was signal-killed, which pi treats as success with whatever output
	// was produced. Abort and timeout come back as ErrShellAborted and
	// *ShellTimeoutError.
	Exec func(ctx context.Context, command, cwd string, options BashExecOptions) (exitCode *int, err error)
}

BashOperations is the process execution the shell tools perform. pi shares one interface between bash and powershell (`PowerShellOperations = BashOperations`), so the port does too.

func EnvBashOperations added in v0.84.20

func EnvBashOperations(env ExecutionEnv) BashOperations

EnvBashOperations backs the shell tools with an ExecutionEnv's Shell half.

func LocalBashOperations added in v0.84.20

func LocalBashOperations(config shellToolConfig) BashOperations

LocalBashOperations runs commands on the local machine through the shell the tool config resolves, porting pi's createLocalBashOperations (bash.ts:84).

type BranchContext

type BranchContext struct {
	Messages      []agent.AgentMessage
	ThinkingLevel string
	Provider      string
	ModelID       string
}

BranchContext is the reconstructed LLM context for a branch.

type BuildSystemPromptOptions

type BuildSystemPromptOptions struct {
	CustomPrompt       string
	SelectedTools      []string
	ToolSnippets       map[string]string
	PromptGuidelines   []string
	AppendSystemPrompt string
	Cwd                string
	ContextFiles       []ContextFile
	Skills             []Skill
	// ReadmePath/DocsPath/ExamplesPath are the absolute pi documentation paths
	// referenced by the "Pi documentation" prompt section. Empty values fall back
	// to ReadmePath()/DocsPath()/ExamplesPath().
	ReadmePath   string
	DocsPath     string
	ExamplesPath string
}

BuildSystemPromptOptions configures buildSystemPrompt.

type CompactionSettings

type CompactionSettings struct {
	Enabled          bool
	ReserveTokens    int
	KeepRecentTokens int
	// SessionID, when set, is the routing session ID summarization requests
	// reuse instead of minting a fresh one per request; cache retention stays
	// "none" either way (pi compact()'s optional sessionId param, upstream
	// 58302d34e "support compaction routing sessions").
	SessionID string
}

CompactionSettings configures automatic context-window compaction (port of pi's CompactionSettings / DEFAULT_COMPACTION_SETTINGS).

type ContextFile

type ContextFile struct {
	Path    string
	Content string
}

ContextFile is a project context file injected into the system prompt.

func LoadProjectContextFiles

func LoadProjectContextFiles(cwd string) []ContextFile

LoadProjectContextFiles discovers context files (AGENTS.override.md, else AGENTS.md/CLAUDE.md): the global one under agentDir first, then each ancestor directory of cwd from root down to cwd. Mirrors loadProjectContextFiles.

type CreateRemoteSessionOptions added in v0.84.17

type CreateRemoteSessionOptions struct {
	Cwd           string
	Model         *protocol.ModelRef
	ThinkingLevel *protocol.ThinkingLevel
}

CreateRemoteSessionOptions are the properties of a session to create.

type EditOperations added in v0.84.20

type EditOperations struct {
	ReadFile  func(ctx context.Context, absolutePath string) ([]byte, error)
	WriteFile func(ctx context.Context, absolutePath, content string) error
	// Access reports whether the file is readable AND writable (pi passes
	// R_OK|W_OK here, where the read tool passes R_OK alone).
	Access func(ctx context.Context, absolutePath string) error
}

EditOperations are the file operations the edit tool performs.

func DefaultEditOperations added in v0.84.20

func DefaultEditOperations() EditOperations

DefaultEditOperations reads and writes the local filesystem.

func EnvEditOperations added in v0.84.20

func EnvEditOperations(env ExecutionEnv) EditOperations

EnvEditOperations backs the edit tool with an ExecutionEnv.

type ExecutionEnv added in v0.84.20

type ExecutionEnv interface {
	FileSystem
	Shell
}

ExecutionEnv is the filesystem and process-execution environment the agent runs against, ported from pi's `ExecutionEnv extends FileSystem, Shell` (packages/agent/src/harness/types.ts:231-315 at ccfe79ed2).

It exists so that "where do files live and how do commands run" is an injected capability rather than a hard-coded call to the local OS: a host can point the agent at a sandbox, a container, or a remote machine without the tools knowing. The port's own default is LocalEnv, which is exactly the behavior the tools had before this seam existed.

Two deliberate renderings of pi's shape, both standing conventions here: pi's `Result<T, FileError>` becomes Go's `(T, error)`, and pi's trailing `abortSignal?: AbortSignal` becomes a leading `context.Context`.

type FileInfo added in v0.84.20

type FileInfo struct {
	Path    string
	Name    string
	Kind    FileKind
	Size    int64
	ModTime time.Time
}

FileInfo describes a path without following symlinks (pi FileInfo).

type FileKind added in v0.84.20

type FileKind string

FileKind is the sort of thing a path addresses (pi FileInfo.kind).

const (
	FileKindFile      FileKind = "file"
	FileKindDirectory FileKind = "directory"
	FileKindSymlink   FileKind = "symlink"
	FileKindOther     FileKind = "other"
)

type FileSystem added in v0.84.20

type FileSystem interface {
	// Cwd is the working directory relative paths resolve against.
	Cwd() string
	// AbsolutePath returns an absolute path without requiring it to exist and
	// without resolving symlinks.
	AbsolutePath(ctx context.Context, path string) (string, error)
	// JoinPath joins segments in the filesystem's namespace without requiring
	// the result to exist.
	JoinPath(ctx context.Context, parts []string) (string, error)
	// ReadTextFile reads a whole UTF-8 file.
	ReadTextFile(ctx context.Context, path string) (string, error)
	// ReadTextLines reads UTF-8 lines, stopping once maxLines have been read.
	// A maxLines of 0 or less means "no limit".
	ReadTextLines(ctx context.Context, path string, maxLines int) ([]string, error)
	// ReadBinaryFile reads a whole file as bytes.
	ReadBinaryFile(ctx context.Context, path string) ([]byte, error)
	// WriteFile creates or overwrites a file, creating parent directories.
	WriteFile(ctx context.Context, path string, content []byte) error
	// AppendFile creates or appends to a file, creating parent directories.
	AppendFile(ctx context.Context, path string, content []byte) error
	// RenameFile atomically renames a file, replacing an existing destination.
	// It does not copy across filesystems.
	RenameFile(ctx context.Context, sourcePath, destinationPath string) error
	// Stat returns metadata for a path without following symlinks.
	//
	// Named Stat rather than pi's `fileInfo` because a Go method may not share
	// a name with the type it returns.
	Stat(ctx context.Context, path string) (FileInfo, error)
	// ListDir lists a directory's direct children without following symlinks.
	ListDir(ctx context.Context, path string) ([]FileInfo, error)
	// CanonicalPath resolves symlinks for an existing path.
	CanonicalPath(ctx context.Context, path string) (string, error)
	// Exists reports whether a path exists. A missing path is (false, nil);
	// anything else — a permission failure, say — is an error.
	Exists(ctx context.Context, path string) (bool, error)
}

FileSystem is the file half of an ExecutionEnv. Paths that are not absolute resolve against Cwd.

type FindOperations added in v0.84.20

type FindOperations struct {
	Exists func(ctx context.Context, absolutePath string) (bool, error)
	// Glob returns paths matching a pattern. Nil keeps pi's behavior, where the
	// default is a placeholder and real matching happens in the tool via fd.
	Glob func(ctx context.Context, pattern, cwd string, ignore []string, limit int) ([]string, error)
}

FindOperations are the file operations the find tool performs.

func DefaultFindOperations added in v0.84.20

func DefaultFindOperations() FindOperations

DefaultFindOperations checks the local filesystem. Glob is nil, matching pi, whose default is a placeholder because real matching happens in the tool.

func EnvFindOperations added in v0.84.20

func EnvFindOperations(env ExecutionEnv) FindOperations

EnvFindOperations backs the find tool with an ExecutionEnv. Glob stays nil, as in pi's default: an env exposes no glob, so matching stays with the tool.

type GrepOperations added in v0.84.20

type GrepOperations struct {
	// IsDirectory reports whether a path is a directory, erroring if it does
	// not exist.
	IsDirectory func(ctx context.Context, absolutePath string) (bool, error)
	// ReadFile reads a file's contents. NOTE the divergence recorded at the top
	// of this file: here this is the PRIMARY scan read, not pi's context-line
	// fetch, because the port matches in Go rather than shelling out to
	// ripgrep. Whatever this returns is what grep can match.
	ReadFile func(ctx context.Context, absolutePath string) ([]byte, error)
}

func DefaultGrepOperations added in v0.84.20

func DefaultGrepOperations() GrepOperations

DefaultGrepOperations reads the local filesystem.

func EnvGrepOperations added in v0.84.20

func EnvGrepOperations(env ExecutionEnv) GrepOperations

EnvGrepOperations backs the grep tool with an ExecutionEnv.

type LocalEnv added in v0.84.20

type LocalEnv struct {
	// Dir is the working directory. An empty Dir means the process's own.
	Dir string
}

LocalEnv is the ExecutionEnv backed by the machine the agent runs on. It is the port's default and reproduces exactly what the tools did before this seam existed.

func NewLocalEnv added in v0.84.20

func NewLocalEnv(dir string) *LocalEnv

NewLocalEnv returns a LocalEnv rooted at dir.

func (*LocalEnv) AbsolutePath added in v0.84.20

func (e *LocalEnv) AbsolutePath(ctx context.Context, path string) (string, error)

AbsolutePath implements FileSystem.

func (*LocalEnv) AppendFile added in v0.84.20

func (e *LocalEnv) AppendFile(ctx context.Context, path string, content []byte) error

AppendFile implements FileSystem.

func (*LocalEnv) CanonicalPath added in v0.84.20

func (e *LocalEnv) CanonicalPath(ctx context.Context, path string) (string, error)

CanonicalPath implements FileSystem.

func (*LocalEnv) Cleanup added in v0.84.20

func (e *LocalEnv) Cleanup() error

Cleanup implements Shell. LocalEnv holds no shell resources between calls — every Exec starts and reaps its own process — so there is nothing to release.

func (*LocalEnv) Cwd added in v0.84.20

func (e *LocalEnv) Cwd() string

Cwd implements FileSystem.

func (*LocalEnv) Exec added in v0.84.20

func (e *LocalEnv) Exec(ctx context.Context, command string, options *ShellExecOptions) (ShellResult, error)

Exec implements Shell by running the command through the machine's shell, the same resolution the bash tool uses (getShellConfig). A non-zero exit is reported in the result; an error means the command could not be started, or the context ended it.

func (*LocalEnv) Exists added in v0.84.20

func (e *LocalEnv) Exists(ctx context.Context, path string) (bool, error)

Exists implements FileSystem.

func (*LocalEnv) JoinPath added in v0.84.20

func (e *LocalEnv) JoinPath(ctx context.Context, parts []string) (string, error)

JoinPath implements FileSystem.

func (*LocalEnv) ListDir added in v0.84.20

func (e *LocalEnv) ListDir(ctx context.Context, path string) ([]FileInfo, error)

ListDir implements FileSystem.

func (*LocalEnv) ReadBinaryFile added in v0.84.20

func (e *LocalEnv) ReadBinaryFile(ctx context.Context, path string) ([]byte, error)

ReadBinaryFile implements FileSystem.

func (*LocalEnv) ReadTextFile added in v0.84.20

func (e *LocalEnv) ReadTextFile(ctx context.Context, path string) (string, error)

ReadTextFile implements FileSystem.

func (*LocalEnv) ReadTextLines added in v0.84.20

func (e *LocalEnv) ReadTextLines(ctx context.Context, path string, maxLines int) ([]string, error)

ReadTextLines implements FileSystem.

func (*LocalEnv) RenameFile added in v0.84.20

func (e *LocalEnv) RenameFile(ctx context.Context, sourcePath, destinationPath string) error

RenameFile implements FileSystem.

func (*LocalEnv) Stat added in v0.84.20

func (e *LocalEnv) Stat(ctx context.Context, path string) (FileInfo, error)

Stat implements FileSystem.

func (*LocalEnv) WriteFile added in v0.84.20

func (e *LocalEnv) WriteFile(ctx context.Context, path string, content []byte) error

WriteFile implements FileSystem.

type LsOperations added in v0.84.20

type LsOperations struct {
	Exists func(ctx context.Context, absolutePath string) (bool, error)
	// Stat reports whether a path is a directory, erroring if not found.
	Stat func(ctx context.Context, absolutePath string) (isDir bool, err error)
	// Readdir lists a directory's entry names.
	Readdir func(ctx context.Context, absolutePath string) ([]string, error)
}

LsOperations are the file operations the ls tool performs.

func DefaultLsOperations added in v0.84.20

func DefaultLsOperations() LsOperations

DefaultLsOperations lists the local filesystem.

func EnvLsOperations added in v0.84.20

func EnvLsOperations(env ExecutionEnv) LsOperations

EnvLsOperations backs the ls tool with an ExecutionEnv.

type NoToolsMode

type NoToolsMode string

NoToolsMode controls default tool suppression (mirrors createAgentSession).

const (
	// NoToolsOff keeps the default built-in tools enabled.
	NoToolsOff NoToolsMode = ""
	// NoToolsAll starts with no tools enabled.
	NoToolsAll NoToolsMode = "all"
	// NoToolsBuiltin disables the default built-in tools but keeps custom tools.
	NoToolsBuiltin NoToolsMode = "builtin"
)

type ProcessImageResult added in v0.2.8

type ProcessImageResult struct {
	Ok       bool
	Data     []byte // raw image bytes to attach (not base64)
	MimeType string
	Hints    []string

	Message string
}

ProcessImageResult mirrors pi's discriminated ProcessImageResult (utils/image-process.ts). On success Ok is true and Data/MimeType/Hints are populated; on failure Ok is false and Message holds the omission note.

type ReadOperations added in v0.84.20

type ReadOperations struct {
	// ReadFile reads a file's contents. Required.
	ReadFile func(ctx context.Context, absolutePath string) ([]byte, error)
	// Access reports whether the file is readable, returning an error if not.
	// Required.
	Access func(ctx context.Context, absolutePath string) error
	// DetectImageMimeType returns the image MIME type for a path, or "" for a
	// non-image. Optional — nil uses the built-in detector, matching pi's `?`.
	DetectImageMimeType func(ctx context.Context, absolutePath string) string
}

ReadOperations are the file operations the read tool performs.

func DefaultReadOperations added in v0.84.20

func DefaultReadOperations() ReadOperations

DefaultReadOperations reads from the local filesystem.

func EnvReadOperations added in v0.84.20

func EnvReadOperations(env ExecutionEnv) ReadOperations

EnvReadOperations backs the read tool with an ExecutionEnv.

type RemoteSession added in v0.84.17

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

RemoteSession drives one remote coding session over a client.Client: it owns the attachment, serializes the mutations that may act on it, and maintains the transcript projection subscribers render. It is the port of pi's RemoteSession (packages/coding-agent/src/client/remote-session.ts).

Concurrency. pi serializes by chaining promises on a single-threaded event loop; here one mutex guards every field, and it is never held while a network request runs or a subscriber is called. Concurrency is refused rather than queued, exactly as in pi: a second mutation attempted while one is in flight fails with a RemoteSessionBusyError instead of waiting its turn. The one exception is Abort, which preempts an in-flight Submit and restores it on the way out.

A second mutex orders what subscribers are told. State changes reach a session from two directions — an operation unwinding on its caller's goroutine and an event folded in on the client's — and unordered delivery would let the older of two states arrive last and stay on screen. notifyMu makes a change and the notification announcing it one indivisible step, as they are on pi's event loop. It is always taken before mu and never after: a subscriber may read the session while it runs, and holding mu across the wait would deadlock against exactly that.

DIVERGENCE (deliberate): every mutation takes a context. pi has no cancellation at all, so this is new surface rather than a changed behavior — but a Go caller that cannot abandon a blocked network call has no way to shut down. Attachment work (Open, Create, Reconnect) runs under context.WithoutCancel: Close waits for it, and honoring the caller's cancellation there would strand the server holding an attachment nobody owns. Close's own context bounds that wait.

func CreateRemoteSession added in v0.84.17

func CreateRemoteSession(
	ctx context.Context,
	c *client.Client,
	create CreateRemoteSessionOptions,
	opts RemoteSessionOptions,
) (*RemoteSession, error)

CreateRemoteSession starts a new session and takes it over.

func OpenRemoteSession added in v0.84.17

func OpenRemoteSession(
	ctx context.Context,
	c *client.Client,
	sessionID string,
	opts RemoteSessionOptions,
) (*RemoteSession, error)

OpenRemoteSession takes an exclusive lease on an existing session, closing the half-built session if the attachment fails so a caller never has to clean up something it never got. The client is borrowed, not owned: closing the session leaves the client connected.

func (*RemoteSession) Abort added in v0.84.17

func (s *RemoteSession) Abort(ctx context.Context) error

Abort stops the running turn. It is the one operation that may preempt another: an abort issued while a submit is still waiting for its response takes over, and the submit's lifecycle is restored underneath it if it is still running when the abort finishes. Aborting an idle session does nothing.

func (*RemoteSession) Close added in v0.84.17

func (s *RemoteSession) Close(ctx context.Context) error

Close disposes of the session: it stops accepting work, fails whatever is in flight, releases the attachment, and drops every subscriber. The borrowed client is left connected. It is pi's dispose(), named for Go's io.Closer convention, and is safe to call more than once — later callers wait for the first and get its result.

Close blocks until the attachment is released, including any acquisition an operation had already started, so that no server-side attachment outlives the session that owns it.

The context bounds this caller's wait, not the disposal's outcome: a caller that gives up gets its own context's error, while the cleanup settles on what it actually achieved, which is what every later caller is told. The detach is issued under the first caller's context — that is the budget the disposal has — so a first caller that abandons a slow detach can still cut it short, but it no longer latches its own impatience as the answer for everyone behind it.

func (*RemoteSession) ConnectionState added in v0.84.17

func (s *RemoteSession) ConnectionState() client.ConnectionState

ConnectionState is where the borrowed client's connection sits.

func (*RemoteSession) Create added in v0.84.17

Create starts a new session and attaches to it, replacing the current attachment.

func (*RemoteSession) Disposed added in v0.84.17

func (s *RemoteSession) Disposed() bool

Disposed reports whether Close has been called.

func (*RemoteSession) ID added in v0.84.17

func (s *RemoteSession) ID() string

ID is the attached session's identifier, or "" while unbound.

func (*RemoteSession) Models added in v0.84.17

func (s *RemoteSession) Models() []protocol.ModelMetadata

Models is what the server can run, as of the last server snapshot. It is empty rather than nil before the first one, matching pi's `?? []`: the only thing that distinguishes the two in Go is a == nil check, and a caller writing one would be asking whether the client has a snapshot, which is not a question this accessor answers.

func (*RemoteSession) OnConnectionStateChange added in v0.84.17

func (s *RemoteSession) OnConnectionStateChange(
	listener func(client.ConnectionStateChange),
) (client.Unsubscribe, error)

OnConnectionStateChange forwards the borrowed client's connection lifecycle.

func (*RemoteSession) Open added in v0.84.17

func (s *RemoteSession) Open(ctx context.Context, sessionID string) error

Open takes an exclusive lease on a session, replacing the current attachment. Reopening the session that is already attached and ready is a no-op.

func (*RemoteSession) Operation added in v0.84.17

func (s *RemoteSession) Operation() (RemoteSessionOperation, bool)

Operation is the mutation in flight. The second result is false when none is.

func (*RemoteSession) Phase added in v0.84.17

func (s *RemoteSession) Phase() (protocol.SessionPhase, bool)

Phase is the attached session's phase. The second result is false while unbound, which is pi's `undefined` phase.

func (*RemoteSession) Reconnect added in v0.84.17

func (s *RemoteSession) Reconnect(ctx context.Context) error

Reconnect rebuilds the transport and reacquires the same session. The old handle is dropped rather than detached: whatever it was leasing died with the connection.

func (*RemoteSession) Sessions added in v0.84.17

func (s *RemoteSession) Sessions() []protocol.SessionMetadata

Sessions is every session the server knows about, as of the last server snapshot. It is empty rather than nil before the first one, for the reason given on Models.

func (*RemoteSession) SetModel added in v0.84.17

func (s *RemoteSession) SetModel(ctx context.Context, model protocol.ModelRef) error

SetModel switches the session's model. Only an idle session may be reconfigured.

func (*RemoteSession) SetThinking added in v0.84.17

func (s *RemoteSession) SetThinking(ctx context.Context, level protocol.ThinkingLevel) error

SetThinking switches the session's thinking level. Only an idle session may be reconfigured.

func (*RemoteSession) Snapshot added in v0.84.17

func (s *RemoteSession) Snapshot() *protocol.SessionSnapshot

Snapshot is the authoritative session state, or nil while unbound.

func (*RemoteSession) State added in v0.84.17

func (s *RemoteSession) State() RemoteSessionState

State is everything a subscriber renders, as of now.

func (*RemoteSession) Submit added in v0.84.17

func (s *RemoteSession) Submit(ctx context.Context, text string) error

Submit sends text to the session: a new turn while idle, a steer while one is running. Text that is blank once trimmed is not a message and is dropped.

func (*RemoteSession) Subscribe added in v0.84.17

func (s *RemoteSession) Subscribe(listener func(RemoteSessionState)) (client.Unsubscribe, error)

Subscribe registers a listener for every state change and calls it once immediately, so a subscriber renders without waiting for the next event. The immediate call is ordered with every other notification, so a subscriber cannot be handed a newer state before the one it registered against.

A listener must not call Subscribe: the registration waits for the notification in flight to finish, which would be the one that called it.

type RemoteSessionBusyError added in v0.84.17

type RemoteSessionBusyError struct{ Operation RemoteSessionOperation }

RemoteSessionBusyError reports an operation refused because another one is already in flight. Only abort may preempt, and only a submit.

func (*RemoteSessionBusyError) Error added in v0.84.17

func (e *RemoteSessionBusyError) Error() string

type RemoteSessionLifecycle added in v0.84.17

type RemoteSessionLifecycle struct {
	Status    RemoteSessionStatus
	Operation RemoteSessionOperation
}

RemoteSessionLifecycle is pi's discriminated lifecycle union flattened into one comparable value: Operation is meaningful only while Status is LifecycleBusy, and is empty otherwise, so two lifecycles compare equal exactly when pi's would deep-equal.

type RemoteSessionOperation added in v0.84.17

type RemoteSessionOperation string

RemoteSessionOperation names the one mutation a RemoteSession may have in flight.

const (
	OperationOpen        RemoteSessionOperation = "open"
	OperationCreate      RemoteSessionOperation = "create"
	OperationSubmit      RemoteSessionOperation = "submit"
	OperationAbort       RemoteSessionOperation = "abort"
	OperationSetModel    RemoteSessionOperation = "setModel"
	OperationSetThinking RemoteSessionOperation = "setThinking"
	OperationReconnect   RemoteSessionOperation = "reconnect"
)

type RemoteSessionOptions added in v0.84.17

type RemoteSessionOptions struct {
	// OnListenerError receives failures raised by subscribers. Subscribers run
	// on whichever goroutine produced the notification, so a panicking one is
	// contained and reported here rather than allowed to corrupt session state.
	//
	// One notification is delivered at a time and in the order the changes
	// happened, so a subscriber that renders whatever it was handed last is
	// never left showing a state the session has moved past. A subscriber may
	// read the session and may unsubscribe itself or a peer while it runs, but
	// it must not call Subscribe or any mutation — those wait for the
	// notification in flight, which is the one it is running.
	OnListenerError func(error)
}

RemoteSessionOptions configures a RemoteSession.

type RemoteSessionState added in v0.84.17

type RemoteSessionState struct {
	Lifecycle RemoteSessionLifecycle
	// Snapshot is the authoritative session state, or nil while unbound. It
	// must be treated as read-only.
	Snapshot *protocol.SessionSnapshot
	// Transcript is the snapshot's transcript with streaming progress projected
	// over it. It is never nil; an unbound session renders as empty.
	Transcript []protocol.TranscriptItem
}

RemoteSessionState is everything a subscriber needs to render the session.

type RemoteSessionStatus added in v0.84.17

type RemoteSessionStatus string

RemoteSessionStatus is where a RemoteSession sits in its lifecycle.

const (
	// LifecycleUnbound means no session is attached.
	LifecycleUnbound RemoteSessionStatus = "unbound"
	// LifecycleReady means a session is attached and idle locally.
	LifecycleReady RemoteSessionStatus = "ready"
	// LifecycleBusy means an operation is in flight.
	LifecycleBusy RemoteSessionStatus = "busy"
	// LifecycleDisposed means the session has been closed and will not work again.
	LifecycleDisposed RemoteSessionStatus = "disposed"
)

type ResizeResult

type ResizeResult struct {
	Data           []byte // raw image bytes to send to the model (not base64)
	MimeType       string
	OriginalWidth  int
	OriginalHeight int
	Width          int
	Height         int
	WasResized     bool
}

ResizeResult mirrors the object pi's resizeImage returns.

func ResizeImageDecision

func ResizeImageDecision(data []byte, mimeType string) (ResizeResult, bool)

ResizeImageDecision exposes the image-pipeline decision (dimensions, format, wasResized) for differential-testing tools. It mirrors pi's resizeImage.

type ResolvedModel

type ResolvedModel struct {
	Model *ai.Model
	// ThinkingLevel is the level parsed from a ":<level>" suffix in the spec
	// (pi parseModelPattern), or "" when the spec carried none.
	ThinkingLevel string
	// Warning carries pi's non-fatal resolution warnings (e.g. the custom-id
	// fallback for an unknown model under a known provider).
	Warning string
}

ResolvedModel is the result of resolving a model spec.

func ResolveModelPattern

func ResolveModelPattern(spec string) (ResolvedModel, error)

ResolveModelPattern ports pi's resolveCliModel (model-resolver.ts): a slash-prefix is treated as a provider ONLY when it matches a known provider; otherwise the whole string is matched as a model id across providers (OpenRouter-style ids contain slashes). Matching is case-insensitive and a ":<thinkingLevel>" suffix (off|minimal|low|medium|high|xhigh) is parsed off and returned alongside. An unknown model under a known provider falls back to a synthetic custom-id model with a warning (pi buildFallbackModel); a thinking-level suffix is stripped from the custom id first (pi 9fd75b8a — upstream gates that parse on --thinking being unset, and ResolveModelPattern takes no thinking argument, so it always behaves as that path).

type RunResult

type RunResult struct {
	// Text is the concatenated text of the final assistant message.
	Text string
	// Messages are the messages produced during this run (prompt → final).
	Messages []agent.AgentMessage
	// ToolCalls are the tool calls the model made during this run.
	ToolCalls []ai.ToolCall
	// Usage is the aggregate token usage + cost across every provider request in
	// this run (multi-turn tool loops are summed).
	Usage ai.Usage
	// StopReason is the final assistant stop reason.
	StopReason ai.StopReason
	// ErrorMessage is set when the run failed or was aborted.
	ErrorMessage string
}

RunResult is the structured outcome of a single Run turn, suited to embedding pi as an SDK rather than a CLI.

type Session

type Session struct {
	Agent    *agent.Agent
	Model    *ai.Model
	Cwd      string
	Recorder *SessionRecorder
	// contains filtered or unexported fields
}

Session is a coding-agent session: an Agent wired with a model, tools, and the coding system prompt.

func NewSession

func NewSession(opts SessionOptions) *Session

NewSession builds a Session. If Tools is nil, the default coding tools are used; if SystemPrompt is empty, a system prompt is built from the tool set.

func (*Session) Abort

func (s *Session) Abort()

Abort cancels the in-flight run, if any.

func (*Session) Continue

func (s *Session) Continue(ctx context.Context) error

Continue continues from the current transcript (last message must be a user or tool-result message, or a queued message must exist).

func (*Session) EnableCompaction

func (s *Session) EnableCompaction(settings CompactionSettings)

EnableCompaction installs an automatic compaction TransformContext on the session's agent using the given settings. When the estimated context exceeds the model's window minus ReserveTokens, older turns are summarized (via the session's model) into a single checkpoint message and recent turns are kept.

func (*Session) FollowUp

func (s *Session) FollowUp(m agent.AgentMessage)

FollowUp queues a message to run after the agent would otherwise stop.

func (*Session) History

func (s *Session) History() []agent.AgentMessage

History returns the current transcript.

func (*Session) LastAssistantText

func (s *Session) LastAssistantText() string

LastAssistantText returns the most recent assistant message text.

func (*Session) LoadHistory

func (s *Session) LoadHistory(messages []agent.AgentMessage)

LoadHistory seeds the agent transcript from a prior session's messages.

func (*Session) Record

func (s *Session) Record(r *SessionRecorder)

Record attaches a SessionRecorder; finalized messages are appended to it. The write is guarded because bash commands read the recorder concurrently for their PI_SESSION_ID/PI_SESSION_FILE metadata. Assigning the exported Recorder field directly bypasses that guard — use this method.

func (*Session) Reset

func (s *Session) Reset() error

Reset clears the transcript. It errors while a run is active.

func (*Session) Run

func (s *Session) Run(ctx context.Context, prompt string, images ...ai.ImageContent) (*RunResult, error)

Run executes a prompt and returns a structured RunResult. Unlike RunPrint it does not write to an io.Writer — use Subscribe for streaming.

func (*Session) RunMessages

func (s *Session) RunMessages(ctx context.Context, prompts []agent.AgentMessage) (*RunResult, error)

RunMessages executes explicit prompt messages and returns a structured result.

func (*Session) RunPrint

func (s *Session) RunPrint(ctx context.Context, w io.Writer, prompt string) (string, error)

RunPrint runs a single prompt and renders streaming output to w, returning the final assistant text. Tool activity is rendered as compact status lines.

func (*Session) SetModel

func (s *Session) SetModel(model *ai.Model, apiKey string)

SetModel switches the active model (and API key) for future turns.

func (*Session) SetThinkingLevel

func (s *Session) SetThinkingLevel(level agent.ThinkingLevel)

SetThinkingLevel sets the reasoning level for future turns.

func (*Session) Steer

func (s *Session) Steer(m agent.AgentMessage)

Steer queues a message to inject after the current assistant turn finishes.

func (*Session) Subscribe

func (s *Session) Subscribe(l agent.Listener) func()

Subscribe registers an agent event listener (passthrough to the Agent), useful for streaming tokens/tool activity into an app UI. Returns an unsubscribe func.

func (*Session) WaitForIdle

func (s *Session) WaitForIdle()

WaitForIdle blocks until the current run and its listeners finish.

type SessionEntry

type SessionEntry struct {
	ID            string
	ParentID      string
	Type          string // "message" | "model_change" | "thinking_level_change" | "branch_summary" | "compaction" | "custom_message" | ...
	Timestamp     string
	Message       ai.Message // for Type=="message"
	Provider      string     // for "model_change"
	ModelID       string     // for "model_change"
	ThinkingLevel string     // for "thinking_level_change"
	Summary       string     // for "branch_summary" / "compaction"
	FromID        string     // for "branch_summary"
	// compaction
	FirstKeptEntryID string
	// RetainedTail holds the compaction's kept tail inlined on the entry (pi
	// CompactionEntry.retainedTail, upstream 9e7582aa). When set, it replaces the
	// firstKeptEntryId walk: the reconstructed context is the summary followed by
	// these messages. Already filtered through convertToLlm (excluded entries drop
	// out during parse).
	RetainedTail []agent.AgentMessage
	// custom_message
	CustomType string
	Content    ai.ContentList
}

SessionEntry is one node in a session tree (port of pi's SessionEntry). Entries form a tree via ID/ParentID; the active branch is the path from a leaf to the root.

type SessionInfo

type SessionInfo struct {
	Path      string
	ID        string
	Cwd       string
	Timestamp string
	Messages  int
}

SessionInfo summarizes a stored session file.

func LatestSession

func LatestSession(cwd string) (SessionInfo, bool)

LatestSession returns the most recent stored session for cwd, if any.

func ListSessions

func ListSessions(cwd string) []SessionInfo

ListSessions returns stored sessions for cwd, newest first.

type SessionOptions

type SessionOptions struct {
	Model *ai.Model
	Cwd   string

	// Tools, when non-nil, is used verbatim and bypasses name-based selection.
	Tools []agent.AgentTool
	// ToolNames is an allowlist of built-in tool names. When nil and NoTools is
	// off, the default set [read, bash, edit, write] is used.
	ToolNames []string
	// ExcludeTools is a denylist applied after ToolNames.
	ExcludeTools []string
	// NoTools suppresses the default built-in tools ("all" or "builtin").
	NoTools NoToolsMode
	// CustomTools are appended to the resolved built-in set.
	CustomTools []agent.AgentTool

	SystemPrompt  string
	ThinkingLevel agent.ThinkingLevel
	APIKey        string
	SessionID     string

	// TrustProject enables discovery of project-local resources under
	// <cwd>/.pi — currently the skills directory. It is pi's isProjectTrusted()
	// and it defaults to FALSE, which is the answer pi itself gives a host with
	// no UI to prompt with (project-trust.ts). Set it only after trust has
	// actually been established: a project skill's name and description reach
	// the system prompt, so an untrusted repo would otherwise get to author part
	// of the prompt. See LoadSkillsWithTrust.
	TrustProject bool

	// Models, when set, is the model runtime used to resolve request auth for
	// summarization requests (pi AgentSession's _modelRuntime). It is needed
	// only for providers that carry their endpoint in the credential rather
	// than the catalog — see Session.summarizationRequestModel. Nil leaves
	// summarization on the session's own model, as before.
	Models ai.Models

	// Per-request provider controls (all optional).
	Temperature     *float64
	MaxTokens       *int
	CacheRetention  ai.CacheRetention
	MaxRetries      int
	TimeoutMs       int
	MaxRetryDelayMs *int
	Transport       ai.Transport
	ThinkingBudgets *ai.ThinkingBudgets
	// Headers are extra HTTP headers merged into every provider request
	// (e.g. OpenAI-Organization). A nil value suppresses a provider default
	// header of that name (see ai.ProviderHeaders).
	Headers ai.ProviderHeaders
	// OnPayload can inspect/replace the provider request body before sending.
	OnPayload func(payload any, model *ai.Model) (any, error)
	// OnResponse is invoked after the HTTP response is received.
	OnResponse func(resp ai.ProviderResponse, model *ai.Model) error
	// BeforeToolCall runs after a tool call's args are validated and before it
	// executes. Return {Block:true, Reason:...} to deny it (the loop emits an
	// error tool result). This is the native equivalent of pi's tool_call
	// extension hook — use it for permission gates, path protection, etc.
	BeforeToolCall func(ctx context.Context, c agent.BeforeToolCallContext) *agent.BeforeToolCallResult
	// AfterToolCall runs after a tool finishes; return overrides for the result.
	AfterToolCall func(ctx context.Context, c agent.AfterToolCallContext) *agent.AfterToolCallResult

	// Compaction, when non-nil, installs automatic context-window compaction.
	// Use &DefaultCompactionSettings for pi's defaults.
	Compaction *CompactionSettings
	// StreamFn overrides the stream function (for tests). Default: ai.StreamSimple.
	StreamFn agent.StreamFn
}

SessionOptions configures a coding Session. The tool fields mirror pi's createAgentSession: when Tools is nil the built-in set is resolved from ToolNames/ExcludeTools/NoTools, then CustomTools are appended.

type SessionRecorder

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

SessionRecorder appends an agent transcript to a JSONL session file, matching pi's append-only format (header + linear message/model/thinking entries).

Writes are withheld until the first assistant message is recorded (pi _persist): a started-but-unused session leaves no file on disk. Pending entries are buffered and flushed atomically on the first assistant message.

func ResumeSession

func ResumeSession(path string) (*SessionRecorder, error)

ResumeSession opens an existing session file for appending, porting pi's SessionManager.setSessionFile resume semantics (session-manager.ts:792-822): entries load from the file, the leaf is the file's last entry (new entries branch from it), and the manager is marked flushed so every subsequent entry appends to the file immediately (no withhold-until-assistant buffering).

func StartSession

func StartSession(cwd string, model *ai.Model, thinkingLevel ...string) (*SessionRecorder, error)

StartSession creates a new session for cwd and buffers the header plus an initial model entry. The session file is created lazily on the first recorded assistant message.

When thinkingLevel is given, a thinking_level_change entry is recorded after the model_change, matching pi's createAgentSession for new sessions (sdk.ts:362-373: appendModelChange then appendThinkingLevelChange; the thinking entry is written even when there is no model). The variadic parameter keeps existing callers source-compatible.

func (*SessionRecorder) Close

func (r *SessionRecorder) Close() error

Close closes the session file.

func (*SessionRecorder) ForkFrom

func (r *SessionRecorder) ForkFrom(entryID string)

ForkFrom sets the parent for subsequent entries to entryID, so new entries branch off an earlier point in the tree instead of extending the latest leaf.

func (*SessionRecorder) ID

func (r *SessionRecorder) ID() string

ID returns the session id.

func (*SessionRecorder) LastEntryID

func (r *SessionRecorder) LastEntryID() string

LastEntryID returns the id of the most recently written entry (a branch point).

func (*SessionRecorder) Path

func (r *SessionRecorder) Path() string

Path returns the session file path.

func (*SessionRecorder) RecordMessage

func (r *SessionRecorder) RecordMessage(m agent.AgentMessage) string

RecordMessage appends a message entry for an agent transcript message and returns its entry id (usable as a fork point).

func (*SessionRecorder) RecordModelChange

func (r *SessionRecorder) RecordModelChange(provider, modelID string)

RecordModelChange appends a model-change entry.

func (*SessionRecorder) RecordThinkingLevel

func (r *SessionRecorder) RecordThinkingLevel(level string)

RecordThinkingLevel appends a thinking-level-change entry.

type SessionTree

type SessionTree struct {
	Header  SessionInfo
	Entries []*SessionEntry

	// LeafID is the active leaf; defaults to the last entry in the file.
	LeafID string
	// contains filtered or unexported fields
}

SessionTree is the parsed entry tree of a session file.

func LoadSessionTree

func LoadSessionTree(path string) (*SessionTree, error)

LoadSessionTree parses a JSONL session file into its entry tree.

func (*SessionTree) Branch

func (t *SessionTree) Branch(fromID ...string) []*SessionEntry

Branch returns the entries along the path from the given leaf (default LeafID) up to the root, in root→leaf order (port of getBranch). An unknown leaf id falls back to the last entry, matching pi.

func (*SessionTree) BuildContext

func (t *SessionTree) BuildContext(leafID ...string) BranchContext

BuildContext reconstructs the LLM message list, thinking level, and model for the active branch. It mirrors pi's buildSessionContext followed by convertToLlm: it handles the compaction checkpoint (emit summary, then kept entries from firstKeptEntryId, then post-compaction entries) and converts custom_message / branch_summary entries to their user-message form with pi's exact wrapper text.

func (*SessionTree) BuildContextNull

func (t *SessionTree) BuildContextNull() BranchContext

BuildContextNull returns the empty context pi produces for an explicit-null leaf (leafId === null) — the "navigated to before the first entry" state.

func (*SessionTree) Leaves

func (t *SessionTree) Leaves() []*SessionEntry

Leaves returns the entries that have no children (the tips of each branch).

type Shell added in v0.84.20

type Shell interface {
	// Exec runs a shell command. A non-zero exit is reported in the result, not
	// as an error; an error means the command could not be run or was cut short.
	Exec(ctx context.Context, command string, options *ShellExecOptions) (ShellResult, error)
	// Cleanup releases shell resources. Best-effort: it must not panic, and a
	// nil return is the normal case.
	Cleanup() error
}

Shell is the process half of an ExecutionEnv.

type ShellExecOptions added in v0.84.20

type ShellExecOptions struct {
	// Cwd overrides the env's working directory for this command. A relative
	// value resolves against the env's Cwd.
	Cwd string
	// Env carries variables for the command. Values here win over inherited
	// ones when InheritEnv is true.
	Env map[string]string
	// InheritEnv controls whether the ambient environment is inherited. Nil
	// means pi's default, which is true — the pointer exists to keep that
	// default reachable from the zero value.
	InheritEnv *bool
	// TimeoutSeconds bounds the command. Zero or less means no timeout.
	TimeoutSeconds int
	// OnStdout and OnStderr receive output chunks as they are produced.
	OnStdout func(chunk string)
	OnStderr func(chunk string)
}

ShellExecOptions are the per-command controls for Shell.Exec (pi ShellExecOptions). The zero value is pi's default in every field: run in the env's Cwd, inherit the ambient environment, no timeout, no streaming.

type ShellResult added in v0.84.20

type ShellResult struct {
	Stdout   string
	Stderr   string
	ExitCode int
}

ShellResult is a finished command (pi's `{stdout, stderr, exitCode}`).

type ShellTimeoutError added in v0.84.20

type ShellTimeoutError struct{ Seconds float64 }

ShellTimeoutError is what a shell operation returns when the command outran its timeout — pi's `throw new Error("timeout:<seconds>")` (bash.ts:464), rendered as a typed error so the tool can recover the seconds with errors.As instead of parsing a message.

func (*ShellTimeoutError) Error added in v0.84.20

func (e *ShellTimeoutError) Error() string

type Skill

type Skill struct {
	Name                   string
	Description            string
	FilePath               string
	BaseDir                string
	DisableModelInvocation bool
}

Skill is a discovered Agent Skill (SKILL.md with frontmatter).

func LoadSkills

func LoadSkills(cwd string) []Skill

LoadSkills discovers skills under the USER skill directories only (~/.pi/agent/skills and ~/.agents/skills). The project directory <cwd>/.pi/skills is NOT scanned.

That omission is pi's behavior, not a gap. pi discovers the project skills dir only inside `if (projectTrusted)` (package-manager.ts:2417 at ccfe79ed2), and resolveProjectTrusted answers FALSE for a host with no UI to ask with (`if (!options.projectTrustContext.hasUI) return false`, project-trust.ts). This port is headless by construction — it is an SDK plus a non-interactive CLI, and it ships no trust prompt — so untrusted is the faithful default and scanning unconditionally was a parity INVERSION on a security default: a skill's name and description go into the system prompt (FormatSkillsForPrompt) together with an instruction to read the file when the task matches, so a hostile repo could put attacker-authored text in front of the model just by being the cwd.

A host that has actually established trust calls LoadSkillsWithTrust, or sets SessionOptions.TrustProject.

Note pi's ancestor <project>/.agents/skills directories are a SEPARATE discovery that this port does not implement at all (2026-08-18 ruling); passing projectTrusted does not enable them.

Diagnostics are discarded; see LoadSkillsWithDiagnostics.

type SkillDiagnostic

type SkillDiagnostic struct {
	Type    string // "warning" | "error"
	Message string
	Path    string
}

SkillDiagnostic mirrors pi's ResourceDiagnostic for skill loading: a validation warning (or error) with the offending file path.

type TranscriptState added in v0.84.17

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

TranscriptState is a client-side projection of one session's transcript: the authoritative snapshot the server sent, plus the streaming progress laid over it. It is the port of pi's TranscriptState (packages/coding-agent/src/client/transcript.ts).

The snapshot always wins. Progress is an optimization that lets a viewer render a turn as it is produced; the moment a newer snapshot arrives the projection is rebuilt from it and every accumulated delta is discarded.

A TranscriptState is immutable. Every operation returns the next state — the receiver is never modified — so a value can be read without a lock while another goroutine folds the next event into it. Items handed in are deep copied on the way in, so a caller that keeps mutating what it passed cannot corrupt a projection.

func NewTranscriptState added in v0.84.17

func NewTranscriptState(snapshot *protocol.SessionSnapshot) *TranscriptState

NewTranscriptState starts a projection from an authoritative snapshot. It is pi's createTranscriptState.

func (*TranscriptState) ApplyProgress added in v0.84.17

func (s *TranscriptState) ApplyProgress(progress protocol.TranscriptProgress) *TranscriptState

ApplyProgress folds in one incremental update. It is pi's applyTranscriptProgress.

A progress variant this port does not recognize leaves the state untouched, which is what pi does with one too: it reaches its delta branch by elimination, looks the unknown shape's absent message id up, finds nothing, and returns the state it was given.

func (*TranscriptState) ApplySnapshot added in v0.84.17

func (s *TranscriptState) ApplySnapshot(snapshot *protocol.SessionSnapshot) *TranscriptState

ApplySnapshot folds in a newer authoritative snapshot, discarding every projected delta. It is pi's applyTranscriptSnapshot.

A lower revision for the same session is stale and ignored. A lower revision for a *different* session id is not: revisions count from the attachment, so switching sessions legitimately moves the counter backwards.

func (*TranscriptState) Snapshot added in v0.84.17

func (s *TranscriptState) Snapshot() *protocol.SessionSnapshot

Snapshot is the authoritative snapshot this projection is built on. The result must be treated as read-only: it is the projection's own copy, shared rather than cloned per call, exactly as pi shares its frozen record.

func (*TranscriptState) Transcript added in v0.84.17

func (s *TranscriptState) Transcript() []protocol.TranscriptItem

Transcript renders the projection: the snapshot's transcript with any projected replacements applied, then the items progress introduced that the snapshot does not know about yet, then the steering messages the server has accepted but not yet folded into a turn. It is pi's selectTranscript.

type TruncationResult

type TruncationResult struct {
	Content               string `json:"content"`
	Truncated             bool   `json:"truncated"`
	TruncatedBy           string `json:"truncatedBy"` // "lines" | "bytes" | "" (pi: null)
	TotalLines            int    `json:"totalLines"`
	TotalBytes            int    `json:"totalBytes"`
	OutputLines           int    `json:"outputLines"`
	OutputBytes           int    `json:"outputBytes"`
	LastLinePartial       bool   `json:"lastLinePartial"`
	FirstLineExceedsLimit bool   `json:"firstLineExceedsLimit"`
	MaxLines              int    `json:"maxLines"`
	MaxBytes              int    `json:"maxBytes"`
}

TruncationResult describes the outcome of a truncation operation. The JSON field names match pi's TruncationResult shape (truncate.ts) so it can be embedded in tool details payloads.

func TruncateHead

func TruncateHead(content string, maxLines, maxBytes int) TruncationResult

TruncateHead keeps the first N lines/bytes (for file reads).

func TruncateTail

func TruncateTail(content string, maxLines, maxBytes int) TruncationResult

TruncateTail keeps the last N lines/bytes (for command output).

type WriteOperations added in v0.84.20

type WriteOperations struct {
	WriteFile func(ctx context.Context, absolutePath, content string) error
	// Mkdir creates a directory and its parents.
	Mkdir func(ctx context.Context, dir string) error
}

WriteOperations are the file operations the write tool performs.

func DefaultWriteOperations added in v0.84.20

func DefaultWriteOperations() WriteOperations

DefaultWriteOperations writes the local filesystem.

func EnvWriteOperations added in v0.84.20

func EnvWriteOperations(env ExecutionEnv) WriteOperations

EnvWriteOperations backs the write tool with an ExecutionEnv. Mkdir is a no-op because ExecutionEnv.WriteFile already creates parent directories, which is pi's own contract for it ("creating parent directories when supported").

Jump to

Keyboard shortcuts

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