diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 3104468abb56..c4011e6ef656 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -179,6 +179,34 @@ function normalizeMessages( .filter((msg): msg is ModelMessage => msg !== undefined && msg.content !== "") } + // Bedrock Z.ai GLM: coerce any string toolUse.input back into a JSON + // object. When we replay the history, GLM streams tool-call args as + // partial strings; if a prior assistant turn stored a not-yet-parsed + // string, Bedrock Converse rejects the next turn with + // "The format of the value at messages.N.content.K.toolUse.input + // is invalid. Provide a json object for the field and try again." + if ( + model.api.npm === "@ai-sdk/amazon-bedrock" && + (model.api.id.toLowerCase().includes("glm") || + model.api.id.toLowerCase().includes("zai_glm")) + ) { + msgs = msgs.map((msg) => { + if (msg.role !== "assistant" || !Array.isArray(msg.content)) return msg + return { + ...msg, + content: msg.content.map((part: any) => { + if (part?.type !== "tool-call") return part + if (part.input == null) return { ...part, input: {} } + try { + return { ...part, input: JSON.parse(part.input || "{}") } + } catch { + return { ...part, input: {} } + } + }), + } + }) + } + if (model.api.id.includes("claude")) { const scrub = (id: string) => id.replace(/[^a-zA-Z0-9_-]/g, "_") msgs = msgs.map((msg) => { @@ -494,6 +522,7 @@ export function temperature(model: Provider.Model) { export function topP(model: Provider.Model) { const id = model.id.toLowerCase() if (id.includes("qwen")) return 1 + if (id.includes("glm") || id.includes("zai_glm")) return 0.95 if (["minimax-m2", "gemini", "kimi-k2.5", "kimi-k2p5", "kimi-k2-5"].some((s) => id.includes(s))) { return 0.95 } @@ -627,7 +656,7 @@ export function variants(model: Provider.Model): Record [ - effort, - { - reasoningConfig: { - type: "enabled", - maxReasoningEffort: effort, + if (id.includes("nova")) { + return Object.fromEntries( + WIDELY_SUPPORTED_EFFORTS.map((effort) => [ + effort, + { + reasoningConfig: { + type: "enabled", + maxReasoningEffort: effort, + }, }, - }, - ]), - ) + ]), + ) + } + + // GLM models only support a binary thinking toggle (not graduated + // effort levels), so we expose a single "high" variant — following + // the same convention as Anthropic/Nova on Bedrock where selecting + // an effort level enables reasoning. Omitting the variant keeps + // reasoning off. + if (id.includes("glm") && model.api.npm === "@ai-sdk/amazon-bedrock") { + return { + high: { additionalModelRequestFields: { reasoning_config: "high" } }, + } + } + + return {} case "@ai-sdk/google-vertex": // https://v5.ai-sdk.dev/providers/ai-sdk-providers/google-vertex