@mars-sea/dsh-commandcode-provider 0.10.1 → 0.10.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 +22 -0
- package/README.md +7 -3
- package/README.zh-CN.md +7 -3
- package/lib/client.js +519 -144
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +122 -30
- package/lib/index.js +767 -576
- package/lib/index.js.map +1 -1
- package/package.json +8 -1
package/lib/index.js
CHANGED
|
@@ -173,12 +173,18 @@ var CommandCodeAccountPool = class {
|
|
|
173
173
|
if (accounts.length === 0) return;
|
|
174
174
|
const routed = selectAccountForModel(accounts, options?.model ?? "", this.deps.modelAccountRules?.());
|
|
175
175
|
if (routed !== void 0) return this.pick(routed);
|
|
176
|
-
const
|
|
176
|
+
const preferred = this.deps.preferredId?.();
|
|
177
|
+
const chosen = selectActiveAccount(accounts, preferred);
|
|
177
178
|
if (chosen !== void 0) return this.pick(chosen);
|
|
178
179
|
await Promise.all(accounts.map(async (account) => {
|
|
179
180
|
if (account.state?.kind === "disabled") return;
|
|
180
181
|
if (options?.exclude !== void 0 && account.key === options.exclude) return;
|
|
181
|
-
|
|
182
|
+
let probe;
|
|
183
|
+
try {
|
|
184
|
+
probe = await this.deps.probeWindow(account.key);
|
|
185
|
+
} catch {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
182
188
|
if (probe === void 0) return;
|
|
183
189
|
if (!probe.exceeded) this.states.delete(account.key);
|
|
184
190
|
else this.states.set(account.key, {
|
|
@@ -187,9 +193,9 @@ var CommandCodeAccountPool = class {
|
|
|
187
193
|
until: probe.resetAt
|
|
188
194
|
});
|
|
189
195
|
}));
|
|
190
|
-
const revived = selectActiveAccount(await this.resolvedAccounts(), this.deps.preferredId?.());
|
|
191
|
-
if (revived !== void 0) return this.pick(revived);
|
|
192
196
|
const latest = await this.resolvedAccounts();
|
|
197
|
+
const revived = selectActiveAccount(latest, preferred);
|
|
198
|
+
if (revived !== void 0) return this.pick(revived);
|
|
193
199
|
if (latest.filter((account) => account.state?.kind === "disabled").length === latest.length) throw new LlmError(`llm-commandcode: every configured Command Code account (${latest.length}) was rejected with 401 — check the stored API keys (Models page / settings) or the auth file;已配置的 ${latest.length} 个 Command Code 账户密钥均被拒绝(401)——请在设置页检查存储的 API 密钥,或重新运行 command-code login`, "INVALID_CREDENTIAL");
|
|
194
200
|
const resets = latest.map((account) => account.state).filter((state) => state !== void 0 && state.kind === "cooldown" && state.until > 0).map((state) => state.until);
|
|
195
201
|
const earliest = resets.length > 0 ? Math.min(...resets) : 0;
|
|
@@ -797,13 +803,14 @@ function subscriptionPlanInfo(planId) {
|
|
|
797
803
|
/**
|
|
798
804
|
* Whether the picker lists `modelId` for an account with the given billing
|
|
799
805
|
* access. Fails open at every uncertainty: no billing data, an unknown plan,
|
|
800
|
-
*
|
|
801
|
-
*
|
|
806
|
+
* a non-finite weight (corrupt billing fact), or a model outside
|
|
807
|
+
* {@link KNOWN_PLANS} all keep the model visible — the server remains the
|
|
808
|
+
* final gate (`403 MODEL_NOT_IN_PLAN`).
|
|
802
809
|
*/
|
|
803
810
|
function modelVisibleInPlan(modelId, access) {
|
|
804
811
|
if (access === void 0) return true;
|
|
805
812
|
if (access.onDemandCredits > 0) return true;
|
|
806
|
-
if (access.tierWeight === void 0) return true;
|
|
813
|
+
if (access.tierWeight === void 0 || !Number.isFinite(access.tierWeight)) return true;
|
|
807
814
|
const tier = KNOWN_PLANS[modelId];
|
|
808
815
|
if (tier === void 0) return true;
|
|
809
816
|
const weight = PLAN_ORDER[tier];
|
|
@@ -897,8 +904,8 @@ function dealLabel(modelId, now = Date.now()) {
|
|
|
897
904
|
}
|
|
898
905
|
/**
|
|
899
906
|
* Compact human-readable context window, e.g. `1_000_000 -> "1M"`,
|
|
900
|
-
* `256_000 -> "256K"`, `262_144 -> "
|
|
901
|
-
* Returns undefined for unknown/absent sizes.
|
|
907
|
+
* `256_000 -> "256K"`, `262_144 -> "262K"` (floor to the nearest K).
|
|
908
|
+
* Returns undefined for unknown/absent sizes; values under 1K render raw.
|
|
902
909
|
*/
|
|
903
910
|
function formatContext(contextWindow) {
|
|
904
911
|
if (contextWindow === void 0 || !Number.isFinite(contextWindow) || contextWindow <= 0) return;
|
|
@@ -907,6 +914,7 @@ function formatContext(contextWindow) {
|
|
|
907
914
|
const rounded = Math.round(m * 10) / 10;
|
|
908
915
|
return `${Number.isInteger(rounded) ? rounded : rounded.toFixed(1)}M`;
|
|
909
916
|
}
|
|
917
|
+
if (contextWindow < 1e3) return String(Math.floor(contextWindow));
|
|
910
918
|
return `${Math.floor(contextWindow / 1e3)}K`;
|
|
911
919
|
}
|
|
912
920
|
/**
|
|
@@ -959,6 +967,10 @@ const DEFAULT_MAX_OUTPUT_TOKENS = 65536;
|
|
|
959
967
|
const MODELS_TIMEOUT_MS = 1e4;
|
|
960
968
|
/** How long the picker's plan-filter billing facts stay cached before refetching. */
|
|
961
969
|
const BILLING_ACCESS_TTL_MS = 3e5;
|
|
970
|
+
/** The entry-plan tier weight (`individual-go` in KNOWN_SUBSCRIPTION_PLANS). */
|
|
971
|
+
const GO_TIER_WEIGHT = 0;
|
|
972
|
+
/** Hard cap on account rotations within one request (one attempt per distinct key). */
|
|
973
|
+
const MAX_ACCOUNT_ROTATIONS = 16;
|
|
962
974
|
/**
|
|
963
975
|
* Subscription statuses the CLI treats as live (`Mr` in command-code's
|
|
964
976
|
* cli.mjs): the plan gate applies only under one of these.
|
|
@@ -1323,6 +1335,288 @@ async function messagesToOpenAI(messages, readImage) {
|
|
|
1323
1335
|
}
|
|
1324
1336
|
/** Account endpoints fetched by one `getUsage()` run (see the classification there). */
|
|
1325
1337
|
const USAGE_ENDPOINT_COUNT = 4;
|
|
1338
|
+
/**
|
|
1339
|
+
* Parse one usage/summary payload into the report's usage section.
|
|
1340
|
+
* Returns undefined when the endpoint answered nothing usable.
|
|
1341
|
+
*/
|
|
1342
|
+
function parseUsageTotals(usage) {
|
|
1343
|
+
if (usage === void 0) return void 0;
|
|
1344
|
+
return {
|
|
1345
|
+
totalCount: numberValue(usage.totalCount) ?? 0,
|
|
1346
|
+
totalCost: numberValue(usage.totalCost) ?? 0,
|
|
1347
|
+
successRate: numberValue(usage.successRate) ?? 0,
|
|
1348
|
+
completedCount: numberValue(usage.completedCount) ?? 0,
|
|
1349
|
+
failedCount: numberValue(usage.failedCount) ?? 0,
|
|
1350
|
+
totalTokensIn: numberValue(usage.totalTokensIn) ?? 0,
|
|
1351
|
+
totalTokensOut: numberValue(usage.totalTokensOut) ?? 0,
|
|
1352
|
+
totalCredits: numberValue(usage.totalCredits) ?? 0,
|
|
1353
|
+
periodBasis: stringValue(usage.periodBasis) ?? "billing-period"
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
/** Parse one window-limit block (`fiveHour` / `weekly`). */
|
|
1357
|
+
function parseWindowLimit(value) {
|
|
1358
|
+
const block = isRecord(value) ? value : void 0;
|
|
1359
|
+
return {
|
|
1360
|
+
used: numberValue(block?.used) ?? 0,
|
|
1361
|
+
cap: numberValue(block?.cap) ?? 0,
|
|
1362
|
+
exceeded: block?.exceeded === true,
|
|
1363
|
+
resetAt: numberValue(block?.resetAt) ?? 0
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Parse one billing/credits payload into the report's credits section.
|
|
1368
|
+
* Returns undefined when the endpoint answered nothing usable.
|
|
1369
|
+
*/
|
|
1370
|
+
function parseCreditLimits(credits) {
|
|
1371
|
+
if (credits === void 0) return void 0;
|
|
1372
|
+
const creditsData = isRecord(credits.credits) ? credits.credits : void 0;
|
|
1373
|
+
const windowLimits = isRecord(credits.windowLimits) ? credits.windowLimits : void 0;
|
|
1374
|
+
const fiveHour = windowLimits !== void 0 && isRecord(windowLimits.fiveHour) ? windowLimits.fiveHour : void 0;
|
|
1375
|
+
const weekly = windowLimits !== void 0 && isRecord(windowLimits.weekly) ? windowLimits.weekly : void 0;
|
|
1376
|
+
if (creditsData === void 0 && fiveHour === void 0 && weekly === void 0) return void 0;
|
|
1377
|
+
return {
|
|
1378
|
+
monthlyCredits: numberValue(creditsData?.monthlyCredits) ?? 0,
|
|
1379
|
+
purchasedCredits: numberValue(creditsData?.purchasedCredits) ?? 0,
|
|
1380
|
+
freeCredits: numberValue(creditsData?.freeCredits) ?? 0,
|
|
1381
|
+
fiveHour: parseWindowLimit(fiveHour),
|
|
1382
|
+
weekly: parseWindowLimit(weekly)
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
/**
|
|
1386
|
+
* Parse the account identity from whoami plus the plan identity from the
|
|
1387
|
+
* subscriptions/credits payloads. Returns the org id for the subscriptions
|
|
1388
|
+
* query alongside, so getUsage fetches whoami first and the rest in parallel.
|
|
1389
|
+
*/
|
|
1390
|
+
function parseAccountIdentity(whoami) {
|
|
1391
|
+
const whoamiData = whoami !== void 0 && isRecord(whoami.user) ? whoami.user : void 0;
|
|
1392
|
+
const orgData = whoami !== void 0 && isRecord(whoami.org) ? whoami.org : void 0;
|
|
1393
|
+
return {
|
|
1394
|
+
account: whoamiData === void 0 ? void 0 : {
|
|
1395
|
+
id: stringValue(whoamiData.id) ?? "",
|
|
1396
|
+
name: stringValue(whoamiData.name) ?? "",
|
|
1397
|
+
userName: stringValue(whoamiData.userName) ?? ""
|
|
1398
|
+
},
|
|
1399
|
+
orgId: orgData === void 0 ? void 0 : stringValue(orgData.id)
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Classify a TOTAL failure: when every endpoint failed with one class of
|
|
1404
|
+
* error, the degraded per-endpoint view would hide the root cause behind
|
|
1405
|
+
* a generic "partial data" note — name it instead.
|
|
1406
|
+
*/
|
|
1407
|
+
function classifyTotalFailure(failures, failedStatuses) {
|
|
1408
|
+
if (failures.length !== USAGE_ENDPOINT_COUNT) return void 0;
|
|
1409
|
+
const codes = failedStatuses.filter((status) => status !== void 0);
|
|
1410
|
+
if (codes.length === USAGE_ENDPOINT_COUNT && codes.every((code) => code === 401)) return "invalid-key";
|
|
1411
|
+
if (codes.length === USAGE_ENDPOINT_COUNT && codes.every((code) => code >= 500)) return "service-unavailable";
|
|
1412
|
+
if (codes.length === 0) return "network";
|
|
1413
|
+
}
|
|
1414
|
+
/** Build the legacy CLI (`/alpha/generate`) request body for one call. */
|
|
1415
|
+
async function buildCliBody(options, connection, facts) {
|
|
1416
|
+
return {
|
|
1417
|
+
config: {
|
|
1418
|
+
workingDir: connection.workingDir,
|
|
1419
|
+
date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
1420
|
+
environment: `${process.platform}-${process.arch}, Node.js ${process.version}`,
|
|
1421
|
+
structure: [],
|
|
1422
|
+
isGitRepo: false,
|
|
1423
|
+
currentBranch: "",
|
|
1424
|
+
mainBranch: "",
|
|
1425
|
+
gitStatus: "",
|
|
1426
|
+
recentCommits: []
|
|
1427
|
+
},
|
|
1428
|
+
memory: null,
|
|
1429
|
+
taste: null,
|
|
1430
|
+
skills: null,
|
|
1431
|
+
params: {
|
|
1432
|
+
model: options.model,
|
|
1433
|
+
messages: await messagesToCC(options.messages, facts.readImage),
|
|
1434
|
+
tools: (options.tools ?? []).map((tool) => ({
|
|
1435
|
+
type: "function",
|
|
1436
|
+
name: tool.name,
|
|
1437
|
+
description: tool.description,
|
|
1438
|
+
input_schema: tool.parameters
|
|
1439
|
+
})),
|
|
1440
|
+
system: facts.systemText,
|
|
1441
|
+
max_tokens: facts.maxTokens,
|
|
1442
|
+
temperature: options.temperature ?? .3,
|
|
1443
|
+
stream: true,
|
|
1444
|
+
...facts.reasoningEffort ? { reasoning_effort: facts.reasoningEffort } : {}
|
|
1445
|
+
},
|
|
1446
|
+
threadId: randomUUID()
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
/** Build the documented Provider Chat Completions request body for one call. */
|
|
1450
|
+
async function buildOpenAIBody(options, facts) {
|
|
1451
|
+
const openAiMessages = [...facts.systemText ? [{
|
|
1452
|
+
role: "system",
|
|
1453
|
+
content: facts.systemText
|
|
1454
|
+
}] : [], ...await messagesToOpenAI(options.messages, facts.readImage)];
|
|
1455
|
+
const openAiTools = (options.tools ?? []).map((tool) => ({
|
|
1456
|
+
type: "function",
|
|
1457
|
+
function: {
|
|
1458
|
+
name: tool.name,
|
|
1459
|
+
description: tool.description,
|
|
1460
|
+
parameters: tool.parameters
|
|
1461
|
+
}
|
|
1462
|
+
}));
|
|
1463
|
+
return {
|
|
1464
|
+
model: options.model,
|
|
1465
|
+
messages: openAiMessages,
|
|
1466
|
+
...openAiTools.length > 0 ? { tools: openAiTools } : {},
|
|
1467
|
+
max_tokens: facts.maxTokens,
|
|
1468
|
+
temperature: options.temperature ?? .3,
|
|
1469
|
+
stream: true,
|
|
1470
|
+
...facts.reasoningEffort ? { reasoning_effort: facts.reasoningEffort } : {}
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1474
|
+
* One pre-stream connect attempt: POST the body and wait for response
|
|
1475
|
+
* headers only (`requestTimeoutMs` must never bound the body stream — see
|
|
1476
|
+
* the caller). Returns the live response plus its cleanup, or the rejection
|
|
1477
|
+
* facts for the rotation loop to classify. Every failure path cleans up
|
|
1478
|
+
* before returning or throwing; on success the caller-abort listener
|
|
1479
|
+
* outlives the connect phase (it aborts a stalled body read), so the
|
|
1480
|
+
* streaming tail calls cleanup.
|
|
1481
|
+
*/
|
|
1482
|
+
async function connectGenerate(deps, key, protocol, body) {
|
|
1483
|
+
const { options, connection, fetchImpl } = deps;
|
|
1484
|
+
const connectAbort = new AbortController();
|
|
1485
|
+
let connectTimedOut = false;
|
|
1486
|
+
const endpoint = protocol === "cli" ? `${connection.apiBase}/alpha/generate` : `${connection.apiBase}/provider/v1/chat/completions`;
|
|
1487
|
+
const connectTimer = setTimeout(() => {
|
|
1488
|
+
connectTimedOut = true;
|
|
1489
|
+
connectAbort.abort(new DOMException(`Command Code API request to ${endpoint} did not respond within ${connection.requestTimeoutMs}ms`, "TimeoutError"));
|
|
1490
|
+
}, connection.requestTimeoutMs);
|
|
1491
|
+
const onCallerAbort = () => {
|
|
1492
|
+
connectAbort.abort(options.signal?.reason);
|
|
1493
|
+
};
|
|
1494
|
+
if (options.signal) {
|
|
1495
|
+
if (options.signal.aborted) onCallerAbort();
|
|
1496
|
+
else options.signal.addEventListener("abort", onCallerAbort, { once: true });
|
|
1497
|
+
}
|
|
1498
|
+
const cleanup = () => {
|
|
1499
|
+
clearTimeout(connectTimer);
|
|
1500
|
+
if (options.signal) options.signal.removeEventListener("abort", onCallerAbort);
|
|
1501
|
+
};
|
|
1502
|
+
let response;
|
|
1503
|
+
const headers = protocol === "cli" ? {
|
|
1504
|
+
"Content-Type": "application/json",
|
|
1505
|
+
Authorization: `Bearer ${key}`,
|
|
1506
|
+
"x-command-code-version": COMMAND_CODE_CLI_VERSION,
|
|
1507
|
+
"x-cli-environment": "production",
|
|
1508
|
+
"x-project-slug": projectSlugFromPath(connection.workingDir),
|
|
1509
|
+
"x-taste-learning": "true",
|
|
1510
|
+
"x-co-flag": "false",
|
|
1511
|
+
...attributionHeaders()
|
|
1512
|
+
} : {
|
|
1513
|
+
"Content-Type": "application/json",
|
|
1514
|
+
Authorization: `Bearer ${key}`,
|
|
1515
|
+
Accept: "text/event-stream",
|
|
1516
|
+
...attributionHeaders()
|
|
1517
|
+
};
|
|
1518
|
+
try {
|
|
1519
|
+
response = await fetchImpl(endpoint, {
|
|
1520
|
+
method: "POST",
|
|
1521
|
+
headers,
|
|
1522
|
+
body: JSON.stringify(body),
|
|
1523
|
+
signal: connectAbort.signal
|
|
1524
|
+
});
|
|
1525
|
+
clearTimeout(connectTimer);
|
|
1526
|
+
} catch (error) {
|
|
1527
|
+
cleanup();
|
|
1528
|
+
if (options.signal?.aborted) throw error;
|
|
1529
|
+
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 });
|
|
1530
|
+
throw new LlmError(`Command Code API request to ${endpoint} failed: ${errorChain(error)};Command Code API 请求连接失败——通常是网络或代理问题,请检查网络或代理设置后重试`, "TRANSPORT", { cause: error });
|
|
1531
|
+
}
|
|
1532
|
+
if (!response.ok) {
|
|
1533
|
+
const errText = await response.text().catch(() => "");
|
|
1534
|
+
cleanup();
|
|
1535
|
+
const retryAfterMs = parseRetryAfterMs(response.headers.get("retry-after"));
|
|
1536
|
+
return retryAfterMs === void 0 ? {
|
|
1537
|
+
status: response.status,
|
|
1538
|
+
errText
|
|
1539
|
+
} : {
|
|
1540
|
+
status: response.status,
|
|
1541
|
+
errText,
|
|
1542
|
+
retryAfterMs
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
return {
|
|
1546
|
+
response,
|
|
1547
|
+
cleanup
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1550
|
+
/** Fresh block-assembly state for one stream. */
|
|
1551
|
+
function createBlockAssembler() {
|
|
1552
|
+
return {
|
|
1553
|
+
nextIndex: 0,
|
|
1554
|
+
textIndex: -1,
|
|
1555
|
+
textContent: "",
|
|
1556
|
+
reasoningIndex: -1,
|
|
1557
|
+
reasoningContent: "",
|
|
1558
|
+
sawContent: false,
|
|
1559
|
+
openAiToolCalls: []
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
function* closeText(asm) {
|
|
1563
|
+
if (asm.textIndex < 0) return;
|
|
1564
|
+
yield {
|
|
1565
|
+
type: "block-end",
|
|
1566
|
+
index: asm.textIndex,
|
|
1567
|
+
block: {
|
|
1568
|
+
type: "text",
|
|
1569
|
+
text: asm.textContent
|
|
1570
|
+
}
|
|
1571
|
+
};
|
|
1572
|
+
asm.textIndex = -1;
|
|
1573
|
+
asm.textContent = "";
|
|
1574
|
+
}
|
|
1575
|
+
function* closeReasoning(asm) {
|
|
1576
|
+
if (asm.reasoningIndex < 0) return;
|
|
1577
|
+
yield {
|
|
1578
|
+
type: "block-end",
|
|
1579
|
+
index: asm.reasoningIndex,
|
|
1580
|
+
block: {
|
|
1581
|
+
type: "reasoning",
|
|
1582
|
+
text: asm.reasoningContent
|
|
1583
|
+
}
|
|
1584
|
+
};
|
|
1585
|
+
asm.reasoningIndex = -1;
|
|
1586
|
+
asm.reasoningContent = "";
|
|
1587
|
+
}
|
|
1588
|
+
function* emitOpenAiToolCalls(asm) {
|
|
1589
|
+
for (const call of asm.openAiToolCalls) {
|
|
1590
|
+
const id = call.id ?? randomUUID();
|
|
1591
|
+
const name = call.name ?? "";
|
|
1592
|
+
const args = call.arguments || "{}";
|
|
1593
|
+
const index = asm.nextIndex++;
|
|
1594
|
+
asm.sawContent = true;
|
|
1595
|
+
yield {
|
|
1596
|
+
type: "block-start",
|
|
1597
|
+
index,
|
|
1598
|
+
blockType: "tool-call"
|
|
1599
|
+
};
|
|
1600
|
+
yield {
|
|
1601
|
+
type: "tool-call-delta",
|
|
1602
|
+
index,
|
|
1603
|
+
id: ToolCallId(id),
|
|
1604
|
+
name,
|
|
1605
|
+
argumentsDelta: args
|
|
1606
|
+
};
|
|
1607
|
+
yield {
|
|
1608
|
+
type: "block-end",
|
|
1609
|
+
index,
|
|
1610
|
+
block: {
|
|
1611
|
+
type: "tool-call",
|
|
1612
|
+
id: ToolCallId(id),
|
|
1613
|
+
name,
|
|
1614
|
+
arguments: args
|
|
1615
|
+
}
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
asm.openAiToolCalls.length = 0;
|
|
1619
|
+
}
|
|
1326
1620
|
var CommandCodeAdapter = class extends LlmAdapter {
|
|
1327
1621
|
deps;
|
|
1328
1622
|
catalog = [];
|
|
@@ -1405,10 +1699,9 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
1405
1699
|
}
|
|
1406
1700
|
return this.catalog;
|
|
1407
1701
|
}
|
|
1408
|
-
async listModels(provider) {
|
|
1702
|
+
async listModels(provider, opts) {
|
|
1409
1703
|
const catalog = await this.loadCatalog();
|
|
1410
|
-
const
|
|
1411
|
-
return catalog.filter((model) => modelVisibleInPlan(model.id, access)).map((model) => {
|
|
1704
|
+
const toInfo = (model) => {
|
|
1412
1705
|
const vision = KNOWN_IMAGE_MODELS.has(model.id);
|
|
1413
1706
|
return {
|
|
1414
1707
|
provider,
|
|
@@ -1417,7 +1710,12 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
1417
1710
|
description: capabilityDescription(model.id, model.contextWindow),
|
|
1418
1711
|
inputModalities: vision ? ["text", "image"] : ["text"]
|
|
1419
1712
|
};
|
|
1420
|
-
}
|
|
1713
|
+
};
|
|
1714
|
+
if (opts?.unfiltered === true) return catalog.map(toInfo).sort(compareByPlan);
|
|
1715
|
+
const access = this.deps.options().filterModelsByPlan === false ? void 0 : await this.loadBillingAccess();
|
|
1716
|
+
const visible = this.deps.options().visibleModels;
|
|
1717
|
+
const allow = Array.isArray(visible) && visible.length > 0 ? new Set(visible.filter((id) => typeof id === "string" && id !== "")) : void 0;
|
|
1718
|
+
return catalog.filter((model) => modelVisibleInPlan(model.id, access)).filter((model) => allow === void 0 || allow.has(model.id)).map(toInfo).sort(compareByPlan);
|
|
1421
1719
|
}
|
|
1422
1720
|
async resolveModel(provider, model, signal) {
|
|
1423
1721
|
const entry = this.catalog.find((m) => m.id === model) ?? (await this.loadCatalog(signal)).find((m) => m.id === model);
|
|
@@ -1450,6 +1748,25 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
1450
1748
|
};
|
|
1451
1749
|
}
|
|
1452
1750
|
/**
|
|
1751
|
+
* Fetch one account endpoint and parse its JSON body. Returns the HTTP
|
|
1752
|
+
* status alongside the parsed record so each caller applies its own
|
|
1753
|
+
* failure accounting: the billing probe fails open silently, the usage
|
|
1754
|
+
* report books failures per endpoint. Non-2xx and non-record bodies come
|
|
1755
|
+
* back without a record; only a transport throw propagates to the caller.
|
|
1756
|
+
*/
|
|
1757
|
+
async fetchEndpointJson(url, headers) {
|
|
1758
|
+
const response = await this.fetchImpl(url, {
|
|
1759
|
+
headers,
|
|
1760
|
+
signal: AbortSignal.timeout(MODELS_TIMEOUT_MS)
|
|
1761
|
+
});
|
|
1762
|
+
if (!response.ok) return { status: response.status };
|
|
1763
|
+
const parsed = await response.json();
|
|
1764
|
+
return {
|
|
1765
|
+
status: response.status,
|
|
1766
|
+
...isRecord(parsed) ? { record: parsed } : {}
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
/**
|
|
1453
1770
|
* The billing facts behind the picker's plan filter, cached for
|
|
1454
1771
|
* {@link BILLING_ACCESS_TTL_MS} and shared across concurrent callers.
|
|
1455
1772
|
* `undefined` means "unknown — show everything" (fail-open).
|
|
@@ -1491,15 +1808,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
1491
1808
|
const connection = this.deps.options();
|
|
1492
1809
|
const headers = await this.accountHeaders(apiKey);
|
|
1493
1810
|
const base = connection.apiBase;
|
|
1494
|
-
const getJson = async (path) => {
|
|
1495
|
-
const response = await this.fetchImpl(`${base}${path}`, {
|
|
1496
|
-
headers,
|
|
1497
|
-
signal: AbortSignal.timeout(MODELS_TIMEOUT_MS)
|
|
1498
|
-
});
|
|
1499
|
-
if (!response.ok) return void 0;
|
|
1500
|
-
const parsed = await response.json();
|
|
1501
|
-
return isRecord(parsed) ? parsed : void 0;
|
|
1502
|
-
};
|
|
1811
|
+
const getJson = async (path) => (await this.fetchEndpointJson(`${base}${path}`, headers)).record;
|
|
1503
1812
|
const whoami = await getJson("/alpha/whoami");
|
|
1504
1813
|
const orgData = whoami && isRecord(whoami.org) ? whoami.org : void 0;
|
|
1505
1814
|
const orgId = orgData === void 0 ? void 0 : stringValue(orgData.id);
|
|
@@ -1549,7 +1858,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
1549
1858
|
if (forced === "cli" || forced === "openai") return forced;
|
|
1550
1859
|
const cached = this.cachedProtocolUseCli(apiKey);
|
|
1551
1860
|
if (cached !== void 0) return cached ? "cli" : "openai";
|
|
1552
|
-
if (this.cachedBillingTierWeight(apiKey) ===
|
|
1861
|
+
if (this.cachedBillingTierWeight(apiKey) === GO_TIER_WEIGHT) {
|
|
1553
1862
|
this.rememberProtocol(apiKey, true);
|
|
1554
1863
|
return "cli";
|
|
1555
1864
|
}
|
|
@@ -1572,17 +1881,13 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
1572
1881
|
const failedStatuses = [];
|
|
1573
1882
|
const getJson = async (path) => {
|
|
1574
1883
|
try {
|
|
1575
|
-
const
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
if (!response.ok) {
|
|
1580
|
-
failures.push(`${path}: HTTP ${response.status}`);
|
|
1581
|
-
failedStatuses.push(response.status);
|
|
1884
|
+
const { status, record } = await this.fetchEndpointJson(`${base}${path}`, headers);
|
|
1885
|
+
if (record === void 0) {
|
|
1886
|
+
failures.push(`${path}: HTTP ${status}`);
|
|
1887
|
+
failedStatuses.push(status);
|
|
1582
1888
|
return;
|
|
1583
1889
|
}
|
|
1584
|
-
|
|
1585
|
-
return isRecord(parsed) ? parsed : void 0;
|
|
1890
|
+
return record;
|
|
1586
1891
|
} catch (error) {
|
|
1587
1892
|
failures.push(`${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1588
1893
|
failedStatuses.push(void 0);
|
|
@@ -1590,51 +1895,19 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
1590
1895
|
}
|
|
1591
1896
|
};
|
|
1592
1897
|
const report = { failures };
|
|
1593
|
-
const
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
const
|
|
1601
|
-
|
|
1602
|
-
const
|
|
1603
|
-
if (
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
successRate: numberValue(usage.successRate) ?? 0,
|
|
1607
|
-
completedCount: numberValue(usage.completedCount) ?? 0,
|
|
1608
|
-
failedCount: numberValue(usage.failedCount) ?? 0,
|
|
1609
|
-
totalTokensIn: numberValue(usage.totalTokensIn) ?? 0,
|
|
1610
|
-
totalTokensOut: numberValue(usage.totalTokensOut) ?? 0,
|
|
1611
|
-
totalCredits: numberValue(usage.totalCredits) ?? 0,
|
|
1612
|
-
periodBasis: stringValue(usage.periodBasis) ?? "billing-period"
|
|
1613
|
-
};
|
|
1614
|
-
const credits = await getJson("/alpha/billing/credits");
|
|
1615
|
-
const creditsData = credits && isRecord(credits.credits) ? credits.credits : void 0;
|
|
1616
|
-
const windowLimits = credits && isRecord(credits.windowLimits) ? credits.windowLimits : void 0;
|
|
1617
|
-
const fiveHour = windowLimits && isRecord(windowLimits.fiveHour) ? windowLimits.fiveHour : void 0;
|
|
1618
|
-
const weekly = windowLimits && isRecord(windowLimits.weekly) ? windowLimits.weekly : void 0;
|
|
1619
|
-
if (creditsData || fiveHour || weekly) report.credits = {
|
|
1620
|
-
monthlyCredits: numberValue(creditsData?.monthlyCredits) ?? 0,
|
|
1621
|
-
purchasedCredits: numberValue(creditsData?.purchasedCredits) ?? 0,
|
|
1622
|
-
freeCredits: numberValue(creditsData?.freeCredits) ?? 0,
|
|
1623
|
-
fiveHour: {
|
|
1624
|
-
used: numberValue(fiveHour?.used) ?? 0,
|
|
1625
|
-
cap: numberValue(fiveHour?.cap) ?? 0,
|
|
1626
|
-
exceeded: fiveHour?.exceeded === true,
|
|
1627
|
-
resetAt: numberValue(fiveHour?.resetAt) ?? 0
|
|
1628
|
-
},
|
|
1629
|
-
weekly: {
|
|
1630
|
-
used: numberValue(weekly?.used) ?? 0,
|
|
1631
|
-
cap: numberValue(weekly?.cap) ?? 0,
|
|
1632
|
-
exceeded: weekly?.exceeded === true,
|
|
1633
|
-
resetAt: numberValue(weekly?.resetAt) ?? 0
|
|
1634
|
-
}
|
|
1635
|
-
};
|
|
1636
|
-
const subscription = await getJson(orgId === void 0 ? "/alpha/billing/subscriptions" : `/alpha/billing/subscriptions?orgId=${encodeURIComponent(orgId)}`);
|
|
1637
|
-
const subData = subscription && isRecord(subscription.data) ? subscription.data : void 0;
|
|
1898
|
+
const { account, orgId } = parseAccountIdentity(await getJson("/alpha/whoami"));
|
|
1899
|
+
if (account !== void 0) report.account = account;
|
|
1900
|
+
const [usage, credits, subscription] = await Promise.all([
|
|
1901
|
+
getJson("/alpha/usage/summary"),
|
|
1902
|
+
getJson("/alpha/billing/credits"),
|
|
1903
|
+
getJson(orgId === void 0 ? "/alpha/billing/subscriptions" : `/alpha/billing/subscriptions?orgId=${encodeURIComponent(orgId)}`)
|
|
1904
|
+
]);
|
|
1905
|
+
const totals = parseUsageTotals(usage);
|
|
1906
|
+
if (totals !== void 0) report.usage = totals;
|
|
1907
|
+
const limits = parseCreditLimits(credits);
|
|
1908
|
+
if (limits !== void 0) report.credits = limits;
|
|
1909
|
+
const subData = subscription !== void 0 && isRecord(subscription.data) ? subscription.data : void 0;
|
|
1910
|
+
const creditsData = credits !== void 0 && isRecord(credits.credits) ? credits.credits : void 0;
|
|
1638
1911
|
const planId = stringValue(subData?.planId) ?? stringValue(creditsData?.planId);
|
|
1639
1912
|
if (subData !== void 0 || planId !== void 0) {
|
|
1640
1913
|
const info = planId === void 0 ? void 0 : subscriptionPlanInfo(planId);
|
|
@@ -1646,12 +1919,8 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
1646
1919
|
currentPeriodEnd: periodEndValue(subData?.currentPeriodEnd)
|
|
1647
1920
|
};
|
|
1648
1921
|
}
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
if (codes.length === USAGE_ENDPOINT_COUNT && codes.every((code) => code === 401)) report.blocked = "invalid-key";
|
|
1652
|
-
else if (codes.length === USAGE_ENDPOINT_COUNT && codes.every((code) => code >= 500)) report.blocked = "service-unavailable";
|
|
1653
|
-
else if (codes.length === 0) report.blocked = "network";
|
|
1654
|
-
}
|
|
1922
|
+
const blocked = classifyTotalFailure(failures, failedStatuses);
|
|
1923
|
+
if (blocked !== void 0) report.blocked = blocked;
|
|
1655
1924
|
return report;
|
|
1656
1925
|
}
|
|
1657
1926
|
/**
|
|
@@ -1702,134 +1971,28 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
1702
1971
|
const reasoningEffort = effort && effort !== "off" && supported?.includes(effort) ? effort : void 0;
|
|
1703
1972
|
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");
|
|
1704
1973
|
let protocol = this.resolveProtocol(apiKey);
|
|
1705
|
-
const buildBody = async (target) => {
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
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: {
|
|
1745
|
-
name: tool.name,
|
|
1746
|
-
description: tool.description,
|
|
1747
|
-
parameters: tool.parameters
|
|
1748
|
-
}
|
|
1749
|
-
}));
|
|
1750
|
-
return {
|
|
1751
|
-
model: options.model,
|
|
1752
|
-
messages: openAiMessages,
|
|
1753
|
-
...openAiTools.length > 0 ? { tools: openAiTools } : {},
|
|
1754
|
-
max_tokens: maxTokens,
|
|
1755
|
-
temperature: options.temperature ?? .3,
|
|
1756
|
-
stream: true,
|
|
1757
|
-
...reasoningEffort ? { reasoning_effort: reasoningEffort } : {}
|
|
1758
|
-
};
|
|
1759
|
-
};
|
|
1974
|
+
const buildBody = async (target) => target === "cli" ? buildCliBody(options, connection, {
|
|
1975
|
+
maxTokens,
|
|
1976
|
+
reasoningEffort,
|
|
1977
|
+
systemText,
|
|
1978
|
+
readImage
|
|
1979
|
+
}) : buildOpenAIBody(options, {
|
|
1980
|
+
maxTokens,
|
|
1981
|
+
reasoningEffort,
|
|
1982
|
+
systemText,
|
|
1983
|
+
readImage
|
|
1984
|
+
});
|
|
1760
1985
|
let body = await buildBody(protocol);
|
|
1761
|
-
const connect = async (key) => {
|
|
1762
|
-
const connectAbort = new AbortController();
|
|
1763
|
-
let connectTimedOut = false;
|
|
1764
|
-
const endpoint = protocol === "cli" ? `${connection.apiBase}/alpha/generate` : `${connection.apiBase}/provider/v1/chat/completions`;
|
|
1765
|
-
const connectTimer = setTimeout(() => {
|
|
1766
|
-
connectTimedOut = true;
|
|
1767
|
-
connectAbort.abort(new DOMException(`Command Code API request to ${endpoint} did not respond within ${connection.requestTimeoutMs}ms`, "TimeoutError"));
|
|
1768
|
-
}, connection.requestTimeoutMs);
|
|
1769
|
-
const onCallerAbort = () => {
|
|
1770
|
-
connectAbort.abort(options.signal?.reason);
|
|
1771
|
-
};
|
|
1772
|
-
if (options.signal) {
|
|
1773
|
-
if (options.signal.aborted) onCallerAbort();
|
|
1774
|
-
else options.signal.addEventListener("abort", onCallerAbort, { once: true });
|
|
1775
|
-
}
|
|
1776
|
-
const cleanup = () => {
|
|
1777
|
-
clearTimeout(connectTimer);
|
|
1778
|
-
if (options.signal) options.signal.removeEventListener("abort", onCallerAbort);
|
|
1779
|
-
};
|
|
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
|
-
};
|
|
1796
|
-
try {
|
|
1797
|
-
response = await this.fetchImpl(endpoint, {
|
|
1798
|
-
method: "POST",
|
|
1799
|
-
headers,
|
|
1800
|
-
body: JSON.stringify(body),
|
|
1801
|
-
signal: connectAbort.signal
|
|
1802
|
-
});
|
|
1803
|
-
clearTimeout(connectTimer);
|
|
1804
|
-
} catch (error) {
|
|
1805
|
-
cleanup();
|
|
1806
|
-
if (options.signal?.aborted) throw 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 });
|
|
1809
|
-
}
|
|
1810
|
-
if (!response.ok) {
|
|
1811
|
-
const errText = await response.text().catch(() => "");
|
|
1812
|
-
cleanup();
|
|
1813
|
-
const retryAfterMs = parseRetryAfterMs(response.headers.get("retry-after"));
|
|
1814
|
-
return retryAfterMs === void 0 ? {
|
|
1815
|
-
status: response.status,
|
|
1816
|
-
errText
|
|
1817
|
-
} : {
|
|
1818
|
-
status: response.status,
|
|
1819
|
-
errText,
|
|
1820
|
-
retryAfterMs
|
|
1821
|
-
};
|
|
1822
|
-
}
|
|
1823
|
-
return {
|
|
1824
|
-
response,
|
|
1825
|
-
cleanup
|
|
1826
|
-
};
|
|
1827
|
-
};
|
|
1828
1986
|
const tried = /* @__PURE__ */ new Set();
|
|
1987
|
+
const connectDeps = {
|
|
1988
|
+
options,
|
|
1989
|
+
connection,
|
|
1990
|
+
fetchImpl: this.fetchImpl
|
|
1991
|
+
};
|
|
1829
1992
|
let connected;
|
|
1830
1993
|
for (;;) {
|
|
1831
1994
|
tried.add(apiKey);
|
|
1832
|
-
const attempt = await
|
|
1995
|
+
const attempt = await connectGenerate(connectDeps, apiKey, protocol, body);
|
|
1833
1996
|
if ("response" in attempt) {
|
|
1834
1997
|
connected = attempt;
|
|
1835
1998
|
break;
|
|
@@ -1850,6 +2013,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
1850
2013
|
}
|
|
1851
2014
|
throw generateHttpError(attempt.status, attempt.errText, attempt.retryAfterMs);
|
|
1852
2015
|
}
|
|
2016
|
+
if (connected === void 0) throw new LlmError("Command Code API connection failed without a response", "TRANSPORT");
|
|
1853
2017
|
const { response, cleanup } = connected;
|
|
1854
2018
|
if (!response.body) {
|
|
1855
2019
|
cleanup();
|
|
@@ -1873,276 +2037,8 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
1873
2037
|
idleTimer = void 0;
|
|
1874
2038
|
}
|
|
1875
2039
|
};
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
let textContent = "";
|
|
1879
|
-
let reasoningIndex = -1;
|
|
1880
|
-
let reasoningContent = "";
|
|
1881
|
-
let sawContent = false;
|
|
1882
|
-
const closeText = function* () {
|
|
1883
|
-
if (textIndex < 0) return;
|
|
1884
|
-
yield {
|
|
1885
|
-
type: "block-end",
|
|
1886
|
-
index: textIndex,
|
|
1887
|
-
block: {
|
|
1888
|
-
type: "text",
|
|
1889
|
-
text: textContent
|
|
1890
|
-
}
|
|
1891
|
-
};
|
|
1892
|
-
textIndex = -1;
|
|
1893
|
-
textContent = "";
|
|
1894
|
-
};
|
|
1895
|
-
const closeReasoning = function* () {
|
|
1896
|
-
if (reasoningIndex < 0) return;
|
|
1897
|
-
yield {
|
|
1898
|
-
type: "block-end",
|
|
1899
|
-
index: reasoningIndex,
|
|
1900
|
-
block: {
|
|
1901
|
-
type: "reasoning",
|
|
1902
|
-
text: reasoningContent
|
|
1903
|
-
}
|
|
1904
|
-
};
|
|
1905
|
-
reasoningIndex = -1;
|
|
1906
|
-
reasoningContent = "";
|
|
1907
|
-
};
|
|
1908
|
-
const handleCliEvent = (event) => {
|
|
1909
|
-
const chunks = [];
|
|
1910
|
-
if (!isRecord(event)) return chunks;
|
|
1911
|
-
switch (event.type) {
|
|
1912
|
-
case "text-delta": {
|
|
1913
|
-
chunks.push(...closeReasoning());
|
|
1914
|
-
if (textIndex < 0) {
|
|
1915
|
-
textIndex = nextIndex++;
|
|
1916
|
-
chunks.push({
|
|
1917
|
-
type: "block-start",
|
|
1918
|
-
index: textIndex,
|
|
1919
|
-
blockType: "text"
|
|
1920
|
-
});
|
|
1921
|
-
}
|
|
1922
|
-
const delta = stringValue(event.text) ?? "";
|
|
1923
|
-
textContent += delta;
|
|
1924
|
-
sawContent = true;
|
|
1925
|
-
chunks.push({
|
|
1926
|
-
type: "text-delta",
|
|
1927
|
-
index: textIndex,
|
|
1928
|
-
text: delta
|
|
1929
|
-
});
|
|
1930
|
-
break;
|
|
1931
|
-
}
|
|
1932
|
-
case "reasoning-delta": {
|
|
1933
|
-
chunks.push(...closeText());
|
|
1934
|
-
if (reasoningIndex < 0) {
|
|
1935
|
-
reasoningIndex = nextIndex++;
|
|
1936
|
-
chunks.push({
|
|
1937
|
-
type: "block-start",
|
|
1938
|
-
index: reasoningIndex,
|
|
1939
|
-
blockType: "reasoning"
|
|
1940
|
-
});
|
|
1941
|
-
}
|
|
1942
|
-
const delta = stringValue(event.text) ?? "";
|
|
1943
|
-
reasoningContent += delta;
|
|
1944
|
-
chunks.push({
|
|
1945
|
-
type: "reasoning-delta",
|
|
1946
|
-
index: reasoningIndex,
|
|
1947
|
-
text: delta
|
|
1948
|
-
});
|
|
1949
|
-
break;
|
|
1950
|
-
}
|
|
1951
|
-
case "reasoning-start":
|
|
1952
|
-
chunks.push(...closeText());
|
|
1953
|
-
break;
|
|
1954
|
-
case "reasoning-end":
|
|
1955
|
-
chunks.push(...closeReasoning());
|
|
1956
|
-
break;
|
|
1957
|
-
case "tool-call": {
|
|
1958
|
-
chunks.push(...closeText(), ...closeReasoning());
|
|
1959
|
-
const id = stringValue(event.toolCallId) ?? randomUUID();
|
|
1960
|
-
const name = stringValue(event.toolName) ?? "";
|
|
1961
|
-
const args = JSON.stringify(recordOrEmpty(event.input ?? event.args ?? event.arguments));
|
|
1962
|
-
const index = nextIndex++;
|
|
1963
|
-
sawContent = true;
|
|
1964
|
-
chunks.push({
|
|
1965
|
-
type: "block-start",
|
|
1966
|
-
index,
|
|
1967
|
-
blockType: "tool-call"
|
|
1968
|
-
}, {
|
|
1969
|
-
type: "tool-call-delta",
|
|
1970
|
-
index,
|
|
1971
|
-
id: ToolCallId(id),
|
|
1972
|
-
name,
|
|
1973
|
-
argumentsDelta: args
|
|
1974
|
-
}, {
|
|
1975
|
-
type: "block-end",
|
|
1976
|
-
index,
|
|
1977
|
-
block: {
|
|
1978
|
-
type: "tool-call",
|
|
1979
|
-
id: ToolCallId(id),
|
|
1980
|
-
name,
|
|
1981
|
-
arguments: args
|
|
1982
|
-
}
|
|
1983
|
-
});
|
|
1984
|
-
break;
|
|
1985
|
-
}
|
|
1986
|
-
case "finish": {
|
|
1987
|
-
chunks.push(...closeText(), ...closeReasoning());
|
|
1988
|
-
const usage = isRecord(event.totalUsage) ? event.totalUsage : void 0;
|
|
1989
|
-
if (usage) {
|
|
1990
|
-
const details = isRecord(usage.inputTokenDetails) ? usage.inputTokenDetails : void 0;
|
|
1991
|
-
const totalInput = numberValue(usage.inputTokens) ?? 0;
|
|
1992
|
-
const cacheRead = numberValue(details?.cacheReadTokens) ?? 0;
|
|
1993
|
-
const cacheWrite = numberValue(details?.cacheWriteTokens) ?? 0;
|
|
1994
|
-
const tokenUsage = {
|
|
1995
|
-
inputTokens: numberValue(details?.noCacheTokens) ?? Math.max(0, totalInput - cacheRead - cacheWrite),
|
|
1996
|
-
outputTokens: numberValue(usage.outputTokens) ?? 0,
|
|
1997
|
-
cacheReadTokens: cacheRead,
|
|
1998
|
-
cacheWriteTokens: cacheWrite
|
|
1999
|
-
};
|
|
2000
|
-
chunks.push({
|
|
2001
|
-
type: "usage",
|
|
2002
|
-
usage: tokenUsage
|
|
2003
|
-
});
|
|
2004
|
-
}
|
|
2005
|
-
chunks.push({
|
|
2006
|
-
type: "finish",
|
|
2007
|
-
reason: mapFinishReason(event.finishReason)
|
|
2008
|
-
});
|
|
2009
|
-
break;
|
|
2010
|
-
}
|
|
2011
|
-
case "error": {
|
|
2012
|
-
const err = isRecord(event.error) ? event.error : void 0;
|
|
2013
|
-
const detail = isRecord(event.error) ? stringValue(event.error.message) ?? JSON.stringify(event.error) : stringValue(event.error) ?? stringValue(event.message) ?? "Stream error";
|
|
2014
|
-
const statusCode = err ? numberValue(err.statusCode) : void 0;
|
|
2015
|
-
const isRetryable = err ? booleanValue(err.isRetryable) : void 0;
|
|
2016
|
-
const retryableStatus = statusCode !== void 0 && (statusCode === 429 || statusCode >= 500);
|
|
2017
|
-
const terminal = hasTerminalStreamMarker(detail);
|
|
2018
|
-
if (!(isRetryable === true || (statusCode !== void 0 ? retryableStatus : isRetryable !== false && !terminal))) throw new LlmError(`Command Code stream error: ${detail}`, "PROVIDER_STREAM_ERROR", statusCode !== void 0 ? { status: statusCode } : void 0);
|
|
2019
|
-
throw new LlmError(`Command Code stream error: ${detail}`, "SERVER", statusCode !== void 0 ? { status: statusCode } : void 0);
|
|
2020
|
-
}
|
|
2021
|
-
}
|
|
2022
|
-
return chunks;
|
|
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);
|
|
2040
|
+
const asm = createBlockAssembler();
|
|
2041
|
+
const handle = (event) => handleEvent(asm, protocol, event);
|
|
2146
2042
|
try {
|
|
2147
2043
|
let finished = false;
|
|
2148
2044
|
for (;;) {
|
|
@@ -2159,14 +2055,17 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
2159
2055
|
const { done, value } = read;
|
|
2160
2056
|
if (done) {
|
|
2161
2057
|
if (idleFired) throw new LlmError(`Command Code API stream from ${connection.apiBase} was idle for ${connection.streamIdleTimeoutMs}ms (no events) and was treated as a dead connection;Command Code API 流式响应已 ${connection.streamIdleTimeoutMs} 毫秒无任何事件,被判定为死连接——长思考模型可在设置中调大流空闲超时`, "TIMEOUT");
|
|
2162
|
-
if (buffer.trim()) for (const chunk of
|
|
2058
|
+
if (buffer.trim()) for (const chunk of handle(parseStreamEventLine(buffer))) {
|
|
2059
|
+
yield chunk;
|
|
2060
|
+
if (chunk.type === "finish") finished = true;
|
|
2061
|
+
}
|
|
2163
2062
|
break;
|
|
2164
2063
|
}
|
|
2165
2064
|
buffer += decoder.decode(value, { stream: true });
|
|
2166
2065
|
const lines = buffer.split("\n");
|
|
2167
2066
|
buffer = lines.pop() ?? "";
|
|
2168
2067
|
for (const line of lines) {
|
|
2169
|
-
const chunks =
|
|
2068
|
+
const chunks = handle(parseStreamEventLine(line));
|
|
2170
2069
|
for (const chunk of chunks) {
|
|
2171
2070
|
yield chunk;
|
|
2172
2071
|
if (chunk.type === "finish") finished = true;
|
|
@@ -2175,10 +2074,10 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
2175
2074
|
if (finished) break;
|
|
2176
2075
|
}
|
|
2177
2076
|
if (!finished) {
|
|
2178
|
-
yield* closeText();
|
|
2179
|
-
yield* closeReasoning();
|
|
2180
|
-
if (protocol === "openai") yield* emitOpenAiToolCalls();
|
|
2181
|
-
if (!sawContent) throw new LlmError("Command Code returned an empty response;Command Code 返回了空响应,重试通常可恢复", "EMPTY_RESPONSE");
|
|
2077
|
+
yield* closeText(asm);
|
|
2078
|
+
yield* closeReasoning(asm);
|
|
2079
|
+
if (protocol === "openai") yield* emitOpenAiToolCalls(asm);
|
|
2080
|
+
if (!asm.sawContent) throw new LlmError("Command Code returned an empty response;Command Code 返回了空响应,重试通常可恢复", "EMPTY_RESPONSE");
|
|
2182
2081
|
yield {
|
|
2183
2082
|
type: "finish",
|
|
2184
2083
|
reason: { kind: "stop" }
|
|
@@ -2192,8 +2091,6 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
2192
2091
|
}
|
|
2193
2092
|
}
|
|
2194
2093
|
};
|
|
2195
|
-
/** Hard cap on account rotations within one request (one attempt per distinct key). */
|
|
2196
|
-
const MAX_ACCOUNT_ROTATIONS = 16;
|
|
2197
2094
|
/**
|
|
2198
2095
|
* Map a pre-stream generate HTTP failure onto a stable LlmError. Command
|
|
2199
2096
|
* Code folds several business rejections into 403 (plan limits, CLI version,
|
|
@@ -2218,6 +2115,223 @@ function generateHttpError(status, errText, retryAfterMs) {
|
|
|
2218
2115
|
});
|
|
2219
2116
|
}
|
|
2220
2117
|
/**
|
|
2118
|
+
* Handle one CLI-transport (`/alpha/generate`) stream event, appending
|
|
2119
|
+
* harness StreamChunks. Pure over the passed assembler — no stream() locals.
|
|
2120
|
+
*/
|
|
2121
|
+
function handleCliEvent(asm, event) {
|
|
2122
|
+
const chunks = [];
|
|
2123
|
+
if (!isRecord(event)) return chunks;
|
|
2124
|
+
switch (event.type) {
|
|
2125
|
+
case "text-delta": {
|
|
2126
|
+
chunks.push(...closeReasoning(asm));
|
|
2127
|
+
if (asm.textIndex < 0) {
|
|
2128
|
+
asm.textIndex = asm.nextIndex++;
|
|
2129
|
+
chunks.push({
|
|
2130
|
+
type: "block-start",
|
|
2131
|
+
index: asm.textIndex,
|
|
2132
|
+
blockType: "text"
|
|
2133
|
+
});
|
|
2134
|
+
}
|
|
2135
|
+
const delta = stringValue(event.text) ?? "";
|
|
2136
|
+
asm.textContent += delta;
|
|
2137
|
+
asm.sawContent = true;
|
|
2138
|
+
chunks.push({
|
|
2139
|
+
type: "text-delta",
|
|
2140
|
+
index: asm.textIndex,
|
|
2141
|
+
text: delta
|
|
2142
|
+
});
|
|
2143
|
+
break;
|
|
2144
|
+
}
|
|
2145
|
+
case "reasoning-delta": {
|
|
2146
|
+
chunks.push(...closeText(asm));
|
|
2147
|
+
if (asm.reasoningIndex < 0) {
|
|
2148
|
+
asm.reasoningIndex = asm.nextIndex++;
|
|
2149
|
+
chunks.push({
|
|
2150
|
+
type: "block-start",
|
|
2151
|
+
index: asm.reasoningIndex,
|
|
2152
|
+
blockType: "reasoning"
|
|
2153
|
+
});
|
|
2154
|
+
}
|
|
2155
|
+
const delta = stringValue(event.text) ?? "";
|
|
2156
|
+
asm.reasoningContent += delta;
|
|
2157
|
+
chunks.push({
|
|
2158
|
+
type: "reasoning-delta",
|
|
2159
|
+
index: asm.reasoningIndex,
|
|
2160
|
+
text: delta
|
|
2161
|
+
});
|
|
2162
|
+
break;
|
|
2163
|
+
}
|
|
2164
|
+
case "reasoning-start":
|
|
2165
|
+
chunks.push(...closeText(asm));
|
|
2166
|
+
break;
|
|
2167
|
+
case "reasoning-end":
|
|
2168
|
+
chunks.push(...closeReasoning(asm));
|
|
2169
|
+
break;
|
|
2170
|
+
case "tool-call": {
|
|
2171
|
+
chunks.push(...closeText(asm), ...closeReasoning(asm));
|
|
2172
|
+
const id = stringValue(event.toolCallId) ?? randomUUID();
|
|
2173
|
+
const name = stringValue(event.toolName) ?? "";
|
|
2174
|
+
const args = JSON.stringify(recordOrEmpty(event.input ?? event.args ?? event.arguments));
|
|
2175
|
+
const index = asm.nextIndex++;
|
|
2176
|
+
asm.sawContent = true;
|
|
2177
|
+
chunks.push({
|
|
2178
|
+
type: "block-start",
|
|
2179
|
+
index,
|
|
2180
|
+
blockType: "tool-call"
|
|
2181
|
+
}, {
|
|
2182
|
+
type: "tool-call-delta",
|
|
2183
|
+
index,
|
|
2184
|
+
id: ToolCallId(id),
|
|
2185
|
+
name,
|
|
2186
|
+
argumentsDelta: args
|
|
2187
|
+
}, {
|
|
2188
|
+
type: "block-end",
|
|
2189
|
+
index,
|
|
2190
|
+
block: {
|
|
2191
|
+
type: "tool-call",
|
|
2192
|
+
id: ToolCallId(id),
|
|
2193
|
+
name,
|
|
2194
|
+
arguments: args
|
|
2195
|
+
}
|
|
2196
|
+
});
|
|
2197
|
+
break;
|
|
2198
|
+
}
|
|
2199
|
+
case "finish": {
|
|
2200
|
+
chunks.push(...closeText(asm), ...closeReasoning(asm));
|
|
2201
|
+
const usage = isRecord(event.totalUsage) ? event.totalUsage : void 0;
|
|
2202
|
+
if (usage) {
|
|
2203
|
+
const details = isRecord(usage.inputTokenDetails) ? usage.inputTokenDetails : void 0;
|
|
2204
|
+
const totalInput = numberValue(usage.inputTokens) ?? 0;
|
|
2205
|
+
const cacheRead = numberValue(details?.cacheReadTokens) ?? 0;
|
|
2206
|
+
const cacheWrite = numberValue(details?.cacheWriteTokens) ?? 0;
|
|
2207
|
+
const tokenUsage = {
|
|
2208
|
+
inputTokens: numberValue(details?.noCacheTokens) ?? Math.max(0, totalInput - cacheRead - cacheWrite),
|
|
2209
|
+
outputTokens: numberValue(usage.outputTokens) ?? 0,
|
|
2210
|
+
cacheReadTokens: cacheRead,
|
|
2211
|
+
cacheWriteTokens: cacheWrite
|
|
2212
|
+
};
|
|
2213
|
+
chunks.push({
|
|
2214
|
+
type: "usage",
|
|
2215
|
+
usage: tokenUsage
|
|
2216
|
+
});
|
|
2217
|
+
}
|
|
2218
|
+
chunks.push({
|
|
2219
|
+
type: "finish",
|
|
2220
|
+
reason: mapFinishReason(event.finishReason)
|
|
2221
|
+
});
|
|
2222
|
+
break;
|
|
2223
|
+
}
|
|
2224
|
+
case "error": {
|
|
2225
|
+
const err = isRecord(event.error) ? event.error : void 0;
|
|
2226
|
+
const detail = isRecord(event.error) ? stringValue(event.error.message) ?? JSON.stringify(event.error) : stringValue(event.error) ?? stringValue(event.message) ?? "Stream error";
|
|
2227
|
+
const statusCode = err ? numberValue(err.statusCode) : void 0;
|
|
2228
|
+
const isRetryable = err ? booleanValue(err.isRetryable) : void 0;
|
|
2229
|
+
const retryableStatus = statusCode !== void 0 && (statusCode === 429 || statusCode >= 500);
|
|
2230
|
+
const terminal = hasTerminalStreamMarker(detail);
|
|
2231
|
+
if (!(isRetryable === true || (statusCode !== void 0 ? retryableStatus : isRetryable !== false && !terminal))) throw new LlmError(`Command Code stream error: ${detail}`, "PROVIDER_STREAM_ERROR", statusCode !== void 0 ? { status: statusCode } : void 0);
|
|
2232
|
+
throw new LlmError(`Command Code stream error: ${detail}`, "SERVER", statusCode !== void 0 ? { status: statusCode } : void 0);
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
return chunks;
|
|
2236
|
+
}
|
|
2237
|
+
/**
|
|
2238
|
+
* Handle one OpenAI-transport (`/provider/v1/chat/completions`) SSE event.
|
|
2239
|
+
* Same assembler contract as {@link handleCliEvent}; tool-call fragments
|
|
2240
|
+
* buffer on the assembler and flush at finish.
|
|
2241
|
+
*/
|
|
2242
|
+
function handleOpenAIEvent(asm, event) {
|
|
2243
|
+
const chunks = [];
|
|
2244
|
+
if (!isRecord(event)) return chunks;
|
|
2245
|
+
const choices = event.choices;
|
|
2246
|
+
if (!Array.isArray(choices) || choices.length === 0) {
|
|
2247
|
+
if (event.usage !== void 0) chunks.push({
|
|
2248
|
+
type: "usage",
|
|
2249
|
+
usage: mapOpenAIUsage(event.usage)
|
|
2250
|
+
});
|
|
2251
|
+
return chunks;
|
|
2252
|
+
}
|
|
2253
|
+
const choice = isRecord(choices[0]) ? choices[0] : {};
|
|
2254
|
+
const delta = isRecord(choice.delta) ? choice.delta : {};
|
|
2255
|
+
const reasoningDelta = stringValue(delta.reasoning) ?? stringValue(delta.reasoning_content) ?? "";
|
|
2256
|
+
if (reasoningDelta !== "") {
|
|
2257
|
+
chunks.push(...closeText(asm));
|
|
2258
|
+
if (asm.reasoningIndex < 0) {
|
|
2259
|
+
asm.reasoningIndex = asm.nextIndex++;
|
|
2260
|
+
chunks.push({
|
|
2261
|
+
type: "block-start",
|
|
2262
|
+
index: asm.reasoningIndex,
|
|
2263
|
+
blockType: "reasoning"
|
|
2264
|
+
});
|
|
2265
|
+
}
|
|
2266
|
+
asm.reasoningContent += reasoningDelta;
|
|
2267
|
+
chunks.push({
|
|
2268
|
+
type: "reasoning-delta",
|
|
2269
|
+
index: asm.reasoningIndex,
|
|
2270
|
+
text: reasoningDelta
|
|
2271
|
+
});
|
|
2272
|
+
}
|
|
2273
|
+
const contentDelta = stringValue(delta.content) ?? "";
|
|
2274
|
+
if (contentDelta !== "") {
|
|
2275
|
+
chunks.push(...closeReasoning(asm));
|
|
2276
|
+
if (asm.textIndex < 0) {
|
|
2277
|
+
asm.textIndex = asm.nextIndex++;
|
|
2278
|
+
chunks.push({
|
|
2279
|
+
type: "block-start",
|
|
2280
|
+
index: asm.textIndex,
|
|
2281
|
+
blockType: "text"
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2284
|
+
asm.textContent += contentDelta;
|
|
2285
|
+
asm.sawContent = true;
|
|
2286
|
+
chunks.push({
|
|
2287
|
+
type: "text-delta",
|
|
2288
|
+
index: asm.textIndex,
|
|
2289
|
+
text: contentDelta
|
|
2290
|
+
});
|
|
2291
|
+
}
|
|
2292
|
+
if (Array.isArray(delta.tool_calls)) {
|
|
2293
|
+
asm.sawContent = true;
|
|
2294
|
+
for (const rawCall of delta.tool_calls) {
|
|
2295
|
+
if (!isRecord(rawCall)) continue;
|
|
2296
|
+
const callIndex = numberValue(rawCall.index) ?? 0;
|
|
2297
|
+
let existing = asm.openAiToolCalls.find((call) => call.index === callIndex);
|
|
2298
|
+
const fn = isRecord(rawCall.function) ? rawCall.function : void 0;
|
|
2299
|
+
const id = stringValue(rawCall.id);
|
|
2300
|
+
const name = fn === void 0 ? void 0 : stringValue(fn.name);
|
|
2301
|
+
const argDelta = fn === void 0 ? void 0 : stringValue(fn.arguments) ?? (fn.arguments === void 0 ? "" : JSON.stringify(fn.arguments));
|
|
2302
|
+
if (!existing) {
|
|
2303
|
+
existing = {
|
|
2304
|
+
index: callIndex,
|
|
2305
|
+
name: name ?? "",
|
|
2306
|
+
arguments: argDelta ?? ""
|
|
2307
|
+
};
|
|
2308
|
+
if (id !== void 0) existing.id = id;
|
|
2309
|
+
asm.openAiToolCalls.push(existing);
|
|
2310
|
+
} else {
|
|
2311
|
+
if (id !== void 0 && existing.id === void 0) existing.id = id;
|
|
2312
|
+
if (name !== void 0 && existing.name === "") existing.name = name;
|
|
2313
|
+
if (argDelta !== void 0) existing.arguments += argDelta;
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
if (choice.finish_reason !== void 0 && choice.finish_reason !== null) {
|
|
2318
|
+
chunks.push(...closeText(asm), ...closeReasoning(asm), ...emitOpenAiToolCalls(asm));
|
|
2319
|
+
if (event.usage !== void 0) chunks.push({
|
|
2320
|
+
type: "usage",
|
|
2321
|
+
usage: mapOpenAIUsage(event.usage)
|
|
2322
|
+
});
|
|
2323
|
+
chunks.push({
|
|
2324
|
+
type: "finish",
|
|
2325
|
+
reason: mapFinishReason(choice.finish_reason)
|
|
2326
|
+
});
|
|
2327
|
+
}
|
|
2328
|
+
return chunks;
|
|
2329
|
+
}
|
|
2330
|
+
/** Dispatch one parsed stream event to the active transport's handler. */
|
|
2331
|
+
function handleEvent(asm, protocol, event) {
|
|
2332
|
+
return protocol === "openai" ? handleOpenAIEvent(asm, event) : handleCliEvent(asm, event);
|
|
2333
|
+
}
|
|
2334
|
+
/**
|
|
2221
2335
|
* Parse an HTTP `Retry-After` value (delay-seconds or an HTTP-date) into
|
|
2222
2336
|
* milliseconds; undefined when absent or unparseable. An HTTP-date in the
|
|
2223
2337
|
* past yields 0, which the caller drops (LlmError wants a positive delay).
|
|
@@ -2237,13 +2351,14 @@ function parseRetryAfterMs(value, now = Date.now()) {
|
|
|
2237
2351
|
const date = Date.parse(trimmed);
|
|
2238
2352
|
if (!Number.isNaN(date)) return Math.max(0, date - now);
|
|
2239
2353
|
}
|
|
2354
|
+
/**
|
|
2355
|
+
* Map a stream finish reason onto the harness taxonomy. The two transports
|
|
2356
|
+
* spell tool-calls differently (`tool-calls` on the CLI transport,
|
|
2357
|
+
* `tool_calls` on the OpenAI transport) but share every other reason, so
|
|
2358
|
+
* one function serves both — a new reason cannot drift between them.
|
|
2359
|
+
*/
|
|
2240
2360
|
function mapFinishReason(reason) {
|
|
2241
|
-
if (reason === "tool-calls") return { kind: "tool-calls" };
|
|
2242
|
-
if (reason === "length" || reason === "max_tokens" || reason === "max-tokens" || reason === "max_output_tokens") return { kind: "max-tokens" };
|
|
2243
|
-
return { kind: "stop" };
|
|
2244
|
-
}
|
|
2245
|
-
function mapOpenAIFinishReason(reason) {
|
|
2246
|
-
if (reason === "tool_calls" || reason === "tool-calls") return { kind: "tool-calls" };
|
|
2361
|
+
if (reason === "tool-calls" || reason === "tool_calls") return { kind: "tool-calls" };
|
|
2247
2362
|
if (reason === "length" || reason === "max_tokens" || reason === "max-tokens" || reason === "max_output_tokens") return { kind: "max-tokens" };
|
|
2248
2363
|
return { kind: "stop" };
|
|
2249
2364
|
}
|
|
@@ -2322,8 +2437,8 @@ const commandcodeCommand = {
|
|
|
2322
2437
|
exceededWarning: " ⚠️ 超限!",
|
|
2323
2438
|
resetSuffix: "重置 {when}",
|
|
2324
2439
|
partialFailures: "⚠️ 部分端点失败: {list}",
|
|
2325
|
-
noData: "
|
|
2326
|
-
errorText: "
|
|
2440
|
+
noData: "(无数据 — 请检查 API 密钥)",
|
|
2441
|
+
errorText: "获取 Command Code 用量失败:{message}",
|
|
2327
2442
|
imageGate: "当前会话已包含图片,而模型 {model} 不支持图片输入;请选择支持图片的模型,或先移除会话中的图片。"
|
|
2328
2443
|
},
|
|
2329
2444
|
en: {
|
|
@@ -2497,7 +2612,7 @@ const REMOTE_SERVICE = "commandcodeUsage";
|
|
|
2497
2612
|
/** The wire namespace all Command Code endpoints share. */
|
|
2498
2613
|
const REMOTE_NAMESPACE = "commandcode";
|
|
2499
2614
|
/**
|
|
2500
|
-
* Build the validator helpers one endpoint uses
|
|
2615
|
+
* Build the validator helpers one endpoint uses. `prefix` names the
|
|
2501
2616
|
* endpoint in the rejection message (e.g. `commandcode/report result:`), so
|
|
2502
2617
|
* each wire file keeps its own diagnostic phrasing while sharing the helper
|
|
2503
2618
|
* bodies.
|
|
@@ -2553,14 +2668,13 @@ const USAGE_REMOTE_PACKAGE = REMOTE_PACKAGE;
|
|
|
2553
2668
|
/** Canonical `<namespace>/<method>` endpoint of the usage report Remote. */
|
|
2554
2669
|
const USAGE_REPORT_ENDPOINT = "commandcode/report";
|
|
2555
2670
|
/**
|
|
2556
|
-
* The shared read/validate helpers
|
|
2557
|
-
* rejection messages name the offending boundary
|
|
2558
|
-
* shares this prefix, matching the historical behavior).
|
|
2671
|
+
* The shared read/validate helpers for the usage report endpoint, prefixed
|
|
2672
|
+
* so rejection messages name the offending boundary.
|
|
2559
2673
|
*/
|
|
2560
|
-
const { reject: reject$1, record, stringField, numberField, booleanField } = makeBoundaryValidator("commandcode/report result:");
|
|
2674
|
+
const { reject: reject$1, record: record$1, stringField: stringField$1, numberField, booleanField } = makeBoundaryValidator("commandcode/report result:");
|
|
2561
2675
|
/** Validate one window-limit block (`fiveHour` / `weekly`). */
|
|
2562
2676
|
function windowLimit(value, field) {
|
|
2563
|
-
const source = record(value, field);
|
|
2677
|
+
const source = record$1(value, field);
|
|
2564
2678
|
return {
|
|
2565
2679
|
used: numberField(source, "used", `${field}.used`),
|
|
2566
2680
|
cap: numberField(source, "cap", `${field}.cap`),
|
|
@@ -2574,7 +2688,7 @@ function windowLimit(value, field) {
|
|
|
2574
2688
|
* malformed frame fails the boundary instead of rendering garbage.
|
|
2575
2689
|
*/
|
|
2576
2690
|
function parseUsageReport(value) {
|
|
2577
|
-
const source = record(value, "report");
|
|
2691
|
+
const source = record$1(value, "report");
|
|
2578
2692
|
const failures = source.failures;
|
|
2579
2693
|
if (!Array.isArray(failures) || failures.some((entry) => typeof entry !== "string")) reject$1("failures");
|
|
2580
2694
|
const report = { failures };
|
|
@@ -2584,15 +2698,15 @@ function parseUsageReport(value) {
|
|
|
2584
2698
|
else reject$1("blocked");
|
|
2585
2699
|
}
|
|
2586
2700
|
if (source.account !== void 0) {
|
|
2587
|
-
const account = record(source.account, "account");
|
|
2701
|
+
const account = record$1(source.account, "account");
|
|
2588
2702
|
report.account = {
|
|
2589
|
-
id: stringField(account, "id", "account.id"),
|
|
2590
|
-
name: stringField(account, "name", "account.name"),
|
|
2591
|
-
userName: stringField(account, "userName", "account.userName")
|
|
2703
|
+
id: stringField$1(account, "id", "account.id"),
|
|
2704
|
+
name: stringField$1(account, "name", "account.name"),
|
|
2705
|
+
userName: stringField$1(account, "userName", "account.userName")
|
|
2592
2706
|
};
|
|
2593
2707
|
}
|
|
2594
2708
|
if (source.usage !== void 0) {
|
|
2595
|
-
const usage = record(source.usage, "usage");
|
|
2709
|
+
const usage = record$1(source.usage, "usage");
|
|
2596
2710
|
report.usage = {
|
|
2597
2711
|
totalCount: numberField(usage, "totalCount", "usage.totalCount"),
|
|
2598
2712
|
totalCost: numberField(usage, "totalCost", "usage.totalCost"),
|
|
@@ -2602,11 +2716,11 @@ function parseUsageReport(value) {
|
|
|
2602
2716
|
totalTokensIn: numberField(usage, "totalTokensIn", "usage.totalTokensIn"),
|
|
2603
2717
|
totalTokensOut: numberField(usage, "totalTokensOut", "usage.totalTokensOut"),
|
|
2604
2718
|
totalCredits: numberField(usage, "totalCredits", "usage.totalCredits"),
|
|
2605
|
-
periodBasis: stringField(usage, "periodBasis", "usage.periodBasis")
|
|
2719
|
+
periodBasis: stringField$1(usage, "periodBasis", "usage.periodBasis")
|
|
2606
2720
|
};
|
|
2607
2721
|
}
|
|
2608
2722
|
if (source.credits !== void 0) {
|
|
2609
|
-
const credits = record(source.credits, "credits");
|
|
2723
|
+
const credits = record$1(source.credits, "credits");
|
|
2610
2724
|
report.credits = {
|
|
2611
2725
|
monthlyCredits: numberField(credits, "monthlyCredits", "credits.monthlyCredits"),
|
|
2612
2726
|
purchasedCredits: numberField(credits, "purchasedCredits", "credits.purchasedCredits"),
|
|
@@ -2616,13 +2730,13 @@ function parseUsageReport(value) {
|
|
|
2616
2730
|
};
|
|
2617
2731
|
}
|
|
2618
2732
|
if (source.plan !== void 0) {
|
|
2619
|
-
const plan = record(source.plan, "plan");
|
|
2733
|
+
const plan = record$1(source.plan, "plan");
|
|
2620
2734
|
const monthly = plan.monthlyCredits;
|
|
2621
2735
|
if (monthly !== null && (typeof monthly !== "number" || !Number.isFinite(monthly))) reject$1("plan.monthlyCredits");
|
|
2622
2736
|
report.plan = {
|
|
2623
|
-
planId: stringField(plan, "planId", "plan.planId"),
|
|
2624
|
-
name: stringField(plan, "name", "plan.name"),
|
|
2625
|
-
status: stringField(plan, "status", "plan.status"),
|
|
2737
|
+
planId: stringField$1(plan, "planId", "plan.planId"),
|
|
2738
|
+
name: stringField$1(plan, "name", "plan.name"),
|
|
2739
|
+
status: stringField$1(plan, "status", "plan.status"),
|
|
2626
2740
|
monthlyCredits: monthly,
|
|
2627
2741
|
currentPeriodEnd: numberField(plan, "currentPeriodEnd", "plan.currentPeriodEnd")
|
|
2628
2742
|
};
|
|
@@ -2631,20 +2745,20 @@ function parseUsageReport(value) {
|
|
|
2631
2745
|
}
|
|
2632
2746
|
/** Parse one untrusted boundary value into a {@link CommandCodeAccountUsage}. */
|
|
2633
2747
|
function parseAccountUsage(value) {
|
|
2634
|
-
const source = record(value, "account");
|
|
2748
|
+
const source = record$1(value, "account");
|
|
2635
2749
|
return {
|
|
2636
|
-
id: stringField(source, "id", "account.id"),
|
|
2637
|
-
label: stringField(source, "label", "account.label"),
|
|
2750
|
+
id: stringField$1(source, "id", "account.id"),
|
|
2751
|
+
label: stringField$1(source, "label", "account.label"),
|
|
2638
2752
|
configured: booleanField(source, "configured", "account.configured"),
|
|
2639
2753
|
active: booleanField(source, "active", "account.active"),
|
|
2640
|
-
mark: stringField(source, "mark", "account.mark"),
|
|
2754
|
+
mark: stringField$1(source, "mark", "account.mark"),
|
|
2641
2755
|
cooldownUntil: numberField(source, "cooldownUntil", "account.cooldownUntil"),
|
|
2642
2756
|
report: parseUsageReport(source.report)
|
|
2643
2757
|
};
|
|
2644
2758
|
}
|
|
2645
2759
|
/** Parse the wire result into a {@link CommandCodeAccountsReport}. */
|
|
2646
2760
|
function parseAccountsReport(value) {
|
|
2647
|
-
const accounts = record(value, "result").accounts;
|
|
2761
|
+
const accounts = record$1(value, "result").accounts;
|
|
2648
2762
|
if (Array.isArray(accounts)) return { accounts: accounts.map(parseAccountUsage) };
|
|
2649
2763
|
return reject$1("accounts");
|
|
2650
2764
|
}
|
|
@@ -2668,19 +2782,27 @@ const USAGE_HOST_CONTRIBUTION = {
|
|
|
2668
2782
|
};
|
|
2669
2783
|
/** Canonical `<namespace>/<method>` endpoint of the model-catalog Remote. */
|
|
2670
2784
|
const MODELS_ENDPOINT = "commandcode/models";
|
|
2785
|
+
/**
|
|
2786
|
+
* The shared read/validate helpers for the model-catalog endpoint — a
|
|
2787
|
+
* separate instance so catalog boundary errors name `commandcode/models`,
|
|
2788
|
+
* not the report endpoint.
|
|
2789
|
+
*/
|
|
2790
|
+
const { record: catalogRecord, stringField: catalogString } = makeBoundaryValidator("commandcode/models result:");
|
|
2671
2791
|
/** Parse one untrusted boundary value into a {@link CommandCodeCatalogModel}. */
|
|
2672
2792
|
function parseCatalogModel(value) {
|
|
2673
|
-
const source =
|
|
2674
|
-
|
|
2675
|
-
id:
|
|
2676
|
-
name:
|
|
2793
|
+
const source = catalogRecord(value, "model");
|
|
2794
|
+
const model = {
|
|
2795
|
+
id: catalogString(source, "id", "model.id"),
|
|
2796
|
+
name: catalogString(source, "name", "model.name")
|
|
2677
2797
|
};
|
|
2798
|
+
if (source.tier !== void 0) model.tier = catalogString(source, "tier", "model.tier");
|
|
2799
|
+
return model;
|
|
2678
2800
|
}
|
|
2679
2801
|
/** Parse the wire result into a {@link CommandCodeCatalog}. */
|
|
2680
2802
|
function parseCatalog(value) {
|
|
2681
|
-
const models =
|
|
2803
|
+
const models = catalogRecord(value, "result").models;
|
|
2682
2804
|
if (Array.isArray(models)) return { models: models.map(parseCatalogModel) };
|
|
2683
|
-
|
|
2805
|
+
throw new TypeError("commandcode/models result: invalid models");
|
|
2684
2806
|
}
|
|
2685
2807
|
/**
|
|
2686
2808
|
* The model-catalog invocation descriptor, sharing the same `commandcodeUsage`
|
|
@@ -2704,45 +2826,29 @@ const REASONS = [
|
|
|
2704
2826
|
];
|
|
2705
2827
|
/** The shared read/validate helpers, prefixed with the login endpoint so
|
|
2706
2828
|
* rejection messages name the offending boundary. */
|
|
2707
|
-
|
|
2708
|
-
* function declaration (not the factory's destructured arrow) so TypeScript's
|
|
2709
|
-
* control-flow analysis recognizes it as never-returning and narrows `state`
|
|
2710
|
-
* after the guard below. */
|
|
2711
|
-
function reject(field) {
|
|
2712
|
-
throw new TypeError(`commandcode/login result: invalid ${field}`);
|
|
2713
|
-
}
|
|
2829
|
+
const { reject, record, stringField } = makeBoundaryValidator("commandcode/login result:");
|
|
2714
2830
|
/**
|
|
2715
2831
|
* Parse one untrusted boundary value into a {@link CommandCodeLoginStatus}.
|
|
2716
2832
|
* Every field is shape-checked so a malformed frame fails the boundary
|
|
2717
2833
|
* instead of leaking into the page.
|
|
2718
2834
|
*/
|
|
2719
2835
|
function parseLoginStatus(value) {
|
|
2720
|
-
|
|
2721
|
-
const source = value;
|
|
2836
|
+
const source = record(value, "status");
|
|
2722
2837
|
const state = source.state;
|
|
2723
|
-
if (state
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
if (
|
|
2727
|
-
status.
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
status.keyName = source.keyName;
|
|
2736
|
-
}
|
|
2737
|
-
if (source.reason !== void 0) {
|
|
2738
|
-
if (!REASONS.includes(source.reason)) reject("reason");
|
|
2739
|
-
status.reason = source.reason;
|
|
2740
|
-
}
|
|
2741
|
-
if (source.message !== void 0) {
|
|
2742
|
-
if (typeof source.message !== "string") reject("message");
|
|
2743
|
-
status.message = source.message;
|
|
2838
|
+
if (state === "idle" || state === "waiting" || state === "success" || state === "failed") {
|
|
2839
|
+
const status = { state };
|
|
2840
|
+
if (source.authUrl !== void 0) status.authUrl = stringField(source, "authUrl", "authUrl");
|
|
2841
|
+
if (source.userName !== void 0) status.userName = stringField(source, "userName", "userName");
|
|
2842
|
+
if (source.keyName !== void 0) status.keyName = stringField(source, "keyName", "keyName");
|
|
2843
|
+
if (source.reason !== void 0) {
|
|
2844
|
+
const reason = source.reason;
|
|
2845
|
+
if (typeof reason === "string" && REASONS.includes(reason)) status.reason = reason;
|
|
2846
|
+
else reject("reason");
|
|
2847
|
+
}
|
|
2848
|
+
if (source.message !== void 0) status.message = stringField(source, "message", "message");
|
|
2849
|
+
return status;
|
|
2744
2850
|
}
|
|
2745
|
-
return
|
|
2851
|
+
return reject("state");
|
|
2746
2852
|
}
|
|
2747
2853
|
/** The strict result codec shared by all three login endpoints. */
|
|
2748
2854
|
const loginStatusSchema = { parse: parseLoginStatus };
|
|
@@ -2792,10 +2898,11 @@ var CommandCodeUsageService = class extends TypertRemoteService {
|
|
|
2792
2898
|
}] };
|
|
2793
2899
|
}
|
|
2794
2900
|
/**
|
|
2795
|
-
* The model catalog for the settings page's
|
|
2796
|
-
*
|
|
2797
|
-
*
|
|
2798
|
-
*
|
|
2901
|
+
* The full model catalog for the settings page's model editors (the
|
|
2902
|
+
* routing-rule editor and the visible-models filter). The browser never
|
|
2903
|
+
* calls the Command Code API directly — the Host serves the catalog
|
|
2904
|
+
* (already fetched/cached by the adapter) so models can be picked from
|
|
2905
|
+
* the live list instead of typed by hand.
|
|
2799
2906
|
*/
|
|
2800
2907
|
async models() {
|
|
2801
2908
|
return this.deps.listModels?.() ?? { models: [] };
|
|
@@ -3107,12 +3214,15 @@ var CommandCodeLoginFlow = class {
|
|
|
3107
3214
|
});
|
|
3108
3215
|
return;
|
|
3109
3216
|
}
|
|
3217
|
+
let bodyBytes = 0;
|
|
3110
3218
|
let body = "";
|
|
3111
3219
|
request.on("data", (chunk) => {
|
|
3220
|
+
bodyBytes += chunk.length;
|
|
3112
3221
|
body += chunk.toString();
|
|
3113
|
-
if (
|
|
3222
|
+
if (bodyBytes > 1e4) request.destroy();
|
|
3114
3223
|
});
|
|
3115
3224
|
request.on("end", () => {
|
|
3225
|
+
if (request.destroyed) return;
|
|
3116
3226
|
let payload;
|
|
3117
3227
|
try {
|
|
3118
3228
|
payload = JSON.parse(body);
|
|
@@ -3248,21 +3358,86 @@ function corsOrigin(origin) {
|
|
|
3248
3358
|
const COMMANDCODE_SEARCH_PROVIDER_ID = "commandcode";
|
|
3249
3359
|
/**
|
|
3250
3360
|
* The factory-declared search provider id dsh ships by default (from
|
|
3251
|
-
* `dsh-base`'s cordis patch `web.config.searchProvider`).
|
|
3252
|
-
*
|
|
3253
|
-
*
|
|
3361
|
+
* `dsh-base`'s cordis patch `web.config.searchProvider`). Kept as a
|
|
3362
|
+
* documented reference only: disabling this plugin's `webSearch` toggle
|
|
3363
|
+
* restores the previously selected backend (see
|
|
3364
|
+
* {@link applyCommandCodeSearchSelection}) — it never forces this default,
|
|
3365
|
+
* because forcing it is what used to silence sibling search plugins such as
|
|
3366
|
+
* modsearch even with Command Code search turned off (issue #26).
|
|
3254
3367
|
*/
|
|
3255
3368
|
const DEFAULT_WEB_SEARCH_PROVIDER_ID = "deepseek-official";
|
|
3256
3369
|
/**
|
|
3257
3370
|
* Point the web seam's search selection at this plugin's provider (`commandcode`).
|
|
3258
3371
|
* Sets the runtime field; the next search call honours it because `search()`
|
|
3259
3372
|
* re-reads `searchProviderId` each time. Returns the prior id (or undefined).
|
|
3373
|
+
* Never throws: a hardened/frozen runtime shape must not break the
|
|
3374
|
+
* settings-save path that calls this — the provider simply stays
|
|
3375
|
+
* registered-but-unselected (the boot-time `searchProvider: commandcode`
|
|
3376
|
+
* cordis patch is the durable alternative).
|
|
3377
|
+
*
|
|
3378
|
+
* @deprecated Prefer {@link applyCommandCodeSearchSelection}: this overload
|
|
3379
|
+
* always overwrites the displaced backend with the factory default on
|
|
3380
|
+
* disable, so turning Command Code search off silences whichever provider
|
|
3381
|
+
* was selected before (e.g. modsearch) instead of restoring it (issue #26).
|
|
3260
3382
|
*/
|
|
3261
3383
|
function selectCommandCodeSearchProvider(web, enable) {
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3384
|
+
try {
|
|
3385
|
+
const field = web;
|
|
3386
|
+
const prior = field.searchProviderId;
|
|
3387
|
+
field.searchProviderId = enable ? COMMANDCODE_SEARCH_PROVIDER_ID : DEFAULT_WEB_SEARCH_PROVIDER_ID;
|
|
3388
|
+
return prior;
|
|
3389
|
+
} catch {
|
|
3390
|
+
return;
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
/** Fresh selection state: the plugin starts out not owning the selection. */
|
|
3394
|
+
function commandCodeSearchSelection() {
|
|
3395
|
+
return {
|
|
3396
|
+
owner: false,
|
|
3397
|
+
displaced: void 0
|
|
3398
|
+
};
|
|
3399
|
+
}
|
|
3400
|
+
/**
|
|
3401
|
+
* Reach one end of the `webSearch` toggle without trampling sibling search
|
|
3402
|
+
* providers (issue #26).
|
|
3403
|
+
*
|
|
3404
|
+
* - Enabling writes `commandcode` and remembers whatever it displaced. When
|
|
3405
|
+
* the plugin already owns the selection (e.g. a settings save while still
|
|
3406
|
+
* on), the original `displaced` value is kept — the field currently holds
|
|
3407
|
+
* our own id, which must never be mistaken for the user's backend.
|
|
3408
|
+
* - Disabling hands the selection back to the remembered backend. When the
|
|
3409
|
+
* state holds no memory (a fresh boot straight into `webSearch: false`),
|
|
3410
|
+
* the field is left alone: the runtime's current value — a sibling's
|
|
3411
|
+
* cordis pin such as `searchProvider: modsearch`, or unset for
|
|
3412
|
+
* auto-select — already says what the user wants.
|
|
3413
|
+
* - When the field already reads `commandcode` at first touch (e.g. a
|
|
3414
|
+
* surviving runtime the plugin did not set, or a manual
|
|
3415
|
+
* `searchProvider: commandcode` pin), `displaced` stays undefined so the
|
|
3416
|
+
* later disable is a no-op rather than a guess at the factory default.
|
|
3417
|
+
*
|
|
3418
|
+
* Never throws: like the low-level rewrite, a hardened runtime shape degrades
|
|
3419
|
+
* to registered-but-unselected.
|
|
3420
|
+
*/
|
|
3421
|
+
function applyCommandCodeSearchSelection(web, state, enable) {
|
|
3422
|
+
try {
|
|
3423
|
+
const field = web;
|
|
3424
|
+
if (enable) {
|
|
3425
|
+
if (state.owner) {
|
|
3426
|
+
field.searchProviderId = COMMANDCODE_SEARCH_PROVIDER_ID;
|
|
3427
|
+
return;
|
|
3428
|
+
}
|
|
3429
|
+
const prior = field.searchProviderId;
|
|
3430
|
+
state.displaced = prior === "commandcode" ? void 0 : prior;
|
|
3431
|
+
field.searchProviderId = COMMANDCODE_SEARCH_PROVIDER_ID;
|
|
3432
|
+
state.owner = true;
|
|
3433
|
+
return;
|
|
3434
|
+
}
|
|
3435
|
+
if (state.owner) {
|
|
3436
|
+
state.owner = false;
|
|
3437
|
+
field.searchProviderId = state.displaced;
|
|
3438
|
+
return;
|
|
3439
|
+
}
|
|
3440
|
+
} catch {}
|
|
3266
3441
|
}
|
|
3267
3442
|
/** Command Code's lower/upper bound on `numResults` (from the CLI's `web_search` schema). */
|
|
3268
3443
|
const MIN_NUM_RESULTS = 1;
|
|
@@ -3313,7 +3488,7 @@ var CommandCodeSearchProvider = class {
|
|
|
3313
3488
|
constructor(deps) {
|
|
3314
3489
|
this.deps = deps;
|
|
3315
3490
|
}
|
|
3316
|
-
/** Cheap local check; must not make network calls. Presence of a
|
|
3491
|
+
/** Cheap local check; must not make network calls. Presence of a parseable base is enough. */
|
|
3317
3492
|
available() {
|
|
3318
3493
|
const base = this.deps.apiBase();
|
|
3319
3494
|
return base.length > 0 && URL.canParse(base);
|
|
@@ -3360,6 +3535,8 @@ var CommandCodeSearchProvider = class {
|
|
|
3360
3535
|
}
|
|
3361
3536
|
} catch (error) {
|
|
3362
3537
|
if (signal?.aborted === true || isAbortError(error)) throw searchAborted(signal, error);
|
|
3538
|
+
const slice = (await response.text().catch(() => "")).trim().slice(0, 200);
|
|
3539
|
+
if (slice !== "") message += `: ${slice}`;
|
|
3363
3540
|
}
|
|
3364
3541
|
throw new WebError(message, "WEB_PROVIDER_ERROR");
|
|
3365
3542
|
}
|
|
@@ -3368,7 +3545,7 @@ var CommandCodeSearchProvider = class {
|
|
|
3368
3545
|
payload = await response.json();
|
|
3369
3546
|
} catch (error) {
|
|
3370
3547
|
if (signal?.aborted === true || isAbortError(error)) throw searchAborted(signal, error);
|
|
3371
|
-
throw new WebError("Command Code web search returned an unparseable response body", "WEB_PROVIDER_ERROR");
|
|
3548
|
+
throw new WebError("Command Code web search returned an unparseable response body", "WEB_PROVIDER_ERROR", { cause: error });
|
|
3372
3549
|
}
|
|
3373
3550
|
const results = payload?.results;
|
|
3374
3551
|
if (!Array.isArray(results)) throw new WebError("Command Code web search returned no results array (the server may have rejected the query)", "WEB_PROVIDER_ERROR");
|
|
@@ -3449,6 +3626,7 @@ const Config = z.object({
|
|
|
3449
3626
|
requestTimeoutMs: z.number().min(1).max(MAX_TIMER_DELAY_MS),
|
|
3450
3627
|
streamIdleTimeoutMs: z.number().min(1).max(MAX_TIMER_DELAY_MS),
|
|
3451
3628
|
filterModelsByPlan: z.boolean(),
|
|
3629
|
+
visibleModels: z.array(z.string()),
|
|
3452
3630
|
webSearch: z.boolean().default(true),
|
|
3453
3631
|
accounts: z.array(z.object({
|
|
3454
3632
|
label: z.string(),
|
|
@@ -3476,7 +3654,8 @@ function resolveAdapterOptions(config) {
|
|
|
3476
3654
|
modelsCachePath: config.modelsCachePath ?? DEFAULT_MODELS_CACHE_PATH,
|
|
3477
3655
|
requestTimeoutMs: config.requestTimeoutMs ?? 6e4,
|
|
3478
3656
|
streamIdleTimeoutMs: config.streamIdleTimeoutMs ?? 3e5,
|
|
3479
|
-
filterModelsByPlan: config.filterModelsByPlan ?? true
|
|
3657
|
+
filterModelsByPlan: config.filterModelsByPlan ?? true,
|
|
3658
|
+
visibleModels: Array.isArray(config.visibleModels) ? config.visibleModels.filter((id) => typeof id === "string" && id !== "") : void 0
|
|
3480
3659
|
};
|
|
3481
3660
|
}
|
|
3482
3661
|
function apply(ctx, config) {
|
|
@@ -3606,18 +3785,26 @@ function apply(ctx, config) {
|
|
|
3606
3785
|
}
|
|
3607
3786
|
});
|
|
3608
3787
|
ctx.effect(() => () => loginFlow.dispose(), "dsh-commandcode-provider: login flow");
|
|
3609
|
-
const
|
|
3610
|
-
return { models: (await adapter.listModels(PROVIDER)).map((model) =>
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3788
|
+
const catalogForEditors = async () => {
|
|
3789
|
+
return { models: (await adapter.listModels(PROVIDER, { unfiltered: true })).map((model) => {
|
|
3790
|
+
const tier = KNOWN_PLANS[model.id];
|
|
3791
|
+
return {
|
|
3792
|
+
id: model.id,
|
|
3793
|
+
name: model.name.replace(/\s*\(CC\)$/, ""),
|
|
3794
|
+
...tier === void 0 ? {} : { tier }
|
|
3795
|
+
};
|
|
3796
|
+
}) };
|
|
3614
3797
|
};
|
|
3615
3798
|
applyUsageRemote(ctx, {
|
|
3616
3799
|
adapter,
|
|
3617
3800
|
reports: usageReports,
|
|
3618
3801
|
login: loginFlow,
|
|
3619
|
-
listModels:
|
|
3802
|
+
listModels: catalogForEditors
|
|
3620
3803
|
});
|
|
3804
|
+
const searchSelection = commandCodeSearchSelection();
|
|
3805
|
+
const applySearchSelection = (enabled) => {
|
|
3806
|
+
if (webRuntime !== void 0) applyCommandCodeSearchSelection(webRuntime, searchSelection, enabled);
|
|
3807
|
+
};
|
|
3621
3808
|
let webRuntime;
|
|
3622
3809
|
ctx.inject(["web"], (webCtx) => {
|
|
3623
3810
|
webRuntime = webCtx.web;
|
|
@@ -3628,7 +3815,11 @@ function apply(ctx, config) {
|
|
|
3628
3815
|
},
|
|
3629
3816
|
apiBase: () => options().apiBase
|
|
3630
3817
|
}));
|
|
3631
|
-
|
|
3818
|
+
applySearchSelection(current().webSearch ?? true);
|
|
3819
|
+
webCtx.effect(() => () => {
|
|
3820
|
+
webRuntime = void 0;
|
|
3821
|
+
applyCommandCodeSearchSelection(webCtx.web, searchSelection, false);
|
|
3822
|
+
}, "dsh-commandcode-provider: web search selection");
|
|
3632
3823
|
});
|
|
3633
3824
|
ctx.inject(["settings"], (settingsCtx) => {
|
|
3634
3825
|
settingsCtx.settings.installSection(ctx, NS, Config, config, {
|
|
@@ -3636,12 +3827,12 @@ function apply(ctx, config) {
|
|
|
3636
3827
|
current = source;
|
|
3637
3828
|
},
|
|
3638
3829
|
onChange: () => {
|
|
3639
|
-
|
|
3830
|
+
applySearchSelection(current().webSearch ?? true);
|
|
3640
3831
|
}
|
|
3641
3832
|
});
|
|
3642
3833
|
});
|
|
3643
3834
|
}
|
|
3644
3835
|
//#endregion
|
|
3645
|
-
export { BILLING_ACCESS_TTL_MS, COMMANDCODE_SEARCH_PROVIDER_ID, COMMAND_CODE_CLI_VERSION, CommandCodeAccountPool, CommandCodeAdapter, CommandCodeLoginFlow, CommandCodeSearchProvider, CommandCodeUsageService, Config, DEFAULT_API_BASE, DEFAULT_GENERATE_MAX_TOKENS, DEFAULT_MAX_OUTPUT_TOKENS, DEFAULT_MODELS_CACHE_PATH, DEFAULT_REQUEST_TIMEOUT_MS, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DEFAULT_WEB_SEARCH_PROVIDER_ID, KNOWN_DEALS, KNOWN_EFFORTS, KNOWN_IMAGE_MODELS, KNOWN_PEAK_PRICING, KNOWN_PLANS, KNOWN_SUBSCRIPTION_PLANS, KNOWN_THINKING_MODELS, LOGIN_ALLOWED_ORIGINS, LOGIN_BEGIN_ENDPOINT, LOGIN_BODY_LIMIT_BYTES, LOGIN_CANCEL_ENDPOINT, LOGIN_MAX_PORT_ATTEMPTS, LOGIN_START_PORT, LOGIN_STATUS_ENDPOINT, LOGIN_TIMEOUT_MS, PLAN_LABELS, PLAN_ORDER, PROVIDER, USAGE_REPORT_ENDPOINT, accountUsable, apply, applyCommands, applyUsageRemote, buildCommandAuthUrl, capabilityDescription, commandDefinition, compareByPlan, dealLabel, formatContext, inject, loginStatusSchema, matchModelRule, modelVisibleInPlan, name, parseLoginStatus, peakPricingLabel, peakPricingState, planLabel, projectSlugFromPath, resolveAdapterOptions, resolveAuthFileApiKey, selectAccountForModel, selectActiveAccount, selectCommandCodeSearchProvider, studioBaseForApiBase, subscriptionPlanInfo, usageReportSchema, validateCommandApiKey };
|
|
3836
|
+
export { BILLING_ACCESS_TTL_MS, COMMANDCODE_SEARCH_PROVIDER_ID, COMMAND_CODE_CLI_VERSION, CommandCodeAccountPool, CommandCodeAdapter, CommandCodeLoginFlow, CommandCodeSearchProvider, CommandCodeUsageService, Config, DEFAULT_API_BASE, DEFAULT_GENERATE_MAX_TOKENS, DEFAULT_MAX_OUTPUT_TOKENS, DEFAULT_MODELS_CACHE_PATH, DEFAULT_REQUEST_TIMEOUT_MS, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DEFAULT_WEB_SEARCH_PROVIDER_ID, KNOWN_DEALS, KNOWN_EFFORTS, KNOWN_IMAGE_MODELS, KNOWN_PEAK_PRICING, KNOWN_PLANS, KNOWN_SUBSCRIPTION_PLANS, KNOWN_THINKING_MODELS, LOGIN_ALLOWED_ORIGINS, LOGIN_BEGIN_ENDPOINT, LOGIN_BODY_LIMIT_BYTES, LOGIN_CANCEL_ENDPOINT, LOGIN_MAX_PORT_ATTEMPTS, LOGIN_START_PORT, LOGIN_STATUS_ENDPOINT, LOGIN_TIMEOUT_MS, PLAN_LABELS, PLAN_ORDER, PROVIDER, USAGE_REPORT_ENDPOINT, accountUsable, apply, applyCommandCodeSearchSelection, applyCommands, applyUsageRemote, buildCommandAuthUrl, capabilityDescription, commandCodeSearchSelection, commandDefinition, compareByPlan, dealLabel, formatContext, inject, loginStatusSchema, matchModelRule, modelVisibleInPlan, name, parseLoginStatus, peakPricingLabel, peakPricingState, planLabel, projectSlugFromPath, resolveAdapterOptions, resolveAuthFileApiKey, selectAccountForModel, selectActiveAccount, selectCommandCodeSearchProvider, studioBaseForApiBase, subscriptionPlanInfo, usageReportSchema, validateCommandApiKey };
|
|
3646
3837
|
|
|
3647
3838
|
//# sourceMappingURL=index.js.map
|