opencode-cmd-provider 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.2 - 2026-08-15
4
+
5
+ Fix: tool-call history round-trip and reasoning effort. Live probes against the Command Code API with opencode confirmed the AI SDK v3 shapes differ from what the converters expected:
6
+
7
+ - Assistant tool-call parts carry arguments under `input`, not `args`/`arguments` — the converter read the legacy fields, so every prior tool call was re-sent with empty `{}` arguments, corrupting multi-turn context.
8
+ - Tool-result outputs use the v3 `{ type: "text" | "error-text" | "json" | "content", ... }` shapes — `resultText` stringified the wrapper, double-encoding results in history. Replaced with `unwrapToolResult`.
9
+ - `reasoning_effort` was silently dropped: opencode passes `providerOptions` namespaced per provider (`{ commandcode: { reasoningEffort } }`), but the model read the top-level keys. Added `resolveProviderReasoning` (namespaced + top-level fallback) so the configured thinking level actually reaches the API.
10
+
11
+ Also: remove duplicated README disclaimer; bump `@ai-sdk/provider` to 4.x and `@types/node` to 26.x.
12
+
3
13
  ## 0.1.1 - 2026-08-15
4
14
 
5
15
  Fix: emit `text-start`/`text-end` and `reasoning-start`/`reasoning-end` stream parts. The live Command Code API sends start/end events with ids; the AI SDK's streamText consumer requires them before deltas, so reasoning-capable models (e.g. `deepseek/deepseek-v4-flash`) failed with "reasoning part <id> not found".
package/README.md CHANGED
@@ -7,8 +7,6 @@ A provider and plugin for [opencode](https://opencode.ai) that connects to the [
7
7
 
8
8
  > **Disclaimer:** This is an unofficial, community-maintained integration. It is not affiliated with, endorsed by, or supported by Command Code. You need your own Command Code account and API key or subscription. Command Code's terms, availability, and pricing apply.
9
9
 
10
- > **Disclaimer:** This is an unofficial, community-maintained integration. It is not affiliated with, endorsed by, or supported by Command Code. You need your own Command Code account and API key or subscription. Command Code's terms, availability, and pricing apply.
11
-
12
10
  ## Install
13
11
 
14
12
  Add the package to your opencode configuration:
@@ -11,7 +11,7 @@ import { parseStreamEventLine, ccEventToStreamPart } from "./stream.js";
11
11
  import { redactCommandCodeErrorText, commandCodeErrorMessage } from "./redact.js";
12
12
  import { calculateCommandCodeCost } from "./cost.js";
13
13
  import { ZERO_MODEL_COST, MODEL_COSTS } from "./pricing.js";
14
- import { mappedReasoningEffort, thinkingMetadataForModel, isReasoningModel } from "./reasoning.js";
14
+ import { mappedReasoningEffort, resolveProviderReasoning, thinkingMetadataForModel, isReasoningModel, } from "./reasoning.js";
15
15
  import { modelSupportsImageInput } from "./modalities.js";
16
16
  import { isRetryableStatus, retryDelayMs, raceAbort, abortError, timeoutError, delay, } from "./retry.js";
17
17
  import { projectSlugFromPath } from "./project-slug.js";
@@ -138,8 +138,7 @@ export class CommandCodeLanguageModel {
138
138
  reasoning: isReasoningModel(this.modelId),
139
139
  thinkingLevelMap: thinkingMetadataForModel(this.modelId)?.thinkingLevelMap,
140
140
  }, {
141
- reasoning: options.providerOptions?.reasoning ??
142
- options.providerOptions?.reasoningEffort,
141
+ reasoning: resolveProviderReasoning(options.providerOptions, "commandcode"),
143
142
  });
144
143
  const maxTokens = Math.min(options.maxOutputTokens ?? DEFAULT_GENERATE_MAX_TOKENS, DEFAULT_GENERATE_MAX_TOKENS);
145
144
  const allowImages = modelSupportsImageInput(this.modelId);
@@ -140,21 +140,41 @@ function completeToolCallIds(messages) {
140
140
  }
141
141
  return new Set([...callIds].filter((id) => resultIds.has(id)));
142
142
  }
