# Tool Result Schema — What the Model Sees Documenting how opencode formats tool results into the LLM context. Source: github.com/anomalyco/opencode (branch `dev`). --- ## Internal Types (TypeScript `@opencode-ai/schema`) ### `ToolResultValue` — the 4-variant result envelope Defined in `packages/llm/src/schema/messages.ts`: ```typescript ToolResultValue = Union([ { type: "json", value: unknown }, // structured data, no text content { type: "text", value: unknown }, // single text item { type: "error", value: unknown }, // tool error { type: "content", value: ToolContent[] }, // multiple items (text + files) ]) ``` ### `ToolContent` — inner content items Defined in `packages/schema/src/llm.ts`: ```typescript ToolContent = Union([ { type: "text", text: string }, { type: "file", uri: string, mime: string, name?: string }, ]) ``` ### `ToolResultPart` — message part injected into conversation ```typescript ToolResultPart = Struct({ type: "tool-result", id: string, // matches ToolCallPart.id name: string, result: ToolResultValue, providerExecuted?: boolean, cache?: CacheHint, metadata?: Record, providerMetadata?: ProviderMetadata, }) ``` ### Conversion: `ToolOutput` → `ToolResultValue` In `ToolOutput.toResultValue()` (`packages/llm/src/schema/messages.ts`): ``` content.length === 0 → {type: "json", value: output.structured} content.length === 1 && is text → {type: "text", value: text} otherwise → {type: "content", value: output.content} ``` --- ## Per-Tool Formatting Each tool defines `toModelOutput()` which returns `{type: "text" | "file"}[]`. ### Bash / Shell File: `packages/core/src/tool/bash.ts` ``` Command exited with code 0. ``` If truncated by `ToolOutputStore.bound()` (default 2000 lines / 50KB): ``` ... output truncated; full content saved to /path/to/tool-output/tool_abc123 ... [last lines here] Command exited with code 0. ``` If timed out: ``` Command exceeded timeout of 120000 ms. Retry with a larger timeout. ``` ### Read (text — opencode tool) File: `packages/opencode/src/tool/read.ts` ``` /path/to/file.ts file 1: line one 2: line two 3: line three ... (Showing lines 1-50 of 200. Use offset=51 to continue.) ``` If a context file (marked as `context` in config): ``` ... ``` ### Read (image — core tool) File: `packages/core/src/tool/read.ts` ``` Image read successfully ``` Plus a file attachment (base64 data URI with mime type). ### Write File: `packages/core/src/tool/write.ts` ``` Wrote file successfully: /path/to/file ``` or ``` Created file successfully: /path/to/file ``` ### Edit File: `packages/core/src/tool/edit.ts` ``` Edited file successfully: /path/to/file Replacements: 1 ```diff -old line +new line ``` Preview shows first 6 lines of old (prefixed `-`) and new (prefixed `+`). ### Glob File: `packages/core/src/tool/glob.ts` ``` src/file1.ts src/file2.ts src/subdir/file3.ts ``` or `No files found` ### Grep File: `packages/core/src/tool/grep.ts` ``` Found 3 matches src/file.ts: Line 10: matching text Line 20: more matching text src/other.ts: Line 5: another match ``` ### WebFetch File: `packages/core/src/tool/webfetch.ts` Raw fetched content as plain text. ### Question File: `packages/core/src/tool/question.ts` ``` User has answered your questions: "What color?"="Blue". You can now continue with the user's answers in mind. ``` --- ## Truncation (ToolOutputStore.bound) File: `packages/core/src/tool-output-store.ts` Default limits: **2000 lines / 50 KB**. When exceeded: - Full output saved to `tool-output/tool_` in managed storage - In-memory text replaced with head/tail preview (~25 lines / ~25KB each) + marker The marker text between head and tail: ``` ... output truncated; full content saved to /path/to/tool-output/tool_abc123 ... ``` --- ## Protocol Lowering (Wire Format) ### OpenAI Chat (`packages/llm/src/protocols/openai-chat.ts`) Always a flat string: ```json {"role": "tool", "tool_call_id": "...", "content": ""} ``` Images are appended as a separate `{role: "user", content: [{type: "image_url", ...}]}` message. ### Anthropic Messages (`packages/llm/src/protocols/anthropic-messages.ts`) ``` tool_result.content = string // for text/error/json variants [{type: "text", text: ...}, // for "content" variant {type: "image", source: {type: "base64", media_type, data}}] ``` ### Gemini / Vertex (`packages/llm/src/protocols/google-gemini.ts`) ``` tool_result → {role: "user", parts: [ {text: ""}, {inlineData: {mimeType, data: base64}} ]} ``` --- ## Summary Table | Tool | What Model Sees | |------|----------------| | **bash** | stdout/stderr + `Command exited with code N` | | **read** (text) | XML-tagged `` with line numbers | | **read** (image) | "Image read successfully" + base64 file attachment | | **write** | "Wrote/Created file successfully: /path" | | **edit** | "Edited file successfully" + diff block | | **glob** | Newline-separated paths (or "No files found") | | **grep** | "Found N matches" + formatted line listing | | **webfetch** | Raw fetched content | | **question** | "User has answered your questions: ..." |