From aa0f96937e6d36646206204e023b42b528b839f8 Mon Sep 17 00:00:00 2001 From: JoshRob297 <99092299+JoshRob297@users.noreply.github.com> Date: Sun, 25 Oct 2026 20:42:25 -0600 Subject: [PATCH] feat: auto-repair reasoning_content for beam models and bump CLI version to 1.88.0 (v1.0.6) - Support models requiring previous assistant reasoning_content in history (e.g. reflection/beam:free) - Prevent HTTP 400 parameter errors in multi-turn assistant and agent workflows - Update CLI_VERSION to 1.88.0 matching current npm release - Unit tests added, 86 pass / 0 fail --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- src/client/api.ts | 2 +- src/hooks/resilience.ts | 21 ++++++++++++++++++++- src/index.ts | 3 ++- tests/sanitizer.test.ts | 23 +++++++++++++++++++++++ 6 files changed, 55 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00292a5..2a00e3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) - SemVer. ## [Unreleased] +## [1.0.6] - 2026-10-25 + +### Added +- Auto-repair for models requiring historical `reasoning_content` (such as `reflection/beam:free`). Prevents HTTP 400 parameter errors in multi-turn assistant and agent workflows by synthesizing compliant thoughts. + +### Changed +- Updated tracked Command Code CLI version signature (`CLI_VERSION`) from `1.73.4` to `1.88.0`. + ## [1.0.5] - 2026-10-24 ### Changed diff --git a/package.json b/package.json index 89b7953..baf907f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "opencode-commandcode", - "version": "1.0.5", + "version": "1.0.6", "description": "Production-grade OpenCode plugin for Command Code: dynamic model discovery, stability hooks and quota telemetry", "main": "index.ts", "type": "module", diff --git a/src/client/api.ts b/src/client/api.ts index 953a13c..07b5d47 100644 --- a/src/client/api.ts +++ b/src/client/api.ts @@ -18,7 +18,7 @@ import { resolveApiKey, resolveApiKeyWithSource } from "../auth/resolver.ts"; import { resolveStateDir } from "../hooks/resilience.ts"; export const API_BASE = "https://api.commandcode.ai"; -export const CLI_VERSION = "1.73.4"; +export const CLI_VERSION = "1.88.0"; const DEFAULT_TIMEOUT_MS = 10_000; const CATALOG_TTL_MS = 6 * 60 * 60 * 1000; diff --git a/src/hooks/resilience.ts b/src/hooks/resilience.ts index b0196a6..0e04c47 100644 --- a/src/hooks/resilience.ts +++ b/src/hooks/resilience.ts @@ -105,7 +105,7 @@ export interface SanitizeResult { * 4. `null` / empty tool response content -> normalized to "{}". * 5. Assistant tool_calls with no answer in their block -> synthetic response. */ -export function sanitizeCommandCodeMessages(messages: Message[]): SanitizeResult { +export function sanitizeCommandCodeMessages(messages: Message[], targetModel?: string): SanitizeResult { if (!Array.isArray(messages) || messages.length === 0) { return { messages: Array.isArray(messages) ? messages : [], modified: false, fixes: [] }; } @@ -225,6 +225,25 @@ export function sanitizeCommandCodeMessages(messages: Message[]): SanitizeResult } } + // 6. Inject reasoning_content for models requiring reasoning history (e.g. reflection/beam). + const requiresReasoning = + typeof targetModel === "string" && + (targetModel.toLowerCase().includes("beam") || targetModel.toLowerCase().includes("reflection")); + + if (requiresReasoning) { + for (let i = 0; i < finalMessages.length; i++) { + const msg = finalMessages[i]!; + if (msg.role === "assistant" && (!msg.reasoning_content || typeof msg.reasoning_content !== "string")) { + msg.reasoning_content = + typeof msg.content === "string" && msg.content.trim().length > 0 + ? `Thought: ${msg.content.slice(0, 100)}` + : "Thought: Processing step"; + fixes.push(`Injected missing reasoning_content at assistant message ${i} for ${targetModel}`); + modified = true; + } + } + } + void syntheticCounter; return { messages: finalMessages, modified, fixes }; } diff --git a/src/index.ts b/src/index.ts index 1c7c61f..88ff822 100644 --- a/src/index.ts +++ b/src/index.ts @@ -195,7 +195,8 @@ export default { } if (!Array.isArray(json.messages)) return; - const { messages, modified, fixes } = sanitizeCommandCodeMessages(json.messages); + const targetModel = typeof json.model === "string" ? json.model : undefined; + const { messages, modified, fixes } = sanitizeCommandCodeMessages(json.messages, targetModel); if (!modified) return; json.messages = messages; diff --git a/tests/sanitizer.test.ts b/tests/sanitizer.test.ts index 2068118..d9ea149 100644 --- a/tests/sanitizer.test.ts +++ b/tests/sanitizer.test.ts @@ -210,3 +210,26 @@ describe("capExcessiveSleepCommand (unwired helper, kept for a future tool hook) expect(res.command).toBe("sleep 10 && ls"); }); }); + + test("injects synthetic reasoning_content on assistant messages when targetModel is reflection/beam", () => { + const messages = [ + { role: "user", content: "hello" }, + { role: "assistant", content: "I am assistant" }, + { role: "user", content: "ping" }, + ]; + const res = sanitizeCommandCodeMessages(messages as any, "reflection/beam:free"); + expect(res.modified).toBe(true); + expect(res.messages[1]?.reasoning_content).toBe("Thought: I am assistant"); + expect(res.fixes.some((f) => f.includes("Injected missing reasoning_content"))).toBe(true); + }); + + test("does not inject reasoning_content when model is not reasoning-dependent", () => { + const messages = [ + { role: "user", content: "hello" }, + { role: "assistant", content: "I am assistant" }, + { role: "user", content: "ping" }, + ]; + const res = sanitizeCommandCodeMessages(messages as any, "deepseek/deepseek-v4.1-flash"); + expect(res.modified).toBe(false); + expect(res.messages[1]?.reasoning_content).toBeUndefined(); + });