@mars-sea/dsh-commandcode-provider 0.10.2 → 0.10.4
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 +41 -1
- package/README.md +44 -2
- package/README.zh-CN.md +44 -2
- package/lib/client.js +132 -35
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +217 -24
- package/lib/index.js +502 -48
- package/lib/index.js.map +1 -1
- package/package.json +4 -2
package/lib/index.js
CHANGED
|
@@ -220,16 +220,38 @@ var CommandCodeAccountPool = class {
|
|
|
220
220
|
until: 0
|
|
221
221
|
});
|
|
222
222
|
}
|
|
223
|
-
/**
|
|
223
|
+
/**
|
|
224
|
+
* One account's key: literal → credential seam → auth file (default slot).
|
|
225
|
+
*
|
|
226
|
+
* Every source is normalized here, at the single point where a slot's key
|
|
227
|
+
* enters the pool. The adapter sends the key through the harness's
|
|
228
|
+
* `assertUsableApiKey()`, which trims it — a stored key from the credentials
|
|
229
|
+
* seam, a `.env` line, or a shell export all pick up surrounding whitespace
|
|
230
|
+
* — and reports that trimmed form back to `markRejected()`. Returning the
|
|
231
|
+
* raw value would file every 429/401 mark under a key no later lookup can
|
|
232
|
+
* find: rotation would re-offer the same account, the account card would show
|
|
233
|
+
* no mark, and the usage endpoints would 401 while chat kept working.
|
|
234
|
+
* Normalizing once makes resolution, probing, marking, and the request path
|
|
235
|
+
* agree on one string.
|
|
236
|
+
*/
|
|
224
237
|
async resolveSlotKey(slot) {
|
|
225
|
-
if (slot.literal !== void 0
|
|
238
|
+
if (slot.literal !== void 0) {
|
|
239
|
+
const literal = normalizeResolvedKey(slot.literal);
|
|
240
|
+
if (literal !== void 0) return literal;
|
|
241
|
+
}
|
|
226
242
|
if (slot.ref !== void 0) {
|
|
227
243
|
const hit = await this.deps.resolveRef(slot.ref);
|
|
228
|
-
if (hit !== void 0
|
|
244
|
+
if (hit !== void 0) {
|
|
245
|
+
const resolved = normalizeResolvedKey(hit);
|
|
246
|
+
if (resolved !== void 0) return resolved;
|
|
247
|
+
}
|
|
229
248
|
}
|
|
230
249
|
if (slot.allowAuthFile) {
|
|
231
250
|
const fromFile = this.deps.authFileKey();
|
|
232
|
-
if (fromFile !== void 0
|
|
251
|
+
if (fromFile !== void 0) {
|
|
252
|
+
const fileKey = normalizeResolvedKey(fromFile);
|
|
253
|
+
if (fileKey !== void 0) return fileKey;
|
|
254
|
+
}
|
|
233
255
|
}
|
|
234
256
|
}
|
|
235
257
|
/** Hand out the chosen account's key. */
|
|
@@ -240,6 +262,16 @@ var CommandCodeAccountPool = class {
|
|
|
240
262
|
};
|
|
241
263
|
}
|
|
242
264
|
};
|
|
265
|
+
/**
|
|
266
|
+
* Normalize one resolved credential to the form every consumer sees. Trim
|
|
267
|
+
* only: a blank-after-trim value means "no key" (the slot is omitted and the
|
|
268
|
+
* caller reports `MISSING_CREDENTIAL`), while characters an HTTP header cannot
|
|
269
|
+
* carry are left for `assertUsableApiKey()` to reject with its own message.
|
|
270
|
+
*/
|
|
271
|
+
function normalizeResolvedKey(value) {
|
|
272
|
+
const trimmed = value.trim();
|
|
273
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
274
|
+
}
|
|
243
275
|
//#endregion
|
|
244
276
|
//#region src/capabilities.ts
|
|
245
277
|
/**
|
|
@@ -338,6 +370,11 @@ const KNOWN_EFFORTS = {
|
|
|
338
370
|
"high",
|
|
339
371
|
"max"
|
|
340
372
|
],
|
|
373
|
+
"deepseek/deepseek-v4.1-flash": [
|
|
374
|
+
"low",
|
|
375
|
+
"high",
|
|
376
|
+
"max"
|
|
377
|
+
],
|
|
341
378
|
"deepseek/deepseek-v4-flash": ["high", "max"],
|
|
342
379
|
"deepseek/deepseek-v4-flash-vision-exp": ["high", "max"],
|
|
343
380
|
"deepseek/deepseek-v4-pro": ["high", "max"],
|
|
@@ -485,6 +522,11 @@ const KNOWN_EFFORTS = {
|
|
|
485
522
|
"high",
|
|
486
523
|
"xhigh",
|
|
487
524
|
"max"
|
|
525
|
+
],
|
|
526
|
+
"MiniMaxAI/MiniMax-M3": [
|
|
527
|
+
"low",
|
|
528
|
+
"medium",
|
|
529
|
+
"high"
|
|
488
530
|
]
|
|
489
531
|
};
|
|
490
532
|
/**
|
|
@@ -520,6 +562,7 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
|
520
562
|
"claude-sonnet-4-6",
|
|
521
563
|
"claude-sonnet-5",
|
|
522
564
|
"deepseek/deepseek-v4-flash-vision-exp",
|
|
565
|
+
"deepseek/deepseek-v4.1-flash",
|
|
523
566
|
"google/gemini-3.1-flash-lite",
|
|
524
567
|
"google/gemini-3.5-flash",
|
|
525
568
|
"google/gemini-3.5-flash-lite",
|
|
@@ -554,7 +597,7 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
|
554
597
|
"z-ai/glm-5.3-flash"
|
|
555
598
|
]);
|
|
556
599
|
/**
|
|
557
|
-
* Models the official CLI's model table (command-code@1.
|
|
600
|
+
* Models the official CLI's model table (command-code@1.53.0) marks
|
|
558
601
|
* `reasoning:!0` but defines no selectable `reasoning_effort` levels — they
|
|
559
602
|
* think automatically, with Command Code driving the depth. This is the
|
|
560
603
|
* authoritative "thinks, effort not adjustable" set: `KNOWN_EFFORTS` (which
|
|
@@ -562,7 +605,7 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
|
562
605
|
* effort levels, and this snapshot is not surfaced in the picker's compact
|
|
563
606
|
* description — it exists for programmatic consumers.
|
|
564
607
|
*
|
|
565
|
-
* Source: the command-code@1.
|
|
608
|
+
* Source: the command-code@1.53.0 bundled model table (dist/cli.mjs),
|
|
566
609
|
* cross-checked with https://commandcode.ai/docs/reference/cli/models.
|
|
567
610
|
* (`stealth/ox-alpha` left this set in command-code@1.32.1, which gave it
|
|
568
611
|
* selectable `['low', 'high', 'max']` efforts; the preview then ended in
|
|
@@ -575,11 +618,16 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
|
575
618
|
* to `KNOWN_EFFORTS`. command-code@1.42.0 added `meituan/LongCat-2.0:free`
|
|
576
619
|
* (reasoning:!0, no efforts). command-code@1.45.0 gave the Muse Spark family
|
|
577
620
|
* (1.1, 1.2, 1.2-contributor, 1.3, 1.3-contributor) selectable
|
|
578
|
-
* `['low', 'medium', 'high', 'xhigh']` efforts — they moved to `KNOWN_EFFORTS`.
|
|
621
|
+
* `['low', 'medium', 'high', 'xhigh']` efforts — they moved to `KNOWN_EFFORTS`.
|
|
622
|
+
* command-code@1.51.3 gave `MiniMaxAI/MiniMax-M3` selectable
|
|
623
|
+
* `['low', 'medium', 'high']` efforts — it moved to `KNOWN_EFFORTS` too.
|
|
624
|
+
* command-code@1.52.0 added `inclusionai/ling-3.0-flash-sante:free`
|
|
625
|
+
* (reasoning:!0, no efforts).)
|
|
626
|
+
* command-code@1.53.0 added `deepseek/deepseek-v4.1-flash` with selectable
|
|
627
|
+
* ['low', 'high', 'max'] efforts, so it lives in `KNOWN_EFFORTS`, not here.)
|
|
579
628
|
* Keep in sync via the dsh-commandcode-upstream skill.
|
|
580
629
|
*/
|
|
581
630
|
const KNOWN_THINKING_MODELS = /* @__PURE__ */ new Set([
|
|
582
|
-
"MiniMaxAI/MiniMax-M3",
|
|
583
631
|
"Qwen/Qwen3.6-Max-Preview",
|
|
584
632
|
"Qwen/Qwen3.6-Plus",
|
|
585
633
|
"Qwen/Qwen3.7-Flash",
|
|
@@ -594,7 +642,8 @@ const KNOWN_THINKING_MODELS = /* @__PURE__ */ new Set([
|
|
|
594
642
|
"thinkingmachines/inkling",
|
|
595
643
|
"thinkingmachines/inkling-small",
|
|
596
644
|
"poolside/laguna-s-2.1-free",
|
|
597
|
-
"meituan/LongCat-2.0:free"
|
|
645
|
+
"meituan/LongCat-2.0:free",
|
|
646
|
+
"inclusionai/ling-3.0-flash-sante:free"
|
|
598
647
|
]);
|
|
599
648
|
/**
|
|
600
649
|
* The minimum subscription plan a model is included in, per the official plan
|
|
@@ -609,7 +658,9 @@ const KNOWN_THINKING_MODELS = /* @__PURE__ */ new Set([
|
|
|
609
658
|
* command-code@1.41.0 added `Qwen/Qwen3.8-Max-0902` (Go) and 1.42.0 added
|
|
610
659
|
* `meituan/LongCat-2.0:free` (Go, free promo); command-code@1.43.0 added
|
|
611
660
|
* `google/gemini-3.8-flash` (GOAT) and 1.44.0 added `meta/muse-spark-1.3`
|
|
612
|
-
* (GOAT) plus its Contributor sibling (Go).
|
|
661
|
+
* (GOAT) plus its Contributor sibling (Go); command-code@1.52.0 added the
|
|
662
|
+
* free `inclusionai/ling-3.0-flash-sante:free` (Go); command-code@1.53.0
|
|
663
|
+
* added `deepseek/deepseek-v4.1-flash` (Go).
|
|
613
664
|
*
|
|
614
665
|
* The Provider API exposes no plan metadata, so this snapshot is the source of
|
|
615
666
|
* truth for the picker's plan annotation — it answers "which plan do I need to
|
|
@@ -633,11 +684,13 @@ const KNOWN_PLANS = {
|
|
|
633
684
|
"Qwen/Qwen3.8-Max": "go",
|
|
634
685
|
"Qwen/Qwen3.8-Max-0902": "go",
|
|
635
686
|
"deepseek/deepseek-v4-flash-fast": "go",
|
|
687
|
+
"deepseek/deepseek-v4.1-flash": "go",
|
|
636
688
|
"deepseek/deepseek-v4-flash": "go",
|
|
637
689
|
"deepseek/deepseek-v4-flash-vision-exp": "go",
|
|
638
690
|
"deepseek/deepseek-v4-pro": "go",
|
|
639
691
|
"gpt-5.6-luna": "go",
|
|
640
692
|
"meituan/LongCat-2.0:free": "go",
|
|
693
|
+
"inclusionai/ling-3.0-flash-sante:free": "go",
|
|
641
694
|
"meta/muse-spark-1.2-contributor": "go",
|
|
642
695
|
"meta/muse-spark-1.3-contributor": "go",
|
|
643
696
|
"moonshotai/Kimi-K2.5": "go",
|
|
@@ -734,8 +787,8 @@ function compareByPlan(a, b) {
|
|
|
734
787
|
}
|
|
735
788
|
/**
|
|
736
789
|
* Subscription plan table, synced from the official CLI bundle's plan maps
|
|
737
|
-
* (located by the `"individual-go"` key in command-code@1.
|
|
738
|
-
* re-verified unchanged through 1.
|
|
790
|
+
* (located by the `"individual-go"` key in command-code@1.53.0 `dist/cli.mjs`,
|
|
791
|
+
* re-verified unchanged through 1.53.0): subscription `planId`
|
|
739
792
|
* prefix → display name and the plan's monthly credit total. This is the
|
|
740
793
|
* account's own subscription (from `/alpha/billing/subscriptions`) — distinct
|
|
741
794
|
* from {@link KNOWN_PLANS}, which maps catalog models to their minimum tier.
|
|
@@ -828,45 +881,69 @@ const KNOWN_DEALS = {
|
|
|
828
881
|
"meituan/LongCat-2.0:free": {
|
|
829
882
|
label: "FREE",
|
|
830
883
|
free: true
|
|
884
|
+
},
|
|
885
|
+
"inclusionai/ling-3.0-flash-sante:free": {
|
|
886
|
+
label: "FREE",
|
|
887
|
+
free: true
|
|
831
888
|
}
|
|
832
889
|
};
|
|
833
890
|
/**
|
|
834
891
|
* Models with time-of-day (peak/off-peak) pricing, per the official pricing
|
|
835
892
|
* page (`/docs/resources/pricing-limits`). Since 2026-08-16 16:00 UTC, DeepSeek
|
|
836
|
-
* charges by the hour: peak hours are 01:00–04:00 and 06:00–10:00 UTC (7h
|
|
837
|
-
* full price)
|
|
838
|
-
*
|
|
839
|
-
*
|
|
840
|
-
*
|
|
893
|
+
* charges by the hour: peak hours are 01:00–04:00 and 06:00–10:00 UTC (7h per
|
|
894
|
+
* weekday, full price) **Monday to Friday only**; the other 17 hours of a
|
|
895
|
+
* weekday and every hour of Saturday/Sunday (UTC) are off-peak at half price.
|
|
896
|
+
* The V4 Flash Vision (exp) variant (command-code@1.32.0) shares the V4 Flash
|
|
897
|
+
* windows and peak prices ($0.44/$1.32) — each row's hover annotation states
|
|
898
|
+
* exactly 2× that row's displayed off-peak prices. The picker shows the
|
|
841
899
|
* *current* state as a compact
|
|
842
900
|
* label (`Peak`/`Half`) matching the English noun style of the other markers
|
|
843
901
|
* (`Image`, `FREE`), so a developer can tell at a glance whether calling the
|
|
844
902
|
* model right now is cheap or expensive.
|
|
845
903
|
*
|
|
846
|
-
*
|
|
847
|
-
*
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
*
|
|
904
|
+
* Authoritative extraction: the pricing page embeds a model JSON array whose
|
|
905
|
+
* hourly-priced entries carry a `timeOfDay` block
|
|
906
|
+
* (`{ windows: "01–04 & 06–10 UTC, Mon–Fri", peakHoursPerDay: 7,
|
|
907
|
+
* offPeakHoursPerDay: 17, peak: {...}, offPeak: {...} }`). Exactly four models
|
|
908
|
+
* carry it: V4 Pro, V4 Flash, V4 Flash Vision (exp), and V4.1 Flash (added in
|
|
909
|
+
* command-code@1.53.0 at $0.15/$0.60 off-peak, $0.30/$1.20 peak — the same
|
|
910
|
+
* schedule as the other three).
|
|
911
|
+
*
|
|
912
|
+
* Extraction caution: the rendered HTML rows are a trap. Each annotation div
|
|
913
|
+
* sits inside its OWN row's container, immediately before the NEXT row starts,
|
|
914
|
+
* so flattening the page to text makes every annotation look like it belongs
|
|
915
|
+
* to the model printed after it — that is how `deepseek/deepseek-v4-flash-fast`
|
|
916
|
+
* was wrongly added here (its row is flat-priced at $0.28/$0.56/$0.07 and has
|
|
917
|
+
* no `timeOfDay` block). Trust the embedded JSON's `timeOfDay` membership and
|
|
918
|
+
* the 2× price relation, never the flat-text neighbor.
|
|
851
919
|
*
|
|
852
|
-
* Keep in sync with the official pricing page when the model set
|
|
853
|
-
* windows change (see the dsh-commandcode-upstream skill).
|
|
920
|
+
* Keep in sync with the official pricing page when the model set, the peak
|
|
921
|
+
* windows, or the weekday rule change (see the dsh-commandcode-upstream skill).
|
|
854
922
|
*/
|
|
855
923
|
const KNOWN_PEAK_PRICING = /* @__PURE__ */ new Set([
|
|
856
924
|
"deepseek/deepseek-v4-pro",
|
|
857
925
|
"deepseek/deepseek-v4-flash",
|
|
858
926
|
"deepseek/deepseek-v4-flash-vision-exp",
|
|
859
|
-
"deepseek/deepseek-v4-flash
|
|
927
|
+
"deepseek/deepseek-v4.1-flash"
|
|
860
928
|
]);
|
|
861
|
-
/**
|
|
929
|
+
/**
|
|
930
|
+
* Peak hours (UTC, hour-of-day range end-exclusive): 01–03 and 06–09.
|
|
931
|
+
* Weekday-only — see `peakPricingState()`; weekends are fully off-peak.
|
|
932
|
+
*/
|
|
862
933
|
const PEAK_HOUR_RANGES = [[1, 4], [6, 10]];
|
|
863
934
|
/**
|
|
864
|
-
* Whether `now` (defaults to `Date.now()`) falls in a peak-pricing
|
|
865
|
-
* time-of-day-priced models.
|
|
935
|
+
* Whether `now` (defaults to `Date.now()`) falls in a peak-pricing window for
|
|
936
|
+
* time-of-day-priced models. Peak rates apply Monday–Friday (UTC) only: the
|
|
937
|
+
* official rule charges Saturday and Sunday completely off-peak for all 24
|
|
938
|
+
* hours, so a weekend timestamp is off-peak even inside `PEAK_HOUR_RANGES`.
|
|
939
|
+
* `undefined` for models outside the snapshot.
|
|
866
940
|
*/
|
|
867
941
|
function peakPricingState(modelId, now = Date.now()) {
|
|
868
942
|
if (!KNOWN_PEAK_PRICING.has(modelId)) return void 0;
|
|
869
|
-
const
|
|
943
|
+
const at = new Date(now);
|
|
944
|
+
const day = at.getUTCDay();
|
|
945
|
+
if (day === 0 || day === 6) return "off-peak";
|
|
946
|
+
const hour = at.getUTCHours();
|
|
870
947
|
return PEAK_HOUR_RANGES.some(([start, end]) => hour >= start && hour < end) ? "peak" : "off-peak";
|
|
871
948
|
}
|
|
872
949
|
/**
|
|
@@ -947,7 +1024,7 @@ function capabilityDescription(modelId, contextWindow, now = Date.now()) {
|
|
|
947
1024
|
* and API key or subscription, and Command Code's terms apply.
|
|
948
1025
|
*
|
|
949
1026
|
* Wire protocol (reverse-engineered by the pi plugin, command-code@1.28.4;
|
|
950
|
-
* re-verified against command-code@1.
|
|
1027
|
+
* re-verified against command-code@1.53.0 — endpoints, request shape, and
|
|
951
1028
|
* stream events unchanged):
|
|
952
1029
|
* POST {apiBase}/alpha/generate
|
|
953
1030
|
* body: { config, memory, taste, skills, params: { model, messages, tools,
|
|
@@ -960,7 +1037,7 @@ function capabilityDescription(modelId, contextWindow, now = Date.now()) {
|
|
|
960
1037
|
* per-request options thunk and an API-key resolver from the plugin entry
|
|
961
1038
|
* (src/index.ts), so a settings change reaches the very next request.
|
|
962
1039
|
*/
|
|
963
|
-
const COMMAND_CODE_CLI_VERSION = "1.
|
|
1040
|
+
const COMMAND_CODE_CLI_VERSION = "1.53.0";
|
|
964
1041
|
const DEFAULT_API_BASE = "https://api.commandcode.ai";
|
|
965
1042
|
const DEFAULT_GENERATE_MAX_TOKENS = 64e3;
|
|
966
1043
|
const DEFAULT_MAX_OUTPUT_TOKENS = 65536;
|
|
@@ -1162,8 +1239,65 @@ function wireToolCallIds(paired) {
|
|
|
1162
1239
|
function blockText(block) {
|
|
1163
1240
|
return block.type === "text" || block.type === "reasoning" ? block.text : "";
|
|
1164
1241
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1242
|
+
/**
|
|
1243
|
+
* Collect text and nested images of one tool result. The harness `read_image`
|
|
1244
|
+
* tool returns both, so dropping the image half is what made a tool-read image
|
|
1245
|
+
* invisible to a Vision model (issue #30). Deduplication keeps a result that
|
|
1246
|
+
* repeats one attachment from paying for the same pixels twice.
|
|
1247
|
+
*/
|
|
1248
|
+
function toolResultMedia(block) {
|
|
1249
|
+
const chunks = [];
|
|
1250
|
+
const images = [];
|
|
1251
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1252
|
+
const walk = (blocks) => {
|
|
1253
|
+
for (const nested of blocks) {
|
|
1254
|
+
if (nested.type === "text" || nested.type === "reasoning") {
|
|
1255
|
+
if (nested.text) chunks.push(nested.text);
|
|
1256
|
+
continue;
|
|
1257
|
+
}
|
|
1258
|
+
if (nested.type === "image") {
|
|
1259
|
+
if (!seen.has(nested.attachment.attachmentId)) {
|
|
1260
|
+
seen.add(nested.attachment.attachmentId);
|
|
1261
|
+
images.push(nested.attachment);
|
|
1262
|
+
}
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
1265
|
+
if (nested.type === "tool-result") walk(nested.content);
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
1268
|
+
walk(block.content);
|
|
1269
|
+
return {
|
|
1270
|
+
text: chunks.join("\n"),
|
|
1271
|
+
images
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1275
|
+
* Result text for the wire's `role: 'tool'` message. A tool that returned only
|
|
1276
|
+
* an image (and possibly a nested image-only tool result) has no text at all,
|
|
1277
|
+
* and neither transport accepts an empty tool content string, so it gets a
|
|
1278
|
+
* descriptor pointing at the user message that follows with the pixels.
|
|
1279
|
+
*/
|
|
1280
|
+
function toolResultTextForWire(media) {
|
|
1281
|
+
return media.text || (media.images.length > 0 ? "(image returned; see the attached image)" : "");
|
|
1282
|
+
}
|
|
1283
|
+
/** Leading line of the user message that carries a tool result's images. */
|
|
1284
|
+
const TOOL_RESULT_IMAGE_TEXT = "Attached image(s) from tool result:";
|
|
1285
|
+
/**
|
|
1286
|
+
* The model-visible note introducing the images carried out of one tool
|
|
1287
|
+
* result. Neither transport merges them back into the tool result, so this
|
|
1288
|
+
* line is what tells the model the image it is about to see belongs to the
|
|
1289
|
+
* tool it just ran rather than to the user; it also leads the part list,
|
|
1290
|
+
* because an image-first content array is what some gateways reject. Count and
|
|
1291
|
+
* pixel dimensions are appended when the tool's own text does not already
|
|
1292
|
+
* state them (a result that returns the image and nothing else).
|
|
1293
|
+
*/
|
|
1294
|
+
function toolResultImageNote(media) {
|
|
1295
|
+
const first = media.images[0];
|
|
1296
|
+
if (first === void 0) return TOOL_RESULT_IMAGE_TEXT;
|
|
1297
|
+
const dimensions = `${first.width}x${first.height} px`;
|
|
1298
|
+
if (first.width <= 0 || first.height <= 0 || media.text.includes(dimensions)) return TOOL_RESULT_IMAGE_TEXT;
|
|
1299
|
+
const count = media.images.length > 1 ? `${media.images.length} images, ` : "";
|
|
1300
|
+
return `${TOOL_RESULT_IMAGE_TEXT} ${count}${dimensions}`;
|
|
1167
1301
|
}
|
|
1168
1302
|
function hasImageContent(message) {
|
|
1169
1303
|
const check = (blocks) => blocks.some((b) => b.type === "image" || b.type === "tool-result" && check(b.content));
|
|
@@ -1231,6 +1365,7 @@ async function messagesToCC(messages, readImage) {
|
|
|
1231
1365
|
if (message.role === "user" && message.source.kind === "tool") {
|
|
1232
1366
|
const block = message.content[0];
|
|
1233
1367
|
if (!block || block.type !== "tool-result" || !paired.has(block.toolCallId)) continue;
|
|
1368
|
+
const media = toolResultMedia(block);
|
|
1234
1369
|
out.push({
|
|
1235
1370
|
role: "tool",
|
|
1236
1371
|
content: [{
|
|
@@ -1239,13 +1374,25 @@ async function messagesToCC(messages, readImage) {
|
|
|
1239
1374
|
toolName: toolNames.get(block.toolCallId) || "unknown",
|
|
1240
1375
|
output: block.isError ? {
|
|
1241
1376
|
type: "error-text",
|
|
1242
|
-
value:
|
|
1377
|
+
value: toolResultTextForWire(media)
|
|
1243
1378
|
} : {
|
|
1244
1379
|
type: "text",
|
|
1245
|
-
value:
|
|
1380
|
+
value: toolResultTextForWire(media)
|
|
1246
1381
|
}
|
|
1247
1382
|
}]
|
|
1248
1383
|
});
|
|
1384
|
+
if (media.images.length > 0) {
|
|
1385
|
+
if (!readImage) throw new LlmError("Image input requires the durable attachment service", "UNSUPPORTED_CONTENT");
|
|
1386
|
+
const carried = [{
|
|
1387
|
+
type: "text",
|
|
1388
|
+
text: toolResultImageNote(media)
|
|
1389
|
+
}];
|
|
1390
|
+
for (const attachment of media.images) carried.push(await imageToCommandCode(attachment, readImage));
|
|
1391
|
+
out.push({
|
|
1392
|
+
role: "user",
|
|
1393
|
+
content: carried
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1249
1396
|
}
|
|
1250
1397
|
}
|
|
1251
1398
|
return out;
|
|
@@ -1324,11 +1471,24 @@ async function messagesToOpenAI(messages, readImage) {
|
|
|
1324
1471
|
if (message.role === "user" && message.source.kind === "tool") {
|
|
1325
1472
|
const block = message.content[0];
|
|
1326
1473
|
if (!block || block.type !== "tool-result" || !paired.has(block.toolCallId)) continue;
|
|
1474
|
+
const media = toolResultMedia(block);
|
|
1327
1475
|
out.push({
|
|
1328
1476
|
role: "tool",
|
|
1329
1477
|
tool_call_id: wireIds.get(block.toolCallId) ?? block.toolCallId,
|
|
1330
|
-
content:
|
|
1478
|
+
content: toolResultTextForWire(media)
|
|
1331
1479
|
});
|
|
1480
|
+
if (media.images.length > 0) {
|
|
1481
|
+
if (!readImage) throw new LlmError("Image input requires the durable attachment service", "UNSUPPORTED_CONTENT");
|
|
1482
|
+
const carried = [{
|
|
1483
|
+
type: "text",
|
|
1484
|
+
text: toolResultImageNote(media)
|
|
1485
|
+
}];
|
|
1486
|
+
for (const attachment of media.images) carried.push(await imageToOpenAI(attachment, readImage));
|
|
1487
|
+
out.push({
|
|
1488
|
+
role: "user",
|
|
1489
|
+
content: carried
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1332
1492
|
}
|
|
1333
1493
|
}
|
|
1334
1494
|
return out;
|
|
@@ -2514,6 +2674,14 @@ function tokensCompact(value) {
|
|
|
2514
2674
|
if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
|
|
2515
2675
|
return String(value);
|
|
2516
2676
|
}
|
|
2677
|
+
/**
|
|
2678
|
+
* Format a success-rate percentage (already in percent units): at most two
|
|
2679
|
+
* decimals, trailing zeros trimmed — mirrors `formatSuccessRate` in
|
|
2680
|
+
* `./client/usage.ts`, which the settings card uses.
|
|
2681
|
+
*/
|
|
2682
|
+
function successRateText(value) {
|
|
2683
|
+
return String(Number(value.toFixed(2)));
|
|
2684
|
+
}
|
|
2517
2685
|
/** Format a millis timestamp as a local date; `n/a` when unset. */
|
|
2518
2686
|
function resetLabel(ms) {
|
|
2519
2687
|
if (ms <= 0) return "n/a";
|
|
@@ -2552,7 +2720,7 @@ function renderReport(report, locale, title) {
|
|
|
2552
2720
|
}
|
|
2553
2721
|
if (report.usage) {
|
|
2554
2722
|
const u = report.usage;
|
|
2555
|
-
lines.push(commandCopy(locale, "usageHeader"), commandCopy(locale, "requestsLine").replace("{n}", String(u.completedCount)).replace("{f}", String(u.failedCount)).replace("{r}",
|
|
2723
|
+
lines.push(commandCopy(locale, "usageHeader"), commandCopy(locale, "requestsLine").replace("{n}", String(u.completedCount)).replace("{f}", String(u.failedCount)).replace("{r}", successRateText(u.successRate)), commandCopy(locale, "costLine").replace("{money}", money(u.totalCost)).replace("{credits}", moneyShort(u.totalCredits)), commandCopy(locale, "tokensLine").replace("{in}", tokensCompact(u.totalTokensIn)).replace("{out}", tokensCompact(u.totalTokensOut)), "");
|
|
2556
2724
|
}
|
|
2557
2725
|
if (report.credits) {
|
|
2558
2726
|
const c = report.credits;
|
|
@@ -3069,6 +3237,14 @@ var CommandCodeLoginFlow = class {
|
|
|
3069
3237
|
timer;
|
|
3070
3238
|
/** Settle hooks of the live attempt's callback promise. */
|
|
3071
3239
|
settle;
|
|
3240
|
+
/**
|
|
3241
|
+
* Attempt generation. A delivered callback keeps validating the key
|
|
3242
|
+
* asynchronously (`complete()`), and that window is open to a cancel or a
|
|
3243
|
+
* fresh `begin()`; the generation lets a late completion recognize that it
|
|
3244
|
+
* no longer owns the status face and stop instead of storing a credential
|
|
3245
|
+
* the user cancelled and flipping the page back to success.
|
|
3246
|
+
*/
|
|
3247
|
+
attemptSeq = 0;
|
|
3072
3248
|
disposed = false;
|
|
3073
3249
|
constructor(deps) {
|
|
3074
3250
|
this.deps = deps;
|
|
@@ -3089,8 +3265,9 @@ var CommandCodeLoginFlow = class {
|
|
|
3089
3265
|
*/
|
|
3090
3266
|
async begin() {
|
|
3091
3267
|
if (this.disposed) throw new Error("login flow has been disposed");
|
|
3092
|
-
if (this.statusValue.state === "waiting") return this.statusValue;
|
|
3268
|
+
if (this.statusValue.state === "waiting" && this.server !== void 0) return this.statusValue;
|
|
3093
3269
|
this.teardown();
|
|
3270
|
+
const attempt = ++this.attemptSeq;
|
|
3094
3271
|
const port = await this.findPort();
|
|
3095
3272
|
const expectedState = this.deps.randomToken?.(32) ?? randomBytes(32).toString("base64url");
|
|
3096
3273
|
const settled = new Promise((resolve, reject) => {
|
|
@@ -3118,7 +3295,7 @@ var CommandCodeLoginFlow = class {
|
|
|
3118
3295
|
});
|
|
3119
3296
|
}, this.deps.timeoutMs ?? 12e4);
|
|
3120
3297
|
this.timer.unref?.();
|
|
3121
|
-
settled.then((credentials) => this.complete(credentials), (failure) => this.failFrom(failure));
|
|
3298
|
+
settled.then((credentials) => this.complete(attempt, credentials), (failure) => this.failFrom(attempt, failure));
|
|
3122
3299
|
return this.statusValue;
|
|
3123
3300
|
}
|
|
3124
3301
|
/** Cancel a waiting attempt; terminal states are untouched. */
|
|
@@ -3235,6 +3412,13 @@ var CommandCodeLoginFlow = class {
|
|
|
3235
3412
|
}
|
|
3236
3413
|
if (typeof payload === "object" && payload !== null && "error" in payload) {
|
|
3237
3414
|
const denial = payload;
|
|
3415
|
+
if (denial.state !== expectedState) {
|
|
3416
|
+
json(403, {
|
|
3417
|
+
success: false,
|
|
3418
|
+
error: "Invalid state token"
|
|
3419
|
+
});
|
|
3420
|
+
return;
|
|
3421
|
+
}
|
|
3238
3422
|
const description = denial.error_description ?? denial.error;
|
|
3239
3423
|
this.settleAttempt(json, 200, { success: true }, new LoginSettleError(denial.error === "access_denied" ? "denied" : "error", typeof description === "string" && description !== "" ? description : "Authorization failed"));
|
|
3240
3424
|
return;
|
|
@@ -3266,10 +3450,20 @@ var CommandCodeLoginFlow = class {
|
|
|
3266
3450
|
if (failure !== void 0) settle.reject(failure);
|
|
3267
3451
|
else if (credentials !== void 0) settle.resolve(credentials);
|
|
3268
3452
|
}
|
|
3269
|
-
/**
|
|
3270
|
-
|
|
3271
|
-
|
|
3453
|
+
/**
|
|
3454
|
+
* Post-validation completion: whoami check, then hand-off to storage.
|
|
3455
|
+
*
|
|
3456
|
+
* Every step re-checks {@link ownsAttempt} first: the whoami round-trip and
|
|
3457
|
+
* the credential write are awaits, and the user may cancel (or start another
|
|
3458
|
+
* attempt) while one is in flight. A completion that no longer owns the
|
|
3459
|
+
* attempt must not write the key or publish a status — otherwise cancel
|
|
3460
|
+
* would report "cancelled" while the credential landed anyway, and the page
|
|
3461
|
+
* would silently flip to success.
|
|
3462
|
+
*/
|
|
3463
|
+
async complete(attempt, credentials) {
|
|
3464
|
+
if (!this.ownsAttempt(attempt)) return;
|
|
3272
3465
|
const validation = await validateCommandApiKey(this.deps.fetchImpl ?? fetch, this.readApiBase(), credentials.apiKey);
|
|
3466
|
+
if (!this.ownsAttempt(attempt)) return;
|
|
3273
3467
|
if (!validation.valid) {
|
|
3274
3468
|
const reason = validation.error === "invalid_key" ? "invalid-key" : validation.error === "network_error" ? "network" : "error";
|
|
3275
3469
|
this.setStatus({
|
|
@@ -3279,9 +3473,11 @@ var CommandCodeLoginFlow = class {
|
|
|
3279
3473
|
});
|
|
3280
3474
|
return;
|
|
3281
3475
|
}
|
|
3476
|
+
if (!this.ownsAttempt(attempt)) return;
|
|
3282
3477
|
try {
|
|
3283
3478
|
await this.deps.storeKey(credentials);
|
|
3284
3479
|
} catch (error) {
|
|
3480
|
+
if (!this.ownsAttempt(attempt)) return;
|
|
3285
3481
|
this.setStatus({
|
|
3286
3482
|
state: "failed",
|
|
3287
3483
|
reason: "unavailable",
|
|
@@ -3289,7 +3485,7 @@ var CommandCodeLoginFlow = class {
|
|
|
3289
3485
|
});
|
|
3290
3486
|
return;
|
|
3291
3487
|
}
|
|
3292
|
-
if (this.
|
|
3488
|
+
if (!this.ownsAttempt(attempt)) return;
|
|
3293
3489
|
this.clearTimer();
|
|
3294
3490
|
this.setStatus({
|
|
3295
3491
|
state: "success",
|
|
@@ -3297,10 +3493,14 @@ var CommandCodeLoginFlow = class {
|
|
|
3297
3493
|
keyName: credentials.keyName
|
|
3298
3494
|
});
|
|
3299
3495
|
}
|
|
3496
|
+
/** Whether one attempt still owns the status face (not cancelled, replaced, or disposed). */
|
|
3497
|
+
ownsAttempt(attempt) {
|
|
3498
|
+
return !this.disposed && this.attemptSeq === attempt && this.statusValue.state === "waiting";
|
|
3499
|
+
}
|
|
3300
3500
|
/** Map a tagged settle rejection onto the status face. */
|
|
3301
|
-
failFrom(failure) {
|
|
3501
|
+
failFrom(attempt, failure) {
|
|
3302
3502
|
if (!(failure instanceof LoginSettleError)) return;
|
|
3303
|
-
if (this.
|
|
3503
|
+
if (!this.ownsAttempt(attempt)) return;
|
|
3304
3504
|
this.setStatus({
|
|
3305
3505
|
state: "failed",
|
|
3306
3506
|
reason: failure.reason,
|
|
@@ -3581,6 +3781,244 @@ var CommandCodeSearchProvider = class {
|
|
|
3581
3781
|
}
|
|
3582
3782
|
};
|
|
3583
3783
|
//#endregion
|
|
3784
|
+
//#region src/tui-settings.ts
|
|
3785
|
+
/** The selector value meaning "no pinned account — follow rotation order". */
|
|
3786
|
+
const ACTIVE_ACCOUNT_AUTO = "auto";
|
|
3787
|
+
/** The selector value meaning "no language override — follow the shell locale". */
|
|
3788
|
+
const LANG_AUTO = "auto";
|
|
3789
|
+
/**
|
|
3790
|
+
* Build the section descriptor. Pure, so tests can pin the exact fields
|
|
3791
|
+
* without a dsh-TUI host.
|
|
3792
|
+
*
|
|
3793
|
+
* Field choices worth keeping: the two option-bearing fields (`activeAccount`,
|
|
3794
|
+
* `lang`) are `text` + `options` rather than `select`, because a `select`
|
|
3795
|
+
* cannot express "unset" — cycling only ever lands on a declared option, so a
|
|
3796
|
+
* `select` would strand the user on a pinned value with no way back to
|
|
3797
|
+
* automatic. The `auto` sentinel plus a `parse` that clears the path keeps the
|
|
3798
|
+
* unset state reachable. The two booleans format their EFFECTIVE default
|
|
3799
|
+
* (`filterModelsByPlan` unset means true at the adapter), so a fresh install
|
|
3800
|
+
* reads true instead of the screen's "(empty)".
|
|
3801
|
+
*/
|
|
3802
|
+
function buildCommandCodeTuiSection(deps) {
|
|
3803
|
+
const ref = deps.apiKeyRef();
|
|
3804
|
+
const slots = deps.accountSlots();
|
|
3805
|
+
return {
|
|
3806
|
+
ns: deps.ns,
|
|
3807
|
+
title: deps.title ?? "Command Code",
|
|
3808
|
+
descriptions: { zh: "Command Code(非官方)" },
|
|
3809
|
+
groups: [
|
|
3810
|
+
{
|
|
3811
|
+
id: "connection",
|
|
3812
|
+
title: "Connection",
|
|
3813
|
+
descriptions: { zh: "连接" }
|
|
3814
|
+
},
|
|
3815
|
+
{
|
|
3816
|
+
id: "models",
|
|
3817
|
+
title: "Models",
|
|
3818
|
+
descriptions: { zh: "模型" }
|
|
3819
|
+
},
|
|
3820
|
+
{
|
|
3821
|
+
id: "advanced",
|
|
3822
|
+
title: "Advanced",
|
|
3823
|
+
descriptions: { zh: "高级" }
|
|
3824
|
+
}
|
|
3825
|
+
],
|
|
3826
|
+
fields: [
|
|
3827
|
+
{
|
|
3828
|
+
path: ["apiKey"],
|
|
3829
|
+
group: "connection",
|
|
3830
|
+
kind: "text",
|
|
3831
|
+
label: "API key",
|
|
3832
|
+
descriptions: { zh: "API 密钥" },
|
|
3833
|
+
secret: { ref },
|
|
3834
|
+
hint: `Stored in the credential store as ${ref}, never in settings.yaml.`,
|
|
3835
|
+
hintDescriptions: { zh: `保存在凭据库(${ref}),不会写入 settings.yaml。` }
|
|
3836
|
+
},
|
|
3837
|
+
{
|
|
3838
|
+
path: ["apiBase"],
|
|
3839
|
+
group: "connection",
|
|
3840
|
+
kind: "text",
|
|
3841
|
+
label: "API base",
|
|
3842
|
+
descriptions: { zh: "API 地址" },
|
|
3843
|
+
placeholder: "https://api.commandcode.ai",
|
|
3844
|
+
hint: "Leave empty for the public Command Code Provider API.",
|
|
3845
|
+
hintDescriptions: { zh: "留空即使用官方 Command Code Provider API。" },
|
|
3846
|
+
parse: (text) => {
|
|
3847
|
+
const trimmed = text.trim();
|
|
3848
|
+
return trimmed === "" ? { kind: "clear" } : {
|
|
3849
|
+
kind: "set",
|
|
3850
|
+
value: trimmed
|
|
3851
|
+
};
|
|
3852
|
+
}
|
|
3853
|
+
},
|
|
3854
|
+
{
|
|
3855
|
+
path: ["filterModelsByPlan"],
|
|
3856
|
+
group: "models",
|
|
3857
|
+
kind: "boolean",
|
|
3858
|
+
label: "Hide out-of-plan models",
|
|
3859
|
+
descriptions: { zh: "隐藏套餐外的模型" },
|
|
3860
|
+
hint: "Keeps models above your subscription tier out of the picker. Fails open.",
|
|
3861
|
+
hintDescriptions: { zh: "在选择器里隐藏超出当前订阅档位的模型;判断不出来时全部显示。" },
|
|
3862
|
+
format: (value) => value === false ? "false" : "true",
|
|
3863
|
+
parse: (text) => ({
|
|
3864
|
+
kind: "set",
|
|
3865
|
+
value: text.trim() === "true"
|
|
3866
|
+
})
|
|
3867
|
+
},
|
|
3868
|
+
{
|
|
3869
|
+
path: ["visibleModels"],
|
|
3870
|
+
group: "models",
|
|
3871
|
+
kind: "text",
|
|
3872
|
+
label: "Visible models",
|
|
3873
|
+
descriptions: { zh: "显示的模型" },
|
|
3874
|
+
hint: "Comma-separated catalog ids; empty shows every model.",
|
|
3875
|
+
hintDescriptions: { zh: "以逗号分隔的模型 id;留空表示显示全部模型。" },
|
|
3876
|
+
format: (value) => (Array.isArray(value) ? value.filter((id) => typeof id === "string" && id !== "") : []).join(", "),
|
|
3877
|
+
parse: (text) => {
|
|
3878
|
+
const ids = text.split(",").map((id) => id.trim()).filter((id) => id !== "");
|
|
3879
|
+
return ids.length === 0 ? { kind: "clear" } : {
|
|
3880
|
+
kind: "set",
|
|
3881
|
+
value: ids
|
|
3882
|
+
};
|
|
3883
|
+
}
|
|
3884
|
+
},
|
|
3885
|
+
{
|
|
3886
|
+
path: ["activeAccount"],
|
|
3887
|
+
group: "advanced",
|
|
3888
|
+
kind: "text",
|
|
3889
|
+
label: "Active account",
|
|
3890
|
+
descriptions: { zh: "当前账号" },
|
|
3891
|
+
hint: "A pinned account id, or auto to follow the rotation order.",
|
|
3892
|
+
hintDescriptions: { zh: "固定使用某个账号的 id;auto 表示按轮换顺序自动选择。" },
|
|
3893
|
+
options: [{
|
|
3894
|
+
value: ACTIVE_ACCOUNT_AUTO,
|
|
3895
|
+
label: "Automatic (rotation order)",
|
|
3896
|
+
descriptions: { zh: "自动(按轮换顺序)" }
|
|
3897
|
+
}, ...slots.map((slot) => ({
|
|
3898
|
+
value: slot.id,
|
|
3899
|
+
label: slot.label,
|
|
3900
|
+
descriptions: { zh: slot.label }
|
|
3901
|
+
}))],
|
|
3902
|
+
format: (value) => typeof value === "string" && value.trim() !== "" ? value : ACTIVE_ACCOUNT_AUTO,
|
|
3903
|
+
parse: (text) => {
|
|
3904
|
+
const trimmed = text.trim();
|
|
3905
|
+
return trimmed === "" || trimmed === "auto" ? { kind: "clear" } : {
|
|
3906
|
+
kind: "set",
|
|
3907
|
+
value: trimmed
|
|
3908
|
+
};
|
|
3909
|
+
}
|
|
3910
|
+
},
|
|
3911
|
+
{
|
|
3912
|
+
path: ["lang"],
|
|
3913
|
+
group: "advanced",
|
|
3914
|
+
kind: "text",
|
|
3915
|
+
label: "Command language",
|
|
3916
|
+
descriptions: { zh: "命令语言" },
|
|
3917
|
+
hint: "Language of the /commandcode dashboard; auto follows the shell locale.",
|
|
3918
|
+
hintDescriptions: { zh: "/commandcode 用量面板的语言;auto 跟随终端 locale。" },
|
|
3919
|
+
options: [
|
|
3920
|
+
{
|
|
3921
|
+
value: LANG_AUTO,
|
|
3922
|
+
label: "Automatic (shell locale)",
|
|
3923
|
+
descriptions: { zh: "自动(跟随终端 locale)" }
|
|
3924
|
+
},
|
|
3925
|
+
{
|
|
3926
|
+
value: "zh",
|
|
3927
|
+
label: "中文"
|
|
3928
|
+
},
|
|
3929
|
+
{
|
|
3930
|
+
value: "en",
|
|
3931
|
+
label: "English"
|
|
3932
|
+
}
|
|
3933
|
+
],
|
|
3934
|
+
format: (value) => value === "zh" || value === "en" ? value : LANG_AUTO,
|
|
3935
|
+
parse: (text) => {
|
|
3936
|
+
const trimmed = text.trim();
|
|
3937
|
+
return trimmed === "" || trimmed === "auto" ? { kind: "clear" } : {
|
|
3938
|
+
kind: "set",
|
|
3939
|
+
value: trimmed
|
|
3940
|
+
};
|
|
3941
|
+
}
|
|
3942
|
+
}
|
|
3943
|
+
]
|
|
3944
|
+
};
|
|
3945
|
+
}
|
|
3946
|
+
/**
|
|
3947
|
+
* The `tuiSettingsSections` service, read defensively.
|
|
3948
|
+
*
|
|
3949
|
+
* The service name is declared by dsh-TUI's own module augmentation, which
|
|
3950
|
+
* this package deliberately does not import, so the typed `Context` has no
|
|
3951
|
+
* such property. The read goes through the REFLECTIVE `ctx.get` rather than a
|
|
3952
|
+
* bare property access: cordis refuses a property read for a service the fiber
|
|
3953
|
+
* never declared in `inject` (`cannot get property … without inject`), and an
|
|
3954
|
+
* unmeet seam must degrade to "no section" instead of throwing out of the
|
|
3955
|
+
* plugin's boot.
|
|
3956
|
+
*/
|
|
3957
|
+
function tuiSettingsService(ctx) {
|
|
3958
|
+
let candidate;
|
|
3959
|
+
try {
|
|
3960
|
+
candidate = ctx.get("tuiSettingsSections");
|
|
3961
|
+
} catch {
|
|
3962
|
+
return;
|
|
3963
|
+
}
|
|
3964
|
+
if (typeof candidate !== "object" || candidate === null) return void 0;
|
|
3965
|
+
const register = candidate.register;
|
|
3966
|
+
if (typeof register !== "function") return void 0;
|
|
3967
|
+
return { register: register.bind(candidate) };
|
|
3968
|
+
}
|
|
3969
|
+
/**
|
|
3970
|
+
* Identity of everything in the section that can change at runtime: the
|
|
3971
|
+
* credential reference behind the API-key field and the account slots behind
|
|
3972
|
+
* the active-account selector. Re-registration is skipped while this matches,
|
|
3973
|
+
* so ordinary settings writes never churn the screen's section list.
|
|
3974
|
+
*/
|
|
3975
|
+
function sectionSignature(section) {
|
|
3976
|
+
const active = section.fields.find((field) => field.path.join(".") === "activeAccount");
|
|
3977
|
+
return JSON.stringify({
|
|
3978
|
+
secret: section.fields.find((field) => field.secret !== void 0)?.secret?.ref ?? "",
|
|
3979
|
+
options: active?.options?.map((option) => option.value) ?? []
|
|
3980
|
+
});
|
|
3981
|
+
}
|
|
3982
|
+
/**
|
|
3983
|
+
* Register the Command Code section on a dsh-TUI host.
|
|
3984
|
+
*
|
|
3985
|
+
* @param ctx - the context of an activated `tuiSettingsSections` injection.
|
|
3986
|
+
* @param deps - plugin-owned facts the section reads.
|
|
3987
|
+
* @returns a refresh function that re-registers the section when a fact it
|
|
3988
|
+
* renders changed (the plugin entry calls it from its settings `onChange`
|
|
3989
|
+
* hook), or `undefined` when the seam is unusable. The returned function is
|
|
3990
|
+
* inert after the fiber is torn down.
|
|
3991
|
+
*/
|
|
3992
|
+
function applyCommandCodeTuiSettings(ctx, deps) {
|
|
3993
|
+
const service = tuiSettingsService(ctx);
|
|
3994
|
+
if (service === void 0) return void 0;
|
|
3995
|
+
let disposed = false;
|
|
3996
|
+
let current;
|
|
3997
|
+
const refresh = () => {
|
|
3998
|
+
if (disposed) return;
|
|
3999
|
+
const section = buildCommandCodeTuiSection(deps);
|
|
4000
|
+
const signature = sectionSignature(section);
|
|
4001
|
+
if (current?.signature === signature) return;
|
|
4002
|
+
current?.dispose();
|
|
4003
|
+
current = void 0;
|
|
4004
|
+
try {
|
|
4005
|
+
current = {
|
|
4006
|
+
signature,
|
|
4007
|
+
dispose: service.register(section)
|
|
4008
|
+
};
|
|
4009
|
+
} catch (error) {
|
|
4010
|
+
ctx.logger?.warn(`llm-commandcode: could not register the dsh-TUI settings section: ${error instanceof Error ? error.message : String(error)}`);
|
|
4011
|
+
}
|
|
4012
|
+
};
|
|
4013
|
+
refresh();
|
|
4014
|
+
ctx.effect(() => () => {
|
|
4015
|
+
disposed = true;
|
|
4016
|
+
current?.dispose();
|
|
4017
|
+
current = void 0;
|
|
4018
|
+
}, "dsh-commandcode-provider: tui settings section");
|
|
4019
|
+
return refresh;
|
|
4020
|
+
}
|
|
4021
|
+
//#endregion
|
|
3584
4022
|
//#region src/index.ts
|
|
3585
4023
|
/**
|
|
3586
4024
|
* dsh-commandcode-provider — DeepSeek Harness LLM provider plugin for Command
|
|
@@ -3619,7 +4057,7 @@ const PROVIDER = "commandcode";
|
|
|
3619
4057
|
const DEFAULT_MODELS_CACHE_PATH = join(homedir(), ".commandcode", "models-cache.json");
|
|
3620
4058
|
const Config = z.object({
|
|
3621
4059
|
apiKeyEnv: z.string().role("credential-ref").default(DEFAULT_API_KEY_ENV),
|
|
3622
|
-
apiKey: z.string(),
|
|
4060
|
+
apiKey: z.string().role("secret"),
|
|
3623
4061
|
apiBase: z.string(),
|
|
3624
4062
|
workingDir: z.string(),
|
|
3625
4063
|
modelsCachePath: z.string(),
|
|
@@ -3631,7 +4069,8 @@ const Config = z.object({
|
|
|
3631
4069
|
accounts: z.array(z.object({
|
|
3632
4070
|
label: z.string(),
|
|
3633
4071
|
apiKeyEnv: z.string().role("credential-ref"),
|
|
3634
|
-
apiKey
|
|
4072
|
+
/** Literal key for one extra slot; see the top-level `apiKey` secret note. */
|
|
4073
|
+
apiKey: z.string().role("secret")
|
|
3635
4074
|
})),
|
|
3636
4075
|
activeAccount: z.string(),
|
|
3637
4076
|
modelAccountRules: z.array(z.object({
|
|
@@ -3821,6 +4260,20 @@ function apply(ctx, config) {
|
|
|
3821
4260
|
applyCommandCodeSearchSelection(webCtx.web, searchSelection, false);
|
|
3822
4261
|
}, "dsh-commandcode-provider: web search selection");
|
|
3823
4262
|
});
|
|
4263
|
+
let refreshTuiSettings;
|
|
4264
|
+
ctx.inject(["tuiSettingsSections"], (tuiCtx) => {
|
|
4265
|
+
refreshTuiSettings = applyCommandCodeTuiSettings(tuiCtx, {
|
|
4266
|
+
ns: NS,
|
|
4267
|
+
apiKeyRef: () => current().apiKeyEnv ?? DEFAULT_API_KEY_ENV,
|
|
4268
|
+
accountSlots: () => slots().map((slot) => ({
|
|
4269
|
+
id: slot.id,
|
|
4270
|
+
label: slot.label
|
|
4271
|
+
}))
|
|
4272
|
+
});
|
|
4273
|
+
tuiCtx.effect(() => () => {
|
|
4274
|
+
refreshTuiSettings = void 0;
|
|
4275
|
+
}, "dsh-commandcode-provider: tui settings handle");
|
|
4276
|
+
});
|
|
3824
4277
|
ctx.inject(["settings"], (settingsCtx) => {
|
|
3825
4278
|
settingsCtx.settings.installSection(ctx, NS, Config, config, {
|
|
3826
4279
|
setSource: (source) => {
|
|
@@ -3828,11 +4281,12 @@ function apply(ctx, config) {
|
|
|
3828
4281
|
},
|
|
3829
4282
|
onChange: () => {
|
|
3830
4283
|
applySearchSelection(current().webSearch ?? true);
|
|
4284
|
+
refreshTuiSettings?.();
|
|
3831
4285
|
}
|
|
3832
4286
|
});
|
|
3833
4287
|
});
|
|
3834
4288
|
}
|
|
3835
4289
|
//#endregion
|
|
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 };
|
|
4290
|
+
export { ACTIVE_ACCOUNT_AUTO, 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, LANG_AUTO, 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, applyCommandCodeTuiSettings, applyCommands, applyUsageRemote, buildCommandAuthUrl, buildCommandCodeTuiSection, 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 };
|
|
3837
4291
|
|
|
3838
4292
|
//# sourceMappingURL=index.js.map
|