refactor(opencode): simplify tool output disable config
This commit is contained in:
@@ -49,13 +49,7 @@ const log = Log.create({ service: "config" })
|
|||||||
// Custom merge function that concatenates array fields instead of replacing them
|
// Custom merge function that concatenates array fields instead of replacing them
|
||||||
// Keep remeda's deep conditional merge type out of hot config-loading paths; TS profiling showed it dominates here.
|
// Keep remeda's deep conditional merge type out of hot config-loading paths; TS profiling showed it dominates here.
|
||||||
function mergeConfig(target: Info, source: Info): Info {
|
function mergeConfig(target: Info, source: Info): Info {
|
||||||
const merged = mergeDeep(target, source) as Info
|
return mergeDeep(target, source) as Info
|
||||||
if (!target.tool_output || !source.tool_output) return merged
|
|
||||||
if (target.tool_output.truncate !== false && source.tool_output.truncate !== false) return merged
|
|
||||||
|
|
||||||
// Disabled truncation and custom limits are separate config modes; the later layer selects the mode.
|
|
||||||
merged.tool_output = source.tool_output
|
|
||||||
return merged
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeConfigConcatArrays(target: Info, source: Info): Info {
|
function mergeConfigConcatArrays(target: Info, source: Info): Info {
|
||||||
@@ -260,13 +254,8 @@ export const Info = Schema.Struct({
|
|||||||
),
|
),
|
||||||
tool_output: Schema.optional(
|
tool_output: Schema.optional(
|
||||||
Schema.Union([
|
Schema.Union([
|
||||||
|
Schema.Literal(false).annotate({ description: "Disable tool output truncation" }),
|
||||||
Schema.Struct({
|
Schema.Struct({
|
||||||
truncate: Schema.Literal(false).annotate({ description: "Disable tool output truncation" }),
|
|
||||||
}).annotate({ parseOptions: { onExcessProperty: "error" } }),
|
|
||||||
Schema.Struct({
|
|
||||||
truncate: Schema.optional(Schema.Literal(true)).annotate({
|
|
||||||
description: "Enable truncating tool output that exceeds the configured limits (default: true)",
|
|
||||||
}),
|
|
||||||
max_lines: Schema.optional(PositiveInt).annotate({
|
max_lines: Schema.optional(PositiveInt).annotate({
|
||||||
description: "Maximum lines of tool output before it is truncated and saved to disk (default: 2000)",
|
description: "Maximum lines of tool output before it is truncated and saved to disk (default: 2000)",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export interface Interface {
|
|||||||
readonly output: (text: string, options?: Options, agent?: Agent.Info) => Effect.Effect<Result>
|
readonly output: (text: string, options?: Options, agent?: Agent.Info) => Effect.Effect<Result>
|
||||||
/**
|
/**
|
||||||
* Resolved truncation limits from `tool_output` in opencode config.
|
* Resolved truncation limits from `tool_output` in opencode config.
|
||||||
* Returns `None` when the user has disabled truncation (`tool_output.truncate: false`),
|
* Returns `None` when the user has disabled truncation (`tool_output: false`),
|
||||||
* in which case callers should pass output through without enforcing thresholds.
|
* in which case callers should pass output through without enforcing thresholds.
|
||||||
*/
|
*/
|
||||||
readonly limits: () => Effect.Effect<Option.Option<Limits>>
|
readonly limits: () => Effect.Effect<Option.Option<Limits>>
|
||||||
@@ -81,7 +81,7 @@ export const layer = Layer.effect(
|
|||||||
if (Option.isNone(configSvc)) return Option.some({ maxLines: MAX_LINES, maxBytes: MAX_BYTES })
|
if (Option.isNone(configSvc)) return Option.some({ maxLines: MAX_LINES, maxBytes: MAX_BYTES })
|
||||||
const cfg = yield* configSvc.value.get().pipe(Effect.catch(() => Effect.succeed(undefined)))
|
const cfg = yield* configSvc.value.get().pipe(Effect.catch(() => Effect.succeed(undefined)))
|
||||||
const tool_output = cfg?.tool_output
|
const tool_output = cfg?.tool_output
|
||||||
if (tool_output?.truncate === false) return Option.none<Limits>()
|
if (tool_output === false) return Option.none<Limits>()
|
||||||
return Option.some({
|
return Option.some({
|
||||||
maxLines: tool_output?.max_lines ?? MAX_LINES,
|
maxLines: tool_output?.max_lines ?? MAX_LINES,
|
||||||
maxBytes: tool_output?.max_bytes ?? MAX_BYTES,
|
maxBytes: tool_output?.max_bytes ?? MAX_BYTES,
|
||||||
|
|||||||
@@ -1292,25 +1292,20 @@ test("config parser preserves permission order while rejecting unknown top-level
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test("tool_output only accepts thresholds when truncation is enabled", () => {
|
test("tool_output accepts thresholds or disables truncation", () => {
|
||||||
expect(ConfigParse.schema(Config.Info, { tool_output: { truncate: false } }, "test").tool_output).toEqual({
|
expect(ConfigParse.schema(Config.Info, { tool_output: false }, "test").tool_output).toBe(false)
|
||||||
truncate: false,
|
|
||||||
})
|
|
||||||
expect(
|
expect(
|
||||||
ConfigParse.schema(Config.Info, { tool_output: { max_lines: 200, max_bytes: 8192 } }, "test").tool_output,
|
ConfigParse.schema(Config.Info, { tool_output: { max_lines: 200, max_bytes: 8192 } }, "test").tool_output,
|
||||||
).toEqual({
|
).toEqual({
|
||||||
max_lines: 200,
|
max_lines: 200,
|
||||||
max_bytes: 8192,
|
max_bytes: 8192,
|
||||||
})
|
})
|
||||||
expect(() =>
|
|
||||||
ConfigParse.schema(Config.Info, { tool_output: { truncate: false, max_lines: 200, max_bytes: 8192 } }, "test"),
|
|
||||||
).toThrow()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it.effect("project tool_output limits replace disabled global truncation", () =>
|
it.effect("project tool_output limits replace disabled global truncation", () =>
|
||||||
withConfigTree(
|
withConfigTree(
|
||||||
{
|
{
|
||||||
global: { tool_output: { truncate: false } },
|
global: { tool_output: false },
|
||||||
project: { tool_output: { max_lines: 200 } },
|
project: { tool_output: { max_lines: 200 } },
|
||||||
},
|
},
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
@@ -1323,10 +1318,10 @@ it.effect("project disabled tool_output replaces global limits", () =>
|
|||||||
withConfigTree(
|
withConfigTree(
|
||||||
{
|
{
|
||||||
global: { tool_output: { max_lines: 200, max_bytes: 8192 } },
|
global: { tool_output: { max_lines: 200, max_bytes: 8192 } },
|
||||||
project: { tool_output: { truncate: false } },
|
project: { tool_output: false },
|
||||||
},
|
},
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
expect((yield* Config.use.get()).tool_output).toEqual({ truncate: false })
|
expect((yield* Config.use.get()).tool_output).toBe(false)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1207,7 +1207,7 @@ describe("tool.shell truncation", () => {
|
|||||||
|
|
||||||
it.live("does not truncate output when tool_output is disabled", () =>
|
it.live("does not truncate output when tool_output is disabled", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const tmp = yield* tmpdirScoped({ config: { tool_output: { truncate: false } } })
|
const tmp = yield* tmpdirScoped({ config: { tool_output: false } })
|
||||||
yield* runIn(
|
yield* runIn(
|
||||||
tmp,
|
tmp,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ describe("Truncate", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const disabledIt = configuredIt({ tool_output: { truncate: false } })
|
const disabledIt = configuredIt({ tool_output: false })
|
||||||
disabledIt.live("does not truncate output when disabled", () =>
|
disabledIt.live("does not truncate output when disabled", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const content = "a".repeat(Truncate.MAX_BYTES + 1)
|
const content = "a".repeat(Truncate.MAX_BYTES + 1)
|
||||||
|
|||||||
@@ -1298,14 +1298,8 @@ export type Config = {
|
|||||||
* Configure tool output truncation. When output exceeds either limit, the full text is written to the truncation directory and a preview is returned.
|
* Configure tool output truncation. When output exceeds either limit, the full text is written to the truncation directory and a preview is returned.
|
||||||
*/
|
*/
|
||||||
tool_output?:
|
tool_output?:
|
||||||
|
| false
|
||||||
| {
|
| {
|
||||||
/**
|
|
||||||
* Disable tool output truncation
|
|
||||||
*/
|
|
||||||
truncate: false
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
truncate?: true
|
|
||||||
max_lines?: number
|
max_lines?: number
|
||||||
max_bytes?: number
|
max_bytes?: number
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -361,32 +361,26 @@ You can control when tool output is truncated using the `tool_output` option. Wh
|
|||||||
{
|
{
|
||||||
"$schema": "https://opencode.ai/config.json",
|
"$schema": "https://opencode.ai/config.json",
|
||||||
"tool_output": {
|
"tool_output": {
|
||||||
"truncate": true,
|
|
||||||
"max_lines": 2000,
|
"max_lines": 2000,
|
||||||
"max_bytes": 51200
|
"max_bytes": 51200
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- `truncate` - Truncate tool output when it exceeds a configured threshold (default: `true`).
|
|
||||||
- `max_lines` - Maximum number of lines before output is truncated (default: `2000`).
|
- `max_lines` - Maximum number of lines before output is truncated (default: `2000`).
|
||||||
- `max_bytes` - Maximum size in bytes before output is truncated (default: `51200`).
|
- `max_bytes` - Maximum size in bytes before output is truncated (default: `51200`).
|
||||||
|
|
||||||
These thresholds apply to output handled by OpenCode's shared truncation layer, including MCP and plugin tool output. Individual tools that page or cap their own results can have separate limits.
|
These thresholds apply to output handled by OpenCode's shared truncation layer, including MCP and plugin tool output. Individual tools that page or cap their own results can have separate limits.
|
||||||
|
|
||||||
To disable shared tool output truncation, set `truncate` to `false`:
|
To disable shared tool output truncation, set `tool_output` to `false`:
|
||||||
|
|
||||||
```json title="opencode.json"
|
```json title="opencode.json"
|
||||||
{
|
{
|
||||||
"$schema": "https://opencode.ai/config.json",
|
"$schema": "https://opencode.ai/config.json",
|
||||||
"tool_output": {
|
"tool_output": false
|
||||||
"truncate": false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`truncate: false` cannot be combined with `max_lines` or `max_bytes`; thresholds only apply when truncation is enabled.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Models
|
### Models
|
||||||
|
|||||||
Reference in New Issue
Block a user