From d886242c51fb9427ae98e382f1702c1d8514453e Mon Sep 17 00:00:00 2001 From: George Larson Date: Mon, 24 Nov 2025 18:42:35 -0500 Subject: [PATCH 1/4] fix(write): support array content from Venice.ai models Some AI models (e.g., GLM 4.6, Qwen 3 Coder 480B) send tool call content as arrays instead of strings. This causes the write tool to fail with: 'Invalid input: expected string, received array' This fix uses Zod's preprocess function to handle both formats: - String content: passed through unchanged - Array content: joined with newlines - Other types: converted to string This maintains backward compatibility while enabling support for Venice.ai and similar models that use array-based content formatting. Related: https://github.com/charmbracelet/crush/pull/1508 --- packages/opencode/src/tool/write.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/tool/write.ts b/packages/opencode/src/tool/write.ts index 7b109261e..1593343f9 100644 --- a/packages/opencode/src/tool/write.ts +++ b/packages/opencode/src/tool/write.ts @@ -14,7 +14,19 @@ import { Agent } from "../agent/agent" export const WriteTool = Tool.define("write", { description: DESCRIPTION, parameters: z.object({ - content: z.string().describe("The content to write to the file"), + content: z.preprocess( + (val) => { + if (typeof val === 'string') { + return val + } + if (Array.isArray(val)) { + // Join array elements with newlines + return val.map(item => String(item)).join('\n') + } + return String(val) + }, + z.string() + ).describe("The content to write to the file"), filePath: z.string().describe("The absolute path to the file to write (must be absolute, not relative)"), }), async execute(params, ctx) { From f362aff497047b55c09309991063c1e7ee592775 Mon Sep 17 00:00:00 2001 From: george larson Date: Mon, 24 Nov 2025 18:48:38 -0500 Subject: [PATCH 2/4] Update packages/opencode/src/tool/write.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/opencode/src/tool/write.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/tool/write.ts b/packages/opencode/src/tool/write.ts index 1593343f9..6ceaf029b 100644 --- a/packages/opencode/src/tool/write.ts +++ b/packages/opencode/src/tool/write.ts @@ -16,7 +16,7 @@ export const WriteTool = Tool.define("write", { parameters: z.object({ content: z.preprocess( (val) => { - if (typeof val === 'string') { + if (typeof val === "string") { return val } if (Array.isArray(val)) { From e210423d2498c95a59e6e6f1a8b867232d867fce Mon Sep 17 00:00:00 2001 From: george larson Date: Mon, 24 Nov 2025 18:49:20 -0500 Subject: [PATCH 3/4] Update packages/opencode/src/tool/write.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/opencode/src/tool/write.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/tool/write.ts b/packages/opencode/src/tool/write.ts index 6ceaf029b..723eea3c3 100644 --- a/packages/opencode/src/tool/write.ts +++ b/packages/opencode/src/tool/write.ts @@ -21,7 +21,12 @@ export const WriteTool = Tool.define("write", { } if (Array.isArray(val)) { // Join array elements with newlines - return val.map(item => String(item)).join('\n') + // Handle edge cases: null/undefined -> "", objects -> JSON.stringify, others -> String + return val.map(item => { + if (item === null || item === undefined) return ""; + if (typeof item === "object") return JSON.stringify(item); + return String(item); + }).join('\n') } return String(val) }, From e782208840546208fda2844d3f087fff6c285b5a Mon Sep 17 00:00:00 2001 From: George Larson Date: Thu, 27 Nov 2025 22:14:57 +0000 Subject: [PATCH 4/4] style: run prettier on write.ts --- packages/opencode/src/tool/write.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/tool/write.ts b/packages/opencode/src/tool/write.ts index 723eea3c3..23c841d98 100644 --- a/packages/opencode/src/tool/write.ts +++ b/packages/opencode/src/tool/write.ts @@ -14,24 +14,25 @@ import { Agent } from "../agent/agent" export const WriteTool = Tool.define("write", { description: DESCRIPTION, parameters: z.object({ - content: z.preprocess( - (val) => { + content: z + .preprocess((val) => { if (typeof val === "string") { return val } if (Array.isArray(val)) { // Join array elements with newlines // Handle edge cases: null/undefined -> "", objects -> JSON.stringify, others -> String - return val.map(item => { - if (item === null || item === undefined) return ""; - if (typeof item === "object") return JSON.stringify(item); - return String(item); - }).join('\n') + return val + .map((item) => { + if (item === null || item === undefined) return "" + if (typeof item === "object") return JSON.stringify(item) + return String(item) + }) + .join("\n") } return String(val) - }, - z.string() - ).describe("The content to write to the file"), + }, z.string()) + .describe("The content to write to the file"), filePath: z.string().describe("The absolute path to the file to write (must be absolute, not relative)"), }), async execute(params, ctx) {