@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/CHANGELOG.md +14 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/lib/client.js +1 -1
- package/lib/index.d.ts +27 -5
- package/lib/index.js +423 -55
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
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.
|
|
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.
|
|
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
|
-
* (`
|
|
728
|
-
*
|
|
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.
|
|
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.
|
|
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
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
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
|
-
|
|
1569
|
-
}
|
|
1570
|
-
|
|
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 ${
|
|
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(
|
|
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 ${
|
|
1618
|
-
throw new LlmError(`Command Code API request to ${
|
|
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
|
|
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 = {
|