refactor(detekt): extract magic-number constants, wrap long lines, drop legit suppressions
Mechanical detekt cleanup across apps/core/infrastructure (behavior-preserving): - MagicNumber 62->10: named constants + removed 'legit' MagicNumber suppressions (Tier level from ordinal, ReplayInferenceProvider CHARS_PER_TOKEN, ConfigLoader DEFAULT_* consts, capability scores, HTTP ranges, column widths, etc.) - MaxLineLength cut to ~0 in production modules (line wraps, no logic change) - Dropped one dead parameter (ConfigLoader.parseArray lineNum) Structural findings (ReturnCount/LongMethod/Complexity/LargeClass) left for a deliberate decomposition pass. Full build + detekt gate green.
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
# 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<string, unknown>,
|
||||
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`
|
||||
|
||||
```
|
||||
<stdout/stderr content>
|
||||
|
||||
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>/path/to/file.ts</path>
|
||||
<type>file</type>
|
||||
<content>
|
||||
1: line one
|
||||
2: line two
|
||||
3: line three
|
||||
...
|
||||
</content>
|
||||
(Showing lines 1-50 of 200. Use offset=51 to continue.)
|
||||
```
|
||||
|
||||
If a context file (marked as `context` in config):
|
||||
|
||||
```
|
||||
<system-reminder>
|
||||
...
|
||||
</system-reminder>
|
||||
```
|
||||
|
||||
### 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_<id>` 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": "<formatted text>"}
|
||||
```
|
||||
|
||||
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: "<formatted>"},
|
||||
{inlineData: {mimeType, data: base64}}
|
||||
]}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary Table
|
||||
|
||||
| Tool | What Model Sees |
|
||||
|------|----------------|
|
||||
| **bash** | stdout/stderr + `Command exited with code N` |
|
||||
| **read** (text) | XML-tagged `<path><type><content>` 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: ..." |
|
||||
Reference in New Issue
Block a user