@mars-sea/dsh-commandcode-provider 0.13.3 → 0.14.0
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 +64 -0
- package/README.md +43 -12
- package/README.zh-CN.md +34 -8
- package/lib/client.js +827 -112
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +217 -79
- package/lib/index.js +802 -209
- package/lib/index.js.map +1 -1
- package/package.json +42 -43
package/lib/index.js
CHANGED
|
@@ -103,7 +103,7 @@ const PLAN_ORDER = Object.fromEntries(PLAN_TIER_ORDER.map((tier, index) => [tier
|
|
|
103
103
|
* subscription-plan labels, deals, and hourly (peak/off-peak) pricing.
|
|
104
104
|
*
|
|
105
105
|
* Everything here is synced from official sources — the command-code CLI
|
|
106
|
-
* bundle's model table (`dist/cli.mjs`, re-verified at command-code@1.82.
|
|
106
|
+
* bundle's model table (`dist/cli.mjs`, re-verified at command-code@1.82.5) and
|
|
107
107
|
* the official plan/pricing/model docs; see the dsh-commandcode-upstream skill
|
|
108
108
|
* for the extraction procedures. Keeping the snapshot in its own module
|
|
109
109
|
* confines those frequent sync diffs here: src/adapter.ts holds only the stable
|
|
@@ -138,6 +138,11 @@ const KNOWN_EFFORTS = {
|
|
|
138
138
|
"medium",
|
|
139
139
|
"xhigh"
|
|
140
140
|
],
|
|
141
|
+
"Qwen/Qwen3.8-Max-Prime": [
|
|
142
|
+
"low",
|
|
143
|
+
"medium",
|
|
144
|
+
"xhigh"
|
|
145
|
+
],
|
|
141
146
|
"claude-fable-5-1": [
|
|
142
147
|
"low",
|
|
143
148
|
"medium",
|
|
@@ -391,6 +396,16 @@ const KNOWN_EFFORTS = {
|
|
|
391
396
|
"high",
|
|
392
397
|
"xhigh"
|
|
393
398
|
],
|
|
399
|
+
"meta/muse-spark-open": [
|
|
400
|
+
"low",
|
|
401
|
+
"medium",
|
|
402
|
+
"high"
|
|
403
|
+
],
|
|
404
|
+
"nvidia/nemotron-3.5-super-vl": [
|
|
405
|
+
"low",
|
|
406
|
+
"medium",
|
|
407
|
+
"high"
|
|
408
|
+
],
|
|
394
409
|
"gpt-6-astra": [
|
|
395
410
|
"low",
|
|
396
411
|
"medium",
|
|
@@ -453,6 +468,13 @@ const KNOWN_EFFORTS = {
|
|
|
453
468
|
"medium",
|
|
454
469
|
"high",
|
|
455
470
|
"xhigh"
|
|
471
|
+
],
|
|
472
|
+
"stealth/cat-jam:free": [
|
|
473
|
+
"low",
|
|
474
|
+
"medium",
|
|
475
|
+
"high",
|
|
476
|
+
"xhigh",
|
|
477
|
+
"max"
|
|
456
478
|
]
|
|
457
479
|
};
|
|
458
480
|
/**
|
|
@@ -480,6 +502,7 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
|
480
502
|
"Qwen/Qwen3.8-Max",
|
|
481
503
|
"Qwen/Qwen3.8-Max-0902",
|
|
482
504
|
"Qwen/Qwen3.8-Omni-Flash",
|
|
505
|
+
"Qwen/Qwen3.8-Max-Prime",
|
|
483
506
|
"claude-fable-5-1",
|
|
484
507
|
"claude-fable-5",
|
|
485
508
|
"claude-haiku-4-5-20251001",
|
|
@@ -518,6 +541,8 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
|
518
541
|
"meta/muse-spark-1.3",
|
|
519
542
|
"meta/muse-spark-1.3-contributor",
|
|
520
543
|
"meta/muse-wonder-1.0",
|
|
544
|
+
"meta/muse-spark-open",
|
|
545
|
+
"nvidia/nemotron-3.5-super-vl",
|
|
521
546
|
"mistral/mistral-large-4",
|
|
522
547
|
"moonshotai/Kimi-K2.5",
|
|
523
548
|
"moonshotai/Kimi-K2.6",
|
|
@@ -540,14 +565,16 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
|
540
565
|
"z-ai/glm-5.3-flashx"
|
|
541
566
|
]);
|
|
542
567
|
/**
|
|
543
|
-
* Models WITHOUT a zero-data-retention upstream
|
|
544
|
-
* registry
|
|
545
|
-
*
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
*
|
|
549
|
-
* 1.
|
|
550
|
-
*
|
|
568
|
+
* Models WITHOUT a zero-data-retention upstream. Through command-code@1.82.2
|
|
569
|
+
* the CLI's own registry answered this: `modelSupportsZdr(id)` was exactly
|
|
570
|
+
* `!nonZdrSet.has(canonicalize(id))`, and `knownModelSupportsZdr` carried the
|
|
571
|
+
* membership in the sibling route table — the UNION of both was this set, and
|
|
572
|
+
* the two anchors disagreed by one id (`meituan/LongCat-2.0:free`, which 1.74.1
|
|
573
|
+
* dropped from the first anchor while the canonical helper kept it).
|
|
574
|
+
* command-code@1.82.4 removes that disagreement by making the verdict a catalog
|
|
575
|
+
* lookup — `resolveKnownCatalogModel({model,catalog})?.zdr ?? false` — so this
|
|
576
|
+
* set is now the live catalog's `zdr:false` membership and nothing more. The
|
|
577
|
+
* official docs (commandcode.ai/docs/resources/
|
|
551
578
|
* zdr) put coverage in prose — "99% of our models have ZDR-capable upstreams … only
|
|
552
579
|
* a small handful of models are affected" — so the CLI's exclusion list is
|
|
553
580
|
* the only per-model evidence there is; a ZDR request naming one of these
|
|
@@ -560,10 +587,19 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
|
560
587
|
* switch is on — the provider remains the routing authority and refuses an
|
|
561
588
|
* unsupported model rather than silently dropping the privacy guarantee.
|
|
562
589
|
*
|
|
563
|
-
* `minimax/minimax-m3-free` is the one entry the public catalog
|
|
564
|
-
* (`/provider/v1/models`) does not serve (it is CLI/pricing-visible
|
|
565
|
-
* from the picker); it stays listed because the CLI carries it and a Go-plan
|
|
566
|
-
* request can still name it.
|
|
590
|
+
* `minimax/minimax-m3-free` is the one entry the public catalog's default view
|
|
591
|
+
* (`/provider/v1/models`) does not serve through the picker (it is CLI/pricing-visible
|
|
592
|
+
* and hidden from the picker); it stays listed because the CLI carries it and a Go-plan
|
|
593
|
+
* request can still name it. Both this row and `stealth/cat-jam:free` ARE served
|
|
594
|
+
* by the `?view=client` catalog, which is where the 1.82.4 verdict comes from.
|
|
595
|
+
*
|
|
596
|
+
* command-code@1.82.4 moved the authority for this verdict: `knownModelSupportsZdr`
|
|
597
|
+
* no longer negates a bundled Set, it returns `resolveKnownCatalogModel({model,
|
|
598
|
+
* catalog})?.zdr ?? false`, so an id the catalog does not carry (or carries with
|
|
599
|
+
* `zdr:false`) is NOT covered. That inverts the default this helper was built on
|
|
600
|
+
* ("not listed" answered TRUE); the snapshot keeps its negative-set shape because
|
|
601
|
+
* 99% of the catalog is still covered, but membership is now read from the
|
|
602
|
+
* `?view=client` catalog and confirmed against the canonical helper set.
|
|
567
603
|
*
|
|
568
604
|
* Keep in sync via the dsh-commandcode-upstream skill: the CLI's registry data
|
|
569
605
|
* (its `zdr:{only:[…]}` provider routes and the per-provider `zdr`/`noTraining`
|
|
@@ -584,6 +620,11 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
|
584
620
|
* `listSelectableModelIds()`, so they are pruned along with the rest of the
|
|
585
621
|
* tables and `supportsZeroDataRetention` now answers TRUE for them, matching
|
|
586
622
|
* what the current CLI says.
|
|
623
|
+
*
|
|
624
|
+
* 1.82.4 then reversed 1.74.1's removal of `meituan/LongCat-2.0:free` and added
|
|
625
|
+
* the `stealth/cat-jam:free` preview, taking this set to 24 members — the size
|
|
626
|
+
* of the live catalog's `zdr:false` list. The canonical helper set still carries
|
|
627
|
+
* 23 because `cat-jam:free` postdates the artifact's embedded snapshot.
|
|
587
628
|
*/
|
|
588
629
|
const KNOWN_NON_ZDR_MODELS = /* @__PURE__ */ new Set([
|
|
589
630
|
"MiniMaxAI/MiniMax-M3",
|
|
@@ -595,10 +636,12 @@ const KNOWN_NON_ZDR_MODELS = /* @__PURE__ */ new Set([
|
|
|
595
636
|
"meta/muse-spark-1.3",
|
|
596
637
|
"meta/muse-spark-1.3-contributor",
|
|
597
638
|
"meta/muse-wonder-1.0",
|
|
639
|
+
"meituan/LongCat-2.0:free",
|
|
598
640
|
"minimax/minimax-m3-free",
|
|
599
641
|
"poolside/laguna-s-2.1-free",
|
|
600
642
|
"sakana/fugu-ultra",
|
|
601
643
|
"stealth/glyph-cluster:free",
|
|
644
|
+
"stealth/cat-jam:free",
|
|
602
645
|
"stepfun/Step-3.7-Flash",
|
|
603
646
|
"stepfun/Step-5-Preview",
|
|
604
647
|
"xai/grok-4.5",
|
|
@@ -646,8 +689,7 @@ const KNOWN_THINKING_MODELS = /* @__PURE__ */ new Set([
|
|
|
646
689
|
"thinkingmachines/inkling",
|
|
647
690
|
"thinkingmachines/inkling-small",
|
|
648
691
|
"poolside/laguna-s-2.1-free",
|
|
649
|
-
"meituan/LongCat-2.0"
|
|
650
|
-
"inclusionai/ling-3.0-flash-sante:free"
|
|
692
|
+
"meituan/LongCat-2.0"
|
|
651
693
|
]);
|
|
652
694
|
/**
|
|
653
695
|
* Catalog models the Provider API serves ONLY through `/provider/v1/messages`
|
|
@@ -715,11 +757,12 @@ function requiresMessagesEndpoint(modelId) {
|
|
|
715
757
|
* catalog ids as of 2026-10-15. Each row carries a comment naming the entry it
|
|
716
758
|
* came from.
|
|
717
759
|
*
|
|
718
|
-
*
|
|
719
|
-
*
|
|
720
|
-
*
|
|
721
|
-
*
|
|
722
|
-
*
|
|
760
|
+
* No row needs a hand-recorded pin as of 2026-10-16: the one that did
|
|
761
|
+
* (`inclusionai/ling-3.0-flash-sante:free`, pinned at 32768 because it had lost
|
|
762
|
+
* both its vendor row and its unanimous third-party vote) left the live catalog
|
|
763
|
+
* with the 1.82.4 catalog migration, so the script's `MANUAL_CEILINGS` map is
|
|
764
|
+
* now empty and every remaining row resolves from a vendor row or a unanimous
|
|
765
|
+
* third-party vote. (`gpt-6.1-sol` (128000) never needed a pin — OpenAI's own row
|
|
723
766
|
* carries it and every other provider carrying the id agrees.) All are still
|
|
724
767
|
* correctable — a stricter gateway costs one refused request, and
|
|
725
768
|
* `streamRequest()` remembers what the endpoint said.
|
|
@@ -769,7 +812,6 @@ const MODEL_OUTPUT_TOKEN_LIMITS = /* @__PURE__ */ new Map([
|
|
|
769
812
|
["gpt-6-luna", 128e3],
|
|
770
813
|
["gpt-6-sol", 128e3],
|
|
771
814
|
["gpt-6.1-sol", 128e3],
|
|
772
|
-
["inclusionai/ling-3.0-flash-sante:free", 32768],
|
|
773
815
|
["meituan/LongCat-2.0", 131072],
|
|
774
816
|
["meta/muse-spark-1.1", 131072],
|
|
775
817
|
["meta/muse-spark-1.2", 131072],
|
|
@@ -820,15 +862,20 @@ const MODEL_OUTPUT_TOKEN_LIMITS = /* @__PURE__ */ new Map([
|
|
|
820
862
|
* `/docs/plans/max` and `/docs/resources/pricing-limits`). Each plan's model
|
|
821
863
|
* list is a superset of the one below it: Go ⊂ GOAT ⊂ Pro ⊂ Provider/Max.
|
|
822
864
|
* Models absent from every plan list (Claude Opus/Fable, Fugu Ultra) are
|
|
823
|
-
* Provider-tier. Re-verified at command-code@1.82.
|
|
824
|
-
*
|
|
865
|
+
* Provider-tier. Re-verified at command-code@1.82.4 (2026-10-16) against the
|
|
866
|
+
* live `?view=client` catalog: 93 ids, still a strict superset chain. Mistral
|
|
867
|
+
* Large 4
|
|
825
868
|
* (1.75.0) was the only addition at the 1.77.0 check; the 1.78.0/1.79.0 pair
|
|
826
869
|
* added Claude Haiku 5.5 at Pro and the free Glyph Cluster preview at Go.
|
|
827
870
|
* 1.79.2 lowers Haiku 5.5's minimum plan to GOAT; 1.80.0 adds Muse Wonder at
|
|
828
|
-
* Max; 1.82.0 adds Gemini 4 Argon at Pro and nothing else moves.
|
|
871
|
+
* Max; 1.82.0 adds Gemini 4 Argon at Pro and nothing else moves. The 1.82.4
|
|
872
|
+
* train adds Qwen 3.8 Max Prime at GOAT plus Muse Spark Open, Nemotron 3.5
|
|
873
|
+
* Super VL and the free Cat Jam preview at Go, and drops Ling 3.0 Flash Sante,
|
|
874
|
+
* which left the live catalog. Every other
|
|
829
875
|
* tier is unchanged.
|
|
830
876
|
* The only removals since 1.49.0 are the two retired stealth previews (Pixel
|
|
831
|
-
* Canary at 1.73.1, Space Bunny Alpha at 1.74.3)
|
|
877
|
+
* Canary at 1.73.1, Space Bunny Alpha at 1.74.3) and Ling 3.0 Flash Sante
|
|
878
|
+
* (2026-10-16). Per-entry tags below name the
|
|
832
879
|
* releases that added or moved each row.
|
|
833
880
|
*
|
|
834
881
|
* The Provider API exposes no plan metadata, so this snapshot is the source of
|
|
@@ -852,6 +899,7 @@ const KNOWN_PLANS = {
|
|
|
852
899
|
"Qwen/Qwen3.8-Flash": "go",
|
|
853
900
|
"Qwen/Qwen3.8-Max": "go",
|
|
854
901
|
"Qwen/Qwen3.8-Max-0902": "go",
|
|
902
|
+
"Qwen/Qwen3.8-Max-Prime": "goat",
|
|
855
903
|
"Qwen/Qwen3.8-Omni-Flash": "go",
|
|
856
904
|
"deepseek/deepseek-v4-flash-fast": "go",
|
|
857
905
|
"deepseek/deepseek-v4.1-flash": "go",
|
|
@@ -862,10 +910,10 @@ const KNOWN_PLANS = {
|
|
|
862
910
|
"gpt-5.6-luna": "go",
|
|
863
911
|
"gpt-6-luna": "go",
|
|
864
912
|
"meituan/LongCat-2.0": "go",
|
|
865
|
-
"inclusionai/ling-3.0-flash-sante:free": "go",
|
|
866
913
|
"inclusionai/ling-3.1-flash": "go",
|
|
867
914
|
"meta/muse-spark-1.2-contributor": "go",
|
|
868
915
|
"meta/muse-spark-1.3-contributor": "go",
|
|
916
|
+
"meta/muse-spark-open": "go",
|
|
869
917
|
"mistral/mistral-large-4": "go",
|
|
870
918
|
"moonshotai/Kimi-K2.5": "go",
|
|
871
919
|
"moonshotai/Kimi-K2.6": "go",
|
|
@@ -873,10 +921,12 @@ const KNOWN_PLANS = {
|
|
|
873
921
|
"moonshotai/Kimi-K2.7-Code-Highspeed": "go",
|
|
874
922
|
"moonshotai/Kimi-K3": "go",
|
|
875
923
|
"nvidia/nemotron-3-ultra-550b-a55b": "go",
|
|
924
|
+
"nvidia/nemotron-3.5-super-vl": "go",
|
|
876
925
|
"poolside/laguna-s-2.1-free": "go",
|
|
877
926
|
"stepfun/Step-3.5-Flash": "go",
|
|
878
927
|
"stepfun/Step-3.7-Flash": "go",
|
|
879
928
|
"stepfun/Step-5-Preview": "go",
|
|
929
|
+
"stealth/cat-jam:free": "go",
|
|
880
930
|
"stealth/glyph-cluster:free": "go",
|
|
881
931
|
"tencent/hy3-paid": "go",
|
|
882
932
|
"tencent/hy4-preview": "go",
|
|
@@ -1069,7 +1119,7 @@ const KNOWN_DEALS = {
|
|
|
1069
1119
|
label: "FREE",
|
|
1070
1120
|
free: true
|
|
1071
1121
|
},
|
|
1072
|
-
"
|
|
1122
|
+
"stealth/cat-jam:free": {
|
|
1073
1123
|
label: "FREE",
|
|
1074
1124
|
free: true
|
|
1075
1125
|
},
|
|
@@ -1825,8 +1875,8 @@ const MODEL_PRICE_ROWS = [
|
|
|
1825
1875
|
],
|
|
1826
1876
|
allowance: {
|
|
1827
1877
|
go: 6,
|
|
1828
|
-
goat:
|
|
1829
|
-
pro:
|
|
1878
|
+
goat: 20,
|
|
1879
|
+
pro: 30
|
|
1830
1880
|
},
|
|
1831
1881
|
contextTiers: [{
|
|
1832
1882
|
"maxContext": 272e3,
|
|
@@ -2146,8 +2196,8 @@ const MODEL_PRICE_ROWS = [
|
|
|
2146
2196
|
],
|
|
2147
2197
|
allowance: {
|
|
2148
2198
|
go: 6,
|
|
2149
|
-
goat:
|
|
2150
|
-
pro:
|
|
2199
|
+
goat: 100,
|
|
2200
|
+
pro: 110
|
|
2151
2201
|
}
|
|
2152
2202
|
},
|
|
2153
2203
|
{
|
|
@@ -2360,6 +2410,19 @@ const MODEL_PRICE_ROWS = [
|
|
|
2360
2410
|
pro: 30
|
|
2361
2411
|
}
|
|
2362
2412
|
},
|
|
2413
|
+
{
|
|
2414
|
+
id: "muse-spark-open",
|
|
2415
|
+
rates: [
|
|
2416
|
+
1.25,
|
|
2417
|
+
4.25,
|
|
2418
|
+
.15
|
|
2419
|
+
],
|
|
2420
|
+
allowance: {
|
|
2421
|
+
go: 6,
|
|
2422
|
+
goat: 20,
|
|
2423
|
+
pro: 30
|
|
2424
|
+
}
|
|
2425
|
+
},
|
|
2363
2426
|
{
|
|
2364
2427
|
id: "muse-wonder-1.0",
|
|
2365
2428
|
rates: [
|
|
@@ -2386,6 +2449,19 @@ const MODEL_PRICE_ROWS = [
|
|
|
2386
2449
|
pro: 30
|
|
2387
2450
|
}
|
|
2388
2451
|
},
|
|
2452
|
+
{
|
|
2453
|
+
id: "nemotron-3.5-super-vl",
|
|
2454
|
+
rates: [
|
|
2455
|
+
.2,
|
|
2456
|
+
.5,
|
|
2457
|
+
.04
|
|
2458
|
+
],
|
|
2459
|
+
allowance: {
|
|
2460
|
+
go: 6,
|
|
2461
|
+
goat: 20,
|
|
2462
|
+
pro: 30
|
|
2463
|
+
}
|
|
2464
|
+
},
|
|
2389
2465
|
{
|
|
2390
2466
|
id: "qwen-3.6-max",
|
|
2391
2467
|
rates: [
|
|
@@ -2664,6 +2740,19 @@ const MODEL_PRICE_ROWS = [
|
|
|
2664
2740
|
goat: 20,
|
|
2665
2741
|
pro: 30
|
|
2666
2742
|
}
|
|
2743
|
+
},
|
|
2744
|
+
{
|
|
2745
|
+
id: "typesafe/jev-1.14.0",
|
|
2746
|
+
rates: [
|
|
2747
|
+
.042,
|
|
2748
|
+
0,
|
|
2749
|
+
0
|
|
2750
|
+
],
|
|
2751
|
+
allowance: {
|
|
2752
|
+
go: 6,
|
|
2753
|
+
goat: 20,
|
|
2754
|
+
pro: 30
|
|
2755
|
+
}
|
|
2667
2756
|
}
|
|
2668
2757
|
];
|
|
2669
2758
|
/**
|
|
@@ -3552,6 +3641,175 @@ function modelIsVisible(id, allowlist, overrides) {
|
|
|
3552
3641
|
const flag = overrides !== null && typeof overrides === "object" && !Array.isArray(overrides) ? overrides[id] : void 0;
|
|
3553
3642
|
return typeof flag === "boolean" ? flag : allowlist.length === 0 || allowlist.includes(id);
|
|
3554
3643
|
}
|
|
3644
|
+
/** 自定义上下文长度接受的上界(token);覆盖任何已知模型窗口仍留有余量。 */
|
|
3645
|
+
const CONTEXT_WINDOW_MAX = 1e7;
|
|
3646
|
+
/**
|
|
3647
|
+
* 判定一个值能否作为自定义上下文长度:边界内的安全整数,符合宿主模型元数据约束。
|
|
3648
|
+
*
|
|
3649
|
+
* 程序化构造的连接可能绕过 schemastery 归一化,所以 `resolveAdapterOptions`
|
|
3650
|
+
* 也要重新判定——越界或非数值直接丢弃,回落到目录公布值,而不是让请求带着
|
|
3651
|
+
* 一个网关会拒绝的窗口上线。
|
|
3652
|
+
*/
|
|
3653
|
+
function isValidContextWindow(value) {
|
|
3654
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 1 && value <= 1e7;
|
|
3655
|
+
}
|
|
3656
|
+
/**
|
|
3657
|
+
* 只接收合法条目的按模型覆盖表;空表、非对象与非法数值一律视为未设置。
|
|
3658
|
+
* 配置校验会拒绝非整数或越界值;本函数额外保护绕过配置校验的程序化调用,
|
|
3659
|
+
* 丢弃坏条目,不把非法窗口交给宿主。返回新对象;未设置时为 `undefined`。
|
|
3660
|
+
*/
|
|
3661
|
+
function readContextWindowOverrides(raw) {
|
|
3662
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
3663
|
+
const entries = Object.entries(raw).filter((entry) => entry[0] !== "" && isValidContextWindow(entry[1]));
|
|
3664
|
+
return entries.length === 0 ? void 0 : Object.fromEntries(entries);
|
|
3665
|
+
}
|
|
3666
|
+
/**
|
|
3667
|
+
* 一次模型解析的有效上下文长度:按模型覆盖 → 全局值 → 目录公布值。
|
|
3668
|
+
*
|
|
3669
|
+
* 适配器只在有目录条目时调用(未知模型保持既有的「无上下文宣告」形状),
|
|
3670
|
+
* 所以 `catalog` 是已知的正数;按模型覆盖与全局值在 `resolveAdapterOptions`
|
|
3671
|
+
* 已清洗,这里对程序化构造的连接再判一次,坏值静默回落到目录值。
|
|
3672
|
+
*/
|
|
3673
|
+
function effectiveContextWindow(catalog, model, options) {
|
|
3674
|
+
const perModel = options.contextWindowOverrides?.[model];
|
|
3675
|
+
if (isValidContextWindow(perModel)) return perModel;
|
|
3676
|
+
if (isValidContextWindow(options.contextWindow)) return options.contextWindow;
|
|
3677
|
+
return catalog;
|
|
3678
|
+
}
|
|
3679
|
+
//#endregion
|
|
3680
|
+
//#region src/output-budget.ts
|
|
3681
|
+
/**
|
|
3682
|
+
* Output-budget bounds shared by the Host, the browser settings page and the
|
|
3683
|
+
* adapter (issue #74).
|
|
3684
|
+
*
|
|
3685
|
+
* The module stays pure (no node imports) so the client bundle can inline it,
|
|
3686
|
+
* exactly like `context-window.ts`. The report behind issue #74 asked for a
|
|
3687
|
+
* knob that stops the adapter from writing `max_tokens` at all; that is the
|
|
3688
|
+
* `0` sentinel here rather than a separate `null` spelling, because the value
|
|
3689
|
+
* travels through one numeric settings field on both surfaces.
|
|
3690
|
+
*/
|
|
3691
|
+
/**
|
|
3692
|
+
* The output budget this bundle asks for, and the ceiling it will never exceed.
|
|
3693
|
+
*
|
|
3694
|
+
* 131 072, chosen against a measured provider limit rather than a round guess.
|
|
3695
|
+
* Probing `/alpha/generate` directly on 2026-09-28: 64 000 → 200, 131 072 → 200,
|
|
3696
|
+
* 196 608 → 200, **200 000 → 200, 200 704 → 400**, 1 000 000 → 400. The server
|
|
3697
|
+
* therefore caps `max_tokens` at 200 000, and it does so identically for a
|
|
3698
|
+
* 262 144-window model (`stealth/pixel-canary`, retired by Command Code on
|
|
3699
|
+
* 2026-10-01 and named here only as the probe's subject) and a 1 000 000-window
|
|
3700
|
+
* one (`stealth/space-bunny-alpha`) — the cap is global, not per model. 131 072
|
|
3701
|
+
* keeps a 35 % margin under it while covering the real output ceiling of the
|
|
3702
|
+
* models that have one (Claude 64 K, Gemini 64 K, several at 128 K).
|
|
3703
|
+
*
|
|
3704
|
+
* The old 64 000 was never a provider requirement: it cost nothing in latency
|
|
3705
|
+
* (a 64 000 request answered headers in 1.28 s against 1.57 s for 131 072) but
|
|
3706
|
+
* it did cap what a model could ever say in one turn.
|
|
3707
|
+
*
|
|
3708
|
+
* No client-side clamp is applied on top of this: an earlier version pre-shrank
|
|
3709
|
+
* `max_tokens` from an estimate of the prompt already sent (`requestContextBudget()`,
|
|
3710
|
+
* issue #67), but that estimate over-corrected on ordinary long conversations —
|
|
3711
|
+
* exactly the "output gets cut short as the conversation grows" symptom issue #74
|
|
3712
|
+
* reported — while a live A/B probe (2026-09-30, `stealth/space-bunny-alpha`,
|
|
3713
|
+
* ~19.6% over its 1,000,000-token window with no client-side shrink) showed the
|
|
3714
|
+
* endpoint answers a genuine overflow with an unambiguous `400 … exceeds the
|
|
3715
|
+
* model's context window`, which `isContextOverflowDetail()` classifies as
|
|
3716
|
+
* `CONTEXT_WINDOW_EXCEEDED` independently of this constant.
|
|
3717
|
+
*
|
|
3718
|
+
* A user-set positive budget can only TIGHTEN this, never raise it — a figure
|
|
3719
|
+
* above the cap would be refused by the gateway anyway, so the settings field
|
|
3720
|
+
* refuses it at the input instead of spending a round trip to learn that.
|
|
3721
|
+
*
|
|
3722
|
+
* Lives here rather than in `adapter.ts` so the browser settings page can bound
|
|
3723
|
+
* its input by the same figure the Host enforces; `adapter.ts` re-exports it
|
|
3724
|
+
* under its historical name.
|
|
3725
|
+
*/
|
|
3726
|
+
const MAX_OUTPUT_TOKEN_LIMIT = 131072;
|
|
3727
|
+
/**
|
|
3728
|
+
* Whether a value can serve as a user output budget: the omit sentinel, or a
|
|
3729
|
+
* whole token count inside the gateway's own ceiling.
|
|
3730
|
+
*
|
|
3731
|
+
* Programmatically built connections can bypass schemastery normalization, so
|
|
3732
|
+
* the resolve step re-judges every value here and drops an out-of-range one
|
|
3733
|
+
* back to "unset" instead of letting the request carry a budget the gateway
|
|
3734
|
+
* refuses.
|
|
3735
|
+
*/
|
|
3736
|
+
function isValidMaxOutputTokens(value) {
|
|
3737
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= 131072;
|
|
3738
|
+
}
|
|
3739
|
+
/**
|
|
3740
|
+
* Apply a user output budget to a derived one.
|
|
3741
|
+
*
|
|
3742
|
+
* `undefined` means the user set nothing, so the derived ceiling stands
|
|
3743
|
+
* unchanged — the default path, and the one every existing profile takes.
|
|
3744
|
+
* {@link OMIT_OUTPUT_TOKENS} is a wire instruction rather than a number, so it
|
|
3745
|
+
* also leaves the derived budget alone; {@link omitOutputTokens} is what turns
|
|
3746
|
+
* it into an absent field.
|
|
3747
|
+
*/
|
|
3748
|
+
function applyMaxOutputTokens(derived, configured) {
|
|
3749
|
+
if (configured === void 0 || configured === 0) return derived;
|
|
3750
|
+
return Math.min(derived, configured);
|
|
3751
|
+
}
|
|
3752
|
+
/**
|
|
3753
|
+
* Whether this attempt must send no `max_tokens` field at all.
|
|
3754
|
+
*
|
|
3755
|
+
* Two guards narrow this beyond the user's own setting, and both are
|
|
3756
|
+
* correctness requirements rather than conveniences:
|
|
3757
|
+
*
|
|
3758
|
+
* - The Messages route is excluded deliberately: the endpoint rejects a body
|
|
3759
|
+
* without the field (`Invalid input: expected number, received undefined at
|
|
3760
|
+
* max_tokens`), so omitting it there would not lengthen an answer, it would
|
|
3761
|
+
* make the whole Claude family unusable. The CLI and OpenAI transports accept
|
|
3762
|
+
* an absent field, which is where the setting does what it promises.
|
|
3763
|
+
* - An explicit per-call budget from the host is an instruction, not a default,
|
|
3764
|
+
* so it wins over the plugin-wide setting. `session-title` is the concrete
|
|
3765
|
+
* case: it asks for 64 tokens precisely because its output must stay bounded,
|
|
3766
|
+
* and dropping the field there would let a title generation run long instead
|
|
3767
|
+
* of making a long answer fit (issue #88's fix depends on that bound).
|
|
3768
|
+
*/
|
|
3769
|
+
function omitOutputTokens(configured, protocol, hostRequestedTokens) {
|
|
3770
|
+
if (configured !== 0) return false;
|
|
3771
|
+
if (protocol === "messages") return false;
|
|
3772
|
+
return hostRequestedTokens === void 0;
|
|
3773
|
+
}
|
|
3774
|
+
//#endregion
|
|
3775
|
+
//#region src/readable-error.ts
|
|
3776
|
+
/**
|
|
3777
|
+
* Readable error text for the settings page AND the Host-side failure paths.
|
|
3778
|
+
*
|
|
3779
|
+
* @module dsh-commandcode-provider/readable-error
|
|
3780
|
+
*/
|
|
3781
|
+
/**
|
|
3782
|
+
* Turn a caught value into readable text.
|
|
3783
|
+
*
|
|
3784
|
+
* `error instanceof Error ? error.message : String(error)` degrades to the
|
|
3785
|
+
* literal string `[object Object]` for a plain object with no `Error`
|
|
3786
|
+
* prototype — which is exactly what a Remote call across the webview/host
|
|
3787
|
+
* boundary can hand back: `dsh-typert-protocol` serializes a rejection for
|
|
3788
|
+
* the wire, and a structurally-cloned value loses its prototype chain even
|
|
3789
|
+
* when the host threw a real `Error` (issue #74). Reading a `message` string
|
|
3790
|
+
* off the plain object first recovers the original text in that case; only a
|
|
3791
|
+
* value with neither an `Error` prototype nor a usable `message` field falls
|
|
3792
|
+
* through to a JSON dump.
|
|
3793
|
+
*
|
|
3794
|
+
* Shared by both halves on purpose. The browser client needs it for rejections
|
|
3795
|
+
* that crossed the RPC boundary, and the Host needs it for values that reached
|
|
3796
|
+
* it from a transport, a JSON body, or a user-thrown non-Error — the same
|
|
3797
|
+
* degradation, one owner.
|
|
3798
|
+
*/
|
|
3799
|
+
function readableErrorText(error) {
|
|
3800
|
+
if (error instanceof Error) return error.message;
|
|
3801
|
+
if (typeof error === "string") return error;
|
|
3802
|
+
if (error !== null && typeof error === "object") {
|
|
3803
|
+
const message = error.message;
|
|
3804
|
+
if (typeof message === "string" && message !== "") return message;
|
|
3805
|
+
try {
|
|
3806
|
+
return JSON.stringify(error);
|
|
3807
|
+
} catch {
|
|
3808
|
+
return Object.prototype.toString.call(error);
|
|
3809
|
+
}
|
|
3810
|
+
}
|
|
3811
|
+
return String(error);
|
|
3812
|
+
}
|
|
3555
3813
|
const THROTTLED_CODE = "THROTTLED";
|
|
3556
3814
|
const boundedCodes = /* @__PURE__ */ new Set([
|
|
3557
3815
|
"EMPTY_RESPONSE",
|
|
@@ -5858,6 +6116,13 @@ function learnedOutputLimit(apiBase, model) {
|
|
|
5858
6116
|
function modelOutputTokenLimit(apiBase, model) {
|
|
5859
6117
|
return Math.min(published.get(apiBase)?.get(model) ?? fallbackLimit(model), learnedOutputLimit(apiBase, model));
|
|
5860
6118
|
}
|
|
6119
|
+
/**
|
|
6120
|
+
* 从本次目录记录的独立输出上限重新推导预算,不能复用已经被旧窗口压低的 maxTokens。
|
|
6121
|
+
* 公布值固定在该记录,不读取并发刷新后的共享目录;只继续采纳同网关更低的学习上限。
|
|
6122
|
+
*/
|
|
6123
|
+
function catalogModelMaxTokens(apiBase, model, contextWindow = model.contextWindow) {
|
|
6124
|
+
return Math.min(contextWindow, model.publishedMaxTokens ?? fallbackLimit(model.id), learnedOutputLimit(apiBase, model.id));
|
|
6125
|
+
}
|
|
5861
6126
|
/** 是否更新共享事实与是否值得重试是两个问题;以本次实发预算判断拒绝。 */
|
|
5862
6127
|
function outputCeilingRefusal(apiBase, model, sent, error) {
|
|
5863
6128
|
if (!(error instanceof LlmError) || !OUTPUT_LIMIT_REJECTION.test(error.message)) return void 0;
|
|
@@ -6013,37 +6278,15 @@ var GatewayFacts = class {
|
|
|
6013
6278
|
* per-request options thunk and an API-key resolver from the plugin entry
|
|
6014
6279
|
* (src/index.ts), so a settings change reaches the very next request.
|
|
6015
6280
|
*/
|
|
6016
|
-
const COMMAND_CODE_CLI_VERSION = "1.82.
|
|
6281
|
+
const COMMAND_CODE_CLI_VERSION = "1.82.5";
|
|
6017
6282
|
const DEFAULT_API_BASE = "https://api.commandcode.ai";
|
|
6018
6283
|
/**
|
|
6019
6284
|
* The output budget this bundle asks for, and the ceiling it will never exceed.
|
|
6020
|
-
*
|
|
6021
|
-
*
|
|
6022
|
-
*
|
|
6023
|
-
* 196 608 → 200, **200 000 → 200, 200 704 → 400**, 1 000 000 → 400. The server
|
|
6024
|
-
* therefore caps `max_tokens` at 200 000, and it does so identically for a
|
|
6025
|
-
* 262 144-window model (`stealth/pixel-canary`, retired by Command Code on
|
|
6026
|
-
* 2026-10-01 and named here only as the probe's subject) and a 1 000 000-window
|
|
6027
|
-
* one (`stealth/space-bunny-alpha`) — the cap is global, not per model. 131 072
|
|
6028
|
-
* keeps a 35 % margin under it while covering the real output ceiling of the
|
|
6029
|
-
* models that have one (Claude 64 K, Gemini 64 K, several at 128 K).
|
|
6030
|
-
*
|
|
6031
|
-
* The old 64 000 was never a provider requirement: it cost nothing in latency
|
|
6032
|
-
* (a 64 000 request answered headers in 1.28 s against 1.57 s for 131 072) but
|
|
6033
|
-
* it did cap what a model could ever say in one turn.
|
|
6034
|
-
*
|
|
6035
|
-
* No client-side clamp is applied on top of this: an earlier version pre-shrank
|
|
6036
|
-
* `max_tokens` from an estimate of the prompt already sent (`requestContextBudget()`,
|
|
6037
|
-
* issue #67), but that estimate over-corrected on ordinary long conversations —
|
|
6038
|
-
* exactly the "output gets cut short as the conversation grows" symptom issue #74
|
|
6039
|
-
* reported — while a live A/B probe (2026-09-30, `stealth/space-bunny-alpha`,
|
|
6040
|
-
* ~19.6% over its 1,000,000-token window with no client-side shrink) showed the
|
|
6041
|
-
* endpoint answers a genuine overflow with an unambiguous `400 … exceeds the
|
|
6042
|
-
* model's context window`, which `isContextOverflowDetail()` below already
|
|
6043
|
-
* classifies as `CONTEXT_WINDOW_EXCEEDED` independently of this constant. See
|
|
6044
|
-
* [决策记录](../docs/决策记录.md).
|
|
6285
|
+
* The figure and its probe evidence live in `./output-budget.ts` — the module
|
|
6286
|
+
* the browser settings page also bounds its input by — and are re-exported here
|
|
6287
|
+
* under the historical name so existing importers are unaffected.
|
|
6045
6288
|
*/
|
|
6046
|
-
const DEFAULT_GENERATE_MAX_TOKENS =
|
|
6289
|
+
const DEFAULT_GENERATE_MAX_TOKENS = MAX_OUTPUT_TOKEN_LIMIT;
|
|
6047
6290
|
const MODELS_TIMEOUT_MS = 1e4;
|
|
6048
6291
|
/** How long the picker's plan-filter billing facts stay cached before refetching. */
|
|
6049
6292
|
const BILLING_ACCESS_TTL_MS = 3e5;
|
|
@@ -7274,7 +7517,7 @@ async function buildCliBody(options, connection, facts) {
|
|
|
7274
7517
|
input_schema: toolParametersSchema(tool.parameters)
|
|
7275
7518
|
})),
|
|
7276
7519
|
system: cliSystem(facts.systemText),
|
|
7277
|
-
max_tokens: facts.maxTokens,
|
|
7520
|
+
...facts.omitMaxTokens ? {} : { max_tokens: facts.maxTokens },
|
|
7278
7521
|
temperature: options.temperature ?? .3,
|
|
7279
7522
|
stream: true,
|
|
7280
7523
|
...facts.reasoningEffort ? { reasoning_effort: facts.reasoningEffort } : {}
|
|
@@ -7300,7 +7543,7 @@ async function buildOpenAIBody(options, facts) {
|
|
|
7300
7543
|
model: options.model,
|
|
7301
7544
|
messages: openAiMessages,
|
|
7302
7545
|
...openAiTools.length > 0 ? { tools: openAiTools } : {},
|
|
7303
|
-
max_tokens: facts.maxTokens,
|
|
7546
|
+
...facts.omitMaxTokens ? {} : { max_tokens: facts.maxTokens },
|
|
7304
7547
|
temperature: options.temperature ?? .3,
|
|
7305
7548
|
stream: true,
|
|
7306
7549
|
...facts.reasoningEffort ? { reasoning_effort: facts.reasoningEffort } : {}
|
|
@@ -7363,6 +7606,16 @@ async function buildMessagesBody(options, facts) {
|
|
|
7363
7606
|
return body;
|
|
7364
7607
|
}
|
|
7365
7608
|
/**
|
|
7609
|
+
* Write the output budget into an already-built body, each transport in its own
|
|
7610
|
+
* shape (the CLI route nests it under `params`). Used to keep a REBUILT body's
|
|
7611
|
+
* figure in step with the budget without duplicating the nesting knowledge, and
|
|
7612
|
+
* only called when the field is not being omitted (issue #74).
|
|
7613
|
+
*/
|
|
7614
|
+
function assignMaxTokens(body, target, maxTokens) {
|
|
7615
|
+
if (target === "cli") recordOrEmpty(body.params).max_tokens = maxTokens;
|
|
7616
|
+
else body.max_tokens = maxTokens;
|
|
7617
|
+
}
|
|
7618
|
+
/**
|
|
7366
7619
|
* One connect attempt's inputs: everything `connectGenerate` needs beyond
|
|
7367
7620
|
* the per-attempt key and protocol. Passed explicitly (not closed over) so
|
|
7368
7621
|
* the rotation loop's mutation of `protocol`/`body` stays visible at the
|
|
@@ -7676,7 +7929,7 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
7676
7929
|
provider,
|
|
7677
7930
|
id: model.id,
|
|
7678
7931
|
name: `${model.name} (CC)`,
|
|
7679
|
-
description: capabilityDescription(model.id, model.contextWindow),
|
|
7932
|
+
description: capabilityDescription(model.id, effectiveContextWindow(model.contextWindow, model.id, connection)),
|
|
7680
7933
|
inputModalities: vision ? ["text", "image"] : ["text"]
|
|
7681
7934
|
};
|
|
7682
7935
|
};
|
|
@@ -7716,16 +7969,17 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
7716
7969
|
const entry = this.gatewayFacts.knownModel(connection, model) ?? (await this.gatewayFacts.refresh(connection, signal)).find((m) => m.id === model);
|
|
7717
7970
|
const efforts = KNOWN_EFFORTS[model];
|
|
7718
7971
|
const vision = KNOWN_IMAGE_MODELS.has(model);
|
|
7972
|
+
const contextWindow = entry === void 0 ? void 0 : effectiveContextWindow(entry.contextWindow, model, connection);
|
|
7719
7973
|
return {
|
|
7720
7974
|
provider,
|
|
7721
7975
|
id: model,
|
|
7722
7976
|
name: entry ? `${entry.name} (CC)` : model,
|
|
7723
|
-
description: capabilityDescription(model,
|
|
7977
|
+
description: capabilityDescription(model, contextWindow),
|
|
7724
7978
|
inputModalities: vision ? ["text", "image"] : ["text"],
|
|
7725
7979
|
toolUpdate: "addition-only",
|
|
7726
|
-
...entry ? {
|
|
7727
|
-
context: { contextWindow
|
|
7728
|
-
defaultMaxTokens: Math.min(
|
|
7980
|
+
...entry !== void 0 && contextWindow !== void 0 ? {
|
|
7981
|
+
context: { contextWindow },
|
|
7982
|
+
defaultMaxTokens: applyMaxOutputTokens(Math.min(catalogModelMaxTokens(connection.apiBase, entry, contextWindow), DEFAULT_GENERATE_MAX_TOKENS), connection.maxOutputTokens)
|
|
7729
7983
|
} : {},
|
|
7730
7984
|
...efforts ? { reasoning: { efforts: efforts.map((effort) => ({
|
|
7731
7985
|
id: ReasoningEffortId(effort),
|
|
@@ -8005,7 +8259,7 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
8005
8259
|
}
|
|
8006
8260
|
return record;
|
|
8007
8261
|
} catch (error) {
|
|
8008
|
-
failures.push(`${path}: ${
|
|
8262
|
+
failures.push(`${path}: ${readableErrorText(error)}`);
|
|
8009
8263
|
failed.push({ kind: "transport" });
|
|
8010
8264
|
return;
|
|
8011
8265
|
}
|
|
@@ -8148,7 +8402,8 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
8148
8402
|
const catalog = await facts.catalog;
|
|
8149
8403
|
options.signal?.throwIfAborted();
|
|
8150
8404
|
const modelEntry = catalog.find((row) => row.id === options.model);
|
|
8151
|
-
const
|
|
8405
|
+
const modelMax = modelEntry === void 0 ? facts.fallbackLimit : catalogModelMaxTokens(connection.apiBase, modelEntry, effectiveContextWindow(modelEntry.contextWindow, options.model, connection));
|
|
8406
|
+
const initialMax = applyMaxOutputTokens(Math.min(options.maxTokens ?? Infinity, modelMax, DEFAULT_GENERATE_MAX_TOKENS), connection.maxOutputTokens);
|
|
8152
8407
|
const budget = {
|
|
8153
8408
|
max: initialMax,
|
|
8154
8409
|
sent: initialMax
|
|
@@ -8188,8 +8443,8 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
8188
8443
|
}
|
|
8189
8444
|
options.signal?.throwIfAborted();
|
|
8190
8445
|
let maxTokens = Math.min(budget.max, learnedOutputLimit(connection.apiBase, options.model));
|
|
8191
|
-
const effort = options.reasoningEffort;
|
|
8192
8446
|
const supported = KNOWN_EFFORTS[options.model];
|
|
8447
|
+
const effort = options.purpose === "session-title" && supported?.includes("off") ? "off" : options.reasoningEffort;
|
|
8193
8448
|
const declared = effort !== void 0 && supported?.includes(effort) ? effort : void 0;
|
|
8194
8449
|
const reasoningEffort = effort === "off" && declared === void 0 ? void 0 : declared;
|
|
8195
8450
|
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");
|
|
@@ -8199,17 +8454,16 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
8199
8454
|
const buildBody = async (target) => {
|
|
8200
8455
|
const finishBody = timing.phase("body");
|
|
8201
8456
|
try {
|
|
8457
|
+
const omitMaxTokens = omitOutputTokens(connection.maxOutputTokens, target, options.maxTokens);
|
|
8202
8458
|
const facts = {
|
|
8203
8459
|
maxTokens,
|
|
8460
|
+
omitMaxTokens,
|
|
8204
8461
|
reasoningEffort,
|
|
8205
8462
|
selectableEffort: supported !== void 0 && supported.length > 0,
|
|
8206
8463
|
systemText,
|
|
8207
8464
|
readImage
|
|
8208
8465
|
};
|
|
8209
|
-
|
|
8210
|
-
if (target === "cli") recordOrEmpty(built.params).max_tokens = maxTokens;
|
|
8211
|
-
else built.max_tokens = maxTokens;
|
|
8212
|
-
return built;
|
|
8466
|
+
return target === "cli" ? await buildCliBody(requestOptions, connection, facts) : target === "messages" ? await buildMessagesBody(requestOptions, facts) : await buildOpenAIBody(requestOptions, facts);
|
|
8213
8467
|
} finally {
|
|
8214
8468
|
finishBody();
|
|
8215
8469
|
}
|
|
@@ -8259,8 +8513,7 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
8259
8513
|
if (lower < maxTokens) {
|
|
8260
8514
|
maxTokens = lower;
|
|
8261
8515
|
budget.max = Math.min(budget.max, lower);
|
|
8262
|
-
if (protocol
|
|
8263
|
-
else body.max_tokens = lower;
|
|
8516
|
+
if (!omitOutputTokens(connection.maxOutputTokens, protocol, options.maxTokens)) assignMaxTokens(body, protocol, lower);
|
|
8264
8517
|
serializedBodies.delete(body);
|
|
8265
8518
|
}
|
|
8266
8519
|
budget.sent = maxTokens;
|
|
@@ -8447,12 +8700,33 @@ function formatMoney(value) {
|
|
|
8447
8700
|
function formatMoneyExact(value) {
|
|
8448
8701
|
return `$${value.toFixed(4)}`;
|
|
8449
8702
|
}
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8453
|
-
|
|
8703
|
+
/**
|
|
8704
|
+
* 紧凑数字:K/M/B 三级,一切档位决策的唯一实现。
|
|
8705
|
+
*
|
|
8706
|
+
* 档位必须**回头判断**:先除以档位再四舍五入会算出 `999_999 → "1000.0K"`、
|
|
8707
|
+
* `999_999_999 → "1000.0M"` 这类既超长、又落在并不存在的档位上的结果,
|
|
8708
|
+
* 因此每个档位算完后若已经够到下一档,就改用下一档重算一次。
|
|
8709
|
+
*
|
|
8710
|
+
* @param value - 非负计数或词元数。
|
|
8711
|
+
* @param keepZeroDecimal - 整数值是否保留一位小数。词元显示沿用 `3.0B` 的旧样式;
|
|
8712
|
+
* 计数显示去掉多余的 `.0`,见 {@link formatCompactCount}。
|
|
8713
|
+
* @returns 紧凑文本;不足 1000 时原样返回该整数。
|
|
8714
|
+
*/
|
|
8715
|
+
function compact(value, keepZeroDecimal) {
|
|
8716
|
+
const rounded = (scaled) => Math.round(scaled * 10) / 10;
|
|
8717
|
+
const text = (scaled) => {
|
|
8718
|
+
const fixed = rounded(scaled);
|
|
8719
|
+
return keepZeroDecimal ? fixed.toFixed(1) : String(fixed);
|
|
8720
|
+
};
|
|
8721
|
+
if (value >= 1e9) return `${text(value / 1e9)}B`;
|
|
8722
|
+
if (value >= 1e6) return rounded(value / 1e6) >= 1e3 ? `${text(value / 1e9)}B` : `${text(value / 1e6)}M`;
|
|
8723
|
+
if (value >= 1e3) return rounded(value / 1e3) >= 1e3 ? `${text(value / 1e6)}M` : `${text(value / 1e3)}K`;
|
|
8454
8724
|
return String(value);
|
|
8455
8725
|
}
|
|
8726
|
+
/** 紧凑词元数:与 {@link formatCompactCount} 共用档位,但整数值保留一位小数(`3.0B`)。 */
|
|
8727
|
+
function formatTokensCompact(value) {
|
|
8728
|
+
return compact(value, true);
|
|
8729
|
+
}
|
|
8456
8730
|
/** The input is already in percent units; callers add the percent sign. */
|
|
8457
8731
|
function formatSuccessRate(value) {
|
|
8458
8732
|
return String(Number(value.toFixed(2)));
|
|
@@ -8553,7 +8827,7 @@ function commandDefinition(deps) {
|
|
|
8553
8827
|
text: renderReport(await adapter.getUsage(), locale)
|
|
8554
8828
|
};
|
|
8555
8829
|
} catch (error) {
|
|
8556
|
-
const message =
|
|
8830
|
+
const message = readableErrorText(error);
|
|
8557
8831
|
return {
|
|
8558
8832
|
kind: "error",
|
|
8559
8833
|
text: commandCopy(locale, "errorText").replace("{message}", message)
|
|
@@ -8871,6 +9145,30 @@ const LOGIN_HOST_CONTRIBUTION = {
|
|
|
8871
9145
|
};
|
|
8872
9146
|
//#endregion
|
|
8873
9147
|
//#region src/enrollment-wire.ts
|
|
9148
|
+
const ENROLLMENT_MESSAGE_KEYS = [
|
|
9149
|
+
"enrollmentCreating",
|
|
9150
|
+
"enrollmentWaiting",
|
|
9151
|
+
"enrollmentWriting",
|
|
9152
|
+
"enrollmentNaming",
|
|
9153
|
+
"enrollmentNameFailed",
|
|
9154
|
+
"enrollmentFinished",
|
|
9155
|
+
"enrollmentCancelling",
|
|
9156
|
+
"enrollmentCancelled",
|
|
9157
|
+
"enrollmentCancelledCleaned",
|
|
9158
|
+
"enrollmentFailed",
|
|
9159
|
+
"enrollmentFailedCleaned",
|
|
9160
|
+
"enrollmentCleanupPending",
|
|
9161
|
+
"enrollmentCleanupFailed",
|
|
9162
|
+
"enrollmentRecordNaming",
|
|
9163
|
+
"enrollmentRecordCleanup",
|
|
9164
|
+
"enrollmentBeginFailed",
|
|
9165
|
+
"enrollmentCancelUnconfirmed",
|
|
9166
|
+
"enrollmentNameRequestFailed",
|
|
9167
|
+
"enrollmentCleanupRequestFailed",
|
|
9168
|
+
"enrollmentDisconnected",
|
|
9169
|
+
"enrollmentHostUnavailable",
|
|
9170
|
+
"enrollmentStatusUnavailable"
|
|
9171
|
+
];
|
|
8874
9172
|
const guard = makeBoundaryValidator("commandcode/enrollment");
|
|
8875
9173
|
const phases = [
|
|
8876
9174
|
"creating",
|
|
@@ -8887,11 +9185,13 @@ function parseEnrollmentState(value) {
|
|
|
8887
9185
|
const source = guard.record(value, "state");
|
|
8888
9186
|
const phase = source.phase;
|
|
8889
9187
|
if (!phases.includes(phase)) return guard.reject("phase");
|
|
9188
|
+
const messageKey = source.messageKey;
|
|
9189
|
+
if (!ENROLLMENT_MESSAGE_KEYS.includes(messageKey)) return guard.reject("messageKey");
|
|
8890
9190
|
const state = {
|
|
8891
9191
|
id: guard.stringField(source, "id", "id"),
|
|
8892
9192
|
ref: guard.stringField(source, "ref", "ref"),
|
|
8893
9193
|
phase,
|
|
8894
|
-
|
|
9194
|
+
messageKey
|
|
8895
9195
|
};
|
|
8896
9196
|
for (const field of ["authUrl", "suggestedName"]) if (source[field] !== void 0) state[field] = guard.stringField(source, field, field);
|
|
8897
9197
|
return state;
|
|
@@ -9583,7 +9883,7 @@ var CommandCodeLoginFlow = class {
|
|
|
9583
9883
|
this.setStatus({
|
|
9584
9884
|
state: "failed",
|
|
9585
9885
|
reason: "unavailable",
|
|
9586
|
-
message:
|
|
9886
|
+
message: readableErrorText(error)
|
|
9587
9887
|
});
|
|
9588
9888
|
return;
|
|
9589
9889
|
}
|
|
@@ -9692,7 +9992,7 @@ var AccountEnrollmentManager = class {
|
|
|
9692
9992
|
id: input.id,
|
|
9693
9993
|
ref: "",
|
|
9694
9994
|
phase: "creating",
|
|
9695
|
-
|
|
9995
|
+
messageKey: "enrollmentCreating"
|
|
9696
9996
|
}
|
|
9697
9997
|
};
|
|
9698
9998
|
this.tasks.set(input.id, task);
|
|
@@ -9700,7 +10000,7 @@ var AccountEnrollmentManager = class {
|
|
|
9700
10000
|
task.state = {
|
|
9701
10001
|
...task.state,
|
|
9702
10002
|
phase: "cleanup-needed",
|
|
9703
|
-
|
|
10003
|
+
messageKey: "enrollmentCleanupPending"
|
|
9704
10004
|
};
|
|
9705
10005
|
});
|
|
9706
10006
|
return task.state;
|
|
@@ -9730,7 +10030,7 @@ var AccountEnrollmentManager = class {
|
|
|
9730
10030
|
id,
|
|
9731
10031
|
ref: "",
|
|
9732
10032
|
phase: "cancelled",
|
|
9733
|
-
|
|
10033
|
+
messageKey: "enrollmentCancelled"
|
|
9734
10034
|
}
|
|
9735
10035
|
};
|
|
9736
10036
|
this.tasks.set(id, task);
|
|
@@ -9743,7 +10043,7 @@ var AccountEnrollmentManager = class {
|
|
|
9743
10043
|
task.state = {
|
|
9744
10044
|
...task.state,
|
|
9745
10045
|
phase: "cancelling",
|
|
9746
|
-
|
|
10046
|
+
messageKey: "enrollmentCancelling"
|
|
9747
10047
|
};
|
|
9748
10048
|
await task.flow?.cancelAndDrain();
|
|
9749
10049
|
await task.work;
|
|
@@ -9780,14 +10080,14 @@ var AccountEnrollmentManager = class {
|
|
|
9780
10080
|
id,
|
|
9781
10081
|
ref: record.ref,
|
|
9782
10082
|
phase: "finished",
|
|
9783
|
-
|
|
10083
|
+
messageKey: "enrollmentFinished"
|
|
9784
10084
|
});
|
|
9785
10085
|
} catch {
|
|
9786
10086
|
return this.publish(task, {
|
|
9787
10087
|
id,
|
|
9788
10088
|
ref: record.ref,
|
|
9789
10089
|
phase: "naming",
|
|
9790
|
-
|
|
10090
|
+
messageKey: "enrollmentNameFailed",
|
|
9791
10091
|
...name ? { suggestedName: name } : {}
|
|
9792
10092
|
});
|
|
9793
10093
|
}
|
|
@@ -9813,7 +10113,7 @@ var AccountEnrollmentManager = class {
|
|
|
9813
10113
|
task.state = {
|
|
9814
10114
|
...task.state,
|
|
9815
10115
|
phase: "cleanup-needed",
|
|
9816
|
-
|
|
10116
|
+
messageKey: "enrollmentCleanupFailed"
|
|
9817
10117
|
};
|
|
9818
10118
|
});
|
|
9819
10119
|
await task.work;
|
|
@@ -9881,7 +10181,7 @@ var AccountEnrollmentManager = class {
|
|
|
9881
10181
|
if (!task.cancelled && status.state === "waiting") task.state = {
|
|
9882
10182
|
...task.state,
|
|
9883
10183
|
phase: "waiting",
|
|
9884
|
-
|
|
10184
|
+
messageKey: "enrollmentWaiting",
|
|
9885
10185
|
...status.authUrl ? { authUrl: status.authUrl } : {}
|
|
9886
10186
|
};
|
|
9887
10187
|
if (task.cancelled) flow.cancelAndDrain();
|
|
@@ -9897,7 +10197,7 @@ var AccountEnrollmentManager = class {
|
|
|
9897
10197
|
task.state = {
|
|
9898
10198
|
...task.state,
|
|
9899
10199
|
phase: "naming",
|
|
9900
|
-
|
|
10200
|
+
messageKey: "enrollmentNaming",
|
|
9901
10201
|
...suggested ? { suggestedName: suggested } : {}
|
|
9902
10202
|
};
|
|
9903
10203
|
try {
|
|
@@ -9910,7 +10210,7 @@ var AccountEnrollmentManager = class {
|
|
|
9910
10210
|
if (task.state.phase === "finished") return;
|
|
9911
10211
|
task.state = {
|
|
9912
10212
|
...task.state,
|
|
9913
|
-
|
|
10213
|
+
messageKey: "enrollmentNameFailed"
|
|
9914
10214
|
};
|
|
9915
10215
|
return;
|
|
9916
10216
|
}
|
|
@@ -9922,7 +10222,7 @@ var AccountEnrollmentManager = class {
|
|
|
9922
10222
|
else task.state = {
|
|
9923
10223
|
...task.state,
|
|
9924
10224
|
phase: task.cancelled ? "cancelled" : "failed",
|
|
9925
|
-
|
|
10225
|
+
messageKey: task.cancelled ? "enrollmentCancelled" : "enrollmentFailed"
|
|
9926
10226
|
};
|
|
9927
10227
|
} finally {
|
|
9928
10228
|
task.flow?.dispose();
|
|
@@ -9933,7 +10233,7 @@ var AccountEnrollmentManager = class {
|
|
|
9933
10233
|
task.state = {
|
|
9934
10234
|
...task.state,
|
|
9935
10235
|
phase: "writing",
|
|
9936
|
-
|
|
10236
|
+
messageKey: "enrollmentWriting"
|
|
9937
10237
|
};
|
|
9938
10238
|
task.writeStarted = true;
|
|
9939
10239
|
await this.deps.set(task.state.ref, key);
|
|
@@ -9974,7 +10274,7 @@ var AccountEnrollmentManager = class {
|
|
|
9974
10274
|
id,
|
|
9975
10275
|
ref,
|
|
9976
10276
|
phase: task.cancelled ? "cancelled" : "failed",
|
|
9977
|
-
|
|
10277
|
+
messageKey: task.cancelled ? "enrollmentCancelledCleaned" : "enrollmentFailedCleaned"
|
|
9978
10278
|
};
|
|
9979
10279
|
}
|
|
9980
10280
|
settings() {
|
|
@@ -10023,12 +10323,134 @@ var AccountEnrollmentManager = class {
|
|
|
10023
10323
|
id: record.id,
|
|
10024
10324
|
ref: record.ref,
|
|
10025
10325
|
phase: record.phase === "naming" ? "naming" : "cleanup-needed",
|
|
10026
|
-
|
|
10326
|
+
messageKey: record.phase === "naming" ? "enrollmentRecordNaming" : "enrollmentRecordCleanup",
|
|
10027
10327
|
...record.phase === "naming" ? { suggestedName: record.label } : {}
|
|
10028
10328
|
};
|
|
10029
10329
|
}
|
|
10030
10330
|
};
|
|
10031
10331
|
//#endregion
|
|
10332
|
+
//#region src/config-volatile.ts
|
|
10333
|
+
/**
|
|
10334
|
+
* Volatile-config helpers for dsh 0.1.7's schema-derived profile Config.
|
|
10335
|
+
*
|
|
10336
|
+
* 0.1.7's `settings.describe()` projects each entry's schema through
|
|
10337
|
+
* `volatileForm()`, so a form contains ONLY nodes carrying `meta.volatile`
|
|
10338
|
+
* and a form edit is refused unless its path lies beneath a marked node. The
|
|
10339
|
+
* loader then resolves each marked field into a frozen `{ get() }` reference
|
|
10340
|
+
* that is written IN PLACE (no remount) and announced with
|
|
10341
|
+
* `loader/volatile-update`. Two helpers keep the rest of the plugin reading a
|
|
10342
|
+
* PLAIN `Config`: `markVolatile()` applies the mark, and
|
|
10343
|
+
* `unwrapVolatileConfig()` reads every top-level field through its reference
|
|
10344
|
+
* when one is present — FRESH per call, because a reference's identity is
|
|
10345
|
+
* stable while its value changes.
|
|
10346
|
+
*
|
|
10347
|
+
* Only TOP-LEVEL fields are marked (a form write names a top-level path such
|
|
10348
|
+
* as `accounts` or `modelVisibility.<id>`), so one unwrap level is complete.
|
|
10349
|
+
* The literal `apiKey` secret stays unmarked on purpose: no settings surface
|
|
10350
|
+
* writes it — page and TUI both write keys through the credentials seam — so a
|
|
10351
|
+
* config-file edit to it reloading the fiber is correct for a secret literal.
|
|
10352
|
+
*
|
|
10353
|
+
* `volatileWrite()` is the third helper and the only WRITE in this module: it
|
|
10354
|
+
* commits into a reference that belongs to ANOTHER plugin entry (`ctx.web`'s
|
|
10355
|
+
* `searchProvider`), which no host API addresses directly. It is the degraded
|
|
10356
|
+
* path behind `ctx.settings.update()`; see its own note for why the disk-backed
|
|
10357
|
+
* path is preferred.
|
|
10358
|
+
*
|
|
10359
|
+
* @module dsh-commandcode-provider/config-volatile
|
|
10360
|
+
*/
|
|
10361
|
+
/**
|
|
10362
|
+
* Mark one schema field volatile.
|
|
10363
|
+
*
|
|
10364
|
+
* `.volatile()` is unconditional: it is a schemastery 3.18.3 feature and the
|
|
10365
|
+
* supported engine ships `~3.18.5`, so the
|
|
10366
|
+
* method is always present. It is reached through a structural member so the caller's
|
|
10367
|
+
* declared field type survives — the mark changes what the schema PARSES to (a
|
|
10368
|
+
* live reference), not the plugin-facing `Config` shape.
|
|
10369
|
+
*/
|
|
10370
|
+
function markVolatile(schema) {
|
|
10371
|
+
return schema.volatile();
|
|
10372
|
+
}
|
|
10373
|
+
/**
|
|
10374
|
+
* Mark every field of a Config field dict volatile, except the named
|
|
10375
|
+
* composition-only secrets (`apiKey` here, which no settings surface writes).
|
|
10376
|
+
*/
|
|
10377
|
+
function markVolatileFields(fields, skip = []) {
|
|
10378
|
+
return Object.fromEntries(Object.entries(fields).map(([name, schema]) => [name, skip.includes(name) ? schema : markVolatile(schema)]));
|
|
10379
|
+
}
|
|
10380
|
+
/**
|
|
10381
|
+
* Whether a resolved config value is a volatile reference.
|
|
10382
|
+
*
|
|
10383
|
+
* Duck-typed on the two properties `createVolatile` produces (frozen object
|
|
10384
|
+
* with a `get` method) rather than cosmokit's private `write` symbol, because
|
|
10385
|
+
* cosmokit is not a peer of this bundle. Config values are plain parsed JSON
|
|
10386
|
+
* (or `!!jsExpr` wrappers), which are never frozen, so the test cannot
|
|
10387
|
+
* misfire on an ordinary field.
|
|
10388
|
+
*/
|
|
10389
|
+
function isVolatileRef(value) {
|
|
10390
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.isFrozen(value) && typeof value.get === "function";
|
|
10391
|
+
}
|
|
10392
|
+
/**
|
|
10393
|
+
* The commit protocol cosmokit's `createVolatile` installs on every reference.
|
|
10394
|
+
*
|
|
10395
|
+
* `Symbol.for` — not a local `Symbol()` — because cosmokit registers it
|
|
10396
|
+
* globally precisely so copies of the shared library loaded twice (ESM/CJS, two
|
|
10397
|
+
* node_modules trees) still recognize each other's references. Reaching it
|
|
10398
|
+
* through the global registry is what lets this bundle commit a value without
|
|
10399
|
+
* taking `@deepseek-ai/cosmokit` as a dependency.
|
|
10400
|
+
*/
|
|
10401
|
+
const VOLATILE_WRITE = Symbol.for("cosmokit.volatile.write");
|
|
10402
|
+
/**
|
|
10403
|
+
* Commit a value into a RUNNING volatile reference and verify it landed.
|
|
10404
|
+
*
|
|
10405
|
+
* This is the DEGRADED write path, and it is deliberately second choice: the
|
|
10406
|
+
* reference is the live config the owner reads per call, but the value never
|
|
10407
|
+
* reaches the profile document, so the next loader commit for that entry (any
|
|
10408
|
+
* `loader/volatile-update`, including one caused by an unrelated field of the
|
|
10409
|
+
* same entry) re-resolves the raw config and overwrites it. The official
|
|
10410
|
+
* `ctx.settings.update()` path exists to avoid exactly that; this one keeps the
|
|
10411
|
+
* CURRENT process coherent when settings is unavailable or refuses the write.
|
|
10412
|
+
*
|
|
10413
|
+
* Reading back is not optional: the write method is an internal protocol, so a
|
|
10414
|
+
* future cosmokit that renames the symbol, hides it, or changes the value shape
|
|
10415
|
+
* must degrade to "not applied" instead of silently pretending success.
|
|
10416
|
+
*
|
|
10417
|
+
* @param ref - Candidate volatile reference; anything else returns `false`.
|
|
10418
|
+
* @param value - Value to commit.
|
|
10419
|
+
* @returns Whether the reference now reads back as `value`.
|
|
10420
|
+
*/
|
|
10421
|
+
function volatileWrite(ref, value) {
|
|
10422
|
+
if (!isVolatileRef(ref)) return false;
|
|
10423
|
+
const write = ref[VOLATILE_WRITE];
|
|
10424
|
+
if (typeof write !== "function") return false;
|
|
10425
|
+
try {
|
|
10426
|
+
write.call(ref, value);
|
|
10427
|
+
} catch {
|
|
10428
|
+
return false;
|
|
10429
|
+
}
|
|
10430
|
+
try {
|
|
10431
|
+
return Object.is(ref.get(), value);
|
|
10432
|
+
} catch {
|
|
10433
|
+
return false;
|
|
10434
|
+
}
|
|
10435
|
+
}
|
|
10436
|
+
/**
|
|
10437
|
+
* Read one config object as PLAIN values.
|
|
10438
|
+
*
|
|
10439
|
+
* Every top-level field found to be a volatile reference is read through
|
|
10440
|
+
* `.get()` — freshly on every call, so a live update lands on the very next
|
|
10441
|
+
* read (the loader resolves EVERY marked node into a reference, unset fields
|
|
10442
|
+
* included, so a loader-parsed config always yields a fresh object here). The
|
|
10443
|
+
* pass-through for a field that is not a reference is what keeps the unmarked
|
|
10444
|
+
* `apiKey` literal readable.
|
|
10445
|
+
*/
|
|
10446
|
+
function unwrapVolatileConfig(config) {
|
|
10447
|
+
const source = config;
|
|
10448
|
+
if (typeof source !== "object" || source === null) return config;
|
|
10449
|
+
const plain = {};
|
|
10450
|
+
for (const [field, value] of Object.entries(source)) plain[field] = isVolatileRef(value) ? value.get() : value;
|
|
10451
|
+
return plain;
|
|
10452
|
+
}
|
|
10453
|
+
//#endregion
|
|
10032
10454
|
//#region src/web-search.ts
|
|
10033
10455
|
/**
|
|
10034
10456
|
* Command Code web-search provider over `ctx.web`.
|
|
@@ -10041,6 +10463,12 @@ var AccountEnrollmentManager = class {
|
|
|
10041
10463
|
* therefore reuses the plugin's existing credential chain and `apiBase`, so
|
|
10042
10464
|
* the model-facing `web_search` tool needs no separate configuration.
|
|
10043
10465
|
*
|
|
10466
|
+
* The second half of this module is the SELECTION seam: which registered search
|
|
10467
|
+
* backend `ctx.web` actually runs. dsh 0.2.1-rc.2 turned that choice into a
|
|
10468
|
+
* per-call `Volatile` field of the `web` profile entry, so it is changed
|
|
10469
|
+
* through the host settings service rather than by assigning a runtime field —
|
|
10470
|
+
* see {@link createCommandCodeSearchSelection}.
|
|
10471
|
+
*
|
|
10044
10472
|
* @module dsh-commandcode-provider/web-search
|
|
10045
10473
|
*/
|
|
10046
10474
|
/** Stable id this provider registers under in `ctx.web`. */
|
|
@@ -10052,54 +10480,273 @@ const COMMANDCODE_SEARCH_PROVIDER_ID = "commandcode";
|
|
|
10052
10480
|
* PREVIOUSLY selected backend, never this default (issue #26).
|
|
10053
10481
|
*/
|
|
10054
10482
|
const DEFAULT_WEB_SEARCH_PROVIDER_ID = "deepseek-official";
|
|
10483
|
+
/**
|
|
10484
|
+
* Profile entry id whose volatile `searchProvider` selects the search backend.
|
|
10485
|
+
*
|
|
10486
|
+
* The hosting profile declares it in `dsh-base`'s cordis patch as
|
|
10487
|
+
* `- id: web` / `name: '@deepseek-ai/dsh-web'`, and the shipped web-search
|
|
10488
|
+
* settings page addresses the very same namespace — that page and this plugin
|
|
10489
|
+
* are therefore two writers of ONE field (see
|
|
10490
|
+
* {@link createCommandCodeSearchSelection} for how they avoid fighting).
|
|
10491
|
+
*/
|
|
10492
|
+
const WEB_SELECTION_NAMESPACE = "web";
|
|
10055
10493
|
/** Fresh selection state: the plugin starts out not owning the selection. */
|
|
10056
10494
|
function commandCodeSearchSelection() {
|
|
10057
10495
|
return {
|
|
10058
10496
|
owner: false,
|
|
10059
|
-
|
|
10060
|
-
|
|
10497
|
+
preexisting: false,
|
|
10498
|
+
foreign: void 0,
|
|
10499
|
+
displaced: void 0
|
|
10061
10500
|
};
|
|
10062
10501
|
}
|
|
10502
|
+
/** Read one member of a plain object or array without trusting its shape. */
|
|
10503
|
+
function member(value, key) {
|
|
10504
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
10505
|
+
return value[key];
|
|
10506
|
+
}
|
|
10507
|
+
/** A string member, or `undefined` for anything else (including `null`). */
|
|
10508
|
+
function text(value) {
|
|
10509
|
+
return typeof value === "string" ? value : void 0;
|
|
10510
|
+
}
|
|
10511
|
+
/** The settings row describing the `web` entry, when the host serves one. */
|
|
10512
|
+
function selectionRow(settings) {
|
|
10513
|
+
let rows;
|
|
10514
|
+
try {
|
|
10515
|
+
rows = settings.describe();
|
|
10516
|
+
} catch {
|
|
10517
|
+
return;
|
|
10518
|
+
}
|
|
10519
|
+
if (!Array.isArray(rows)) return void 0;
|
|
10520
|
+
return rows.find((row) => text(member(row, "ns")) === "web");
|
|
10521
|
+
}
|
|
10063
10522
|
/**
|
|
10064
|
-
*
|
|
10065
|
-
* providers (issue #26).
|
|
10066
|
-
*
|
|
10067
|
-
* - Enabling writes `commandcode` and remembers whatever it displaced. When
|
|
10068
|
-
* the plugin already owns the selection, the original `displaced` is kept —
|
|
10069
|
-
* the field currently holds our own id, which must never be mistaken for the
|
|
10070
|
-
* user's backend.
|
|
10071
|
-
* - Disabling hands the selection back to the remembered backend only when
|
|
10072
|
-
* this plugin actually took it over. A fresh boot straight into
|
|
10073
|
-
* `webSearch: false`, or a `preexisting` field, leaves it ALONE: writing the
|
|
10074
|
-
* empty memory back would clear the user's own `searchProvider: commandcode`
|
|
10075
|
-
* pin and hand the selection to dsh-web's auto-select, where a second usable
|
|
10076
|
-
* provider makes every later search throw `WEB_PROVIDER_AMBIGUOUS`.
|
|
10523
|
+
* The volatile reference backing `web.searchProvider`.
|
|
10077
10524
|
*
|
|
10078
|
-
*
|
|
10079
|
-
* to
|
|
10525
|
+
* `config` is a TypeScript-private parameter property on `WebRuntime`, so at
|
|
10526
|
+
* runtime it is an ordinary own field — the only handle to a reference the host
|
|
10527
|
+
* exposes no public accessor for. Reading it structurally means a hardened
|
|
10528
|
+
* runtime degrades to "not writable" instead of throwing.
|
|
10080
10529
|
*/
|
|
10081
|
-
function
|
|
10530
|
+
function searchProviderRef(web) {
|
|
10531
|
+
return member(member(web, "config"), "searchProvider");
|
|
10532
|
+
}
|
|
10533
|
+
/** Observe the live selection; `undefined` when nothing can be observed. */
|
|
10534
|
+
function readView(deps) {
|
|
10535
|
+
const settings = deps.settings();
|
|
10536
|
+
if (settings !== void 0) {
|
|
10537
|
+
const row = selectionRow(settings);
|
|
10538
|
+
if (row !== void 0) {
|
|
10539
|
+
const user = member(row, "user");
|
|
10540
|
+
return {
|
|
10541
|
+
configured: text(member(member(row, "value"), "searchProvider")),
|
|
10542
|
+
pinned: text(member(user, "searchProvider")),
|
|
10543
|
+
base: text(member(member(row, "base"), "searchProvider")),
|
|
10544
|
+
user: typeof user === "object" && user !== null && !Array.isArray(user) ? { ...user } : {},
|
|
10545
|
+
persistent: true
|
|
10546
|
+
};
|
|
10547
|
+
}
|
|
10548
|
+
}
|
|
10549
|
+
const ref = searchProviderRef(deps.web);
|
|
10550
|
+
if (!isVolatileRef(ref)) return void 0;
|
|
10551
|
+
let current;
|
|
10082
10552
|
try {
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10553
|
+
current = ref.get();
|
|
10554
|
+
} catch {
|
|
10555
|
+
return;
|
|
10556
|
+
}
|
|
10557
|
+
if (current !== void 0 && typeof current !== "string") return void 0;
|
|
10558
|
+
return {
|
|
10559
|
+
configured: current,
|
|
10560
|
+
pinned: void 0,
|
|
10561
|
+
base: void 0,
|
|
10562
|
+
user: {},
|
|
10563
|
+
persistent: false
|
|
10564
|
+
};
|
|
10565
|
+
}
|
|
10566
|
+
/**
|
|
10567
|
+
* The selection seam over `ctx.web`.
|
|
10568
|
+
*
|
|
10569
|
+
* ## Why settings rather than the runtime instance
|
|
10570
|
+
*
|
|
10571
|
+
* 0.2.1-rc.2's `WebRuntime` reads `this.config.searchProvider.get()` on EVERY
|
|
10572
|
+
* search and fetch, and the loader commits a settings write into that same
|
|
10573
|
+
* reference in place (`Entry._commitVolatile`) without remounting the entry.
|
|
10574
|
+
* Writing the reference directly therefore works, but leaves the value out of
|
|
10575
|
+
* the profile: the next commit for that entry — including one caused by an
|
|
10576
|
+
* unrelated field such as `fetchProvider` — re-resolves the raw config and
|
|
10577
|
+
* overwrites it. `ctx.settings.update()` / `replace()` is the documented path
|
|
10578
|
+
* and the one the shipped web-search settings page uses, so this plugin takes
|
|
10579
|
+
* it first and falls back to the reference only when settings is unavailable or
|
|
10580
|
+
* refuses (see `volatileWrite`).
|
|
10581
|
+
*
|
|
10582
|
+
* ## Two writers, one field
|
|
10583
|
+
*
|
|
10584
|
+
* The shipped web-search settings page writes `web.searchProvider` too
|
|
10585
|
+
* (`deepseek-search-api` / `deepseek-official`). This seam never displaces a
|
|
10586
|
+
* pin it did not write: a non-`commandcode` value in the USER LAYER stops the
|
|
10587
|
+
* takeover, the value is recorded on {@link CommandCodeSearchSelection.foreign}
|
|
10588
|
+
* and named in one `info` line, and the provider stays registered but
|
|
10589
|
+
* unselected. A value the user layer does not carry is the deployment's own
|
|
10590
|
+
* default, and taking it over is the toggle's whole purpose. A user-layer
|
|
10591
|
+
* `commandcode` is treated as ours: nothing else writes that id, so it can only
|
|
10592
|
+
* be a previous run of this plugin or a deliberate manual pin, and turning the
|
|
10593
|
+
* toggle off restores the composed default in both cases.
|
|
10594
|
+
*
|
|
10595
|
+
* @param deps - the running `web` service, a settings resolver, and a log sink.
|
|
10596
|
+
* @returns a synchronous handle; failures are logged, never thrown.
|
|
10597
|
+
*/
|
|
10598
|
+
function createCommandCodeSearchSelection(deps) {
|
|
10599
|
+
const state = commandCodeSearchSelection();
|
|
10600
|
+
let pending;
|
|
10601
|
+
let activeKey;
|
|
10602
|
+
let running = false;
|
|
10603
|
+
let closed = false;
|
|
10604
|
+
let waiters = [];
|
|
10605
|
+
const warn = (message) => {
|
|
10606
|
+
try {
|
|
10607
|
+
deps.logger.warn(message);
|
|
10608
|
+
} catch {}
|
|
10609
|
+
};
|
|
10610
|
+
const info = (message) => {
|
|
10611
|
+
try {
|
|
10612
|
+
deps.logger.info(message);
|
|
10613
|
+
} catch {}
|
|
10614
|
+
};
|
|
10615
|
+
/** Commit `commandcode` in memory; the last resort before giving up. */
|
|
10616
|
+
const memoryWrite = (value) => {
|
|
10617
|
+
const written = volatileWrite(searchProviderRef(deps.web), value);
|
|
10618
|
+
if (!written) warn("llm-commandcode: web search selection could not be applied (settings refused the write and the running web config is not writable); the provider stays registered but unselected");
|
|
10619
|
+
return written;
|
|
10620
|
+
};
|
|
10621
|
+
/** Run one intent against a FRESH observation; never throws. */
|
|
10622
|
+
const perform = async (intent) => {
|
|
10623
|
+
let view;
|
|
10624
|
+
try {
|
|
10625
|
+
view = readView(deps);
|
|
10626
|
+
} catch {
|
|
10627
|
+
view = void 0;
|
|
10628
|
+
}
|
|
10629
|
+
if (view === void 0) return;
|
|
10630
|
+
const settings = view.persistent ? deps.settings() : void 0;
|
|
10631
|
+
if (intent.kind === "take") {
|
|
10632
|
+
if (view.pinned !== void 0 && view.pinned !== "commandcode") return;
|
|
10633
|
+
if (settings !== void 0) try {
|
|
10634
|
+
await settings.update("web", { searchProvider: COMMANDCODE_SEARCH_PROVIDER_ID });
|
|
10087
10635
|
return;
|
|
10636
|
+
} catch (error) {
|
|
10637
|
+
warn(`llm-commandcode: writing web.searchProvider through settings failed (${error instanceof Error ? error.message : String(error)}); falling back to the running config`);
|
|
10088
10638
|
}
|
|
10089
|
-
|
|
10090
|
-
state.preexisting = prior === COMMANDCODE_SEARCH_PROVIDER_ID;
|
|
10091
|
-
state.displaced = state.preexisting ? void 0 : prior;
|
|
10092
|
-
field.searchProviderId = COMMANDCODE_SEARCH_PROVIDER_ID;
|
|
10093
|
-
state.owner = true;
|
|
10639
|
+
memoryWrite(COMMANDCODE_SEARCH_PROVIDER_ID);
|
|
10094
10640
|
return;
|
|
10095
10641
|
}
|
|
10096
|
-
if (
|
|
10642
|
+
if (view.pinned !== void 0 && view.pinned !== "commandcode") return;
|
|
10643
|
+
if (settings !== void 0) {
|
|
10644
|
+
const section = { ...view.user };
|
|
10645
|
+
delete section.searchProvider;
|
|
10646
|
+
if (view.base !== void 0) section.searchProvider = view.base;
|
|
10647
|
+
try {
|
|
10648
|
+
await settings.replace("web", section);
|
|
10649
|
+
return;
|
|
10650
|
+
} catch (error) {
|
|
10651
|
+
warn(`llm-commandcode: restoring web.searchProvider through settings failed (${error instanceof Error ? error.message : String(error)}); falling back to the running config`);
|
|
10652
|
+
}
|
|
10653
|
+
}
|
|
10654
|
+
memoryWrite(intent.displaced);
|
|
10655
|
+
};
|
|
10656
|
+
const flush = () => {
|
|
10657
|
+
const done = waiters;
|
|
10658
|
+
waiters = [];
|
|
10659
|
+
for (const resolve of done) resolve();
|
|
10660
|
+
};
|
|
10661
|
+
const drain = async () => {
|
|
10662
|
+
running = true;
|
|
10663
|
+
try {
|
|
10664
|
+
while (pending !== void 0 && !closed) {
|
|
10665
|
+
const item = pending;
|
|
10666
|
+
pending = void 0;
|
|
10667
|
+
activeKey = item.key;
|
|
10668
|
+
await perform(item.intent);
|
|
10669
|
+
}
|
|
10670
|
+
} finally {
|
|
10671
|
+
running = false;
|
|
10672
|
+
activeKey = void 0;
|
|
10673
|
+
flush();
|
|
10674
|
+
}
|
|
10675
|
+
};
|
|
10676
|
+
/**
|
|
10677
|
+
* Queue one intent, skipping an identical one already queued or in flight. A
|
|
10678
|
+
* later DIFFERENT intent replaces the queued one: the toggle is a state, not a
|
|
10679
|
+
* log, so only the newest position has to land.
|
|
10680
|
+
*/
|
|
10681
|
+
const submit = (key, intent) => {
|
|
10682
|
+
if (closed) return;
|
|
10683
|
+
if (key === activeKey && pending === void 0) return;
|
|
10684
|
+
if (pending?.key === key) return;
|
|
10685
|
+
pending = {
|
|
10686
|
+
key,
|
|
10687
|
+
intent
|
|
10688
|
+
};
|
|
10689
|
+
if (!running) drain();
|
|
10690
|
+
};
|
|
10691
|
+
const apply = (enable) => {
|
|
10692
|
+
try {
|
|
10693
|
+
const view = readView(deps);
|
|
10694
|
+
if (view === void 0) return;
|
|
10695
|
+
if (!enable) {
|
|
10696
|
+
if (!state.owner) return;
|
|
10697
|
+
state.owner = false;
|
|
10698
|
+
if (state.preexisting) {
|
|
10699
|
+
state.preexisting = false;
|
|
10700
|
+
return;
|
|
10701
|
+
}
|
|
10702
|
+
submit("restore", {
|
|
10703
|
+
kind: "restore",
|
|
10704
|
+
displaced: state.displaced
|
|
10705
|
+
});
|
|
10706
|
+
return;
|
|
10707
|
+
}
|
|
10708
|
+
if (view.pinned !== void 0 && view.pinned !== "commandcode") {
|
|
10709
|
+
if (state.foreign !== view.pinned) {
|
|
10710
|
+
state.foreign = view.pinned;
|
|
10711
|
+
info(`llm-commandcode: web search backend "${view.pinned}" was chosen in the web-search settings page; leaving that explicit choice in place, so the commandcode provider stays registered but unselected`);
|
|
10712
|
+
}
|
|
10713
|
+
state.owner = false;
|
|
10714
|
+
state.preexisting = false;
|
|
10715
|
+
return;
|
|
10716
|
+
}
|
|
10717
|
+
state.foreign = void 0;
|
|
10718
|
+
if (view.configured === "commandcode") {
|
|
10719
|
+
state.owner = view.pinned === COMMANDCODE_SEARCH_PROVIDER_ID;
|
|
10720
|
+
state.preexisting = !state.owner;
|
|
10721
|
+
if (state.owner && state.displaced === void 0) state.displaced = view.base;
|
|
10722
|
+
return;
|
|
10723
|
+
}
|
|
10724
|
+
state.displaced = view.configured;
|
|
10725
|
+
state.preexisting = false;
|
|
10726
|
+
state.owner = true;
|
|
10727
|
+
submit("take", {
|
|
10728
|
+
kind: "take",
|
|
10729
|
+
displaced: view.configured
|
|
10730
|
+
});
|
|
10731
|
+
} catch (error) {
|
|
10732
|
+
warn(`llm-commandcode: aligning the web search selection failed (${error instanceof Error ? error.message : String(error)})`);
|
|
10733
|
+
}
|
|
10734
|
+
};
|
|
10735
|
+
return {
|
|
10736
|
+
state,
|
|
10737
|
+
apply,
|
|
10738
|
+
settled: () => running || pending !== void 0 ? new Promise((resolve) => {
|
|
10739
|
+
waiters.push(resolve);
|
|
10740
|
+
}) : Promise.resolve(),
|
|
10741
|
+
release: () => {
|
|
10742
|
+
closed = true;
|
|
10743
|
+
pending = void 0;
|
|
10744
|
+
if (state.owner && !state.preexisting) memoryWrite(state.displaced);
|
|
10097
10745
|
state.owner = false;
|
|
10098
|
-
if (!state.preexisting) field.searchProviderId = state.displaced;
|
|
10099
10746
|
state.preexisting = false;
|
|
10100
|
-
|
|
10747
|
+
if (!running) flush();
|
|
10101
10748
|
}
|
|
10102
|
-
}
|
|
10749
|
+
};
|
|
10103
10750
|
}
|
|
10104
10751
|
/** Command Code's lower/upper bound on `numResults` (from the CLI's `web_search` schema). */
|
|
10105
10752
|
const MIN_NUM_RESULTS = 1;
|
|
@@ -10183,7 +10830,7 @@ var CommandCodeSearchProvider = class {
|
|
|
10183
10830
|
});
|
|
10184
10831
|
} catch (error) {
|
|
10185
10832
|
if (signal?.aborted === true || isAbortError(error)) throw searchAborted(signal, error);
|
|
10186
|
-
throw new WebError(`Command Code web search request failed: ${
|
|
10833
|
+
throw new WebError(`Command Code web search request failed: ${readableErrorText(error)}`, "WEB_PROVIDER_ERROR", { cause: error });
|
|
10187
10834
|
}
|
|
10188
10835
|
if (!response.ok) {
|
|
10189
10836
|
let message = `Command Code web search failed (HTTP ${response.status})`;
|
|
@@ -10237,7 +10884,7 @@ var CommandCodeSearchProvider = class {
|
|
|
10237
10884
|
if (code === "MISSING_CREDENTIAL") throw new WebError(error.message, "WEB_PROVIDER_CREDENTIAL_MISSING", { cause: error });
|
|
10238
10885
|
if (code === "INVALID_CREDENTIAL" || code === "RATE_LIMIT") throw new WebError(error.message, "WEB_PROVIDER_ERROR", { cause: error });
|
|
10239
10886
|
}
|
|
10240
|
-
throw new WebError(`Command Code web search credential resolution failed: ${
|
|
10887
|
+
throw new WebError(`Command Code web search credential resolution failed: ${readableErrorText(error)}`, "WEB_PROVIDER_ERROR", { cause: error });
|
|
10241
10888
|
}
|
|
10242
10889
|
if (key === void 0 || key.length === 0) throw new WebError("Command Code web search has no API key; store COMMANDCODE_API_KEY through the credentials service (the web Models page writes it), export it in the launching environment, set config.apiKey, or run `command-code login` to write ~/.commandcode/auth.json", "WEB_PROVIDER_CREDENTIAL_MISSING");
|
|
10243
10890
|
return key;
|
|
@@ -10568,7 +11215,7 @@ function applyCommandCodeTuiSettings(ctx, deps) {
|
|
|
10568
11215
|
dispose: service.register(section)
|
|
10569
11216
|
};
|
|
10570
11217
|
} catch (error) {
|
|
10571
|
-
ctx.logger?.warn(`llm-commandcode: could not register the dsh-TUI settings section: ${
|
|
11218
|
+
ctx.logger?.warn(`llm-commandcode: could not register the dsh-TUI settings section: ${readableErrorText(error)}`);
|
|
10572
11219
|
}
|
|
10573
11220
|
};
|
|
10574
11221
|
refresh();
|
|
@@ -10651,78 +11298,6 @@ function transportBudgetMessage(failureMessage, maxRetries) {
|
|
|
10651
11298
|
return `${failureMessage};Command Code API 连接在连续重试后仍失败——已停止重试以免整轮卡死。插件已自动重试 ${maxRetries} 次。通常说明当前网络到 api.commandcode.ai 的连接不通,常见原因是代理未生效:DSH 只读取环境变量 HTTPS_PROXY/HTTP_PROXY(系统代理/PAC 不会被读取),请确认启动 DSH 的终端里已导出代理,或先关闭代理直连后重试。网络恢复后直接重发即可 — Command Code API transport still failed after ${maxRetries} automatic retries, which were stopped so the turn could not stall for minutes. This is a connectivity problem between this machine and api.commandcode.ai, not a context-window or request-size rejection. Common cause: the proxy is not reaching the harness — DSH reads HTTPS_PROXY/HTTP_PROXY from the environment only (a Windows system proxy/PAC setting is not consulted), so export it in the launching shell, or turn the proxy off and retry. Resend when the network recovers; the budget is per request and has been reset`;
|
|
10652
11299
|
}
|
|
10653
11300
|
//#endregion
|
|
10654
|
-
//#region src/config-volatile.ts
|
|
10655
|
-
/**
|
|
10656
|
-
* Volatile-config helpers for dsh 0.1.7's schema-derived profile Config.
|
|
10657
|
-
*
|
|
10658
|
-
* 0.1.7's `settings.describe()` projects each entry's schema through
|
|
10659
|
-
* `volatileForm()`, so a form contains ONLY nodes carrying `meta.volatile`
|
|
10660
|
-
* and a form edit is refused unless its path lies beneath a marked node. The
|
|
10661
|
-
* loader then resolves each marked field into a frozen `{ get() }` reference
|
|
10662
|
-
* that is written IN PLACE (no remount) and announced with
|
|
10663
|
-
* `loader/volatile-update`. Two helpers keep the rest of the plugin reading a
|
|
10664
|
-
* PLAIN `Config`: `markVolatile()` applies the mark, and
|
|
10665
|
-
* `unwrapVolatileConfig()` reads every top-level field through its reference
|
|
10666
|
-
* when one is present — FRESH per call, because a reference's identity is
|
|
10667
|
-
* stable while its value changes.
|
|
10668
|
-
*
|
|
10669
|
-
* Only TOP-LEVEL fields are marked (a form write names a top-level path such
|
|
10670
|
-
* as `accounts` or `modelVisibility.<id>`), so one unwrap level is complete.
|
|
10671
|
-
* The literal `apiKey` secret stays unmarked on purpose: no settings surface
|
|
10672
|
-
* writes it — page and TUI both write keys through the credentials seam — so a
|
|
10673
|
-
* config-file edit to it reloading the fiber is correct for a secret literal.
|
|
10674
|
-
*
|
|
10675
|
-
* @module dsh-commandcode-provider/config-volatile
|
|
10676
|
-
*/
|
|
10677
|
-
/**
|
|
10678
|
-
* Mark one schema field volatile.
|
|
10679
|
-
*
|
|
10680
|
-
* `.volatile()` is unconditional: it is a schemastery 3.18.3 feature and every
|
|
10681
|
-
* supported engine ships `~3.18.4` or `~3.18.5-alpha.1`, so the
|
|
10682
|
-
* method is always present. It is reached through a structural member so the caller's
|
|
10683
|
-
* declared field type survives — the mark changes what the schema PARSES to (a
|
|
10684
|
-
* live reference), not the plugin-facing `Config` shape.
|
|
10685
|
-
*/
|
|
10686
|
-
function markVolatile(schema) {
|
|
10687
|
-
return schema.volatile();
|
|
10688
|
-
}
|
|
10689
|
-
/**
|
|
10690
|
-
* Mark every field of a Config field dict volatile, except the named
|
|
10691
|
-
* composition-only secrets (`apiKey` here, which no settings surface writes).
|
|
10692
|
-
*/
|
|
10693
|
-
function markVolatileFields(fields, skip = []) {
|
|
10694
|
-
return Object.fromEntries(Object.entries(fields).map(([name, schema]) => [name, skip.includes(name) ? schema : markVolatile(schema)]));
|
|
10695
|
-
}
|
|
10696
|
-
/**
|
|
10697
|
-
* Whether a resolved config value is a volatile reference.
|
|
10698
|
-
*
|
|
10699
|
-
* Duck-typed on the two properties `createVolatile` produces (frozen object
|
|
10700
|
-
* with a `get` method) rather than cosmokit's private `write` symbol, because
|
|
10701
|
-
* cosmokit is not a peer of this bundle. Config values are plain parsed JSON
|
|
10702
|
-
* (or `!!jsExpr` wrappers), which are never frozen, so the test cannot
|
|
10703
|
-
* misfire on an ordinary field.
|
|
10704
|
-
*/
|
|
10705
|
-
function isVolatileRef(value) {
|
|
10706
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.isFrozen(value) && typeof value.get === "function";
|
|
10707
|
-
}
|
|
10708
|
-
/**
|
|
10709
|
-
* Read one config object as PLAIN values.
|
|
10710
|
-
*
|
|
10711
|
-
* Every top-level field found to be a volatile reference is read through
|
|
10712
|
-
* `.get()` — freshly on every call, so a live update lands on the very next
|
|
10713
|
-
* read (the loader resolves EVERY marked node into a reference, unset fields
|
|
10714
|
-
* included, so a loader-parsed config always yields a fresh object here). The
|
|
10715
|
-
* pass-through for a field that is not a reference is what keeps the unmarked
|
|
10716
|
-
* `apiKey` literal readable.
|
|
10717
|
-
*/
|
|
10718
|
-
function unwrapVolatileConfig(config) {
|
|
10719
|
-
const source = config;
|
|
10720
|
-
if (typeof source !== "object" || source === null) return config;
|
|
10721
|
-
const plain = {};
|
|
10722
|
-
for (const [field, value] of Object.entries(source)) plain[field] = isVolatileRef(value) ? value.get() : value;
|
|
10723
|
-
return plain;
|
|
10724
|
-
}
|
|
10725
|
-
//#endregion
|
|
10726
11301
|
//#region src/index.ts
|
|
10727
11302
|
/**
|
|
10728
11303
|
* dsh-commandcode-provider — DeepSeek Harness LLM provider plugin for Command
|
|
@@ -10780,6 +11355,9 @@ const Config = z.object(markVolatileFields({
|
|
|
10780
11355
|
filterModelsByPlan: z.boolean(),
|
|
10781
11356
|
visibleModels: z.array(z.string()),
|
|
10782
11357
|
modelVisibility: z.dict(z.boolean()),
|
|
11358
|
+
contextWindow: z.number().min(1).max(CONTEXT_WINDOW_MAX).step(1),
|
|
11359
|
+
contextWindowOverrides: z.dict(z.number().min(1).max(CONTEXT_WINDOW_MAX).step(1)),
|
|
11360
|
+
maxOutputTokens: z.number().min(0).max(MAX_OUTPUT_TOKEN_LIMIT).step(1),
|
|
10783
11361
|
webSearch: z.boolean().default(true),
|
|
10784
11362
|
showSidebarQuota: z.boolean().default(false),
|
|
10785
11363
|
accounts: z.array(z.object({
|
|
@@ -10825,6 +11403,9 @@ function resolveAdapterOptions(config) {
|
|
|
10825
11403
|
filterModelsByPlan: config.filterModelsByPlan ?? true,
|
|
10826
11404
|
visibleModels: Array.isArray(config.visibleModels) ? config.visibleModels.filter((id) => typeof id === "string" && id !== "") : void 0,
|
|
10827
11405
|
modelVisibility: readModelVisibility(config.modelVisibility),
|
|
11406
|
+
contextWindow: isValidContextWindow(config.contextWindow) ? config.contextWindow : void 0,
|
|
11407
|
+
contextWindowOverrides: readContextWindowOverrides(config.contextWindowOverrides),
|
|
11408
|
+
maxOutputTokens: isValidMaxOutputTokens(config.maxOutputTokens) ? config.maxOutputTokens : void 0,
|
|
10828
11409
|
zdr: config.zdr === true
|
|
10829
11410
|
};
|
|
10830
11411
|
}
|
|
@@ -10985,7 +11566,7 @@ function apply(ctx, config) {
|
|
|
10985
11566
|
else try {
|
|
10986
11567
|
report = await adapter.getUsage(account.key, connection);
|
|
10987
11568
|
} catch (error) {
|
|
10988
|
-
report = { failures: [
|
|
11569
|
+
report = { failures: [readableErrorText(error)] };
|
|
10989
11570
|
}
|
|
10990
11571
|
const state = account?.state;
|
|
10991
11572
|
const usable = accountUsable(state);
|
|
@@ -11067,13 +11648,24 @@ function apply(ctx, config) {
|
|
|
11067
11648
|
listModels: catalogForEditors,
|
|
11068
11649
|
analytics: createAnalyticsReader(ctx, adapter)
|
|
11069
11650
|
});
|
|
11070
|
-
const
|
|
11651
|
+
const selectionLogger = {
|
|
11652
|
+
warn: (message) => {
|
|
11653
|
+
ctx.logger.warn(message);
|
|
11654
|
+
},
|
|
11655
|
+
info: (message) => {
|
|
11656
|
+
ctx.logger.info(message);
|
|
11657
|
+
}
|
|
11658
|
+
};
|
|
11659
|
+
let searchSelection;
|
|
11071
11660
|
const applySearchSelection = (enabled) => {
|
|
11072
|
-
|
|
11661
|
+
searchSelection?.apply(enabled);
|
|
11073
11662
|
};
|
|
11074
|
-
let webRuntime;
|
|
11075
11663
|
ctx.inject(["web"], (webCtx) => {
|
|
11076
|
-
|
|
11664
|
+
searchSelection = createCommandCodeSearchSelection({
|
|
11665
|
+
web: webCtx.web,
|
|
11666
|
+
settings: () => ctx.get("settings"),
|
|
11667
|
+
logger: selectionLogger
|
|
11668
|
+
});
|
|
11077
11669
|
webCtx.web.registerSearchProvider(new CommandCodeSearchProvider({
|
|
11078
11670
|
resolveKey: async (apiBase) => {
|
|
11079
11671
|
const connection = options();
|
|
@@ -11085,8 +11677,8 @@ function apply(ctx, config) {
|
|
|
11085
11677
|
}));
|
|
11086
11678
|
applySearchSelection(current().webSearch ?? true);
|
|
11087
11679
|
webCtx.effect(() => () => {
|
|
11088
|
-
|
|
11089
|
-
|
|
11680
|
+
searchSelection?.release();
|
|
11681
|
+
searchSelection = void 0;
|
|
11090
11682
|
}, "dsh-commandcode-provider: web search selection");
|
|
11091
11683
|
});
|
|
11092
11684
|
let refreshTuiSettings;
|
|
@@ -11131,6 +11723,7 @@ function apply(ctx, config) {
|
|
|
11131
11723
|
dispose();
|
|
11132
11724
|
};
|
|
11133
11725
|
}, "dsh-commandcode-provider: settings auto-form policy");
|
|
11726
|
+
applySearchSelection(current().webSearch ?? true);
|
|
11134
11727
|
});
|
|
11135
11728
|
const loaderEvents = ctx;
|
|
11136
11729
|
ctx.effect(() => {
|
|
@@ -11144,6 +11737,6 @@ function apply(ctx, config) {
|
|
|
11144
11737
|
}, "dsh-commandcode-provider: volatile config updates");
|
|
11145
11738
|
}
|
|
11146
11739
|
//#endregion
|
|
11147
|
-
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_NON_ZDR_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,
|
|
11740
|
+
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_NON_ZDR_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, WEB_SELECTION_NAMESPACE, accountUsable, apply, applyCommandCodeTuiSettings, applyCommands, applyUsageRemote, buildCommandAuthUrl, buildCommandCodeTuiSection, capabilityDescription, commandCodeSearchSelection, commandDefinition, compareByPlan, createCommandCodeSearchSelection, dealLabel, formatContext, inject, loginStatusSchema, matchModelRule, modelVisibleInPlan, name, parseLoginStatus, peakPricingLabel, peakPricingState, planLabel, projectSlugFromPath, resolveAdapterOptions, resolveAuthFileApiKey, selectAccountForModel, selectActiveAccount, studioBaseForApiBase, subscriptionPlanInfo, supportsZeroDataRetention, usageReportSchema, validateCommandApiKey };
|
|
11148
11741
|
|
|
11149
11742
|
//# sourceMappingURL=index.js.map
|