From 9d72ff84a33b4893714e729bbae2ace210bcfd47 Mon Sep 17 00:00:00 2001 From: Nathan Balzotti Date: Tue, 14 Apr 2026 01:19:06 -0700 Subject: [PATCH 1/2] fix: add shell:true for Windows .cmd file spawn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Windows, spawn() with a .cmd file path (e.g. claude.cmd) throws EINVAL without shell:true. This causes the plugin to silently fail — the CLI process never starts, and OpenCode receives an empty response. Uses process.platform === "win32" to only enable shell mode on Windows, avoiding unnecessary shell overhead on Unix. Fixes both spawn sites: session-manager (persistent sessions) and claude-code-language-model (doGenerate one-shot calls). --- src/claude-code-language-model.ts | 1 + src/session-manager.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/claude-code-language-model.ts b/src/claude-code-language-model.ts index cc65276..ba0e10b 100644 --- a/src/claude-code-language-model.ts +++ b/src/claude-code-language-model.ts @@ -200,6 +200,7 @@ export class ClaudeCodeLanguageModel implements LanguageModelV2 { const proc = spawn(this.config.cliPath, cliArgs, { cwd, stdio: ["pipe", "pipe", "pipe"], + shell: process.platform === "win32", env: { ...process.env, TERM: "xterm-256color" }, }) diff --git a/src/session-manager.ts b/src/session-manager.ts index cbf0be0..5330aa5 100644 --- a/src/session-manager.ts +++ b/src/session-manager.ts @@ -53,6 +53,7 @@ export function spawnClaudeProcess( const proc = spawn(cliPath, cliArgs, { cwd, stdio: ["pipe", "pipe", "pipe"], + shell: process.platform === "win32", env: { ...process.env, TERM: "xterm-256color" }, }) From d59ea7a1e0543ab5b4f0a3324a4a11de8762b4c0 Mon Sep 17 00:00:00 2001 From: Nathan Balzotti Date: Wed, 15 Apr 2026 08:31:32 -0700 Subject: [PATCH 2/2] fix: mcpConfigPath support, empty text filter, AI SDK usage format - Add mcpConfigPath to config/settings types, pass --mcp-config flag - Filter empty text blocks in message-builder to prevent API errors - Update LanguageModelV2Usage to new nested format (inputTokens.total/cacheRead) - Replace empty user message with "Continue." to avoid cache_control errors Co-Authored-By: Claude Opus 4.6 --- src/claude-code-language-model.ts | 65 +++++++++++++++++-------------- src/index.ts | 1 + src/message-builder.ts | 8 ++-- src/session-manager.ts | 7 +++- src/types.ts | 2 + 5 files changed, 50 insertions(+), 33 deletions(-) diff --git a/src/claude-code-language-model.ts b/src/claude-code-language-model.ts index ba0e10b..3a16fa6 100644 --- a/src/claude-code-language-model.ts +++ b/src/claude-code-language-model.ts @@ -144,10 +144,9 @@ export class ClaudeCodeLanguageModel implements LanguageModelV2 { content: [{ type: "text", text }] as any, finishReason: "stop", usage: { - inputTokens: 0, - outputTokens: 0, - totalTokens: 0, - }, + inputTokens: { total: 0, noCache: 0, cacheRead: undefined, cacheWrite: undefined }, + outputTokens: { total: 0, text: undefined, reasoning: undefined }, + } as unknown as LanguageModelV2Usage, request: { body: { text: "" } }, response: { id: generateId(), @@ -185,6 +184,7 @@ export class ClaudeCodeLanguageModel implements LanguageModelV2 { skipPermissions: this.config.skipPermissions !== false, includeSessionId: false, model: this.modelId, + mcpConfigPath: this.config.mcpConfigPath, }) log.info("doGenerate starting", { @@ -397,14 +397,19 @@ export class ClaudeCodeLanguageModel implements LanguageModelV2 { } as any) } - const usage: LanguageModelV2Usage = { - inputTokens: result.usage?.input_tokens, - outputTokens: result.usage?.output_tokens, - totalTokens: - result.usage?.input_tokens && result.usage?.output_tokens - ? result.usage.input_tokens + result.usage.output_tokens - : undefined, - } + const usage = { + inputTokens: { + total: result.usage?.input_tokens ?? 0, + noCache: undefined, + cacheRead: (result.usage as any)?.cache_read_input_tokens ?? undefined, + cacheWrite: undefined, + }, + outputTokens: { + total: result.usage?.output_tokens ?? 0, + text: undefined, + reasoning: undefined, + }, + } as unknown as LanguageModelV2Usage return { content, @@ -456,10 +461,9 @@ export class ClaudeCodeLanguageModel implements LanguageModelV2 { type: "finish", finishReason: "stop", usage: { - inputTokens: 0, - outputTokens: 0, - totalTokens: 0, - }, + inputTokens: { total: 0, noCache: 0, cacheRead: undefined, cacheWrite: undefined }, + outputTokens: { total: 0, text: undefined, reasoning: undefined }, + } as unknown as LanguageModelV2Usage, providerMetadata: { "claude-code": { synthetic: true, @@ -506,6 +510,7 @@ export class ClaudeCodeLanguageModel implements LanguageModelV2 { sessionKey: sk, skipPermissions, model: this.modelId, + mcpConfigPath: this.config.mcpConfigPath, }) const stream = new ReadableStream({ @@ -999,15 +1004,18 @@ export class ClaudeCodeLanguageModel implements LanguageModelV2 { finishReason: toolCallMap.size > 0 ? "tool-calls" : "stop", usage: { - inputTokens: msg.usage?.input_tokens, - outputTokens: msg.usage?.output_tokens, - totalTokens: - msg.usage?.input_tokens && - msg.usage?.output_tokens - ? msg.usage.input_tokens + - msg.usage.output_tokens - : undefined, - }, + inputTokens: { + total: msg.usage?.input_tokens ?? 0, + noCache: undefined, + cacheRead: (msg.usage as any)?.cache_read_input_tokens ?? undefined, + cacheWrite: undefined, + }, + outputTokens: { + total: msg.usage?.output_tokens ?? 0, + text: undefined, + reasoning: undefined, + }, + } as unknown as LanguageModelV2Usage, providerMetadata: { "claude-code": resultMeta, }, @@ -1042,10 +1050,9 @@ export class ClaudeCodeLanguageModel implements LanguageModelV2 { type: "finish", finishReason: "stop", usage: { - inputTokens: undefined, - outputTokens: undefined, - totalTokens: undefined, - }, + inputTokens: { total: undefined, noCache: undefined, cacheRead: undefined, cacheWrite: undefined }, + outputTokens: { total: undefined, text: undefined, reasoning: undefined }, + } as unknown as LanguageModelV2Usage, providerMetadata: { "claude-code": resultMeta, }, diff --git a/src/index.ts b/src/index.ts index 8e74f47..0c8442a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,6 +21,7 @@ export function createClaudeCode( cliPath, cwd, skipPermissions: settings.skipPermissions ?? true, + mcpConfigPath: settings.mcpConfigPath, }) } diff --git a/src/message-builder.ts b/src/message-builder.ts index aaae2f0..a5d30ba 100644 --- a/src/message-builder.ts +++ b/src/message-builder.ts @@ -101,10 +101,13 @@ Now continuing with the current message: for (const msg of messages) { if (msg.role === "user") { if (typeof msg.content === "string") { - content.push({ type: "text", text: msg.content }) + if (msg.content.trim()) { + content.push({ type: "text", text: msg.content }) + } } else if (Array.isArray(msg.content)) { for (const part of msg.content as any[]) { if (part.type === "text") { + if (!part.text || !part.text.trim()) continue content.push({ type: "text", text: part.text }) } else if (part.type === "tool-result") { const p = part as any @@ -136,11 +139,10 @@ Now continuing with the current message: type: "user", message: { role: "user", - content: [{ type: "text", text: "" }], + content: [{ type: "text", text: "Continue." }], }, }) } - return JSON.stringify({ type: "user", message: { diff --git a/src/session-manager.ts b/src/session-manager.ts index 5330aa5..a68184e 100644 --- a/src/session-manager.ts +++ b/src/session-manager.ts @@ -108,8 +108,9 @@ export function buildCliArgs(opts: { skipPermissions: boolean includeSessionId?: boolean model?: string + mcpConfigPath?: string }): string[] { - const { sessionKey, skipPermissions, includeSessionId = true, model } = opts + const { sessionKey, skipPermissions, includeSessionId = true, model, mcpConfigPath } = opts const args = [ "--output-format", "stream-json", @@ -133,6 +134,10 @@ export function buildCliArgs(opts: { args.push("--dangerously-skip-permissions") } + if (mcpConfigPath) { + args.push("--mcp-config", mcpConfigPath) + } + return args } diff --git a/src/types.ts b/src/types.ts index 89ab498..9c3fb06 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,6 +3,7 @@ export interface ClaudeCodeConfig { cliPath: string cwd?: string skipPermissions?: boolean + mcpConfigPath?: string } export interface ClaudeCodeProviderSettings { @@ -10,6 +11,7 @@ export interface ClaudeCodeProviderSettings { cwd?: string name?: string skipPermissions?: boolean + mcpConfigPath?: string } /**