diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index d2bdc5278..6086f0c3d 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -253,17 +253,22 @@ export const Info = Schema.Struct({ }), ), tool_output: Schema.optional( - Schema.Struct({ - truncate: Schema.optional(Schema.Boolean).annotate({ - description: "Enable truncating tool output that exceeds the configured limits (default: true)", + Schema.Union([ + 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({ + description: "Maximum lines of tool output before it is truncated and saved to disk (default: 2000)", + }), + max_bytes: Schema.optional(PositiveInt).annotate({ + description: "Maximum bytes of tool output before it is truncated and saved to disk (default: 51200)", + }), }), - max_lines: Schema.optional(PositiveInt).annotate({ - description: "Maximum lines of tool output before it is truncated and saved to disk (default: 2000)", - }), - max_bytes: Schema.optional(PositiveInt).annotate({ - description: "Maximum bytes of tool output before it is truncated and saved to disk (default: 51200)", - }), - }), + ]), ).annotate({ description: "Configure tool output truncation. When output exceeds either limit, the full text is written to the truncation directory and a preview is returned.", diff --git a/packages/opencode/src/skill/prompt/customize-opencode.md b/packages/opencode/src/skill/prompt/customize-opencode.md index efb963f6a..95e7dc353 100644 --- a/packages/opencode/src/skill/prompt/customize-opencode.md +++ b/packages/opencode/src/skill/prompt/customize-opencode.md @@ -23,6 +23,10 @@ shape before writing config, **fetch that URL and read the schema directly** rather than guessing. opencode hard-fails on invalid config, so the cost of a wrong shape is a broken startup. +The full schema is large. Prefer using JavaScript or Bash to fetch and extract +the relevant property definition instead of reading the entire schema into +context when you only need to check one setting. + Independently, every `opencode.json` should declare `"$schema": "https://opencode.ai/config.json"` so the user's editor catches mistakes as they type. @@ -126,7 +130,7 @@ Every field is optional. "mcp_timeout": 30000 }, - "tool_output": { "truncate": false, "max_lines": 200, "max_bytes": 8192 }, + "tool_output": { "max_lines": 200, "max_bytes": 8192 }, "compaction": { "auto": true, "tail_turns": 15 } } @@ -140,7 +144,6 @@ Shape notes worth being explicit about: - `plugin` is an array of strings or `[name, options]` tuples, not an object. - `mcp[name].command` is an array of strings, never a single string. `type` is required. - `permission` is either a string action or an object keyed by tool name. -- `tool_output.truncate: false` disables shared tool output truncation; use `max_lines` and `max_bytes` to override its thresholds. ## Skills diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 04dcde32e..cd1db63da 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -1455,6 +1455,21 @@ test("config parser preserves permission order while rejecting unknown top-level } }) +test("tool_output only accepts thresholds when truncation is enabled", () => { + expect(ConfigParse.schema(Config.Info, { tool_output: { truncate: false } }, "test").tool_output).toEqual({ + truncate: false, + }) + expect( + ConfigParse.schema(Config.Info, { tool_output: { max_lines: 200, max_bytes: 8192 } }, "test").tool_output, + ).toEqual({ + max_lines: 200, + max_bytes: 8192, + }) + expect(() => + ConfigParse.schema(Config.Info, { tool_output: { truncate: false, max_lines: 200, max_bytes: 8192 } }, "test"), + ).toThrow() +}) + // MCP config merging tests it.instance("project config can override MCP server enabled status", () => diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index f20f4e8a5..1cbfa799a 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1294,11 +1294,21 @@ export type Config = { enterprise?: { url?: string } - tool_output?: { - truncate?: boolean - max_lines?: number - max_bytes?: number - } + /** + * 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?: + | { + /** + * Disable tool output truncation + */ + truncate: false + } + | { + truncate?: true + max_lines?: number + max_bytes?: number + } compaction?: { auto?: boolean prune?: boolean diff --git a/packages/web/src/content/docs/config.mdx b/packages/web/src/content/docs/config.mdx index 0be2699d3..f6927384c 100644 --- a/packages/web/src/content/docs/config.mdx +++ b/packages/web/src/content/docs/config.mdx @@ -385,6 +385,8 @@ To disable shared tool output truncation, set `truncate` to `false`: } ``` +`truncate: false` cannot be combined with `max_lines` or `max_bytes`; thresholds only apply when truncation is enabled. + --- ### Models