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:
2026-07-12 23:12:28 +04:00
parent 135a34eb2f
commit aee2e67c66
70 changed files with 1034 additions and 236 deletions
+254
View File
@@ -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: ..." |