143
- function resultText(result) {
143
+ function unwrapToolResult(result) {
144
144
  if (typeof result === "string")
145
145
  return result;
146
146
  if (Array.isArray(result))
147
- return result.map(resultText).filter(Boolean).join("\n");
148
- if (result && typeof result === "object") {
149
- const text = result.text;
150
- if (typeof text === "string")
147
+ return result.map(unwrapToolResult).filter(Boolean).join("\n");
148
+ if (!isRecord(result))
149
+ return String(result ?? "");
150
+ switch (result.type) {
151
+ case "text":
152
+ case "error-text":
153
+ return stringValue(result.value) ?? "";
154
+ case "json":
155
+ case "error-json":
156
+ return JSON.stringify(result.value);
157
+ case "execution-denied":
158
+ return stringValue(result.reason) ?? "Tool execution denied.";
159
+ case "content": {
160
+ const text = Array.isArray(result.value)
161
+ ? result.value
162
+ .map((entry) => isRecord(entry) && entry.type === "text" ? (stringValue(entry.text) ?? "") : "")
163
+ .filter(Boolean)
164
+ .join("\n")
165
+ : "";
151
166
  return text;
152
- const content = result.content;
153
- if (content !== undefined)
154
- return resultText(content);
155
- return JSON.stringify(result);
167
+ }
168
+ default: {
169
+ const text = stringValue(result.text);
170
+ if (text !== undefined)
171
+ return text;
172
+ const content = result.content;
173
+ if (content !== undefined)
174
+ return unwrapToolResult(content);
175
+ return JSON.stringify(result);
176
+ }
156
177
  }
157
- return String(result ?? "");
158
178
  }
159
179
  export function messagesToCC(messages, options = {}) {
160
180
  const allowImages = options.allowImages ?? false;
@@ -183,7 +203,7 @@ export function messagesToCC(messages, options = {}) {
183
203
  type: "tool-call",
184
204
  toolCallId,
185
205
  toolName: stringValue(content.toolName) ?? "",
186
- input: recordOrEmpty(content.args ?? content.arguments),
206
+ input: recordOrEmpty(content.input ?? content.args ?? content.arguments),
187
207
  });
188
208
  }
189
209
  }
@@ -203,8 +223,8 @@ export function messagesToCC(messages, options = {}) {
203
223
  toolCallId,
204
224
  toolName: stringValue(content.toolName) ?? "",
205
225
  output: content.isError
206
- ? { type: "error-text", value: resultText(content.result ?? content.output) }
207
- : { type: "text", value: resultText(content.result ?? content.output) },
226
+ ? { type: "error-text", value: unwrapToolResult(content.result ?? content.output) }
227
+ : { type: "text", value: unwrapToolResult(content.result ?? content.output) },
208
228
  },
209
229
  ],
210
230
  });
@@ -26,4 +26,13 @@ export declare function mappedReasoningEffort(model: {
26
26
  }, options?: {
27
27
  reasoning?: string;
28
28
  }): string | undefined;
29
+ /**
30
+ * Resolves the user's requested reasoning effort from AI SDK v3 call
31
+ * providerOptions. The v3 shape namespaces options per provider
32
+ * (`{ commandcode: { reasoningEffort } }`), but some runtimes pass the
33
+ * effort at the top level — accept both. Without this, opencode's
34
+ * `reasoningEffort` agent setting is silently dropped and the API falls
35
+ * back to its default reasoning depth.
36
+ */
37
+ export declare function resolveProviderReasoning(providerOptions: unknown, providerId: string): string | undefined;
29
38
  export {};
@@ -72,3 +72,29 @@ export function mappedReasoningEffort(model, options) {
72
72
  const mapped = model.thinkingLevelMap?.[level];
73
73
  return typeof mapped === "string" && mapped !== "off" ? mapped : undefined;
74
74
  }
75
+ function isRecord(value) {
76
+ return typeof value === "object" && value !== null && !Array.isArray(value);
77
+ }
78
+ function stringValue(value) {
79
+ return typeof value === "string" ? value : undefined;
80
+ }
81
+ /**
82
+ * Resolves the user's requested reasoning effort from AI SDK v3 call
83
+ * providerOptions. The v3 shape namespaces options per provider
84
+ * (`{ commandcode: { reasoningEffort } }`), but some runtimes pass the
85
+ * effort at the top level — accept both. Without this, opencode's
86
+ * `reasoningEffort` agent setting is silently dropped and the API falls
87
+ * back to its default reasoning depth.
88
+ */
89
+ export function resolveProviderReasoning(providerOptions, providerId) {
90
+ if (!isRecord(providerOptions))
91
+ return undefined;
92
+ const topLevel = stringValue(providerOptions.reasoning) ?? stringValue(providerOptions.reasoningEffort);
93
+ if (topLevel)
94
+ return topLevel;
95
+ const namespaced = providerOptions[providerId];
96
+ if (isRecord(namespaced)) {
97
+ return stringValue(namespaced.reasoning) ?? stringValue(namespaced.reasoningEffort);
98
+ }
99
+ return undefined;
100
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-cmd-provider",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Command Code provider + plugin for opencode",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -38,11 +38,11 @@
38
38
  "url": "git+https://github.com/rashidrazak/opencode-cmd-provider.git"
39
39
  },
40
40
  "dependencies": {
41
- "@ai-sdk/provider": "^3.0.8"
41
+ "@ai-sdk/provider": "^4.0.7"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@opencode-ai/plugin": "*",
45
- "@types/node": "^25.9.5",
45
+ "@types/node": "^26.2.0",
46
46
  "prettier": "^3.9.6",
47
47
  "tsx": "^4.23.12",
48
48
  "typescript": "^7.0.2"