diff --git a/bun.lock b/bun.lock index d55db4568d34..3c003226f87b 100644 --- a/bun.lock +++ b/bun.lock @@ -572,6 +572,7 @@ "ignore": "7.0.5", "immer": "11.1.4", "jsonc-parser": "3.3.1", + "jsonrepair": "3.14.0", "mime-types": "3.0.2", "minimatch": "10.0.3", "npm-package-arg": "13.0.2", @@ -3974,6 +3975,8 @@ "jsonparse": ["jsonparse@1.3.1", "", {}, "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="], + "jsonrepair": ["jsonrepair@3.14.0", "", { "bin": { "jsonrepair": "bin/cli.js" } }, "sha512-tWPGKMZf/8UPim+fcW2EfcQ/d/7aKUrP6IECz9G3Tu6Q5dX0orSleqJ9z6sSw7qrQkjF8/Edo4DvsWBZ8H+HNg=="], + "jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="], "just-diff": ["just-diff@6.0.2", "", {}, "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA=="], diff --git a/packages/opencode/package.json b/packages/opencode/package.json index b775b1280d42..36a801c60b44 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -125,6 +125,7 @@ "ignore": "7.0.5", "immer": "11.1.4", "jsonc-parser": "3.3.1", + "jsonrepair": "3.14.0", "mime-types": "3.0.2", "minimatch": "10.0.3", "npm-package-arg": "13.0.2", diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index adacfc431549..b20822365341 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -26,6 +26,7 @@ import { EffectBridge } from "@/effect/bridge" import { RuntimeFlags } from "@/effect/runtime-flags" import * as Option from "effect/Option" import * as OtelTracer from "@effect/opentelemetry/Tracer" +import { jsonrepair } from "jsonrepair" import { LLMAISDK } from "./llm/ai-sdk" import { LLMNativeRuntime } from "./llm/native-runtime" import { LLMRequestPrep } from "./llm/request" @@ -301,6 +302,22 @@ const live: Layer.Layer< toolName: lower, } } + + const toolInput = failed.toolCall.input + if (typeof toolInput === "string" && toolInput.trim().startsWith("{")) { + try { + const repaired = jsonrepair(toolInput) + if (repaired !== toolInput) { + return { + ...failed.toolCall, + input: repaired, + } + } + } catch { + // jsonrepair failed, fall through to invalid tool handler + } + } + return { ...failed.toolCall, input: JSON.stringify({