Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/client/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
21 changes: 20 additions & 1 deletion src/hooks/resilience.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [] };
}
Expand Down Expand Up @@ -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 };
}
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
23 changes: 23 additions & 0 deletions tests/sanitizer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
Loading