@mars-sea/dsh-commandcode-provider 0.10.0 → 0.10.1

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/lib/index.js CHANGED
@@ -464,13 +464,21 @@ const KNOWN_EFFORTS = {
464
464
  "low",
465
465
  "medium",
466
466
  "high",
467
- "xhigh"
467
+ "xhigh",
468
+ "max"
468
469
  ],
469
470
  "meta/muse-spark-1.3-contributor": [
470
471
  "low",
471
472
  "medium",
472
473
  "high",
473
474
  "xhigh"
475
+ ],
476
+ "gpt-6-astra": [
477
+ "low",
478
+ "medium",
479
+ "high",
480
+ "xhigh",
481
+ "max"
474
482
  ]
475
483
  };
476
484
  /**
@@ -519,12 +527,12 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
519
527
  "gpt-5.6-luna",
520
528
  "gpt-5.6-sol",
521
529
  "gpt-5.6-terra",
530
+ "gpt-6-astra",
522
531
  "meta/muse-spark-1.1",
523
532
  "meta/muse-spark-1.2",
524
533
  "meta/muse-spark-1.2-contributor",
525
534
  "meta/muse-spark-1.3",
526
535
  "meta/muse-spark-1.3-contributor",
527
- "minimax/minimax-m3-free",
528
536
  "moonshotai/Kimi-K2.5",
529
537
  "moonshotai/Kimi-K2.6",
530
538
  "moonshotai/Kimi-K2.7-Code",
@@ -540,7 +548,7 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
540
548
  "z-ai/glm-5.3-flash"
541
549
  ]);
542
550
  /**
543
- * Models the official CLI's model table (command-code@1.46.0) marks
551
+ * Models the official CLI's model table (command-code@1.50.0) marks
544
552
  * `reasoning:!0` but defines no selectable `reasoning_effort` levels — they
545
553
  * think automatically, with Command Code driving the depth. This is the
546
554
  * authoritative "thinks, effort not adjustable" set: `KNOWN_EFFORTS` (which
@@ -548,7 +556,7 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
548
556
  * effort levels, and this snapshot is not surfaced in the picker's compact
549
557
  * description — it exists for programmatic consumers.
550
558
  *
551
- * Source: the command-code@1.46.0 bundled model table (dist/cli.mjs),
559
+ * Source: the command-code@1.50.0 bundled model table (dist/cli.mjs),
552
560
  * cross-checked with https://commandcode.ai/docs/reference/cli/models.
553
561
  * (`stealth/ox-alpha` left this set in command-code@1.32.1, which gave it
554
562
  * selectable `['low', 'high', 'max']` efforts; the preview then ended in
@@ -571,7 +579,6 @@ const KNOWN_THINKING_MODELS = /* @__PURE__ */ new Set([
571
579
  "Qwen/Qwen3.7-Flash",
572
580
  "Qwen/Qwen3.7-Max",
573
581
  "Qwen/Qwen3.7-Plus",
574
- "minimax/minimax-m3-free",
575
582
  "moonshotai/Kimi-K2.7-Code",
576
583
  "moonshotai/Kimi-K2.7-Code-Highspeed",
577
584
  "stepfun/Step-3.5-Flash",
@@ -624,12 +631,9 @@ const KNOWN_PLANS = {
624
631
  "deepseek/deepseek-v4-flash-vision-exp": "go",
625
632
  "deepseek/deepseek-v4-pro": "go",
626
633
  "gpt-5.6-luna": "go",
627
- "inclusionai/ling-3.0-flash-free": "go",
628
634
  "meituan/LongCat-2.0:free": "go",
629
635
  "meta/muse-spark-1.2-contributor": "go",
630
636
  "meta/muse-spark-1.3-contributor": "go",
631
- "minimax/minimax-m2.7-free": "go",
632
- "minimax/minimax-m3-free": "go",
633
637
  "moonshotai/Kimi-K2.5": "go",
634
638
  "moonshotai/Kimi-K2.6": "go",
635
639
  "moonshotai/Kimi-K2.7-Code": "go",
@@ -639,7 +643,6 @@ const KNOWN_PLANS = {
639
643
  "poolside/laguna-s-2.1-free": "go",
640
644
  "stepfun/Step-3.5-Flash": "go",
641
645
  "stepfun/Step-3.7-Flash": "go",
642
- "tencent/Hy3": "go",
643
646
  "tencent/hy3-paid": "go",
644
647
  "tencent/hy4-preview": "go",
645
648
  "thinkingmachines/inkling": "go",
@@ -677,6 +680,7 @@ const KNOWN_PLANS = {
677
680
  "claude-opus-4-7": "provider",
678
681
  "claude-opus-4-8": "provider",
679
682
  "claude-opus-5": "provider",
683
+ "gpt-6-astra": "provider",
680
684
  "sakana/fugu-ultra": "provider"
681
685
  };
682
686
  /** Official display labels for each plan tier. */
@@ -724,8 +728,8 @@ function compareByPlan(a, b) {
724
728
  }
725
729
  /**
726
730
  * Subscription plan table, synced from the official CLI bundle's plan maps
727
- * (`Nn`/`$n` in command-code@1.31.0 `dist/cli.mjs`, re-verified unchanged
728
- * against 1.32.2 where they appear as `Zn`/`er`): subscription `planId`
731
+ * (located by the `"individual-go"` key in command-code@1.50.0 `dist/cli.mjs`,
732
+ * re-verified unchanged through 1.50.0): subscription `planId`
729
733
  * prefix → display name and the plan's monthly credit total. This is the
730
734
  * account's own subscription (from `/alpha/billing/subscriptions`) — distinct
731
735
  * from {@link KNOWN_PLANS}, which maps catalog models to their minimum tier.
@@ -935,7 +939,7 @@ function capabilityDescription(modelId, contextWindow, now = Date.now()) {
935
939
  * and API key or subscription, and Command Code's terms apply.
936
940
  *
937
941
  * Wire protocol (reverse-engineered by the pi plugin, command-code@1.28.4;
938
- * re-verified against command-code@1.44.0 — endpoints, request shape, and
942
+ * re-verified against command-code@1.50.0 — endpoints, request shape, and
939
943
  * stream events unchanged):
940
944
  * POST {apiBase}/alpha/generate
941
945
  * body: { config, memory, taste, skills, params: { model, messages, tools,
@@ -948,7 +952,7 @@ function capabilityDescription(modelId, contextWindow, now = Date.now()) {
948
952
  * per-request options thunk and an API-key resolver from the plugin entry
949
953
  * (src/index.ts), so a settings change reaches the very next request.
950
954
  */
951
- const COMMAND_CODE_CLI_VERSION = "1.47.0";
955
+ const COMMAND_CODE_CLI_VERSION = "1.50.0";
952
956
  const DEFAULT_API_BASE = "https://api.commandcode.ai";
953
957
  const DEFAULT_GENERATE_MAX_TOKENS = 64e3;
954
958
  const DEFAULT_MAX_OUTPUT_TOKENS = 65536;
@@ -1109,6 +1113,40 @@ function pairedToolCalls(messages) {
1109
1113
  names
1110
1114
  };
1111
1115
  }
1116
+ /**
1117
+ * The Command Code gateway rejects tool call ids longer than 64 characters
1118
+ * (`input[N].call_id` must be `<= 64`, issue #23). Cross-provider histories
1119
+ * can carry longer ids — e.g. switching to Command Code mid-session after
1120
+ * another provider issued the call — so overlong paired ids are remapped to
1121
+ * short per-request aliases. Correlation only needs to hold within one
1122
+ * request (each call travels with its result in the same body), so a
1123
+ * sequential alias is enough: no durable state, no cross-request stability,
1124
+ * and the harness log keeps the original ids.
1125
+ */
1126
+ const MAX_WIRE_TOOL_CALL_ID_LENGTH = 64;
1127
+ /**
1128
+ * Map each paired tool-call id to its wire id: ids within the gateway limit
1129
+ * pass through verbatim, overlong ids get a collision-free `cc-<n>` alias.
1130
+ * Callers must resolve BOTH the tool-call and its paired tool-result through
1131
+ * the returned map so the pair stays correlated.
1132
+ */
1133
+ function wireToolCallIds(paired) {
1134
+ const wire = /* @__PURE__ */ new Map();
1135
+ const taken = /* @__PURE__ */ new Set();
1136
+ for (const id of paired) if (id.length <= MAX_WIRE_TOOL_CALL_ID_LENGTH) {
1137
+ wire.set(id, id);
1138
+ taken.add(id);
1139
+ }
1140
+ let seq = 1;
1141
+ for (const id of paired) {
1142
+ if (wire.has(id)) continue;
1143
+ let alias = `cc-${seq++}`;
1144
+ while (taken.has(alias)) alias = `cc-${seq++}`;
1145
+ wire.set(id, alias);
1146
+ taken.add(alias);
1147
+ }
1148
+ return wire;
1149
+ }
1112
1150
  function blockText(block) {
1113
1151
  return block.type === "text" || block.type === "reasoning" ? block.text : "";
1114
1152
  }
@@ -1139,6 +1177,7 @@ async function imageToCommandCode(ref, readImage) {
1139
1177
  async function messagesToCC(messages, readImage) {
1140
1178
  const out = [];
1141
1179
  const { ids: paired, names: toolNames } = pairedToolCalls(messages);
1180
+ const wireIds = wireToolCallIds(paired);
1142
1181
  for (const message of messages) {
1143
1182
  if (message.role === "system") continue;
1144
1183
  if (message.role === "user" && message.source.kind !== "tool") {
@@ -1167,7 +1206,7 @@ async function messagesToCC(messages, readImage) {
1167
1206
  });
1168
1207
  else if (block.type === "tool-call" && paired.has(block.id)) parts.push({
1169
1208
  type: "tool-call",
1170
- toolCallId: block.id,
1209
+ toolCallId: wireIds.get(block.id) ?? block.id,
1171
1210
  toolName: block.name,
1172
1211
  input: recordOrEmpty(block.arguments)
1173
1212
  });
@@ -1184,7 +1223,7 @@ async function messagesToCC(messages, readImage) {
1184
1223
  role: "tool",
1185
1224
  content: [{
1186
1225
  type: "tool-result",
1187
- toolCallId: block.toolCallId,
1226
+ toolCallId: wireIds.get(block.toolCallId) ?? block.toolCallId,
1188
1227
  toolName: toolNames.get(block.toolCallId) || "unknown",
1189
1228
  output: block.isError ? {
1190
1229
  type: "error-text",
@@ -1199,6 +1238,89 @@ async function messagesToCC(messages, readImage) {
1199
1238
  }
1200
1239
  return out;
1201
1240
  }
1241
+ /**
1242
+ * Convert one image reference to the OpenAI Chat Completions wire format:
1243
+ * `{ type: 'image_url', image_url: { url: 'data:...;base64,...' } }`.
1244
+ */
1245
+ async function imageToOpenAI(ref, readImage) {
1246
+ const data = await readImage(ref);
1247
+ return {
1248
+ type: "image_url",
1249
+ image_url: { url: `data:${ref.mediaType};base64,${Buffer.from(data).toString("base64")}` }
1250
+ };
1251
+ }
1252
+ /**
1253
+ * Convert harness messages to the OpenAI Chat Completions request shape.
1254
+ *
1255
+ * Unlike the Command Code CLI transport, this transport is the documented
1256
+ * Provider API surface. DeepSeek's thinking-mode contract requires historical
1257
+ * `reasoning_content` to be passed back whenever tools are in play, so this
1258
+ * converter intentionally DOES replay reasoning blocks as `reasoning_content`
1259
+ * on assistant messages. Only tool calls with a paired tool result are
1260
+ * replayed (same policy as the CLI path).
1261
+ */
1262
+ async function messagesToOpenAI(messages, readImage) {
1263
+ const out = [];
1264
+ const { ids: paired } = pairedToolCalls(messages);
1265
+ const wireIds = wireToolCallIds(paired);
1266
+ for (const message of messages) {
1267
+ if (message.role === "system") continue;
1268
+ if (message.role === "user" && message.source.kind !== "tool") {
1269
+ const parts = [];
1270
+ for (const block of message.content) {
1271
+ if (block.type === "text") parts.push({
1272
+ type: "text",
1273
+ text: block.text
1274
+ });
1275
+ if (block.type === "image") {
1276
+ if (!readImage) throw new LlmError("Image input requires the durable attachment service", "UNSUPPORTED_CONTENT");
1277
+ parts.push(await imageToOpenAI(block.attachment, readImage));
1278
+ }
1279
+ }
1280
+ if (parts.length === 0) continue;
1281
+ if (!parts.some((part) => part.type === "image_url") && parts.length === 1) out.push({
1282
+ role: "user",
1283
+ content: parts[0].text
1284
+ });
1285
+ else out.push({
1286
+ role: "user",
1287
+ content: parts
1288
+ });
1289
+ continue;
1290
+ }
1291
+ if (message.role === "assistant") {
1292
+ const text = message.content.filter((block) => block.type === "text").map((block) => block.text).join("");
1293
+ const reasoning = message.content.filter((block) => block.type === "reasoning").map((block) => block.text).join("");
1294
+ const toolCalls = message.content.filter((block) => block.type === "tool-call" && paired.has(block.id)).map((block) => ({
1295
+ id: wireIds.get(block.id) ?? block.id,
1296
+ type: "function",
1297
+ function: {
1298
+ name: block.name,
1299
+ arguments: block.arguments
1300
+ }
1301
+ }));
1302
+ if (text === "" && reasoning === "" && toolCalls.length === 0) continue;
1303
+ const assistant = {
1304
+ role: "assistant",
1305
+ content: text === "" ? null : text
1306
+ };
1307
+ if (reasoning !== "") assistant.reasoning_content = reasoning;
1308
+ if (toolCalls.length > 0) assistant.tool_calls = toolCalls;
1309
+ out.push(assistant);
1310
+ continue;
1311
+ }
1312
+ if (message.role === "user" && message.source.kind === "tool") {
1313
+ const block = message.content[0];
1314
+ if (!block || block.type !== "tool-result" || !paired.has(block.toolCallId)) continue;
1315
+ out.push({
1316
+ role: "tool",
1317
+ tool_call_id: wireIds.get(block.toolCallId) ?? block.toolCallId,
1318
+ content: toolResultText(block)
1319
+ });
1320
+ }
1321
+ }
1322
+ return out;
1323
+ }
1202
1324
  /** Account endpoints fetched by one `getUsage()` run (see the classification there). */
1203
1325
  const USAGE_ENDPOINT_COUNT = 4;
1204
1326
  var CommandCodeAdapter = class extends LlmAdapter {
@@ -1208,6 +1330,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
1208
1330
  resolveAttachments;
1209
1331
  billingAccess = /* @__PURE__ */ new Map();
1210
1332
  billingAccessInflight = /* @__PURE__ */ new Map();
1333
+ protocolCache = /* @__PURE__ */ new Map();
1211
1334
  constructor(deps) {
1212
1335
  super();
1213
1336
  this.deps = deps;
@@ -1397,6 +1520,41 @@ var CommandCodeAdapter = class extends LlmAdapter {
1397
1520
  return;
1398
1521
  }
1399
1522
  }
1523
+ /** Fresh cached billing tier weight for a key, or undefined when not known. */
1524
+ cachedBillingTierWeight(apiKey) {
1525
+ const hit = this.billingAccess.get(apiKey);
1526
+ if (hit === void 0 || Date.now() - hit.at >= 3e5) return void 0;
1527
+ return hit.value?.tierWeight;
1528
+ }
1529
+ /** Cached protocol decision for a key, or undefined when expired/unknown. */
1530
+ cachedProtocolUseCli(apiKey) {
1531
+ const hit = this.protocolCache.get(apiKey);
1532
+ if (hit === void 0 || Date.now() - hit.at >= 9e5) return void 0;
1533
+ return hit.useCli;
1534
+ }
1535
+ rememberProtocol(apiKey, useCli) {
1536
+ this.protocolCache.set(apiKey, {
1537
+ useCli,
1538
+ at: Date.now()
1539
+ });
1540
+ }
1541
+ /**
1542
+ * Choose the initial protocol for one request. A fresh protocol cache entry
1543
+ * wins; otherwise a cached (not network-fetched) billing tier of Go is
1544
+ * treated as CLI-only. Unknown accounts default to Provider API and fall
1545
+ * back only after an `upgrade_required` rejection.
1546
+ */
1547
+ resolveProtocol(apiKey) {
1548
+ const forced = this.deps.options().protocol;
1549
+ if (forced === "cli" || forced === "openai") return forced;
1550
+ const cached = this.cachedProtocolUseCli(apiKey);
1551
+ if (cached !== void 0) return cached ? "cli" : "openai";
1552
+ if (this.cachedBillingTierWeight(apiKey) === 0) {
1553
+ this.rememberProtocol(apiKey, true);
1554
+ return "cli";
1555
+ }
1556
+ return "openai";
1557
+ }
1400
1558
  /**
1401
1559
  * Fetch account, usage, credit, and subscription state from the Command
1402
1560
  * Code account endpoints (`/alpha/whoami`, `/alpha/usage/summary`,
@@ -1543,44 +1701,70 @@ var CommandCodeAdapter = class extends LlmAdapter {
1543
1701
  const supported = KNOWN_EFFORTS[options.model];
1544
1702
  const reasoningEffort = effort && effort !== "off" && supported?.includes(effort) ? effort : void 0;
1545
1703
  const systemText = [options.system ?? "", ...options.messages.filter((m) => m.role === "system").map((m) => m.content.map(blockText).filter(Boolean).join("\n"))].filter(Boolean).join("\n\n");
1546
- const body = {
1547
- config: {
1548
- workingDir: connection.workingDir,
1549
- date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
1550
- environment: `${process.platform}-${process.arch}, Node.js ${process.version}`,
1551
- structure: [],
1552
- isGitRepo: false,
1553
- currentBranch: "",
1554
- mainBranch: "",
1555
- gitStatus: "",
1556
- recentCommits: []
1557
- },
1558
- memory: null,
1559
- taste: null,
1560
- skills: null,
1561
- params: {
1562
- model: options.model,
1563
- messages: await messagesToCC(options.messages, readImage),
1564
- tools: (options.tools ?? []).map((tool) => ({
1565
- type: "function",
1704
+ let protocol = this.resolveProtocol(apiKey);
1705
+ const buildBody = async (target) => {
1706
+ if (target === "cli") return {
1707
+ config: {
1708
+ workingDir: connection.workingDir,
1709
+ date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
1710
+ environment: `${process.platform}-${process.arch}, Node.js ${process.version}`,
1711
+ structure: [],
1712
+ isGitRepo: false,
1713
+ currentBranch: "",
1714
+ mainBranch: "",
1715
+ gitStatus: "",
1716
+ recentCommits: []
1717
+ },
1718
+ memory: null,
1719
+ taste: null,
1720
+ skills: null,
1721
+ params: {
1722
+ model: options.model,
1723
+ messages: await messagesToCC(options.messages, readImage),
1724
+ tools: (options.tools ?? []).map((tool) => ({
1725
+ type: "function",
1726
+ name: tool.name,
1727
+ description: tool.description,
1728
+ input_schema: tool.parameters
1729
+ })),
1730
+ system: systemText,
1731
+ max_tokens: maxTokens,
1732
+ temperature: options.temperature ?? .3,
1733
+ stream: true,
1734
+ ...reasoningEffort ? { reasoning_effort: reasoningEffort } : {}
1735
+ },
1736
+ threadId: randomUUID()
1737
+ };
1738
+ const openAiMessages = [...systemText ? [{
1739
+ role: "system",
1740
+ content: systemText
1741
+ }] : [], ...await messagesToOpenAI(options.messages, readImage)];
1742
+ const openAiTools = (options.tools ?? []).map((tool) => ({
1743
+ type: "function",
1744
+ function: {
1566
1745
  name: tool.name,
1567
1746
  description: tool.description,
1568
- input_schema: tool.parameters
1569
- })),
1570
- system: systemText,
1747
+ parameters: tool.parameters
1748
+ }
1749
+ }));
1750
+ return {
1751
+ model: options.model,
1752
+ messages: openAiMessages,
1753
+ ...openAiTools.length > 0 ? { tools: openAiTools } : {},
1571
1754
  max_tokens: maxTokens,
1572
1755
  temperature: options.temperature ?? .3,
1573
1756
  stream: true,
1574
1757
  ...reasoningEffort ? { reasoning_effort: reasoningEffort } : {}
1575
- },
1576
- threadId: randomUUID()
1758
+ };
1577
1759
  };
1760
+ let body = await buildBody(protocol);
1578
1761
  const connect = async (key) => {
1579
1762
  const connectAbort = new AbortController();
1580
1763
  let connectTimedOut = false;
1764
+ const endpoint = protocol === "cli" ? `${connection.apiBase}/alpha/generate` : `${connection.apiBase}/provider/v1/chat/completions`;
1581
1765
  const connectTimer = setTimeout(() => {
1582
1766
  connectTimedOut = true;
1583
- connectAbort.abort(new DOMException(`Command Code API request to ${connection.apiBase}/alpha/generate did not respond within ${connection.requestTimeoutMs}ms`, "TimeoutError"));
1767
+ connectAbort.abort(new DOMException(`Command Code API request to ${endpoint} did not respond within ${connection.requestTimeoutMs}ms`, "TimeoutError"));
1584
1768
  }, connection.requestTimeoutMs);
1585
1769
  const onCallerAbort = () => {
1586
1770
  connectAbort.abort(options.signal?.reason);
@@ -1594,19 +1778,25 @@ var CommandCodeAdapter = class extends LlmAdapter {
1594
1778
  if (options.signal) options.signal.removeEventListener("abort", onCallerAbort);
1595
1779
  };
1596
1780
  let response;
1781
+ const headers = protocol === "cli" ? {
1782
+ "Content-Type": "application/json",
1783
+ Authorization: `Bearer ${key}`,
1784
+ "x-command-code-version": COMMAND_CODE_CLI_VERSION,
1785
+ "x-cli-environment": "production",
1786
+ "x-project-slug": projectSlugFromPath(connection.workingDir),
1787
+ "x-taste-learning": "true",
1788
+ "x-co-flag": "false",
1789
+ ...attributionHeaders()
1790
+ } : {
1791
+ "Content-Type": "application/json",
1792
+ Authorization: `Bearer ${key}`,
1793
+ Accept: "text/event-stream",
1794
+ ...attributionHeaders()
1795
+ };
1597
1796
  try {
1598
- response = await this.fetchImpl(`${connection.apiBase}/alpha/generate`, {
1797
+ response = await this.fetchImpl(endpoint, {
1599
1798
  method: "POST",
1600
- headers: {
1601
- "Content-Type": "application/json",
1602
- Authorization: `Bearer ${key}`,
1603
- "x-command-code-version": COMMAND_CODE_CLI_VERSION,
1604
- "x-cli-environment": "production",
1605
- "x-project-slug": projectSlugFromPath(connection.workingDir),
1606
- "x-taste-learning": "true",
1607
- "x-co-flag": "false",
1608
- ...attributionHeaders()
1609
- },
1799
+ headers,
1610
1800
  body: JSON.stringify(body),
1611
1801
  signal: connectAbort.signal
1612
1802
  });
@@ -1614,8 +1804,8 @@ var CommandCodeAdapter = class extends LlmAdapter {
1614
1804
  } catch (error) {
1615
1805
  cleanup();
1616
1806
  if (options.signal?.aborted) throw error;
1617
- if (connectTimedOut || error instanceof DOMException && error.name === "TimeoutError") throw new LlmError(`Command Code API request to ${connection.apiBase}/alpha/generate did not respond within ${connection.requestTimeoutMs}ms: ${errorChain(error)};Command Code API 请求在 ${connection.requestTimeoutMs} 毫秒内未收到响应——通常是网络或代理问题,请检查后重试`, "TIMEOUT", { cause: error });
1618
- throw new LlmError(`Command Code API request to ${connection.apiBase}/alpha/generate failed: ${errorChain(error)};Command Code API 请求连接失败——通常是网络或代理问题,请检查网络或代理设置后重试`, "TRANSPORT", { cause: error });
1807
+ if (connectTimedOut || error instanceof DOMException && error.name === "TimeoutError") throw new LlmError(`Command Code API request to ${endpoint} did not respond within ${connection.requestTimeoutMs}ms: ${errorChain(error)};Command Code API 请求在 ${connection.requestTimeoutMs} 毫秒内未收到响应——通常是网络或代理问题,请检查后重试`, "TIMEOUT", { cause: error });
1808
+ throw new LlmError(`Command Code API request to ${endpoint} failed: ${errorChain(error)};Command Code API 请求连接失败——通常是网络或代理问题,请检查网络或代理设置后重试`, "TRANSPORT", { cause: error });
1619
1809
  }
1620
1810
  if (!response.ok) {
1621
1811
  const errText = await response.text().catch(() => "");
@@ -1644,6 +1834,12 @@ var CommandCodeAdapter = class extends LlmAdapter {
1644
1834
  connected = attempt;
1645
1835
  break;
1646
1836
  }
1837
+ if (protocol === "openai" && isUpgradeRequiredError(attempt.status, attempt.errText)) {
1838
+ protocol = "cli";
1839
+ this.rememberProtocol(apiKey, true);
1840
+ body = await buildBody("cli");
1841
+ continue;
1842
+ }
1647
1843
  const rotate = this.deps.rotateApiKey;
1648
1844
  if ((attempt.status === 429 || attempt.status === 401) && rotate !== void 0 && options.signal?.aborted !== true && tried.size < MAX_ACCOUNT_ROTATIONS) {
1649
1845
  const next = await rotate(apiKey, attempt.status === 429 ? "rate-limit" : "invalid-credential", connection, options.model);
@@ -1709,7 +1905,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
1709
1905
  reasoningIndex = -1;
1710
1906
  reasoningContent = "";
1711
1907
  };
1712
- const handleEvent = (event) => {
1908
+ const handleCliEvent = (event) => {
1713
1909
  const chunks = [];
1714
1910
  if (!isRecord(event)) return chunks;
1715
1911
  switch (event.type) {
@@ -1825,6 +2021,128 @@ var CommandCodeAdapter = class extends LlmAdapter {
1825
2021
  }
1826
2022
  return chunks;
1827
2023
  };
2024
+ const openAiToolCalls = [];
2025
+ const emitOpenAiToolCalls = function* () {
2026
+ for (const call of openAiToolCalls) {
2027
+ const id = call.id ?? randomUUID();
2028
+ const name = call.name ?? "";
2029
+ const args = call.arguments || "{}";
2030
+ const index = nextIndex++;
2031
+ sawContent = true;
2032
+ yield {
2033
+ type: "block-start",
2034
+ index,
2035
+ blockType: "tool-call"
2036
+ };
2037
+ yield {
2038
+ type: "tool-call-delta",
2039
+ index,
2040
+ id: ToolCallId(id),
2041
+ name,
2042
+ argumentsDelta: args
2043
+ };
2044
+ yield {
2045
+ type: "block-end",
2046
+ index,
2047
+ block: {
2048
+ type: "tool-call",
2049
+ id: ToolCallId(id),
2050
+ name,
2051
+ arguments: args
2052
+ }
2053
+ };
2054
+ }
2055
+ openAiToolCalls.length = 0;
2056
+ };
2057
+ const handleOpenAIEvent = (event) => {
2058
+ const chunks = [];
2059
+ if (!isRecord(event)) return chunks;
2060
+ const choices = event.choices;
2061
+ if (!Array.isArray(choices) || choices.length === 0) {
2062
+ if (event.usage !== void 0) chunks.push({
2063
+ type: "usage",
2064
+ usage: mapOpenAIUsage(event.usage)
2065
+ });
2066
+ return chunks;
2067
+ }
2068
+ const choice = isRecord(choices[0]) ? choices[0] : {};
2069
+ const delta = isRecord(choice.delta) ? choice.delta : {};
2070
+ const reasoningDelta = stringValue(delta.reasoning) ?? stringValue(delta.reasoning_content) ?? "";
2071
+ if (reasoningDelta !== "") {
2072
+ chunks.push(...closeText());
2073
+ if (reasoningIndex < 0) {
2074
+ reasoningIndex = nextIndex++;
2075
+ chunks.push({
2076
+ type: "block-start",
2077
+ index: reasoningIndex,
2078
+ blockType: "reasoning"
2079
+ });
2080
+ }
2081
+ reasoningContent += reasoningDelta;
2082
+ chunks.push({
2083
+ type: "reasoning-delta",
2084
+ index: reasoningIndex,
2085
+ text: reasoningDelta
2086
+ });
2087
+ }
2088
+ const contentDelta = stringValue(delta.content) ?? "";
2089
+ if (contentDelta !== "") {
2090
+ chunks.push(...closeReasoning());
2091
+ if (textIndex < 0) {
2092
+ textIndex = nextIndex++;
2093
+ chunks.push({
2094
+ type: "block-start",
2095
+ index: textIndex,
2096
+ blockType: "text"
2097
+ });
2098
+ }
2099
+ textContent += contentDelta;
2100
+ sawContent = true;
2101
+ chunks.push({
2102
+ type: "text-delta",
2103
+ index: textIndex,
2104
+ text: contentDelta
2105
+ });
2106
+ }
2107
+ if (Array.isArray(delta.tool_calls)) {
2108
+ sawContent = true;
2109
+ for (const rawCall of delta.tool_calls) {
2110
+ if (!isRecord(rawCall)) continue;
2111
+ const callIndex = numberValue(rawCall.index) ?? 0;
2112
+ let existing = openAiToolCalls.find((call) => call.index === callIndex);
2113
+ const fn = isRecord(rawCall.function) ? rawCall.function : void 0;
2114
+ const id = stringValue(rawCall.id);
2115
+ const name = fn === void 0 ? void 0 : stringValue(fn.name);
2116
+ const argDelta = fn === void 0 ? void 0 : stringValue(fn.arguments) ?? (fn.arguments === void 0 ? "" : JSON.stringify(fn.arguments));
2117
+ if (!existing) {
2118
+ existing = {
2119
+ index: callIndex,
2120
+ name: name ?? "",
2121
+ arguments: argDelta ?? ""
2122
+ };
2123
+ if (id !== void 0) existing.id = id;
2124
+ openAiToolCalls.push(existing);
2125
+ } else {
2126
+ if (id !== void 0 && existing.id === void 0) existing.id = id;
2127
+ if (name !== void 0 && existing.name === "") existing.name = name;
2128
+ if (argDelta !== void 0) existing.arguments += argDelta;
2129
+ }
2130
+ }
2131
+ }
2132
+ if (choice.finish_reason !== void 0 && choice.finish_reason !== null) {
2133
+ chunks.push(...closeText(), ...closeReasoning(), ...emitOpenAiToolCalls());
2134
+ if (event.usage !== void 0) chunks.push({
2135
+ type: "usage",
2136
+ usage: mapOpenAIUsage(event.usage)
2137
+ });
2138
+ chunks.push({
2139
+ type: "finish",
2140
+ reason: mapOpenAIFinishReason(choice.finish_reason)
2141
+ });
2142
+ }
2143
+ return chunks;
2144
+ };
2145
+ const handleEvent = (event) => protocol === "openai" ? handleOpenAIEvent(event) : handleCliEvent(event);
1828
2146
  try {
1829
2147
  let finished = false;
1830
2148
  for (;;) {
@@ -1859,6 +2177,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
1859
2177
  if (!finished) {
1860
2178
  yield* closeText();
1861
2179
  yield* closeReasoning();
2180
+ if (protocol === "openai") yield* emitOpenAiToolCalls();
1862
2181
  if (!sawContent) throw new LlmError("Command Code returned an empty response;Command Code 返回了空响应,重试通常可恢复", "EMPTY_RESPONSE");
1863
2182
  yield {
1864
2183
  type: "finish",
@@ -1923,6 +2242,55 @@ function mapFinishReason(reason) {
1923
2242
  if (reason === "length" || reason === "max_tokens" || reason === "max-tokens" || reason === "max_output_tokens") return { kind: "max-tokens" };
1924
2243
  return { kind: "stop" };
1925
2244
  }
2245
+ function mapOpenAIFinishReason(reason) {
2246
+ if (reason === "tool_calls" || reason === "tool-calls") return { kind: "tool-calls" };
2247
+ if (reason === "length" || reason === "max_tokens" || reason === "max-tokens" || reason === "max_output_tokens") return { kind: "max-tokens" };
2248
+ return { kind: "stop" };
2249
+ }
2250
+ /**
2251
+ * True when a Provider API pre-stream rejection is Command Code's Go-plan
2252
+ * gate (`upgrade_required`). Only this exact class of rejection should fall
2253
+ * back to the CLI /alpha/generate transport; other 4xx/5xx must surface as
2254
+ * ordinary errors so real account/model problems are not masked.
2255
+ */
2256
+ function isUpgradeRequiredError(status, errText) {
2257
+ if (status !== 403) return false;
2258
+ const lower = errText.toLowerCase();
2259
+ if (lower.includes("upgrade_required")) return true;
2260
+ if (lower.includes("go plan") && lower.includes("api access")) return true;
2261
+ if (lower.includes("only plan without api access")) return true;
2262
+ if (lower.includes("upgrade to goat or higher")) return true;
2263
+ try {
2264
+ const parsed = JSON.parse(errText);
2265
+ if (isRecord(parsed)) {
2266
+ const error = isRecord(parsed.error) ? parsed.error : parsed;
2267
+ if ((stringValue(error.code) ?? stringValue(error.type))?.toLowerCase() === "upgrade_required") return true;
2268
+ const message = stringValue(error.message) ?? "";
2269
+ if (message.toLowerCase().includes("go plan") && message.toLowerCase().includes("api access")) return true;
2270
+ }
2271
+ } catch {}
2272
+ return false;
2273
+ }
2274
+ /** Map an OpenAI Chat Completions usage payload to harness disjoint counts. */
2275
+ function mapOpenAIUsage(usage) {
2276
+ const source = isRecord(usage) ? usage : {};
2277
+ const promptTokens = numberValue(source.prompt_tokens) ?? 0;
2278
+ const outputTokens = numberValue(source.completion_tokens) ?? 0;
2279
+ const totalTokens = numberValue(source.total_tokens);
2280
+ const promptDetails = isRecord(source.prompt_tokens_details) ? source.prompt_tokens_details : void 0;
2281
+ const cacheRead = numberValue(promptDetails?.cached_tokens) ?? 0;
2282
+ const cacheWrite = numberValue(promptDetails?.cache_creation_input_tokens) ?? 0;
2283
+ const reasoningTokens = numberValue((isRecord(source.completion_tokens_details) ? source.completion_tokens_details : void 0)?.reasoning_tokens);
2284
+ const usageOut = {
2285
+ inputTokens: Math.max(0, promptTokens - cacheRead - cacheWrite),
2286
+ outputTokens,
2287
+ cacheReadTokens: cacheRead
2288
+ };
2289
+ if (cacheWrite > 0) usageOut.cacheWriteTokens = cacheWrite;
2290
+ if (reasoningTokens !== void 0) usageOut.reasoningTokens = reasoningTokens;
2291
+ if (totalTokens !== void 0) usageOut.totalTokens = totalTokens;
2292
+ return usageOut;
2293
+ }
1926
2294
  //#endregion
1927
2295
  //#region src/command-locales.ts
1928
2296
  const commandcodeCommand = {