@mars-sea/dsh-commandcode-provider 0.12.0 → 0.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +109 -0
- package/README.md +7 -3
- package/README.zh-CN.md +8 -3
- package/lib/client.js +245 -69
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +177 -51
- package/lib/index.js +1642 -306
- package/lib/index.js.map +1 -1
- package/package.json +41 -38
package/lib/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { CONTEXT_WINDOW_EXCEEDED_CODE, IMAGE_OFFLOAD_REQUIRED_CODE, LlmAdapter,
|
|
|
6
6
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
7
7
|
import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
|
|
8
8
|
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
9
|
-
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
9
|
+
import { appendFile, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
10
10
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
11
11
|
import { TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
12
12
|
import { createServer } from "node:http";
|
|
@@ -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.
|
|
106
|
+
* bundle's model table (`dist/cli.mjs`, re-verified at command-code@1.73.0) 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
|
|
@@ -408,6 +408,18 @@ const KNOWN_EFFORTS = {
|
|
|
408
408
|
"low",
|
|
409
409
|
"medium",
|
|
410
410
|
"xhigh"
|
|
411
|
+
],
|
|
412
|
+
"gpt-6.1-sol": [
|
|
413
|
+
"low",
|
|
414
|
+
"medium",
|
|
415
|
+
"high",
|
|
416
|
+
"xhigh",
|
|
417
|
+
"max"
|
|
418
|
+
],
|
|
419
|
+
"inclusionai/ling-3.1-flash:free": [
|
|
420
|
+
"low",
|
|
421
|
+
"medium",
|
|
422
|
+
"high"
|
|
411
423
|
]
|
|
412
424
|
};
|
|
413
425
|
/**
|
|
@@ -464,6 +476,7 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
|
464
476
|
"gpt-6-astra",
|
|
465
477
|
"gpt-6-luna",
|
|
466
478
|
"gpt-6-sol",
|
|
479
|
+
"gpt-6.1-sol",
|
|
467
480
|
"meta/muse-spark-1.1",
|
|
468
481
|
"meta/muse-spark-1.2",
|
|
469
482
|
"meta/muse-spark-1.2-contributor",
|
|
@@ -493,7 +506,7 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
|
493
506
|
]);
|
|
494
507
|
/**
|
|
495
508
|
* Models WITHOUT a zero-data-retention upstream, per the official CLI's own
|
|
496
|
-
* registry (`command-code@1.
|
|
509
|
+
* registry (`command-code@1.73.0` `dist/cli.mjs`): `modelSupportsZdr(id)` is
|
|
497
510
|
* exactly `!nonZdrSet.has(canonicalize(id))`, and `knownModelSupportsZdr`
|
|
498
511
|
* carries the same membership in the sibling route table — the UNION of both
|
|
499
512
|
* is this set. Reading only the sibling route table would drop `meituan/
|
|
@@ -594,16 +607,22 @@ const KNOWN_THINKING_MODELS = /* @__PURE__ */ new Set([
|
|
|
594
607
|
/**
|
|
595
608
|
* Catalog models the Provider API serves ONLY through `/provider/v1/messages`
|
|
596
609
|
* (Anthropic Messages shape). Every other model in the catalog answers on
|
|
597
|
-
* `/provider/v1/chat/completions
|
|
598
|
-
*
|
|
599
|
-
*
|
|
610
|
+
* `/provider/v1/chat/completions`.
|
|
611
|
+
*
|
|
612
|
+
* Measured 2026-09-16 (command-code@1.54.0, 69 models posted to
|
|
613
|
+
* `/provider/v1/chat/completions`): exactly the then-entire Claude family
|
|
614
|
+
* refused with HTTP 400 `Model "<id>" must be called via
|
|
615
|
+
* /provider/v1/messages (Anthropic Messages shape)`, and every one of them is
|
|
616
|
+
* routed normally by `/alpha/generate`.
|
|
600
617
|
*
|
|
601
|
-
*
|
|
602
|
-
*
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
*
|
|
618
|
+
* Re-measured 2026-09-29 against the 84-model catalog: the same ten
|
|
619
|
+
* `claude-*` ids are the only ones carrying `supported_endpoints:
|
|
620
|
+
* ["/messages"]`, and posting a non-Claude model to the endpoint answers 400
|
|
621
|
+
* `Model "<id>" is not supported on this endpoint. Use
|
|
622
|
+
* /provider/v1/chat/completions for OpenAI and OSS models.` The adapter routes
|
|
623
|
+
* on that field when the catalog supplies it, so this set is the FALLBACK for
|
|
624
|
+
* a cached or hand-built catalog, not the primary source — see
|
|
625
|
+
* `requiresMessagesEndpoint()`.
|
|
607
626
|
*
|
|
608
627
|
* Note what this list is NOT: it is not a plan gate. `claude-sonnet-5` is
|
|
609
628
|
* Pro-tier and `claude-opus-4-8` Provider-tier, so the accounts entitled to
|
|
@@ -612,20 +631,21 @@ const KNOWN_THINKING_MODELS = /* @__PURE__ */ new Set([
|
|
|
612
631
|
* request before this snapshot existed.
|
|
613
632
|
*
|
|
614
633
|
* `requiresMessagesEndpoint()` additionally treats any `claude-*` id as
|
|
615
|
-
* Messages-only, so a Claude model added upstream
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
*
|
|
619
|
-
*
|
|
620
|
-
* riding the CLI transport it already works on.
|
|
634
|
+
* Messages-only, so a Claude model added upstream is still routed correctly
|
|
635
|
+
* by an un-updated plugin instead of hard-failing with the 400 above. The
|
|
636
|
+
* worst case of that rule going stale the other way (upstream teaching
|
|
637
|
+
* `/provider/v1/chat/completions` to serve Claude) is one model riding a
|
|
638
|
+
* transport it already works on.
|
|
621
639
|
*
|
|
622
640
|
* Keep in sync when models ship (see the dsh-commandcode-upstream skill).
|
|
623
641
|
*/
|
|
624
642
|
const MESSAGES_ONLY_MODELS = /* @__PURE__ */ new Set([
|
|
643
|
+
"claude-sonnet-5-5",
|
|
625
644
|
"claude-sonnet-5",
|
|
626
645
|
"claude-sonnet-4-6",
|
|
627
646
|
"claude-fable-5-1",
|
|
628
647
|
"claude-fable-5",
|
|
648
|
+
"claude-opus-5-5",
|
|
629
649
|
"claude-opus-5",
|
|
630
650
|
"claude-opus-4-8",
|
|
631
651
|
"claude-opus-4-7",
|
|
@@ -636,12 +656,139 @@ function requiresMessagesEndpoint(modelId) {
|
|
|
636
656
|
return MESSAGES_ONLY_MODELS.has(modelId) || modelId.startsWith("claude-");
|
|
637
657
|
}
|
|
638
658
|
/**
|
|
659
|
+
* Per-model output ceilings, which `/provider/v1/models` does NOT publish (it
|
|
660
|
+
* carries `context_length` and `supported_endpoints` only) while every endpoint
|
|
661
|
+
* refuses a larger `max_tokens` per model. Sending the context window as the
|
|
662
|
+
* output budget is what produced issue #71: `claude-sonnet-5-5` has a
|
|
663
|
+
* 1 000 000-token window and a 128 000-token ceiling, the adapter derived
|
|
664
|
+
* 131072, and the endpoint refused every request of a fresh process.
|
|
665
|
+
*
|
|
666
|
+
* **Generated — do not hand-edit.** `scripts/sync-output-limits.mjs` rewrites
|
|
667
|
+
* this literal from models.dev; run it after a catalog sync, and use its
|
|
668
|
+
* `--check` in CI. Command Code resells these models through their VENDOR's own
|
|
669
|
+
* API, so the vendor's published ceiling is the value recorded: 76 of the 86
|
|
670
|
+
* catalog ids as of 2026-09-30. Each row carries a comment naming the entry it
|
|
671
|
+
* came from.
|
|
672
|
+
*
|
|
673
|
+
* Two of those 76 rest on more than a vendor lookup. `gpt-6.1-sol` (128000) comes
|
|
674
|
+
* from OpenAI's own row and is corroborated by every other provider carrying it.
|
|
675
|
+
* `inclusionai/ling-3.1-flash:free` (32768) is hand-recorded in the script's
|
|
676
|
+
* `MANUAL_CEILINGS`: models.dev has no `ling-3.1` entry yet, so it takes the Ling
|
|
677
|
+
* family's uniform ceiling. Both are still correctable — a stricter gateway costs
|
|
678
|
+
* one refused request, and `streamRequest()` remembers what the endpoint said.
|
|
679
|
+
*
|
|
680
|
+
* The 10 unlisted models are ones no vendor publishes and the aggregators
|
|
681
|
+
* disagree about. They are NOT a gap: a refusal states the ceiling in full, and
|
|
682
|
+
* `streamRequest()` reads it off, retries once and remembers it for the model
|
|
683
|
+
* (issue #71). A vendor figure the gateway turns out to be stricter than costs
|
|
684
|
+
* one round trip; a conservative guess costs half of every answer, forever —
|
|
685
|
+
* which is why the unlisted path defers to the endpoint rather than to a
|
|
686
|
+
* fallback here.
|
|
687
|
+
*
|
|
688
|
+
* An unlisted Messages-route model still falls back to
|
|
689
|
+
* {@link DEFAULT_MESSAGES_MAX_TOKENS} before any request is made, so a ceiling
|
|
690
|
+
* is never absent when the body is built.
|
|
691
|
+
*/
|
|
692
|
+
const MODEL_OUTPUT_TOKEN_LIMITS = /* @__PURE__ */ new Map([
|
|
693
|
+
["claude-fable-5", 128e3],
|
|
694
|
+
["claude-fable-5-1", 128e3],
|
|
695
|
+
["claude-haiku-4-5-20251001", 64e3],
|
|
696
|
+
["claude-opus-4-7", 128e3],
|
|
697
|
+
["claude-opus-4-8", 128e3],
|
|
698
|
+
["claude-opus-5", 128e3],
|
|
699
|
+
["claude-opus-5-5", 128e3],
|
|
700
|
+
["claude-sonnet-4-6", 128e3],
|
|
701
|
+
["claude-sonnet-5", 128e3],
|
|
702
|
+
["claude-sonnet-5-5", 128e3],
|
|
703
|
+
["deepseek/deepseek-v4-flash", 393216],
|
|
704
|
+
["deepseek/deepseek-v4-flash-vision-exp", 393216],
|
|
705
|
+
["deepseek/deepseek-v4-pro", 393216],
|
|
706
|
+
["google/gemini-3.1-flash-lite", 65536],
|
|
707
|
+
["google/gemini-3.5-flash", 65536],
|
|
708
|
+
["google/gemini-3.5-flash-lite", 65536],
|
|
709
|
+
["google/gemini-3.6-flash", 65536],
|
|
710
|
+
["google/gemini-3.7-flash", 65536],
|
|
711
|
+
["google/gemini-3.8-flash", 65536],
|
|
712
|
+
["gpt-5.3-codex", 128e3],
|
|
713
|
+
["gpt-5.4", 128e3],
|
|
714
|
+
["gpt-5.4-mini", 128e3],
|
|
715
|
+
["gpt-5.5", 128e3],
|
|
716
|
+
["gpt-5.6-luna", 128e3],
|
|
717
|
+
["gpt-5.6-sol", 128e3],
|
|
718
|
+
["gpt-5.6-terra", 128e3],
|
|
719
|
+
["gpt-6-astra", 128e3],
|
|
720
|
+
["gpt-6-luna", 128e3],
|
|
721
|
+
["gpt-6-sol", 128e3],
|
|
722
|
+
["gpt-6.1-sol", 128e3],
|
|
723
|
+
["inclusionai/ling-3.0-flash-sante:free", 32768],
|
|
724
|
+
["inclusionai/ling-3.1-flash:free", 32768],
|
|
725
|
+
["meituan/LongCat-2.0", 131072],
|
|
726
|
+
["meta/muse-spark-1.1", 131072],
|
|
727
|
+
["meta/muse-spark-1.2", 131072],
|
|
728
|
+
["meta/muse-spark-1.2-contributor", 131072],
|
|
729
|
+
["meta/muse-spark-1.3", 131072],
|
|
730
|
+
["meta/muse-spark-1.3-contributor", 131072],
|
|
731
|
+
["MiniMaxAI/MiniMax-M2.5", 131072],
|
|
732
|
+
["MiniMaxAI/MiniMax-M2.7", 131072],
|
|
733
|
+
["MiniMaxAI/MiniMax-M3", 512e3],
|
|
734
|
+
["moonshotai/Kimi-K2.6", 262144],
|
|
735
|
+
["moonshotai/Kimi-K2.7-Code", 262144],
|
|
736
|
+
["moonshotai/Kimi-K2.7-Code-Highspeed", 262144],
|
|
737
|
+
["moonshotai/Kimi-K3", 1048576],
|
|
738
|
+
["nvidia/nemotron-3-ultra-550b-a55b", 65536],
|
|
739
|
+
["poolside/laguna-s-2.1-free", 32768],
|
|
740
|
+
["Qwen/Qwen3.6-Max-Preview", 65536],
|
|
741
|
+
["Qwen/Qwen3.6-Plus", 65536],
|
|
742
|
+
["Qwen/Qwen3.7-Flash", 131072],
|
|
743
|
+
["Qwen/Qwen3.7-Max", 131072],
|
|
744
|
+
["Qwen/Qwen3.7-Plus", 131072],
|
|
745
|
+
["Qwen/Qwen3.8-Flash", 131072],
|
|
746
|
+
["Qwen/Qwen3.8-Max", 131072],
|
|
747
|
+
["Qwen/Qwen3.8-Omni-Flash", 131072],
|
|
748
|
+
["sakana/fugu-ultra", 1e6],
|
|
749
|
+
["stealth/pixel-canary", 131072],
|
|
750
|
+
["stealth/space-bunny-alpha", 524288],
|
|
751
|
+
["stepfun/Step-3.5-Flash", 256e3],
|
|
752
|
+
["stepfun/Step-3.7-Flash", 256e3],
|
|
753
|
+
["stepfun/Step-5-Preview", 65536],
|
|
754
|
+
["thinkingmachines/inkling", 65536],
|
|
755
|
+
["xai/grok-4.5", 5e5],
|
|
756
|
+
["xai/grok-4.6", 5e5],
|
|
757
|
+
["xai/grok-4.7", 5e5],
|
|
758
|
+
["xiaomi/mimo-v2.5", 131072],
|
|
759
|
+
["xiaomi/mimo-v2.5-pro", 131072],
|
|
760
|
+
["xiaomi/mimo-v2.6-flash", 131072],
|
|
761
|
+
["xiaomi/mimo-v2.6-pro", 131072],
|
|
762
|
+
["xiaomi/mimo-v2.6-pro-ultraspeed", 131072],
|
|
763
|
+
["z-ai/glm-5.3-flash", 131072],
|
|
764
|
+
["z-ai/glm-5.3-flashx", 131072],
|
|
765
|
+
["zai-org/GLM-5", 131072],
|
|
766
|
+
["zai-org/GLM-5.1", 131072],
|
|
767
|
+
["zai-org/GLM-5.2", 131072],
|
|
768
|
+
["zai-org/GLM-5.3", 131072]
|
|
769
|
+
]);
|
|
770
|
+
/**
|
|
771
|
+
* Output ceiling for a Messages-route model that {@link MODEL_OUTPUT_TOKEN_LIMITS}
|
|
772
|
+
* does not carry — today only models no vendor publishes yet. Deliberately
|
|
773
|
+
* conservative: a 400 on `max_tokens` is a wasted round trip on every request,
|
|
774
|
+
* whereas a lower ceiling only shortens an answer, and the endpoint states the
|
|
775
|
+
* real one the first time it refuses, at which point the runtime overrides this
|
|
776
|
+
* anyway (issue #71).
|
|
777
|
+
*
|
|
778
|
+
* 64 000 is not arbitrary: it is `claude-haiku-4-5-20251001`'s actual ceiling,
|
|
779
|
+
* the lowest in the Claude family. A Claude model added to the catalog before
|
|
780
|
+
* its row exists here therefore answers at a size the family really supports,
|
|
781
|
+
* and is corrected upward on its first rejection rather than clamped here
|
|
782
|
+
* forever.
|
|
783
|
+
*/
|
|
784
|
+
const DEFAULT_MESSAGES_MAX_TOKENS = 64e3;
|
|
785
|
+
/**
|
|
639
786
|
* The minimum subscription plan a model is included in, per the official plan
|
|
640
787
|
* pages (`/docs/plans/go`, `/docs/plans/goat`, `/docs/plans/pro`,
|
|
641
788
|
* `/docs/plans/max` and `/docs/resources/pricing-limits`). Each plan's model
|
|
642
789
|
* list is a superset of the one below it: Go ⊂ GOAT ⊂ Pro ⊂ Provider/Max.
|
|
643
790
|
* Models absent from every plan list (Claude Opus/Fable, Fugu Ultra) are
|
|
644
|
-
* Provider-tier. Re-verified at command-code@1.
|
|
791
|
+
* Provider-tier. Re-verified at command-code@1.73.0 (2026-10-01): 86 catalog
|
|
645
792
|
* ids at 53/62/76/84 cumulative, a strict superset chain — every release since
|
|
646
793
|
* 1.49.0 has been additive with no tier move, and per-entry tags below name the
|
|
647
794
|
* release that added each row.
|
|
@@ -678,6 +825,7 @@ const KNOWN_PLANS = {
|
|
|
678
825
|
"gpt-6-luna": "go",
|
|
679
826
|
"meituan/LongCat-2.0": "go",
|
|
680
827
|
"inclusionai/ling-3.0-flash-sante:free": "go",
|
|
828
|
+
"inclusionai/ling-3.1-flash:free": "go",
|
|
681
829
|
"meta/muse-spark-1.2-contributor": "go",
|
|
682
830
|
"meta/muse-spark-1.3-contributor": "go",
|
|
683
831
|
"moonshotai/Kimi-K2.5": "go",
|
|
@@ -738,7 +886,8 @@ const KNOWN_PLANS = {
|
|
|
738
886
|
"claude-opus-5-5": "provider",
|
|
739
887
|
"claude-opus-5": "provider",
|
|
740
888
|
"gpt-6-astra": "provider",
|
|
741
|
-
"sakana/fugu-ultra": "provider"
|
|
889
|
+
"sakana/fugu-ultra": "provider",
|
|
890
|
+
"gpt-6.1-sol": "max"
|
|
742
891
|
};
|
|
743
892
|
/**
|
|
744
893
|
* Whether a model is free (requests cost no credits), per the pricing page's
|
|
@@ -767,7 +916,7 @@ function compareByPlan(a, b) {
|
|
|
767
916
|
/**
|
|
768
917
|
* Subscription plan table, synced from the official CLI bundle's plan maps
|
|
769
918
|
* (located by the `"individual-go"` key in `dist/cli.mjs`, re-verified unchanged
|
|
770
|
-
* through command-code@1.
|
|
919
|
+
* through command-code@1.73.0): subscription `planId` prefix → display name and
|
|
771
920
|
* the plan's monthly credit total. This is the account's own subscription
|
|
772
921
|
* (from `/alpha/billing/subscriptions`) — distinct from {@link KNOWN_PLANS},
|
|
773
922
|
* which maps catalog models to their minimum tier.
|
|
@@ -782,6 +931,11 @@ const KNOWN_SUBSCRIPTION_PLANS = {
|
|
|
782
931
|
monthlyCredits: 10,
|
|
783
932
|
tierWeight: 0
|
|
784
933
|
},
|
|
934
|
+
"individual-go-v1": {
|
|
935
|
+
name: "Go",
|
|
936
|
+
monthlyCredits: 10,
|
|
937
|
+
tierWeight: 0
|
|
938
|
+
},
|
|
785
939
|
"individual-goat": {
|
|
786
940
|
name: "GOAT",
|
|
787
941
|
monthlyCredits: 70,
|
|
@@ -870,10 +1024,6 @@ const KNOWN_DEALS = {
|
|
|
870
1024
|
"MiniMaxAI/MiniMax-M3": { label: "50% off" },
|
|
871
1025
|
"xiaomi/mimo-v2.5-pro": { label: "99% off" },
|
|
872
1026
|
"xiaomi/mimo-v2.5": { label: "98% off" },
|
|
873
|
-
"xai/grok-4.7": {
|
|
874
|
-
label: "40% off",
|
|
875
|
-
expiresAt: "2026-09-27T23:59:59.999Z"
|
|
876
|
-
},
|
|
877
1027
|
"poolside/laguna-s-2.1-free": {
|
|
878
1028
|
label: "FREE",
|
|
879
1029
|
free: true
|
|
@@ -882,6 +1032,10 @@ const KNOWN_DEALS = {
|
|
|
882
1032
|
label: "FREE",
|
|
883
1033
|
free: true
|
|
884
1034
|
},
|
|
1035
|
+
"inclusionai/ling-3.1-flash:free": {
|
|
1036
|
+
label: "FREE",
|
|
1037
|
+
free: true
|
|
1038
|
+
},
|
|
885
1039
|
"stealth/space-bunny-alpha": {
|
|
886
1040
|
label: "FREE",
|
|
887
1041
|
free: true
|
|
@@ -1032,16 +1186,19 @@ function capabilityDescription(modelId, contextWindow, now = Date.now()) {
|
|
|
1032
1186
|
* rather than against hard-coded numbers, so this table and the subscription
|
|
1033
1187
|
* table cannot drift apart silently.
|
|
1034
1188
|
*/
|
|
1035
|
-
const ALLOWANCE_TIERS = [
|
|
1189
|
+
const ALLOWANCE_TIERS = [
|
|
1190
|
+
"go",
|
|
1191
|
+
"goat",
|
|
1192
|
+
"pro"
|
|
1193
|
+
];
|
|
1036
1194
|
/**
|
|
1037
1195
|
* Which allowance bracket a plan-tier weight belongs to, or undefined when that
|
|
1038
1196
|
* plan has no per-model allowance.
|
|
1039
1197
|
*
|
|
1040
1198
|
* Weights come from `KNOWN_SUBSCRIPTION_PLANS` (go 0 · goat 1 · pro 2 ·
|
|
1041
1199
|
* provider 3 · max/ultra 4) and {@link PLAN_ORDER} is the same scale, which is
|
|
1042
|
-
* what makes the lookup exact:
|
|
1043
|
-
*
|
|
1044
|
-
* fabricated number rather than a missing one.
|
|
1200
|
+
* what makes the lookup exact: Provider、Max、Ultra
|
|
1201
|
+
* 没有本地额度;缺项时不能用相邻套餐的数字代替。
|
|
1045
1202
|
*/
|
|
1046
1203
|
function allowanceTierForWeight(tierWeight) {
|
|
1047
1204
|
if (tierWeight === void 0) return void 0;
|
|
@@ -1069,6 +1226,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1069
1226
|
12.5
|
|
1070
1227
|
],
|
|
1071
1228
|
allowance: {
|
|
1229
|
+
go: 6,
|
|
1072
1230
|
goat: 20,
|
|
1073
1231
|
pro: 20
|
|
1074
1232
|
}
|
|
@@ -1082,6 +1240,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1082
1240
|
12.5
|
|
1083
1241
|
],
|
|
1084
1242
|
allowance: {
|
|
1243
|
+
go: 6,
|
|
1085
1244
|
goat: 20,
|
|
1086
1245
|
pro: 20
|
|
1087
1246
|
}
|
|
@@ -1095,6 +1254,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1095
1254
|
1.25
|
|
1096
1255
|
],
|
|
1097
1256
|
allowance: {
|
|
1257
|
+
go: 6,
|
|
1098
1258
|
goat: 20,
|
|
1099
1259
|
pro: 20
|
|
1100
1260
|
}
|
|
@@ -1108,6 +1268,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1108
1268
|
6.25
|
|
1109
1269
|
],
|
|
1110
1270
|
allowance: {
|
|
1271
|
+
go: 6,
|
|
1111
1272
|
goat: 20,
|
|
1112
1273
|
pro: 20
|
|
1113
1274
|
}
|
|
@@ -1121,6 +1282,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1121
1282
|
6.25
|
|
1122
1283
|
],
|
|
1123
1284
|
allowance: {
|
|
1285
|
+
go: 6,
|
|
1124
1286
|
goat: 20,
|
|
1125
1287
|
pro: 20
|
|
1126
1288
|
}
|
|
@@ -1134,6 +1296,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1134
1296
|
6.25
|
|
1135
1297
|
],
|
|
1136
1298
|
allowance: {
|
|
1299
|
+
go: 6,
|
|
1137
1300
|
goat: 20,
|
|
1138
1301
|
pro: 20
|
|
1139
1302
|
}
|
|
@@ -1147,6 +1310,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1147
1310
|
6.25
|
|
1148
1311
|
],
|
|
1149
1312
|
allowance: {
|
|
1313
|
+
go: 6,
|
|
1150
1314
|
goat: 20,
|
|
1151
1315
|
pro: 20
|
|
1152
1316
|
}
|
|
@@ -1160,6 +1324,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1160
1324
|
5
|
|
1161
1325
|
],
|
|
1162
1326
|
allowance: {
|
|
1327
|
+
go: 6,
|
|
1163
1328
|
goat: 20,
|
|
1164
1329
|
pro: 20
|
|
1165
1330
|
}
|
|
@@ -1173,6 +1338,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1173
1338
|
3.75
|
|
1174
1339
|
],
|
|
1175
1340
|
allowance: {
|
|
1341
|
+
go: 6,
|
|
1176
1342
|
goat: 20,
|
|
1177
1343
|
pro: 20
|
|
1178
1344
|
}
|
|
@@ -1186,6 +1352,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1186
1352
|
2.5
|
|
1187
1353
|
],
|
|
1188
1354
|
allowance: {
|
|
1355
|
+
go: 6,
|
|
1189
1356
|
goat: 20,
|
|
1190
1357
|
pro: 20
|
|
1191
1358
|
}
|
|
@@ -1199,6 +1366,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1199
1366
|
2.5
|
|
1200
1367
|
],
|
|
1201
1368
|
allowance: {
|
|
1369
|
+
go: 6,
|
|
1202
1370
|
goat: 10,
|
|
1203
1371
|
pro: 20
|
|
1204
1372
|
}
|
|
@@ -1216,6 +1384,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1216
1384
|
.006
|
|
1217
1385
|
],
|
|
1218
1386
|
allowance: {
|
|
1387
|
+
go: 6,
|
|
1219
1388
|
goat: 60,
|
|
1220
1389
|
pro: 70
|
|
1221
1390
|
}
|
|
@@ -1228,6 +1397,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1228
1397
|
.07
|
|
1229
1398
|
],
|
|
1230
1399
|
allowance: {
|
|
1400
|
+
go: 8,
|
|
1231
1401
|
goat: 20,
|
|
1232
1402
|
pro: 30
|
|
1233
1403
|
}
|
|
@@ -1245,6 +1415,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1245
1415
|
.006
|
|
1246
1416
|
],
|
|
1247
1417
|
allowance: {
|
|
1418
|
+
go: 6,
|
|
1248
1419
|
goat: 20,
|
|
1249
1420
|
pro: 30
|
|
1250
1421
|
}
|
|
@@ -1262,6 +1433,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1262
1433
|
.044
|
|
1263
1434
|
],
|
|
1264
1435
|
allowance: {
|
|
1436
|
+
go: 6,
|
|
1265
1437
|
goat: 20,
|
|
1266
1438
|
pro: 30
|
|
1267
1439
|
}
|
|
@@ -1279,6 +1451,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1279
1451
|
.006
|
|
1280
1452
|
],
|
|
1281
1453
|
allowance: {
|
|
1454
|
+
go: 10,
|
|
1282
1455
|
goat: 60,
|
|
1283
1456
|
pro: 70
|
|
1284
1457
|
}
|
|
@@ -1296,6 +1469,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1296
1469
|
.032
|
|
1297
1470
|
],
|
|
1298
1471
|
allowance: {
|
|
1472
|
+
go: 6,
|
|
1299
1473
|
goat: 60,
|
|
1300
1474
|
pro: 70
|
|
1301
1475
|
}
|
|
@@ -1308,6 +1482,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1308
1482
|
.5
|
|
1309
1483
|
],
|
|
1310
1484
|
allowance: {
|
|
1485
|
+
go: 6,
|
|
1311
1486
|
goat: 20,
|
|
1312
1487
|
pro: 20
|
|
1313
1488
|
}
|
|
@@ -1320,6 +1495,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1320
1495
|
.03
|
|
1321
1496
|
],
|
|
1322
1497
|
allowance: {
|
|
1498
|
+
go: 6,
|
|
1323
1499
|
goat: 20,
|
|
1324
1500
|
pro: 20
|
|
1325
1501
|
}
|
|
@@ -1332,6 +1508,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1332
1508
|
.15
|
|
1333
1509
|
],
|
|
1334
1510
|
allowance: {
|
|
1511
|
+
go: 6,
|
|
1335
1512
|
goat: 20,
|
|
1336
1513
|
pro: 20
|
|
1337
1514
|
}
|
|
@@ -1344,6 +1521,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1344
1521
|
.03
|
|
1345
1522
|
],
|
|
1346
1523
|
allowance: {
|
|
1524
|
+
go: 6,
|
|
1347
1525
|
goat: 20,
|
|
1348
1526
|
pro: 20
|
|
1349
1527
|
}
|
|
@@ -1356,6 +1534,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1356
1534
|
.15
|
|
1357
1535
|
],
|
|
1358
1536
|
allowance: {
|
|
1537
|
+
go: 6,
|
|
1359
1538
|
goat: 20,
|
|
1360
1539
|
pro: 20
|
|
1361
1540
|
}
|
|
@@ -1369,6 +1548,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1369
1548
|
.08334
|
|
1370
1549
|
],
|
|
1371
1550
|
allowance: {
|
|
1551
|
+
go: 6,
|
|
1372
1552
|
goat: 40,
|
|
1373
1553
|
pro: 50
|
|
1374
1554
|
}
|
|
@@ -1381,6 +1561,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1381
1561
|
.15
|
|
1382
1562
|
],
|
|
1383
1563
|
allowance: {
|
|
1564
|
+
go: 6,
|
|
1384
1565
|
goat: 40,
|
|
1385
1566
|
pro: 50
|
|
1386
1567
|
}
|
|
@@ -1393,6 +1574,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1393
1574
|
.2
|
|
1394
1575
|
],
|
|
1395
1576
|
allowance: {
|
|
1577
|
+
go: 6,
|
|
1396
1578
|
goat: 20,
|
|
1397
1579
|
pro: 30
|
|
1398
1580
|
}
|
|
@@ -1405,6 +1587,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1405
1587
|
.26
|
|
1406
1588
|
],
|
|
1407
1589
|
allowance: {
|
|
1590
|
+
go: 6,
|
|
1408
1591
|
goat: 20,
|
|
1409
1592
|
pro: 30
|
|
1410
1593
|
}
|
|
@@ -1417,6 +1600,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1417
1600
|
.26
|
|
1418
1601
|
],
|
|
1419
1602
|
allowance: {
|
|
1603
|
+
go: 10,
|
|
1420
1604
|
goat: 70,
|
|
1421
1605
|
pro: 80
|
|
1422
1606
|
}
|
|
@@ -1429,6 +1613,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1429
1613
|
.5
|
|
1430
1614
|
],
|
|
1431
1615
|
allowance: {
|
|
1616
|
+
go: 9,
|
|
1432
1617
|
goat: 20,
|
|
1433
1618
|
pro: 30
|
|
1434
1619
|
}
|
|
@@ -1441,6 +1626,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1441
1626
|
.26
|
|
1442
1627
|
],
|
|
1443
1628
|
allowance: {
|
|
1629
|
+
go: 10,
|
|
1444
1630
|
goat: 20,
|
|
1445
1631
|
pro: 30
|
|
1446
1632
|
}
|
|
@@ -1453,6 +1639,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1453
1639
|
.03
|
|
1454
1640
|
],
|
|
1455
1641
|
allowance: {
|
|
1642
|
+
go: 10,
|
|
1456
1643
|
goat: 40,
|
|
1457
1644
|
pro: 50
|
|
1458
1645
|
}
|
|
@@ -1465,6 +1652,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1465
1652
|
.075
|
|
1466
1653
|
],
|
|
1467
1654
|
allowance: {
|
|
1655
|
+
go: 6,
|
|
1468
1656
|
goat: 20,
|
|
1469
1657
|
pro: 30
|
|
1470
1658
|
}
|
|
@@ -1478,6 +1666,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1478
1666
|
0
|
|
1479
1667
|
],
|
|
1480
1668
|
allowance: {
|
|
1669
|
+
go: 6,
|
|
1481
1670
|
goat: 20,
|
|
1482
1671
|
pro: 20
|
|
1483
1672
|
}
|
|
@@ -1491,6 +1680,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1491
1680
|
0
|
|
1492
1681
|
],
|
|
1493
1682
|
allowance: {
|
|
1683
|
+
go: 6,
|
|
1494
1684
|
goat: 20,
|
|
1495
1685
|
pro: 20
|
|
1496
1686
|
}
|
|
@@ -1504,6 +1694,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1504
1694
|
0
|
|
1505
1695
|
],
|
|
1506
1696
|
allowance: {
|
|
1697
|
+
go: 6,
|
|
1507
1698
|
goat: 20,
|
|
1508
1699
|
pro: 20
|
|
1509
1700
|
}
|
|
@@ -1517,6 +1708,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1517
1708
|
0
|
|
1518
1709
|
],
|
|
1519
1710
|
allowance: {
|
|
1711
|
+
go: 6,
|
|
1520
1712
|
goat: 20,
|
|
1521
1713
|
pro: 20
|
|
1522
1714
|
}
|
|
@@ -1530,6 +1722,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1530
1722
|
.25
|
|
1531
1723
|
],
|
|
1532
1724
|
allowance: {
|
|
1725
|
+
go: 6,
|
|
1533
1726
|
goat: 20,
|
|
1534
1727
|
pro: 30
|
|
1535
1728
|
},
|
|
@@ -1557,6 +1750,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1557
1750
|
6.25
|
|
1558
1751
|
],
|
|
1559
1752
|
allowance: {
|
|
1753
|
+
go: 6,
|
|
1560
1754
|
goat: 70,
|
|
1561
1755
|
pro: 80
|
|
1562
1756
|
},
|
|
@@ -1584,6 +1778,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1584
1778
|
2.5
|
|
1585
1779
|
],
|
|
1586
1780
|
allowance: {
|
|
1781
|
+
go: 6,
|
|
1587
1782
|
goat: 20,
|
|
1588
1783
|
pro: 20
|
|
1589
1784
|
},
|
|
@@ -1611,6 +1806,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1611
1806
|
12.5
|
|
1612
1807
|
],
|
|
1613
1808
|
allowance: {
|
|
1809
|
+
go: 6,
|
|
1614
1810
|
goat: 20,
|
|
1615
1811
|
pro: 20
|
|
1616
1812
|
},
|
|
@@ -1638,6 +1834,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1638
1834
|
.125
|
|
1639
1835
|
],
|
|
1640
1836
|
allowance: {
|
|
1837
|
+
go: 6,
|
|
1641
1838
|
goat: 20,
|
|
1642
1839
|
pro: 30
|
|
1643
1840
|
},
|
|
@@ -1665,6 +1862,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1665
1862
|
2.5
|
|
1666
1863
|
],
|
|
1667
1864
|
allowance: {
|
|
1865
|
+
go: 6,
|
|
1668
1866
|
goat: 20,
|
|
1669
1867
|
pro: 20
|
|
1670
1868
|
},
|
|
@@ -1683,6 +1881,34 @@ const MODEL_PRICE_ROWS = [
|
|
|
1683
1881
|
5
|
|
1684
1882
|
] }]
|
|
1685
1883
|
},
|
|
1884
|
+
{
|
|
1885
|
+
id: "gpt-6.1-sol",
|
|
1886
|
+
rates: [
|
|
1887
|
+
2,
|
|
1888
|
+
10,
|
|
1889
|
+
.1,
|
|
1890
|
+
2.5
|
|
1891
|
+
],
|
|
1892
|
+
allowance: {
|
|
1893
|
+
go: 6,
|
|
1894
|
+
goat: 20,
|
|
1895
|
+
pro: 20
|
|
1896
|
+
},
|
|
1897
|
+
contextTiers: [{
|
|
1898
|
+
"maxContext": 272e3,
|
|
1899
|
+
"rates": [
|
|
1900
|
+
2,
|
|
1901
|
+
10,
|
|
1902
|
+
.1,
|
|
1903
|
+
2.5
|
|
1904
|
+
]
|
|
1905
|
+
}, { "rates": [
|
|
1906
|
+
4,
|
|
1907
|
+
15,
|
|
1908
|
+
.2,
|
|
1909
|
+
5
|
|
1910
|
+
] }]
|
|
1911
|
+
},
|
|
1686
1912
|
{
|
|
1687
1913
|
id: "grok-4.5",
|
|
1688
1914
|
rates: [
|
|
@@ -1691,6 +1917,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1691
1917
|
.5
|
|
1692
1918
|
],
|
|
1693
1919
|
allowance: {
|
|
1920
|
+
go: 6,
|
|
1694
1921
|
goat: 20,
|
|
1695
1922
|
pro: 30
|
|
1696
1923
|
}
|
|
@@ -1703,6 +1930,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1703
1930
|
.5
|
|
1704
1931
|
],
|
|
1705
1932
|
allowance: {
|
|
1933
|
+
go: 6,
|
|
1706
1934
|
goat: 20,
|
|
1707
1935
|
pro: 30
|
|
1708
1936
|
},
|
|
@@ -1722,43 +1950,27 @@ const MODEL_PRICE_ROWS = [
|
|
|
1722
1950
|
{
|
|
1723
1951
|
id: "grok-4.7",
|
|
1724
1952
|
rates: [
|
|
1725
|
-
1.2,
|
|
1726
|
-
3.6,
|
|
1727
|
-
.3
|
|
1728
|
-
],
|
|
1729
|
-
listRates: [
|
|
1730
1953
|
2,
|
|
1731
1954
|
6,
|
|
1732
1955
|
.5
|
|
1733
1956
|
],
|
|
1734
1957
|
allowance: {
|
|
1958
|
+
go: 6,
|
|
1735
1959
|
goat: 20,
|
|
1736
1960
|
pro: 30
|
|
1737
1961
|
},
|
|
1738
1962
|
contextTiers: [{
|
|
1739
1963
|
"maxContext": 2e5,
|
|
1740
1964
|
"rates": [
|
|
1741
|
-
1.2,
|
|
1742
|
-
3.6,
|
|
1743
|
-
.3
|
|
1744
|
-
],
|
|
1745
|
-
"listRates": [
|
|
1746
1965
|
2,
|
|
1747
1966
|
6,
|
|
1748
1967
|
.5
|
|
1749
1968
|
]
|
|
1750
|
-
}, {
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
],
|
|
1756
|
-
"listRates": [
|
|
1757
|
-
4,
|
|
1758
|
-
12,
|
|
1759
|
-
1
|
|
1760
|
-
]
|
|
1761
|
-
}]
|
|
1969
|
+
}, { "rates": [
|
|
1970
|
+
4,
|
|
1971
|
+
12,
|
|
1972
|
+
1
|
|
1973
|
+
] }]
|
|
1762
1974
|
},
|
|
1763
1975
|
{
|
|
1764
1976
|
id: "inkling",
|
|
@@ -1768,6 +1980,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1768
1980
|
.17
|
|
1769
1981
|
],
|
|
1770
1982
|
allowance: {
|
|
1983
|
+
go: 6,
|
|
1771
1984
|
goat: 20,
|
|
1772
1985
|
pro: 30
|
|
1773
1986
|
}
|
|
@@ -1780,6 +1993,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1780
1993
|
.1
|
|
1781
1994
|
],
|
|
1782
1995
|
allowance: {
|
|
1996
|
+
go: 6,
|
|
1783
1997
|
goat: 20,
|
|
1784
1998
|
pro: 30
|
|
1785
1999
|
}
|
|
@@ -1792,6 +2006,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1792
2006
|
.1
|
|
1793
2007
|
],
|
|
1794
2008
|
allowance: {
|
|
2009
|
+
go: 6,
|
|
1795
2010
|
goat: 20,
|
|
1796
2011
|
pro: 30
|
|
1797
2012
|
}
|
|
@@ -1804,6 +2019,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1804
2019
|
.16
|
|
1805
2020
|
],
|
|
1806
2021
|
allowance: {
|
|
2022
|
+
go: 6,
|
|
1807
2023
|
goat: 20,
|
|
1808
2024
|
pro: 30
|
|
1809
2025
|
}
|
|
@@ -1816,6 +2032,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1816
2032
|
.19
|
|
1817
2033
|
],
|
|
1818
2034
|
allowance: {
|
|
2035
|
+
go: 9,
|
|
1819
2036
|
goat: 60,
|
|
1820
2037
|
pro: 70
|
|
1821
2038
|
}
|
|
@@ -1828,6 +2045,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1828
2045
|
.38
|
|
1829
2046
|
],
|
|
1830
2047
|
allowance: {
|
|
2048
|
+
go: 6,
|
|
1831
2049
|
goat: 20,
|
|
1832
2050
|
pro: 30
|
|
1833
2051
|
}
|
|
@@ -1840,6 +2058,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1840
2058
|
.3
|
|
1841
2059
|
],
|
|
1842
2060
|
allowance: {
|
|
2061
|
+
go: 8,
|
|
1843
2062
|
goat: 20,
|
|
1844
2063
|
pro: 30
|
|
1845
2064
|
}
|
|
@@ -1852,6 +2071,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1852
2071
|
.006
|
|
1853
2072
|
],
|
|
1854
2073
|
allowance: {
|
|
2074
|
+
go: 10,
|
|
1855
2075
|
goat: 50,
|
|
1856
2076
|
pro: 60
|
|
1857
2077
|
}
|
|
@@ -1869,6 +2089,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1869
2089
|
.16
|
|
1870
2090
|
],
|
|
1871
2091
|
allowance: {
|
|
2092
|
+
go: 6,
|
|
1872
2093
|
goat: 30,
|
|
1873
2094
|
pro: 40
|
|
1874
2095
|
}
|
|
@@ -1886,6 +2107,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1886
2107
|
.4
|
|
1887
2108
|
],
|
|
1888
2109
|
allowance: {
|
|
2110
|
+
go: 6,
|
|
1889
2111
|
goat: 20,
|
|
1890
2112
|
pro: 30
|
|
1891
2113
|
}
|
|
@@ -1898,6 +2120,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1898
2120
|
.0028
|
|
1899
2121
|
],
|
|
1900
2122
|
allowance: {
|
|
2123
|
+
go: 6,
|
|
1901
2124
|
goat: 20,
|
|
1902
2125
|
pro: 30
|
|
1903
2126
|
}
|
|
@@ -1910,6 +2133,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1910
2133
|
.0036
|
|
1911
2134
|
],
|
|
1912
2135
|
allowance: {
|
|
2136
|
+
go: 6,
|
|
1913
2137
|
goat: 20,
|
|
1914
2138
|
pro: 30
|
|
1915
2139
|
}
|
|
@@ -1922,6 +2146,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1922
2146
|
.036
|
|
1923
2147
|
],
|
|
1924
2148
|
allowance: {
|
|
2149
|
+
go: 6,
|
|
1925
2150
|
goat: 10,
|
|
1926
2151
|
pro: 20
|
|
1927
2152
|
}
|
|
@@ -1934,6 +2159,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1934
2159
|
.03
|
|
1935
2160
|
],
|
|
1936
2161
|
allowance: {
|
|
2162
|
+
go: 6,
|
|
1937
2163
|
goat: 20,
|
|
1938
2164
|
pro: 30
|
|
1939
2165
|
}
|
|
@@ -1946,6 +2172,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1946
2172
|
.06
|
|
1947
2173
|
],
|
|
1948
2174
|
allowance: {
|
|
2175
|
+
go: 6,
|
|
1949
2176
|
goat: 20,
|
|
1950
2177
|
pro: 30
|
|
1951
2178
|
}
|
|
@@ -1963,6 +2190,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1963
2190
|
.12
|
|
1964
2191
|
],
|
|
1965
2192
|
allowance: {
|
|
2193
|
+
go: 8,
|
|
1966
2194
|
goat: 47,
|
|
1967
2195
|
pro: 57
|
|
1968
2196
|
}
|
|
@@ -1975,6 +2203,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1975
2203
|
.15
|
|
1976
2204
|
],
|
|
1977
2205
|
allowance: {
|
|
2206
|
+
go: 6,
|
|
1978
2207
|
goat: 20,
|
|
1979
2208
|
pro: 20
|
|
1980
2209
|
}
|
|
@@ -1987,6 +2216,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1987
2216
|
.15
|
|
1988
2217
|
],
|
|
1989
2218
|
allowance: {
|
|
2219
|
+
go: 6,
|
|
1990
2220
|
goat: 20,
|
|
1991
2221
|
pro: 30
|
|
1992
2222
|
}
|
|
@@ -1999,6 +2229,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
1999
2229
|
.002
|
|
2000
2230
|
],
|
|
2001
2231
|
allowance: {
|
|
2232
|
+
go: 6,
|
|
2002
2233
|
goat: 20,
|
|
2003
2234
|
pro: 30
|
|
2004
2235
|
}
|
|
@@ -2011,6 +2242,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
2011
2242
|
.15
|
|
2012
2243
|
],
|
|
2013
2244
|
allowance: {
|
|
2245
|
+
go: 6,
|
|
2014
2246
|
goat: 20,
|
|
2015
2247
|
pro: 30
|
|
2016
2248
|
}
|
|
@@ -2023,6 +2255,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
2023
2255
|
.002
|
|
2024
2256
|
],
|
|
2025
2257
|
allowance: {
|
|
2258
|
+
go: 6,
|
|
2026
2259
|
goat: 20,
|
|
2027
2260
|
pro: 30
|
|
2028
2261
|
}
|
|
@@ -2035,6 +2268,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
2035
2268
|
.12
|
|
2036
2269
|
],
|
|
2037
2270
|
allowance: {
|
|
2271
|
+
go: 6,
|
|
2038
2272
|
goat: 20,
|
|
2039
2273
|
pro: 30
|
|
2040
2274
|
}
|
|
@@ -2048,6 +2282,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
2048
2282
|
1.63
|
|
2049
2283
|
],
|
|
2050
2284
|
allowance: {
|
|
2285
|
+
go: 6,
|
|
2051
2286
|
goat: 20,
|
|
2052
2287
|
pro: 30
|
|
2053
2288
|
}
|
|
@@ -2060,6 +2295,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
2060
2295
|
.1
|
|
2061
2296
|
],
|
|
2062
2297
|
allowance: {
|
|
2298
|
+
go: 10,
|
|
2063
2299
|
goat: 33,
|
|
2064
2300
|
pro: 43
|
|
2065
2301
|
},
|
|
@@ -2085,6 +2321,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
2085
2321
|
.038
|
|
2086
2322
|
],
|
|
2087
2323
|
allowance: {
|
|
2324
|
+
go: 6,
|
|
2088
2325
|
goat: 20,
|
|
2089
2326
|
pro: 30
|
|
2090
2327
|
},
|
|
@@ -2124,6 +2361,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
2124
2361
|
3.13
|
|
2125
2362
|
],
|
|
2126
2363
|
allowance: {
|
|
2364
|
+
go: 10,
|
|
2127
2365
|
goat: 33,
|
|
2128
2366
|
pro: 43
|
|
2129
2367
|
}
|
|
@@ -2137,6 +2375,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
2137
2375
|
.5
|
|
2138
2376
|
],
|
|
2139
2377
|
allowance: {
|
|
2378
|
+
go: 10,
|
|
2140
2379
|
goat: 33,
|
|
2141
2380
|
pro: 43
|
|
2142
2381
|
},
|
|
@@ -2163,6 +2402,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
2163
2402
|
.04
|
|
2164
2403
|
],
|
|
2165
2404
|
allowance: {
|
|
2405
|
+
go: 10,
|
|
2166
2406
|
goat: 70,
|
|
2167
2407
|
pro: 80
|
|
2168
2408
|
}
|
|
@@ -2175,6 +2415,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
2175
2415
|
.016
|
|
2176
2416
|
],
|
|
2177
2417
|
allowance: {
|
|
2418
|
+
go: 6,
|
|
2178
2419
|
goat: 20,
|
|
2179
2420
|
pro: 30
|
|
2180
2421
|
}
|
|
@@ -2188,6 +2429,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
2188
2429
|
2.5
|
|
2189
2430
|
],
|
|
2190
2431
|
allowance: {
|
|
2432
|
+
go: 7,
|
|
2191
2433
|
goat: 20,
|
|
2192
2434
|
pro: 30
|
|
2193
2435
|
}
|
|
@@ -2200,6 +2442,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
2200
2442
|
.25
|
|
2201
2443
|
],
|
|
2202
2444
|
allowance: {
|
|
2445
|
+
go: 7,
|
|
2203
2446
|
goat: 20,
|
|
2204
2447
|
pro: 30
|
|
2205
2448
|
}
|
|
@@ -2212,6 +2455,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
2212
2455
|
.016
|
|
2213
2456
|
],
|
|
2214
2457
|
allowance: {
|
|
2458
|
+
go: 6,
|
|
2215
2459
|
goat: 20,
|
|
2216
2460
|
pro: 30
|
|
2217
2461
|
}
|
|
@@ -2224,6 +2468,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
2224
2468
|
.02
|
|
2225
2469
|
],
|
|
2226
2470
|
allowance: {
|
|
2471
|
+
go: 6,
|
|
2227
2472
|
goat: 20,
|
|
2228
2473
|
pro: 30
|
|
2229
2474
|
}
|
|
@@ -2236,6 +2481,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
2236
2481
|
.04
|
|
2237
2482
|
],
|
|
2238
2483
|
allowance: {
|
|
2484
|
+
go: 6,
|
|
2239
2485
|
goat: 20,
|
|
2240
2486
|
pro: 30
|
|
2241
2487
|
}
|
|
@@ -2248,6 +2494,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
2248
2494
|
.05
|
|
2249
2495
|
],
|
|
2250
2496
|
allowance: {
|
|
2497
|
+
go: 6,
|
|
2251
2498
|
goat: 20,
|
|
2252
2499
|
pro: 30
|
|
2253
2500
|
}
|
|
@@ -2260,6 +2507,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
2260
2507
|
.035
|
|
2261
2508
|
],
|
|
2262
2509
|
allowance: {
|
|
2510
|
+
go: 10,
|
|
2263
2511
|
goat: 70,
|
|
2264
2512
|
pro: 80
|
|
2265
2513
|
}
|
|
@@ -2272,6 +2520,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
2272
2520
|
.042
|
|
2273
2521
|
],
|
|
2274
2522
|
allowance: {
|
|
2523
|
+
go: 6,
|
|
2275
2524
|
goat: 20,
|
|
2276
2525
|
pro: 30
|
|
2277
2526
|
}
|
|
@@ -2284,6 +2533,7 @@ const MODEL_PRICE_ROWS = [
|
|
|
2284
2533
|
0
|
|
2285
2534
|
],
|
|
2286
2535
|
allowance: {
|
|
2536
|
+
go: 6,
|
|
2287
2537
|
goat: 20,
|
|
2288
2538
|
pro: 30
|
|
2289
2539
|
}
|
|
@@ -2597,6 +2847,37 @@ function installCostProjection(ctx) {
|
|
|
2597
2847
|
*/
|
|
2598
2848
|
const IDENTITY_ENCODING_HEADER = { "accept-encoding": "identity" };
|
|
2599
2849
|
//#endregion
|
|
2850
|
+
//#region src/timeout-limits.ts
|
|
2851
|
+
const MAX_TIMEOUT_SECONDS = 3600;
|
|
2852
|
+
//#endregion
|
|
2853
|
+
//#region src/model-visibility.ts
|
|
2854
|
+
/** 网页、终端与模型目录共用的选择规则;显式开关优先于白名单。 */
|
|
2855
|
+
function modelIsVisible(id, allowlist, overrides) {
|
|
2856
|
+
const flag = overrides !== null && typeof overrides === "object" && !Array.isArray(overrides) ? overrides[id] : void 0;
|
|
2857
|
+
return typeof flag === "boolean" ? flag : allowlist.length === 0 || allowlist.includes(id);
|
|
2858
|
+
}
|
|
2859
|
+
const THROTTLED_CODE = "THROTTLED";
|
|
2860
|
+
const boundedCodes = /* @__PURE__ */ new Set([
|
|
2861
|
+
"EMPTY_RESPONSE",
|
|
2862
|
+
"SERVER",
|
|
2863
|
+
"TIMEOUT",
|
|
2864
|
+
THROTTLED_CODE
|
|
2865
|
+
]);
|
|
2866
|
+
const counts$1 = /* @__PURE__ */ new WeakMap();
|
|
2867
|
+
function absorbTransientFailure(agent, code) {
|
|
2868
|
+
if (!boundedCodes.has(code)) return "ignored";
|
|
2869
|
+
const used = counts$1.get(agent) ?? 0;
|
|
2870
|
+
if (used >= 3) return "exhausted";
|
|
2871
|
+
counts$1.set(agent, used + 1);
|
|
2872
|
+
return "retry";
|
|
2873
|
+
}
|
|
2874
|
+
function resetTransientFailures(agent) {
|
|
2875
|
+
counts$1.delete(agent);
|
|
2876
|
+
}
|
|
2877
|
+
function transientBudgetMessage(code, message) {
|
|
2878
|
+
return `Command Code 请求在 3 次自动重试后仍失败,已停止重试(${code})。请稍后重新发送;若反复出现,请检查服务状态或请求耗时诊断。 原因:${message}`;
|
|
2879
|
+
}
|
|
2880
|
+
//#endregion
|
|
2600
2881
|
//#region src/accounts.ts
|
|
2601
2882
|
/**
|
|
2602
2883
|
* Multi-account pool for the Command Code provider (host side).
|
|
@@ -2760,28 +3041,10 @@ var CommandCodeAccountPool = class {
|
|
|
2760
3041
|
if (routed !== void 0) return this.pick(routed);
|
|
2761
3042
|
const chosen = selectActiveAccount(available, preferred);
|
|
2762
3043
|
if (chosen !== void 0) {
|
|
2763
|
-
const
|
|
2764
|
-
|
|
2765
|
-
const revived = await this.probeExplicit(explicit);
|
|
2766
|
-
if (revived !== void 0) return this.pick(revived);
|
|
2767
|
-
}
|
|
2768
|
-
return this.pick(chosen);
|
|
3044
|
+
const revived = await this.reviveExplicit(available, options?.model ?? "", preferred, chosen);
|
|
3045
|
+
return this.pick(revived ?? chosen);
|
|
2769
3046
|
}
|
|
2770
|
-
await
|
|
2771
|
-
if (account.state?.kind === "disabled") return;
|
|
2772
|
-
let probe;
|
|
2773
|
-
try {
|
|
2774
|
-
probe = await this.deps.probeWindow(account.key);
|
|
2775
|
-
} catch {
|
|
2776
|
-
return;
|
|
2777
|
-
}
|
|
2778
|
-
if (probe === void 0) return;
|
|
2779
|
-
if (!probe.exceeded) {
|
|
2780
|
-
this.states.delete(account.key);
|
|
2781
|
-
return;
|
|
2782
|
-
}
|
|
2783
|
-
this.stampWindowMark(account, probe);
|
|
2784
|
-
}));
|
|
3047
|
+
await this.probeAllMarked(available);
|
|
2785
3048
|
const latestAccounts = await this.resolvedAccounts();
|
|
2786
3049
|
const revived = selectActiveAccount(tried === void 0 || tried.length === 0 ? latestAccounts : latestAccounts.filter((account) => !tried.includes(account.key)), preferred);
|
|
2787
3050
|
if (revived !== void 0) return this.pick(revived);
|
|
@@ -2789,6 +3052,34 @@ var CommandCodeAccountPool = class {
|
|
|
2789
3052
|
throw allAccountsUnusable(latestAccounts.length > 0 ? latestAccounts : accounts);
|
|
2790
3053
|
}
|
|
2791
3054
|
/**
|
|
3055
|
+
* Which account is serving right now, for display (the settings page's
|
|
3056
|
+
* account card, the sidebar usage stats) — without consuming a request's
|
|
3057
|
+
* `tried` budget and without throwing when nothing can serve.
|
|
3058
|
+
*
|
|
3059
|
+
* Reads and self-heals exactly like {@link resolveKey} would: an `unknown`
|
|
3060
|
+
* mark on the pinned account is re-probed (throttled the same way, sharing
|
|
3061
|
+
* the same per-key timestamp), and a pool where nothing currently resolves
|
|
3062
|
+
* gets one batch probe pass too. Without this, the badge read a possibly
|
|
3063
|
+
* stale mark forever — nothing but an actual generate request ever cleared
|
|
3064
|
+
* an `unknown` mark, so a page left open with no chat activity kept
|
|
3065
|
+
* showing "switched to the fallback account" long after a real request
|
|
3066
|
+
* would have quietly recovered (issue #51's follow-up report: the account
|
|
3067
|
+
* card and the sidebar stats both stuck on the wrong account).
|
|
3068
|
+
*
|
|
3069
|
+
* No `model` parameter: the badge describes the pool as a whole, not one
|
|
3070
|
+
* request, so model-routing rules are deliberately not consulted here —
|
|
3071
|
+
* matching what the direct `selectActiveAccount` call this replaces did.
|
|
3072
|
+
*/
|
|
3073
|
+
async activeAccount() {
|
|
3074
|
+
const accounts = await this.resolvedAccounts();
|
|
3075
|
+
if (accounts.length === 0) return void 0;
|
|
3076
|
+
const preferred = this.deps.preferredId?.();
|
|
3077
|
+
const chosen = selectActiveAccount(accounts, preferred);
|
|
3078
|
+
if (chosen !== void 0) return await this.reviveExplicit(accounts, "", preferred, chosen) ?? chosen;
|
|
3079
|
+
await this.probeAllMarked(accounts);
|
|
3080
|
+
return selectActiveAccount(await this.resolvedAccounts(), preferred);
|
|
3081
|
+
}
|
|
3082
|
+
/**
|
|
2792
3083
|
* Record a rejection against one key. A `throttled` rejection (the plain 429
|
|
2793
3084
|
* that named no window) marks the key with the `throttle` cause, leaving
|
|
2794
3085
|
* rotation exactly like a window mark but keeping the pool's own diagnosis at
|
|
@@ -2938,6 +3229,46 @@ var CommandCodeAccountPool = class {
|
|
|
2938
3229
|
until: 0
|
|
2939
3230
|
});
|
|
2940
3231
|
}
|
|
3232
|
+
/**
|
|
3233
|
+
* The explicit-account fallback re-check: when `chosen` (what rotation
|
|
3234
|
+
* would currently serve) is not the account the user actually asked for,
|
|
3235
|
+
* re-probe that explicit account's mark before accepting the fallback —
|
|
3236
|
+
* so a rate-limit mark never demotes the user's own selection for good
|
|
3237
|
+
* (issue #51). Returns the revived account, or `undefined` when there is
|
|
3238
|
+
* nothing to revive (explicit account already serving, not due for a
|
|
3239
|
+
* probe, or the probe found it still exceeded). Shared by {@link resolveKey}
|
|
3240
|
+
* (the request path) and {@link activeAccount} (the display path), so both
|
|
3241
|
+
* agree on what "currently usable" means instead of drifting apart.
|
|
3242
|
+
*/
|
|
3243
|
+
async reviveExplicit(accounts, model, preferred, chosen) {
|
|
3244
|
+
const explicit = this.explicitAccount(accounts, model, preferred);
|
|
3245
|
+
if (explicit === void 0 || explicit.key === chosen.key || !this.canProbeExplicit(explicit)) return;
|
|
3246
|
+
return this.probeExplicit(explicit);
|
|
3247
|
+
}
|
|
3248
|
+
/**
|
|
3249
|
+
* Probe every marked account's real usage window when nothing currently
|
|
3250
|
+
* resolves. Disabled (401) keys are not probed — an invalid key stays
|
|
3251
|
+
* invalid. A throwing probe counts as "unknown" (like a failed one): it
|
|
3252
|
+
* must not turn the all-exhausted path into a raw rejection instead of
|
|
3253
|
+
* RATE_LIMIT. Shared by {@link resolveKey} and {@link activeAccount}.
|
|
3254
|
+
*/
|
|
3255
|
+
async probeAllMarked(accounts) {
|
|
3256
|
+
await Promise.all(accounts.map(async (account) => {
|
|
3257
|
+
if (account.state?.kind === "disabled") return;
|
|
3258
|
+
let probe;
|
|
3259
|
+
try {
|
|
3260
|
+
probe = await this.deps.probeWindow(account.key);
|
|
3261
|
+
} catch {
|
|
3262
|
+
return;
|
|
3263
|
+
}
|
|
3264
|
+
if (probe === void 0) return;
|
|
3265
|
+
if (!probe.exceeded) {
|
|
3266
|
+
this.states.delete(account.key);
|
|
3267
|
+
return;
|
|
3268
|
+
}
|
|
3269
|
+
this.stampWindowMark(account, probe);
|
|
3270
|
+
}));
|
|
3271
|
+
}
|
|
2941
3272
|
/** Hand out the chosen account's key. */
|
|
2942
3273
|
pick(account) {
|
|
2943
3274
|
return {
|
|
@@ -2973,7 +3304,7 @@ var CommandCodeAccountPool = class {
|
|
|
2973
3304
|
function allAccountsUnusable(accounts) {
|
|
2974
3305
|
if (accounts.filter((account) => account.state?.kind === "disabled").length === accounts.length) return new LlmError(`llm-commandcode: every configured Command Code account (${accounts.length}) was rejected with 401 — check the stored API keys (Models page / settings) or the auth file;已配置的 ${accounts.length} 个 Command Code 账户密钥均被拒绝(401)——请在设置页检查存储的 API 密钥,或重新运行 command-code login`, "INVALID_CREDENTIAL");
|
|
2975
3306
|
const windowMarked = accounts.map((account) => account.state).filter((state) => state !== void 0).filter((state) => state.cause === "window");
|
|
2976
|
-
if (windowMarked.length === 0) return new LlmError(`llm-commandcode: all ${accounts.length} Command Code account(s) are rate limited (429) — the provider did not report an exhausted usage window; retrying;全部 ${accounts.length} 个 Command Code 账户被限流(429)——服务商未报告用量窗口用尽,正在重试`,
|
|
3307
|
+
if (windowMarked.length === 0) return new LlmError(`llm-commandcode: all ${accounts.length} Command Code account(s) are rate limited (429) — the provider did not report an exhausted usage window; retrying;全部 ${accounts.length} 个 Command Code 账户被限流(429)——服务商未报告用量窗口用尽,正在重试`, THROTTLED_CODE);
|
|
2977
3308
|
const resets = windowMarked.filter((state) => state.kind === "cooldown" && state.until > 0).map((state) => state.until);
|
|
2978
3309
|
const earliest = resets.length > 0 ? Math.min(...resets) : 0;
|
|
2979
3310
|
const wait = earliest > 0 ? Math.max(1e3, earliest - Date.now()) : 0;
|
|
@@ -3394,6 +3725,95 @@ function openStreamTrace(scope, options = {}) {
|
|
|
3394
3725
|
};
|
|
3395
3726
|
}
|
|
3396
3727
|
//#endregion
|
|
3728
|
+
//#region src/request-timing.ts
|
|
3729
|
+
/** 请求耗时只记录阶段、计数和状态;不记录消息、凭据、请求头或错误正文。 */
|
|
3730
|
+
const REQUEST_TIMING_ENV = "DSH_COMMANDCODE_TIMING";
|
|
3731
|
+
/** 每次生成独立计时;可注入时钟验证顺序,不依赖真实等待。 */
|
|
3732
|
+
var RequestTiming = class {
|
|
3733
|
+
sink;
|
|
3734
|
+
now;
|
|
3735
|
+
started;
|
|
3736
|
+
summary;
|
|
3737
|
+
constructor(model, sink, now = () => performance.now()) {
|
|
3738
|
+
this.sink = sink;
|
|
3739
|
+
this.now = now;
|
|
3740
|
+
this.started = now();
|
|
3741
|
+
this.summary = {
|
|
3742
|
+
version: 1,
|
|
3743
|
+
startedAt: Date.now(),
|
|
3744
|
+
model,
|
|
3745
|
+
outcome: "cancelled",
|
|
3746
|
+
totalMs: 0,
|
|
3747
|
+
phasesMs: {
|
|
3748
|
+
credentials: 0,
|
|
3749
|
+
images: 0,
|
|
3750
|
+
body: 0,
|
|
3751
|
+
serialization: 0,
|
|
3752
|
+
headers: 0
|
|
3753
|
+
},
|
|
3754
|
+
attempts: []
|
|
3755
|
+
};
|
|
3756
|
+
}
|
|
3757
|
+
/** 返回结束函数,确保成功和异常都计入同一阶段。阶段可嵌套,不能直接相加。 */
|
|
3758
|
+
phase(name) {
|
|
3759
|
+
if (!this.sink) return () => {};
|
|
3760
|
+
const start = this.now();
|
|
3761
|
+
return () => {
|
|
3762
|
+
this.summary.phasesMs[name] += this.now() - start;
|
|
3763
|
+
};
|
|
3764
|
+
}
|
|
3765
|
+
attempt(protocol, bodyBytes) {
|
|
3766
|
+
if (!this.sink) return () => {};
|
|
3767
|
+
const start = this.now();
|
|
3768
|
+
const attempt = {
|
|
3769
|
+
protocol,
|
|
3770
|
+
bodyBytes,
|
|
3771
|
+
headersMs: 0
|
|
3772
|
+
};
|
|
3773
|
+
this.summary.attempts.push(attempt);
|
|
3774
|
+
return (status) => {
|
|
3775
|
+
Object.assign(attempt, {
|
|
3776
|
+
headersMs: this.now() - start,
|
|
3777
|
+
...status === void 0 ? {} : { status }
|
|
3778
|
+
});
|
|
3779
|
+
this.summary.phasesMs.headers += attempt.headersMs;
|
|
3780
|
+
};
|
|
3781
|
+
}
|
|
3782
|
+
first(kind) {
|
|
3783
|
+
if (this.sink && this.summary[kind] === void 0) this.summary[kind] = this.now() - this.started;
|
|
3784
|
+
}
|
|
3785
|
+
async close(outcome, errorCode) {
|
|
3786
|
+
if (!this.sink) return;
|
|
3787
|
+
this.summary.outcome = outcome;
|
|
3788
|
+
this.summary.totalMs = this.now() - this.started;
|
|
3789
|
+
if (errorCode !== void 0) this.summary.errorCode = errorCode;
|
|
3790
|
+
try {
|
|
3791
|
+
await this.sink(this.summary);
|
|
3792
|
+
} catch {}
|
|
3793
|
+
}
|
|
3794
|
+
};
|
|
3795
|
+
/** 与原始流诊断独立开关,便于仅收集数值耗时。 */
|
|
3796
|
+
function openRequestTiming(model, sink) {
|
|
3797
|
+
if (sink) return new RequestTiming(model, sink);
|
|
3798
|
+
const raw = process.env[REQUEST_TIMING_ENV]?.trim();
|
|
3799
|
+
if (!raw || [
|
|
3800
|
+
"0",
|
|
3801
|
+
"false",
|
|
3802
|
+
"no",
|
|
3803
|
+
"off"
|
|
3804
|
+
].includes(raw.toLowerCase())) return new RequestTiming(model);
|
|
3805
|
+
const path = [
|
|
3806
|
+
"1",
|
|
3807
|
+
"true",
|
|
3808
|
+
"yes",
|
|
3809
|
+
"on"
|
|
3810
|
+
].includes(raw.toLowerCase()) ? join(tmpdir(), "dsh-commandcode-timing.jsonl") : raw;
|
|
3811
|
+
return new RequestTiming(model, async (summary) => {
|
|
3812
|
+
await mkdir(dirname(path), { recursive: true });
|
|
3813
|
+
await appendFile(path, `${JSON.stringify(summary)}\n`, { mode: 384 });
|
|
3814
|
+
});
|
|
3815
|
+
}
|
|
3816
|
+
//#endregion
|
|
3397
3817
|
//#region src/adapter.ts
|
|
3398
3818
|
/**
|
|
3399
3819
|
* DeepSeek Harness LLM adapter for the Command Code Provider API.
|
|
@@ -3402,21 +3822,113 @@ function openStreamTrace(scope, options = {}) {
|
|
|
3402
3822
|
* community-maintained integration; you need your own Command Code account
|
|
3403
3823
|
* and API key or subscription, and Command Code's terms apply.
|
|
3404
3824
|
*
|
|
3405
|
-
* Owns the
|
|
3825
|
+
* Owns the three chat transports (`/alpha/generate`, `/provider/v1/chat/completions`,
|
|
3826
|
+
* `/provider/v1/messages`),
|
|
3406
3827
|
* message conversion, SSE/JSONL parsing, the catalog + its on-disk cache, and
|
|
3407
3828
|
* the pre-stream account rotation loop. The wire protocol is
|
|
3408
|
-
* reverse-engineered (command-code@1.28.4, re-verified through 1.
|
|
3409
|
-
*
|
|
3829
|
+
* reverse-engineered (command-code@1.28.4, re-verified through 1.73.0);
|
|
3830
|
+
* docs/agent-reference/adapter-protocol.md holds the full protocol record.
|
|
3410
3831
|
*
|
|
3411
3832
|
* The adapter is deliberately free of cordis/schemastery: it receives a
|
|
3412
3833
|
* per-request options thunk and an API-key resolver from the plugin entry
|
|
3413
3834
|
* (src/index.ts), so a settings change reaches the very next request.
|
|
3414
3835
|
*/
|
|
3836
|
+
/**
|
|
3837
|
+
* Output ceilings the endpoint has named in a live rejection, per model.
|
|
3838
|
+
*
|
|
3839
|
+
* The snapshot in ./capabilities.ts carries the vendor-published ceiling for
|
|
3840
|
+
* 74 of the 84 catalog models (see scripts/sync-output-limits.mjs). The other
|
|
3841
|
+
* 10 are models no vendor publishes yet, and a gateway is free to sit below
|
|
3842
|
+
* the vendor figure even where one exists — so this map is the correction
|
|
3843
|
+
* channel, not a fallback of last resort: the endpoint states the model's own
|
|
3844
|
+
* ceiling in its rejection, so one wasted round trip is enough to know it for
|
|
3845
|
+
* the rest of the process.
|
|
3846
|
+
*
|
|
3847
|
+
* Shared by adapters for the same gateway, but not across apiBase values: a
|
|
3848
|
+
* proxy can enforce a smaller ceiling for the same model id. Only ever tightens
|
|
3849
|
+
* within that gateway, so a stale catalog row cannot undo a live refusal.
|
|
3850
|
+
*/
|
|
3851
|
+
const learnedOutputLimits = /* @__PURE__ */ new Map();
|
|
3852
|
+
/**
|
|
3853
|
+
* Output ceilings the CATALOG published, per model.
|
|
3854
|
+
*
|
|
3855
|
+
* `/provider/v1/models` carries `max_output_tokens` in its schema and the
|
|
3856
|
+
* official pi provider already prefers it, but the endpoint does not send it
|
|
3857
|
+
* yet — so this is empty today and fills in on its own the day it does. It sits
|
|
3858
|
+
* above the models.dev snapshot and below what a live rejection proved, because
|
|
3859
|
+
* that ordering is also the order of trust: the gateway knows its own ceiling,
|
|
3860
|
+
* a rejection proves it, and models.dev is a third party describing the same
|
|
3861
|
+
* model. Restored from the on-disk catalog cache so a published ceiling
|
|
3862
|
+
* survives a restart without waiting for the next catalog fetch.
|
|
3863
|
+
*/
|
|
3864
|
+
const publishedOutputLimits = /* @__PURE__ */ new Map();
|
|
3865
|
+
/** Keep model facts from one gateway from constraining another gateway. */
|
|
3866
|
+
function outputLimitsFor(store, apiBase) {
|
|
3867
|
+
let limits = store.get(apiBase);
|
|
3868
|
+
if (limits === void 0) {
|
|
3869
|
+
limits = /* @__PURE__ */ new Map();
|
|
3870
|
+
store.set(apiBase, limits);
|
|
3871
|
+
}
|
|
3872
|
+
return limits;
|
|
3873
|
+
}
|
|
3874
|
+
/**
|
|
3875
|
+
* The output ceiling this gateway route sends for `modelId`.
|
|
3876
|
+
*
|
|
3877
|
+
* `/provider/v1/models` publishes `context_length` and `supported_endpoints`
|
|
3878
|
+
* but no output cap today, while each endpoint refuses a larger `max_tokens` per
|
|
3879
|
+
* model — the Messages endpoint names its own in the rejection (`max_tokens:
|
|
3880
|
+
* 200000 > 128000, which is the maximum allowed number of output tokens for
|
|
3881
|
+
* claude-sonnet-5-5`). What the endpoint has already named wins, then the
|
|
3882
|
+
* catalog's own figure once it publishes one, then the snapshot, then — for
|
|
3883
|
+
* the Messages-route models only — the conservative value, because the ceilings
|
|
3884
|
+
* actually observed are below the global default and assuming otherwise would
|
|
3885
|
+
* fail every request.
|
|
3886
|
+
*/
|
|
3887
|
+
function modelOutputTokenLimit(apiBase, modelId) {
|
|
3888
|
+
const learned = outputLimitsFor(learnedOutputLimits, apiBase).get(modelId);
|
|
3889
|
+
const known = outputLimitsFor(publishedOutputLimits, apiBase).get(modelId) ?? MODEL_OUTPUT_TOKEN_LIMITS.get(modelId);
|
|
3890
|
+
if (learned !== void 0 && (known === void 0 || learned < known)) return learned;
|
|
3891
|
+
if (known !== void 0) return known;
|
|
3892
|
+
return requiresMessagesEndpoint(modelId) ? DEFAULT_MESSAGES_MAX_TOKENS : DEFAULT_MAX_OUTPUT_TOKENS;
|
|
3893
|
+
}
|
|
3894
|
+
/**
|
|
3895
|
+
* Record a ceiling the endpoint just stated, if it is one this route can act
|
|
3896
|
+
* on: a real number, and one below what the next request would otherwise send.
|
|
3897
|
+
* A ceiling at or above the current budget describes a request that was not
|
|
3898
|
+
* refused for being too large, and acting on it would be a guess.
|
|
3899
|
+
*/
|
|
3900
|
+
function noteOutputCeiling(apiBase, modelId, ceiling) {
|
|
3901
|
+
if (!Number.isSafeInteger(ceiling) || ceiling <= 0) return false;
|
|
3902
|
+
if (ceiling >= modelOutputTokenLimit(apiBase, modelId)) return false;
|
|
3903
|
+
outputLimitsFor(learnedOutputLimits, apiBase).set(modelId, ceiling);
|
|
3904
|
+
return true;
|
|
3905
|
+
}
|
|
3906
|
+
/**
|
|
3907
|
+
* The ceiling to retry this turn with, when `error` is a request-side output
|
|
3908
|
+
* refusal the route can act on — else undefined, meaning surface it.
|
|
3909
|
+
*
|
|
3910
|
+
* Read from the message, so it covers both shapes the same refusal arrives in:
|
|
3911
|
+
* an HTTP 400 (`PROVIDER_HTTP_ERROR`) and an in-stream error frame
|
|
3912
|
+
* (`PROVIDER_STREAM_ERROR`). Both carry the endpoint's wording, and both are
|
|
3913
|
+
* already classified as output refusals rather than context overflows by the
|
|
3914
|
+
* time they reach here.
|
|
3915
|
+
*
|
|
3916
|
+
* Recording is what makes the retry pay off: the ceiling is kept for this
|
|
3917
|
+
* gateway and model, so a refused turn leaves its next request correct.
|
|
3918
|
+
*/
|
|
3919
|
+
function outputCeilingRefusal(apiBase, modelId, error) {
|
|
3920
|
+
if (!(error instanceof LlmError)) return void 0;
|
|
3921
|
+
const detail = error.message;
|
|
3922
|
+
if (!OUTPUT_LIMIT_REJECTION.test(detail)) return void 0;
|
|
3923
|
+
const stated = parseOutputCeiling(detail);
|
|
3924
|
+
if (stated === void 0) return void 0;
|
|
3925
|
+
return noteOutputCeiling(apiBase, modelId, stated) ? stated : void 0;
|
|
3926
|
+
}
|
|
3415
3927
|
/** Keep the two user-facing languages and error metadata together. */
|
|
3416
3928
|
function bilingual(code, en, zh, options) {
|
|
3417
3929
|
return new LlmError(`${en};${zh}`, code, options);
|
|
3418
3930
|
}
|
|
3419
|
-
const COMMAND_CODE_CLI_VERSION = "1.
|
|
3931
|
+
const COMMAND_CODE_CLI_VERSION = "1.73.0";
|
|
3420
3932
|
const DEFAULT_API_BASE = "https://api.commandcode.ai";
|
|
3421
3933
|
/**
|
|
3422
3934
|
* The output budget this bundle asks for, and the ceiling it will never exceed.
|
|
@@ -3434,9 +3946,16 @@ const DEFAULT_API_BASE = "https://api.commandcode.ai";
|
|
|
3434
3946
|
* (a 64 000 request answered headers in 1.28 s against 1.57 s for 131 072) but
|
|
3435
3947
|
* it did cap what a model could ever say in one turn.
|
|
3436
3948
|
*
|
|
3437
|
-
*
|
|
3438
|
-
* from
|
|
3439
|
-
*
|
|
3949
|
+
* No client-side clamp is applied on top of this: an earlier version pre-shrank
|
|
3950
|
+
* `max_tokens` from an estimate of the prompt already sent (`requestContextBudget()`,
|
|
3951
|
+
* issue #67), but that estimate over-corrected on ordinary long conversations —
|
|
3952
|
+
* exactly the "output gets cut short as the conversation grows" symptom issue #74
|
|
3953
|
+
* reported — while a live A/B probe (2026-09-30, `stealth/space-bunny-alpha`,
|
|
3954
|
+
* ~19.6% over its 1,000,000-token window with no client-side shrink) showed the
|
|
3955
|
+
* endpoint answers a genuine overflow with an unambiguous `400 … exceeds the
|
|
3956
|
+
* model's context window`, which `isContextOverflowDetail()` below already
|
|
3957
|
+
* classifies as `CONTEXT_WINDOW_EXCEEDED` independently of this constant. See
|
|
3958
|
+
* [决策记录](../docs/决策记录.md).
|
|
3440
3959
|
*/
|
|
3441
3960
|
const DEFAULT_GENERATE_MAX_TOKENS = 131072;
|
|
3442
3961
|
/**
|
|
@@ -3449,9 +3968,10 @@ const DEFAULT_GENERATE_MAX_TOKENS = 131072;
|
|
|
3449
3968
|
* `Math.min(contextLength, this)` before the ceiling above is ever consulted.
|
|
3450
3969
|
*/
|
|
3451
3970
|
const DEFAULT_MAX_OUTPUT_TOKENS = 131072;
|
|
3452
|
-
/** Preserve a small usable answer budget when the heuristic overshoots. */
|
|
3453
|
-
const MIN_CONTEXT_OUTPUT_TOKENS = 1024;
|
|
3454
3971
|
const MODELS_TIMEOUT_MS = 1e4;
|
|
3972
|
+
/** 目录短期复用;刷新失败只短暂退避,避免反复阻塞模型选择器。 */
|
|
3973
|
+
const MODEL_CATALOG_TTL_MS = 3e5;
|
|
3974
|
+
const MODEL_CATALOG_RETRY_MS = 1e4;
|
|
3455
3975
|
/** How long the picker's plan-filter billing facts stay cached before refetching. */
|
|
3456
3976
|
const BILLING_ACCESS_TTL_MS = 3e5;
|
|
3457
3977
|
/** The entry-plan tier weight (`individual-go` in KNOWN_SUBSCRIPTION_PLANS). */
|
|
@@ -3489,7 +4009,49 @@ const ACTIVE_SUBSCRIPTION_STATUSES = /* @__PURE__ */ new Set([
|
|
|
3489
4009
|
const DEFAULT_REQUEST_TIMEOUT_MS = 3e5;
|
|
3490
4010
|
/** Stream idle timeout: a generation that stalls this long is a dead connection. */
|
|
3491
4011
|
const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 3e5;
|
|
3492
|
-
|
|
4012
|
+
/** Version 3 binds cached catalog facts to their gateway; older files lack a safe source. */
|
|
4013
|
+
const MODEL_CACHE_VERSION = 3;
|
|
4014
|
+
/**
|
|
4015
|
+
* A lone UTF-16 surrogate half: a high surrogate not followed by a low one, or
|
|
4016
|
+
* a low surrogate not preceded by a high one. Such a half is not a character —
|
|
4017
|
+
* it only arises from a truncated slice, a lossy decode, or a lone `\uD83D`
|
|
4018
|
+
* escape — and it survives `JSON.stringify` as an escape (`"\ud83d"`) rather
|
|
4019
|
+
* than failing locally, so the request leaves the process carrying a string no
|
|
4020
|
+
* UTF-8 consumer can round-trip. Providers running strict decoders reject it.
|
|
4021
|
+
*
|
|
4022
|
+
* This is the same expression `@earendil-works/pi-ai` applies to every outgoing
|
|
4023
|
+
* string on this endpoint. Valid astral characters (emoji and anything else
|
|
4024
|
+
* outside the BMP) are PAIRED surrogates and pass through untouched.
|
|
4025
|
+
*/
|
|
4026
|
+
const LONE_SURROGATE_PATTERN = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
|
|
4027
|
+
/**
|
|
4028
|
+
* A cheap pre-test for "this string contains any surrogate code unit at all",
|
|
4029
|
+
* paired or not. The exact pattern above needs lookaround, which V8 cannot
|
|
4030
|
+
* optimize into a fast scan; this one is a plain range test. Real prompts are
|
|
4031
|
+
* overwhelmingly ASCII-plus-CJK with no surrogates, so the common case pays one
|
|
4032
|
+
* linear scan instead of the lookaround pass. Measured on a 412 KB request
|
|
4033
|
+
* body: 1.40 ms with the exact pattern alone against 0.77 ms with this gate,
|
|
4034
|
+
* where a bare `JSON.stringify` is 0.59 ms.
|
|
4035
|
+
*/
|
|
4036
|
+
const ANY_SURROGATE_PATTERN = /[\uD800-\uDFFF]/;
|
|
4037
|
+
/**
|
|
4038
|
+
* Drop lone surrogate halves from one outgoing string.
|
|
4039
|
+
*
|
|
4040
|
+
* Applied at the single point a request body is serialized, so every field of
|
|
4041
|
+
* every transport is covered — system text, message content, tool schemas and
|
|
4042
|
+
* tool results alike — instead of enumerating the fields that can carry text
|
|
4043
|
+
* and missing one. The value is returned unchanged whenever it holds no
|
|
4044
|
+
* surrogate at all, which is the ordinary case.
|
|
4045
|
+
*
|
|
4046
|
+
* The `test()` call deliberately uses the flagless {@link ANY_SURROGATE_PATTERN}:
|
|
4047
|
+
* a global regex is stateful, so testing with one would advance `lastIndex` and
|
|
4048
|
+
* make consecutive calls on the same string alternate between true and false.
|
|
4049
|
+
* `replace()` resets `lastIndex` itself, so the global pattern is safe there.
|
|
4050
|
+
*/
|
|
4051
|
+
function sanitizeSurrogates(value) {
|
|
4052
|
+
if (typeof value !== "string") return value;
|
|
4053
|
+
return ANY_SURROGATE_PATTERN.test(value) ? value.replace(LONE_SURROGATE_PATTERN, "") : value;
|
|
4054
|
+
}
|
|
3493
4055
|
/** Hash one request value without retaining or logging its contents. */
|
|
3494
4056
|
function fingerprintValue(value) {
|
|
3495
4057
|
let serialized;
|
|
@@ -3662,15 +4224,39 @@ const CLI_CONTEXT_OVERFLOW_PATTERN = /prompt is too long|context.*(length|window
|
|
|
3662
4224
|
function isContextOverflowDetail(detail) {
|
|
3663
4225
|
return isContextWindowExceededError(detail) || CLI_CONTEXT_OVERFLOW_PATTERN.test(detail);
|
|
3664
4226
|
}
|
|
3665
|
-
/**
|
|
3666
|
-
|
|
4227
|
+
/**
|
|
4228
|
+
* A request-side `max_tokens` refusal, told apart from a context overflow that
|
|
4229
|
+
* mentions the same field. Kept narrow on purpose: the overflow pattern also
|
|
4230
|
+
* matches a bare `max_tokens`, so this must name the specific shapes the
|
|
4231
|
+
* endpoint uses — a ceiling comparison, its prose about output tokens, or a
|
|
4232
|
+
* request-validation error on the field.
|
|
4233
|
+
*/
|
|
4234
|
+
const OUTPUT_LIMIT_REJECTION = new RegExp([
|
|
4235
|
+
"max_tokens:\\s*\\d+\\s*>\\s*\\d+",
|
|
4236
|
+
"maximum allowed number of output tokens",
|
|
4237
|
+
"invalid input[^\\n]*max_tokens",
|
|
4238
|
+
"expected number[^\\n]*at max_tokens"
|
|
4239
|
+
].join("|"), "i");
|
|
4240
|
+
/**
|
|
4241
|
+
* The output ceiling the endpoint states in a `max_tokens` refusal, read off
|
|
4242
|
+
* its own comparison: `max_tokens: 131072 > 128000` yields 128000.
|
|
4243
|
+
*
|
|
4244
|
+
* Only the comparison form is parsed. The other shapes
|
|
4245
|
+
* {@link OUTPUT_LIMIT_REJECTION} matches — a bare validation error, or the
|
|
4246
|
+
* prose without the numbers — name no ceiling, and guessing one would be worse
|
|
4247
|
+
* than failing: a fabricated value is indistinguishable from a real one at the
|
|
4248
|
+
* point where it decides the next request's budget.
|
|
4249
|
+
*/
|
|
4250
|
+
function parseOutputCeiling(detail) {
|
|
4251
|
+
const stated = /max_tokens:\s*\d+\s*>\s*(\d+)/i.exec(detail)?.[1];
|
|
4252
|
+
if (stated === void 0) return void 0;
|
|
4253
|
+
const ceiling = Number(stated);
|
|
4254
|
+
return Number.isSafeInteger(ceiling) && ceiling > 0 ? ceiling : void 0;
|
|
4255
|
+
}
|
|
3667
4256
|
/**
|
|
3668
4257
|
* The advice floor for a Provider-API timeout: doubling a 60 s budget is still
|
|
3669
4258
|
* short for a slow upstream first token, so the suggestion never goes below 5
|
|
3670
|
-
* minutes.
|
|
3671
|
-
* ceiling on purpose — a single header wait that outlasts the whole retry
|
|
3672
|
-
* backoff is not a budget, and clamping to the engine constant would also pull
|
|
3673
|
-
* a runtime dependency into the bundle.
|
|
4259
|
+
* minutes. 提示上限与页面可编辑范围相同,重试退避上限不约束单次请求。
|
|
3674
4260
|
*/
|
|
3675
4261
|
const MIN_SUGGESTED_TIMEOUT_MS = 3e5;
|
|
3676
4262
|
/**
|
|
@@ -3694,10 +4280,13 @@ const MIN_SUGGESTED_TIMEOUT_MS = 3e5;
|
|
|
3694
4280
|
* @param timeoutMs - the header budget that just expired.
|
|
3695
4281
|
*/
|
|
3696
4282
|
function headersTimeoutAdvice(protocol, timeoutMs) {
|
|
3697
|
-
if (protocol === "openai")
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
4283
|
+
if (protocol === "openai") {
|
|
4284
|
+
const suggestionMs = Math.min(MAX_TIMEOUT_SECONDS * 1e3, Math.max(timeoutMs * 2, MIN_SUGGESTED_TIMEOUT_MS));
|
|
4285
|
+
return {
|
|
4286
|
+
cause: "upstream-first-token",
|
|
4287
|
+
...suggestionMs > timeoutMs ? { suggestionMs } : {}
|
|
4288
|
+
};
|
|
4289
|
+
}
|
|
3701
4290
|
return { cause: "gateway" };
|
|
3702
4291
|
}
|
|
3703
4292
|
/**
|
|
@@ -3711,6 +4300,8 @@ function headersTimeoutAdvice(protocol, timeoutMs) {
|
|
|
3711
4300
|
* answer either way.
|
|
3712
4301
|
*/
|
|
3713
4302
|
const MAX_CONSECUTIVE_HEADER_TIMEOUTS = 3;
|
|
4303
|
+
/** Streak key for a session-less call, so those keep the old single-slot semantics instead of never escalating. */
|
|
4304
|
+
const GLOBAL_HEADER_TIMEOUT_KEY = "\0global";
|
|
3714
4305
|
/** UTF-8 byte length, computed only on the timeout path so a large body is encoded once, not per attempt. */
|
|
3715
4306
|
function utf8ByteLength(text) {
|
|
3716
4307
|
return new TextEncoder().encode(text).length;
|
|
@@ -3777,9 +4368,21 @@ function streamErrorToLlmError(value, fallbackMessage) {
|
|
|
3777
4368
|
message
|
|
3778
4369
|
].filter((part) => part !== void 0 && part !== "").join(" ");
|
|
3779
4370
|
const statusOption = statusCode !== void 0 ? { status: statusCode } : void 0;
|
|
4371
|
+
if (OUTPUT_LIMIT_REJECTION.test(detail)) {
|
|
4372
|
+
const stated = /max_tokens:\s*\d+\s*>\s*\d+/i.exec(detail)?.[0];
|
|
4373
|
+
return bilingual("PROVIDER_STREAM_ERROR", `Command Code refused the request's max_tokens for this model` + (stated !== void 0 ? ` (${stated})` : "") + " — the session is not oversized, so compacting it will not help; lower \"Max output tokens\" or pick a model with a larger output ceiling", `Command Code 拒绝了本次请求的 max_tokens(该模型的输出上限更低)` + (stated !== void 0 ? `(${stated})` : "") + "——会话内容并未超长,压缩上下文无效;请调低「最大输出 token」或改用输出上限更大的模型", statusOption);
|
|
4374
|
+
}
|
|
3780
4375
|
if (isContextOverflowDetail(detail)) return bilingual(CONTEXT_WINDOW_EXCEEDED_CODE, `Command Code stream error: ${message}`, "Command Code API 拒绝了这次请求:内容超出模型上下文窗口。正在压缩上下文后重试;如仍失败,请新建会话或减少上下文", statusOption);
|
|
3781
4376
|
const terminal = hasTerminalStreamMarker(detail);
|
|
3782
4377
|
if (!(isRetryable === true || (isRetryable === false || terminal ? false : statusCode !== void 0 ? statusCode !== void 0 && (statusCode === 429 || statusCode >= 500) : true))) return new LlmError(`Command Code stream error: ${message}`, "PROVIDER_STREAM_ERROR", statusOption);
|
|
4378
|
+
const rejection = classifyAccountRejection(statusCode ?? 0, JSON.stringify(value) ?? message);
|
|
4379
|
+
if (rejection?.reason === "rate-limit" || rejection?.reason === "throttled") {
|
|
4380
|
+
const wait = rejection.resetAtMs === void 0 ? 0 : Math.min(RETRY_MAX_DELAY_MS, Math.max(1e3, rejection.resetAtMs - Date.now()));
|
|
4381
|
+
return bilingual(rejection.reason === "rate-limit" ? "RATE_LIMIT" : THROTTLED_CODE, `Command Code stream error: ${message}`, rejection.reason === "rate-limit" ? "Command Code 用量窗口已限流,等待恢复" : "Command Code 请求暂时被限流,正在重试", {
|
|
4382
|
+
...statusOption,
|
|
4383
|
+
...wait > 0 ? { providerRetryAfterMs: wait } : {}
|
|
4384
|
+
});
|
|
4385
|
+
}
|
|
3783
4386
|
return new LlmError(`Command Code stream error: ${message}`, "SERVER", statusOption);
|
|
3784
4387
|
}
|
|
3785
4388
|
function recordOrEmpty(value) {
|
|
@@ -3944,8 +4547,9 @@ function resolveAuthFileApiKey() {
|
|
|
3944
4547
|
return apiKeyFromCredentialRecord(parsed.commandcode) ?? apiKeyFromCredentialRecord(parsed["command-code"]);
|
|
3945
4548
|
} catch {}
|
|
3946
4549
|
}
|
|
3947
|
-
function parseCatalogResponse(value) {
|
|
4550
|
+
function parseCatalogResponse(value, apiBase) {
|
|
3948
4551
|
if (!isRecord(value) || value.object !== "list" || !Array.isArray(value.data)) throw new LlmError("Unexpected Command Code models response shape", "PROVIDER_PROTOCOL_ERROR");
|
|
4552
|
+
publishedOutputLimits.set(apiBase, /* @__PURE__ */ new Map());
|
|
3949
4553
|
const models = [];
|
|
3950
4554
|
for (const entry of value.data) {
|
|
3951
4555
|
if (!isRecord(entry)) continue;
|
|
@@ -3953,33 +4557,52 @@ function parseCatalogResponse(value) {
|
|
|
3953
4557
|
const name = stringValue(entry.name);
|
|
3954
4558
|
const contextLength = numberValue(entry.context_length);
|
|
3955
4559
|
if (!id || !name || !contextLength || contextLength <= 0) continue;
|
|
4560
|
+
const endpoints = Array.isArray(entry.supported_endpoints) ? entry.supported_endpoints.filter((item) => typeof item === "string") : [];
|
|
4561
|
+
const published = numberValue(entry.max_output_tokens);
|
|
4562
|
+
if (published !== void 0 && published > 0) outputLimitsFor(publishedOutputLimits, apiBase).set(id, published);
|
|
3956
4563
|
models.push({
|
|
3957
4564
|
id,
|
|
3958
4565
|
name,
|
|
3959
4566
|
contextWindow: contextLength,
|
|
3960
|
-
maxTokens: Math.min(contextLength,
|
|
4567
|
+
maxTokens: Math.min(contextLength, modelOutputTokenLimit(apiBase, id)),
|
|
4568
|
+
...published !== void 0 && published > 0 ? { publishedMaxTokens: published } : {},
|
|
4569
|
+
supportedEndpoints: endpoints
|
|
3961
4570
|
});
|
|
3962
4571
|
}
|
|
3963
4572
|
if (models.length === 0) throw new LlmError("Command Code returned an empty model catalog", "PROVIDER_PROTOCOL_ERROR");
|
|
3964
4573
|
return models;
|
|
3965
4574
|
}
|
|
3966
|
-
async function readModelsCache(cachePath) {
|
|
4575
|
+
async function readModelsCache(cachePath, apiBase, restorePublished = true) {
|
|
3967
4576
|
const parsed = JSON.parse(await readFile(cachePath, "utf-8"));
|
|
3968
|
-
if (!isRecord(parsed) || parsed.version !== MODEL_CACHE_VERSION || !Array.isArray(parsed.models)) throw new Error(`Invalid model cache at ${cachePath}`);
|
|
3969
|
-
|
|
4577
|
+
if (!isRecord(parsed) || parsed.version !== MODEL_CACHE_VERSION || parsed.apiBase !== apiBase || !Array.isArray(parsed.models)) throw new Error(`Invalid model cache at ${cachePath}`);
|
|
4578
|
+
if (restorePublished) publishedOutputLimits.set(apiBase, /* @__PURE__ */ new Map());
|
|
4579
|
+
return parsed.models.map((entry) => {
|
|
4580
|
+
const model = isRecord(entry) ? entry : {};
|
|
4581
|
+
const endpoints = Array.isArray(model.supportedEndpoints) ? model.supportedEndpoints.filter((item) => typeof item === "string") : [];
|
|
4582
|
+
const id = stringValue(model.id);
|
|
4583
|
+
const published = numberValue(model.publishedMaxTokens);
|
|
4584
|
+
if (restorePublished && id !== void 0 && published !== void 0 && published > 0) outputLimitsFor(publishedOutputLimits, apiBase).set(id, published);
|
|
4585
|
+
const contextWindow = numberValue(model.contextWindow);
|
|
4586
|
+
return {
|
|
4587
|
+
...model,
|
|
4588
|
+
supportedEndpoints: endpoints,
|
|
4589
|
+
maxTokens: id === void 0 || contextWindow === void 0 ? numberValue(model.maxTokens) ?? 131072 : Math.min(contextWindow, modelOutputTokenLimit(apiBase, id), published !== void 0 && published > 0 ? published : Infinity)
|
|
4590
|
+
};
|
|
4591
|
+
});
|
|
3970
4592
|
}
|
|
3971
|
-
async function writeModelsCache(cachePath, models) {
|
|
4593
|
+
async function writeModelsCache(cachePath, apiBase, models, mayCommit) {
|
|
3972
4594
|
await mkdir(dirname(cachePath), { recursive: true });
|
|
3973
|
-
const tmp = `${cachePath}.${process.pid}.tmp`;
|
|
4595
|
+
const tmp = `${cachePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
3974
4596
|
try {
|
|
3975
4597
|
await writeFile(tmp, `${JSON.stringify({
|
|
3976
4598
|
version: MODEL_CACHE_VERSION,
|
|
4599
|
+
apiBase,
|
|
3977
4600
|
models
|
|
3978
4601
|
}, null, 2)}\n`, {
|
|
3979
4602
|
encoding: "utf-8",
|
|
3980
4603
|
mode: 384
|
|
3981
4604
|
});
|
|
3982
|
-
await rename(tmp, cachePath);
|
|
4605
|
+
if (mayCommit()) await rename(tmp, cachePath);
|
|
3983
4606
|
} finally {
|
|
3984
4607
|
await rm(tmp, { force: true }).catch(() => void 0);
|
|
3985
4608
|
}
|
|
@@ -4229,6 +4852,38 @@ function seenImageOffloadCount(messages, model) {
|
|
|
4229
4852
|
async function readRequestImage(attachments, ref) {
|
|
4230
4853
|
return attachments.readImageRequest(ref, requestImageTarget(ref));
|
|
4231
4854
|
}
|
|
4855
|
+
/** 同一次生成及其协议切换共享图片读取,最多同时准备四张,避免内存峰值失控。 */
|
|
4856
|
+
function requestImages(attachments, signal, timing) {
|
|
4857
|
+
const cache = /* @__PURE__ */ new Map();
|
|
4858
|
+
const read = (ref) => {
|
|
4859
|
+
signal?.throwIfAborted();
|
|
4860
|
+
const key = JSON.stringify([ref.attachmentId, requestImageTarget(ref)]);
|
|
4861
|
+
let pending = cache.get(key);
|
|
4862
|
+
if (!pending) {
|
|
4863
|
+
pending = readRequestImage(attachments, ref);
|
|
4864
|
+
cache.set(key, pending);
|
|
4865
|
+
}
|
|
4866
|
+
return pending;
|
|
4867
|
+
};
|
|
4868
|
+
read.prepare = async (refs) => {
|
|
4869
|
+
if (refs.length === 0) return;
|
|
4870
|
+
const finishImages = timing.phase("images");
|
|
4871
|
+
let cursor = 0;
|
|
4872
|
+
try {
|
|
4873
|
+
const workers = Array.from({ length: Math.min(4, refs.length) }, async () => {
|
|
4874
|
+
while (cursor < refs.length) {
|
|
4875
|
+
signal?.throwIfAborted();
|
|
4876
|
+
await read(refs[cursor++]);
|
|
4877
|
+
}
|
|
4878
|
+
});
|
|
4879
|
+
const failure = (await Promise.allSettled(workers)).find((result) => result.status === "rejected");
|
|
4880
|
+
if (failure?.status === "rejected") throw failure.reason;
|
|
4881
|
+
} finally {
|
|
4882
|
+
finishImages();
|
|
4883
|
+
}
|
|
4884
|
+
};
|
|
4885
|
+
return read;
|
|
4886
|
+
}
|
|
4232
4887
|
/**
|
|
4233
4888
|
* Price one request occurrence.
|
|
4234
4889
|
*
|
|
@@ -4272,6 +4927,17 @@ async function imageToCommandCode(ref, readImage) {
|
|
|
4272
4927
|
};
|
|
4273
4928
|
}
|
|
4274
4929
|
/**
|
|
4930
|
+
* The signature recorded for the reasoning block at `position` of a
|
|
4931
|
+
* historical assistant message, or undefined when the message was produced
|
|
4932
|
+
* by another route or an older plugin version.
|
|
4933
|
+
*/
|
|
4934
|
+
function messagesSignatureAt(message, position) {
|
|
4935
|
+
const replay = message.source?.replayState;
|
|
4936
|
+
if (!isRecord(replay) || !Array.isArray(replay.blocks)) return void 0;
|
|
4937
|
+
const entry = replay.blocks[position];
|
|
4938
|
+
return isRecord(entry) ? stringValue(entry.signature) : void 0;
|
|
4939
|
+
}
|
|
4940
|
+
/**
|
|
4275
4941
|
* The common pairing and image-carrier ordering for both transports. Each
|
|
4276
4942
|
* codec owns its wire shape: CLI content blocks and Chat Completions
|
|
4277
4943
|
* reasoning/tool calls differ beyond the image envelope.
|
|
@@ -4279,6 +4945,16 @@ async function imageToCommandCode(ref, readImage) {
|
|
|
4279
4945
|
async function convertMessages(messages, readImage, codec) {
|
|
4280
4946
|
const out = [];
|
|
4281
4947
|
const { ids: paired, names: toolNames } = pairedToolCalls(messages);
|
|
4948
|
+
if (readImage?.prepare) {
|
|
4949
|
+
const refs = [];
|
|
4950
|
+
for (const message of messages) {
|
|
4951
|
+
const result = toolResultOf(message);
|
|
4952
|
+
if (message.role === "user" && !result) {
|
|
4953
|
+
for (const block of message.content) if (block.type === "image") refs.push(block.attachment);
|
|
4954
|
+
} else if (result && paired.has(result.toolCallId)) refs.push(...toolResultMedia(result).images);
|
|
4955
|
+
}
|
|
4956
|
+
await readImage.prepare(refs);
|
|
4957
|
+
}
|
|
4282
4958
|
const wireIds = wireToolCallIds(paired);
|
|
4283
4959
|
const pendingImages = [];
|
|
4284
4960
|
const flushPendingImages = () => {
|
|
@@ -4314,8 +4990,8 @@ async function convertMessages(messages, readImage, codec) {
|
|
|
4314
4990
|
if (!result || !paired.has(result.toolCallId)) continue;
|
|
4315
4991
|
const media = toolResultMedia(result);
|
|
4316
4992
|
const wireId = wireIds.get(result.toolCallId) ?? result.toolCallId;
|
|
4317
|
-
out.push(codec.encodeTool(result, media, wireId, toolNames.get(result.toolCallId) || "unknown"));
|
|
4318
|
-
if (media.images.length > 0) {
|
|
4993
|
+
out.push(await codec.encodeTool(result, media, wireId, toolNames.get(result.toolCallId) || "unknown", readImage));
|
|
4994
|
+
if (!codec.carriesToolResultMedia && media.images.length > 0) {
|
|
4319
4995
|
const carried = [{
|
|
4320
4996
|
type: "text",
|
|
4321
4997
|
text: toolResultImageNote(media, wireId)
|
|
@@ -4328,6 +5004,7 @@ async function convertMessages(messages, readImage, codec) {
|
|
|
4328
5004
|
return out;
|
|
4329
5005
|
}
|
|
4330
5006
|
const ccMessageCodec = {
|
|
5007
|
+
carriesToolResultMedia: false,
|
|
4331
5008
|
encodeImage: imageToCommandCode,
|
|
4332
5009
|
encodeUser: (parts) => ({
|
|
4333
5010
|
role: "user",
|
|
@@ -4395,6 +5072,7 @@ async function imageToOpenAI(ref, readImage) {
|
|
|
4395
5072
|
* replayed (same policy as the CLI path).
|
|
4396
5073
|
*/
|
|
4397
5074
|
const openAiMessageCodec = {
|
|
5075
|
+
carriesToolResultMedia: false,
|
|
4398
5076
|
encodeImage: imageToOpenAI,
|
|
4399
5077
|
encodeUser: (parts) => {
|
|
4400
5078
|
return !parts.some((part) => part.type === "image_url") && parts.length === 1 ? {
|
|
@@ -4434,6 +5112,119 @@ const openAiMessageCodec = {
|
|
|
4434
5112
|
async function messagesToOpenAI(messages, readImage) {
|
|
4435
5113
|
return convertMessages(messages, readImage, openAiMessageCodec);
|
|
4436
5114
|
}
|
|
5115
|
+
/**
|
|
5116
|
+
* Convert one image reference to the Messages wire shape. Unlike the other two
|
|
5117
|
+
* transports this one keeps the native media type rather than a data URL, and
|
|
5118
|
+
* it may be used INSIDE a tool result — `tool_result.content` accepts image
|
|
5119
|
+
* blocks, which is why this codec sets `carriesToolResultMedia`.
|
|
5120
|
+
*/
|
|
5121
|
+
async function imageToMessages(ref, readImage) {
|
|
5122
|
+
const version = await readImage(ref);
|
|
5123
|
+
return {
|
|
5124
|
+
type: "image",
|
|
5125
|
+
source: {
|
|
5126
|
+
type: "base64",
|
|
5127
|
+
media_type: version.mediaType,
|
|
5128
|
+
data: Buffer.from(version.data).toString("base64")
|
|
5129
|
+
}
|
|
5130
|
+
};
|
|
5131
|
+
}
|
|
5132
|
+
/** A tool call's `arguments` is a raw JSON string in the harness, an object here. */
|
|
5133
|
+
function parseToolArguments(raw) {
|
|
5134
|
+
try {
|
|
5135
|
+
const parsed = JSON.parse(raw);
|
|
5136
|
+
return isRecord(parsed) ? parsed : {};
|
|
5137
|
+
} catch {
|
|
5138
|
+
return {};
|
|
5139
|
+
}
|
|
5140
|
+
}
|
|
5141
|
+
/**
|
|
5142
|
+
* Convert harness messages to the Anthropic Messages request shape.
|
|
5143
|
+
*
|
|
5144
|
+
* Differences from the other two transports, all measured against the live
|
|
5145
|
+
* endpoint on 2026-09-29:
|
|
5146
|
+
*
|
|
5147
|
+
* - Content is always a block array; there is no string shorthand, so a
|
|
5148
|
+
* single-text user turn is still `{ role: 'user', content: [{type:'text'…}] }`.
|
|
5149
|
+
* - Tool results live in a `user` turn as `tool_result` blocks (not a
|
|
5150
|
+
* `role: 'tool'` message) and may carry images natively, so no image is
|
|
5151
|
+
* split into a separate user message here.
|
|
5152
|
+
* - A replayed `thinking` block is only valid WITH its `signature`: the
|
|
5153
|
+
* endpoint answers `thinking.signature: Field required` without it, and
|
|
5154
|
+
* `each thinking block must contain thinking` when the text is empty and
|
|
5155
|
+
* the signature is blank. A reasoning block with no recorded signature is
|
|
5156
|
+
* therefore dropped rather than sent — replaying it would fail the request,
|
|
5157
|
+
* while omitting it is accepted (verified 200 both ways).
|
|
5158
|
+
* - The conversation must end with a user turn; the endpoint refuses an
|
|
5159
|
+
* assistant prefill (`This model does not support assistant message
|
|
5160
|
+
* prefill`), which `convertMessages` already guarantees for a tool loop.
|
|
5161
|
+
*/
|
|
5162
|
+
const messagesMessageCodec = {
|
|
5163
|
+
carriesToolResultMedia: true,
|
|
5164
|
+
encodeImage: imageToMessages,
|
|
5165
|
+
encodeUser: (parts) => ({
|
|
5166
|
+
role: "user",
|
|
5167
|
+
content: parts
|
|
5168
|
+
}),
|
|
5169
|
+
encodeAssistant: (message, paired, wireIds) => {
|
|
5170
|
+
const parts = [];
|
|
5171
|
+
let position = 0;
|
|
5172
|
+
for (const block of message.content) {
|
|
5173
|
+
if (block.type === "text") {
|
|
5174
|
+
parts.push({
|
|
5175
|
+
type: "text",
|
|
5176
|
+
text: block.text
|
|
5177
|
+
});
|
|
5178
|
+
position++;
|
|
5179
|
+
continue;
|
|
5180
|
+
}
|
|
5181
|
+
if (block.type === "reasoning") {
|
|
5182
|
+
const signature = messagesSignatureAt(message, position);
|
|
5183
|
+
if (signature !== void 0) parts.push({
|
|
5184
|
+
type: "thinking",
|
|
5185
|
+
thinking: block.text,
|
|
5186
|
+
signature
|
|
5187
|
+
});
|
|
5188
|
+
position++;
|
|
5189
|
+
continue;
|
|
5190
|
+
}
|
|
5191
|
+
if (block.type === "tool-call" && paired.has(block.id)) {
|
|
5192
|
+
parts.push({
|
|
5193
|
+
type: "tool_use",
|
|
5194
|
+
id: wireIds.get(block.id) ?? block.id,
|
|
5195
|
+
name: block.name,
|
|
5196
|
+
input: parseToolArguments(block.arguments)
|
|
5197
|
+
});
|
|
5198
|
+
position++;
|
|
5199
|
+
}
|
|
5200
|
+
}
|
|
5201
|
+
return parts.length > 0 ? {
|
|
5202
|
+
role: "assistant",
|
|
5203
|
+
content: parts
|
|
5204
|
+
} : void 0;
|
|
5205
|
+
},
|
|
5206
|
+
encodeTool: async (result, media, wireId, _toolName, readImage) => {
|
|
5207
|
+
const content = [];
|
|
5208
|
+
if (readImage) for (const attachment of media.images) content.push(await imageToMessages(attachment, readImage));
|
|
5209
|
+
content.push({
|
|
5210
|
+
type: "text",
|
|
5211
|
+
text: toolResultTextForWire(media)
|
|
5212
|
+
});
|
|
5213
|
+
const toolResult = {
|
|
5214
|
+
type: "tool_result",
|
|
5215
|
+
tool_use_id: wireId,
|
|
5216
|
+
content
|
|
5217
|
+
};
|
|
5218
|
+
if (result.isError) toolResult.is_error = true;
|
|
5219
|
+
return {
|
|
5220
|
+
role: "user",
|
|
5221
|
+
content: [toolResult]
|
|
5222
|
+
};
|
|
5223
|
+
}
|
|
5224
|
+
};
|
|
5225
|
+
async function messagesToMessages(messages, readImage) {
|
|
5226
|
+
return convertMessages(messages, readImage, messagesMessageCodec);
|
|
5227
|
+
}
|
|
4437
5228
|
/** Account endpoints fetched by one `getUsage()` run (see the classification there). */
|
|
4438
5229
|
const USAGE_ENDPOINT_COUNT = 4;
|
|
4439
5230
|
/**
|
|
@@ -4561,6 +5352,50 @@ function cliSystem(systemText) {
|
|
|
4561
5352
|
cache_control: { type: "ephemeral" }
|
|
4562
5353
|
}];
|
|
4563
5354
|
}
|
|
5355
|
+
/**
|
|
5356
|
+
* The Anthropic ephemeral cache marker, shared by every breakpoint this route
|
|
5357
|
+
* sets. One object is reused across positions because it is only ever
|
|
5358
|
+
* serialized, never mutated.
|
|
5359
|
+
*
|
|
5360
|
+
* No `ttl` is sent: the default 5-minute entry covers a continuously advancing
|
|
5361
|
+
* session, and `ttl: "1h"` is a separate billable tier whose read/write prices
|
|
5362
|
+
* are identical — it would only lengthen survival, not reduce cost.
|
|
5363
|
+
*/
|
|
5364
|
+
const EPHEMERAL_CACHE_CONTROL = { type: "ephemeral" };
|
|
5365
|
+
/**
|
|
5366
|
+
* Content-block types that may carry the rolling breakpoint. Matches what the
|
|
5367
|
+
* official Command Code provider for pi marks through
|
|
5368
|
+
* `@earendil-works/pi-ai`'s `anthropic-messages` transport (its own list also
|
|
5369
|
+
* holds pi-internal `tool_addition` / `tool_removal`, which this route never
|
|
5370
|
+
* emits). Assistant-side blocks are deliberately excluded: a breakpoint on a
|
|
5371
|
+
* `thinking` or `tool_use` block caches a prefix the next turn invalidates.
|
|
5372
|
+
*/
|
|
5373
|
+
const CACHEABLE_BLOCK_TYPES = /* @__PURE__ */ new Set([
|
|
5374
|
+
"text",
|
|
5375
|
+
"image",
|
|
5376
|
+
"tool_result"
|
|
5377
|
+
]);
|
|
5378
|
+
/**
|
|
5379
|
+
* Mark the last user turn as the rolling cache breakpoint.
|
|
5380
|
+
*
|
|
5381
|
+
* Anthropic caching is explicit — a prefix is stored only when a
|
|
5382
|
+
* `cache_control` marker names it — so an unmarked route re-prefills the whole
|
|
5383
|
+
* replayed history at the full input price on every turn. Measured cost of that
|
|
5384
|
+
* omission on a 10-turn session: $0.5648, all of it at the undiscounted
|
|
5385
|
+
* $2/M input rate, against $0.1869 with the three breakpoints this route sets.
|
|
5386
|
+
*
|
|
5387
|
+
* The final user message is the rolling boundary: DSH resends the whole
|
|
5388
|
+
* history each turn, so a marker here grows with the conversation, whereas a
|
|
5389
|
+
* fixed `system`-only marker would leave every later turn's history
|
|
5390
|
+
* uncached. {@link messagesMessageCodec} renders both user input and tool
|
|
5391
|
+
* results as `role: 'user'`, so this covers a tool loop's trailing turn too.
|
|
5392
|
+
*/
|
|
5393
|
+
function markLastUserCacheControl(messages) {
|
|
5394
|
+
const last = messages[messages.length - 1];
|
|
5395
|
+
if (!isRecord(last) || last.role !== "user" || !Array.isArray(last.content)) return;
|
|
5396
|
+
const lastBlock = last.content[last.content.length - 1];
|
|
5397
|
+
if (isRecord(lastBlock) && typeof lastBlock.type === "string" && CACHEABLE_BLOCK_TYPES.has(lastBlock.type)) lastBlock.cache_control = EPHEMERAL_CACHE_CONTROL;
|
|
5398
|
+
}
|
|
4564
5399
|
/** Build the legacy CLI (`/alpha/generate`) request body for one call. */
|
|
4565
5400
|
async function buildCliBody(options, connection, facts) {
|
|
4566
5401
|
return {
|
|
@@ -4578,6 +5413,7 @@ async function buildCliBody(options, connection, facts) {
|
|
|
4578
5413
|
memory: null,
|
|
4579
5414
|
taste: null,
|
|
4580
5415
|
skills: null,
|
|
5416
|
+
permissionMode: "standard",
|
|
4581
5417
|
params: {
|
|
4582
5418
|
model: options.model,
|
|
4583
5419
|
messages: await messagesToCC(options.messages, facts.readImage),
|
|
@@ -4621,39 +5457,107 @@ async function buildOpenAIBody(options, facts) {
|
|
|
4621
5457
|
};
|
|
4622
5458
|
}
|
|
4623
5459
|
/**
|
|
4624
|
-
*
|
|
4625
|
-
*
|
|
4626
|
-
*
|
|
4627
|
-
*
|
|
4628
|
-
*
|
|
4629
|
-
*
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
5460
|
+
* Build the Anthropic Messages request body for one call.
|
|
5461
|
+
*
|
|
5462
|
+
* Measured deviations from DeepSeek's own Messages API (the shape
|
|
5463
|
+
* `dsh-llm-deepseek` implements), all confirmed on 2026-09-29:
|
|
5464
|
+
*
|
|
5465
|
+
* - `thinking` accepts ONLY `{ type: 'adaptive' }` here. `enabled` and
|
|
5466
|
+
* `disabled` are refused with `"thinking.type.enabled" is not supported for
|
|
5467
|
+
* this model. Use "thinking.type.adaptive" and "output_config.effort" to
|
|
5468
|
+
* control thinking behavior.`, and a `between_tools` value fails validation
|
|
5469
|
+
* outright. The model reasons on its own; this switch only admits it.
|
|
5470
|
+
* - Reasoning strength travels in `output_config.effort`, whose accepted
|
|
5471
|
+
* values are `low | medium | high | xhigh | max` — there is no `none`, and
|
|
5472
|
+
* `xhigh` is absent from DeepSeek's own type.
|
|
5473
|
+
* - `stop_sequences` is a real field here, but this adapter still rejects
|
|
5474
|
+
* `GenerateOptions.stop` in `stream()` because Command Code documents no
|
|
5475
|
+
* equivalent for the other two transports.
|
|
5476
|
+
* - **`temperature` is never sent.** Adaptive thinking constrains it to 1:
|
|
5477
|
+
* `` `temperature` may only be set to 1 when thinking is enabled or in
|
|
5478
|
+
* adaptive mode `` (measured 2026-09-29 — every request carrying the other
|
|
5479
|
+
* transports' 0.3 default was refused with HTTP 400). Omitting the field
|
|
5480
|
+
* is the only way to honour both the thinking switch and a caller that
|
|
5481
|
+
* asked for a temperature, so Claude loses sampling control on this route
|
|
5482
|
+
* because the endpoint, not this adapter, removed it.
|
|
5483
|
+
* - `system` travels as a one-block ARRAY carrying `cache_control`, not as the
|
|
5484
|
+
* bare string the other two transports send. Anthropic's caching is opt-in
|
|
5485
|
+
* per block, and this route is the only Claude path, so the marker is what
|
|
5486
|
+
* makes a session's replayed history bill at the cache-read rate instead of
|
|
5487
|
+
* the full input rate. See {@link EPHEMERAL_CACHE_CONTROL}.
|
|
5488
|
+
*/
|
|
5489
|
+
async function buildMessagesBody(options, facts) {
|
|
5490
|
+
const body = {
|
|
5491
|
+
model: options.model,
|
|
5492
|
+
max_tokens: facts.maxTokens,
|
|
5493
|
+
stream: true,
|
|
5494
|
+
messages: await messagesToMessages(options.messages, facts.readImage)
|
|
4656
5495
|
};
|
|
5496
|
+
if (facts.selectableEffort) body.thinking = { type: "adaptive" };
|
|
5497
|
+
if (facts.systemText) body.system = [{
|
|
5498
|
+
type: "text",
|
|
5499
|
+
text: facts.systemText,
|
|
5500
|
+
cache_control: EPHEMERAL_CACHE_CONTROL
|
|
5501
|
+
}];
|
|
5502
|
+
if (facts.reasoningEffort) body.output_config = { effort: facts.reasoningEffort };
|
|
5503
|
+
const tools = (options.tools ?? []).map((tool) => ({
|
|
5504
|
+
name: tool.name,
|
|
5505
|
+
description: tool.description,
|
|
5506
|
+
input_schema: toolParametersSchema(tool.parameters)
|
|
5507
|
+
}));
|
|
5508
|
+
if (tools.length > 0) {
|
|
5509
|
+
tools[tools.length - 1].cache_control = EPHEMERAL_CACHE_CONTROL;
|
|
5510
|
+
body.tools = tools;
|
|
5511
|
+
}
|
|
5512
|
+
markLastUserCacheControl(body.messages);
|
|
5513
|
+
return body;
|
|
5514
|
+
}
|
|
5515
|
+
/**
|
|
5516
|
+
* One connect attempt's inputs: everything `connectGenerate` needs beyond
|
|
5517
|
+
* the per-attempt key and protocol. Passed explicitly (not closed over) so
|
|
5518
|
+
* the rotation loop's mutation of `protocol`/`body` stays visible at the
|
|
5519
|
+
* call site.
|
|
5520
|
+
*/
|
|
5521
|
+
/** The generate endpoint one protocol posts to. */
|
|
5522
|
+
function protocolEndpoint(protocol, apiBase) {
|
|
5523
|
+
if (protocol === "cli") return `${apiBase}/alpha/generate`;
|
|
5524
|
+
return protocol === "messages" ? `${apiBase}/provider/v1/messages` : `${apiBase}/provider/v1/chat/completions`;
|
|
5525
|
+
}
|
|
5526
|
+
/** 错误正文只供诊断与分类使用,不能让坏网关占满内存或无限拖住重试。 */
|
|
5527
|
+
async function readGenerateErrorBody(response, abort, timeoutMs) {
|
|
5528
|
+
if (response.body === null) return "";
|
|
5529
|
+
const reader = response.body.getReader();
|
|
5530
|
+
const decoder = new TextDecoder();
|
|
5531
|
+
let text = "";
|
|
5532
|
+
let bytes = 0;
|
|
5533
|
+
const limit = 65536;
|
|
5534
|
+
let removeAbort = () => void 0;
|
|
5535
|
+
const stopped = new Promise((resolve) => {
|
|
5536
|
+
if (abort.signal.aborted) resolve(void 0);
|
|
5537
|
+
else abort.signal.addEventListener("abort", onAbort, { once: true });
|
|
5538
|
+
function onAbort() {
|
|
5539
|
+
resolve(void 0);
|
|
5540
|
+
}
|
|
5541
|
+
removeAbort = () => abort.signal.removeEventListener("abort", onAbort);
|
|
5542
|
+
});
|
|
5543
|
+
const timer = setTimeout(() => abort.abort(), Math.min(timeoutMs, 3e4));
|
|
5544
|
+
try {
|
|
5545
|
+
while (bytes < limit) {
|
|
5546
|
+
const read = await Promise.race([reader.read(), stopped]);
|
|
5547
|
+
if (read === void 0 || read.done) break;
|
|
5548
|
+
const remaining = limit - bytes;
|
|
5549
|
+
text += decoder.decode(read.value.subarray(0, remaining), { stream: true });
|
|
5550
|
+
bytes += read.value.byteLength;
|
|
5551
|
+
}
|
|
5552
|
+
return text + decoder.decode();
|
|
5553
|
+
} catch {
|
|
5554
|
+
return text + decoder.decode();
|
|
5555
|
+
} finally {
|
|
5556
|
+
clearTimeout(timer);
|
|
5557
|
+
removeAbort();
|
|
5558
|
+
reader.cancel().catch(() => void 0);
|
|
5559
|
+
reader.releaseLock();
|
|
5560
|
+
}
|
|
4657
5561
|
}
|
|
4658
5562
|
/**
|
|
4659
5563
|
* One pre-stream connect attempt: POST the body and wait for response
|
|
@@ -4663,11 +5567,11 @@ function requestContextBudget(protocol, body, messages, model, contextWindow, re
|
|
|
4663
5567
|
* on success the caller-abort listener outlives the connect phase (it aborts a
|
|
4664
5568
|
* stalled body read), so the streaming tail calls cleanup.
|
|
4665
5569
|
*/
|
|
4666
|
-
async function connectGenerate(deps, key, protocol,
|
|
5570
|
+
async function connectGenerate(deps, key, protocol, serializedBody) {
|
|
4667
5571
|
const { options, connection, fetchImpl } = deps;
|
|
4668
5572
|
const connectAbort = new AbortController();
|
|
4669
5573
|
let connectTimedOut = false;
|
|
4670
|
-
const endpoint = protocol
|
|
5574
|
+
const endpoint = protocolEndpoint(protocol, connection.apiBase);
|
|
4671
5575
|
const connectTimer = setTimeout(() => {
|
|
4672
5576
|
connectTimedOut = true;
|
|
4673
5577
|
connectAbort.abort(new DOMException(`Command Code API request to ${endpoint} did not respond within ${connection.requestTimeoutMs}ms`, "TimeoutError"));
|
|
@@ -4685,26 +5589,25 @@ async function connectGenerate(deps, key, protocol, body) {
|
|
|
4685
5589
|
};
|
|
4686
5590
|
let response;
|
|
4687
5591
|
const zdr = connection.zdr === true;
|
|
4688
|
-
const
|
|
5592
|
+
const sharedHeaders = {
|
|
4689
5593
|
"Content-Type": "application/json",
|
|
4690
5594
|
...IDENTITY_ENCODING_HEADER,
|
|
4691
5595
|
Authorization: `Bearer ${key}`,
|
|
5596
|
+
...zdr ? { "x-cmd-zdr": "1" } : {},
|
|
5597
|
+
...attributionHeaders()
|
|
5598
|
+
};
|
|
5599
|
+
const headers = protocol === "cli" ? {
|
|
5600
|
+
...sharedHeaders,
|
|
4692
5601
|
"x-command-code-version": COMMAND_CODE_CLI_VERSION,
|
|
4693
5602
|
"x-cli-environment": "production",
|
|
4694
5603
|
"x-project-slug": projectSlugFromPath(connection.workingDir),
|
|
4695
|
-
"x-taste-learning": "
|
|
4696
|
-
"x-co-flag": "false"
|
|
4697
|
-
...zdr ? { "x-cmd-zdr": "1" } : {},
|
|
4698
|
-
...attributionHeaders()
|
|
5604
|
+
"x-taste-learning": "false",
|
|
5605
|
+
"x-co-flag": "false"
|
|
4699
5606
|
} : {
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
Authorization: `Bearer ${key}`,
|
|
4703
|
-
Accept: "text/event-stream",
|
|
4704
|
-
...zdr ? { "x-cmd-zdr": "1" } : {},
|
|
4705
|
-
...attributionHeaders()
|
|
5607
|
+
...sharedHeaders,
|
|
5608
|
+
Accept: "text/event-stream"
|
|
4706
5609
|
};
|
|
4707
|
-
const
|
|
5610
|
+
const finishHeaders = deps.timing.attempt(protocol, Buffer.byteLength(serializedBody));
|
|
4708
5611
|
try {
|
|
4709
5612
|
response = await fetchImpl(endpoint, {
|
|
4710
5613
|
method: "POST",
|
|
@@ -4712,8 +5615,10 @@ async function connectGenerate(deps, key, protocol, body) {
|
|
|
4712
5615
|
body: serializedBody,
|
|
4713
5616
|
signal: connectAbort.signal
|
|
4714
5617
|
});
|
|
5618
|
+
finishHeaders(response.status);
|
|
4715
5619
|
clearTimeout(connectTimer);
|
|
4716
5620
|
} catch (error) {
|
|
5621
|
+
finishHeaders();
|
|
4717
5622
|
cleanup();
|
|
4718
5623
|
if (options.signal?.aborted) throw error;
|
|
4719
5624
|
if (connectTimedOut || error instanceof DOMException && error.name === "TimeoutError") throw headersTimeoutError({
|
|
@@ -4727,8 +5632,13 @@ async function connectGenerate(deps, key, protocol, body) {
|
|
|
4727
5632
|
}
|
|
4728
5633
|
deps.onResponse(response);
|
|
4729
5634
|
if (!response.ok) {
|
|
4730
|
-
|
|
4731
|
-
|
|
5635
|
+
let errText;
|
|
5636
|
+
try {
|
|
5637
|
+
errText = await readGenerateErrorBody(response, connectAbort, connection.requestTimeoutMs);
|
|
5638
|
+
if (options.signal?.aborted) throw options.signal.reason ?? new DOMException("已取消请求", "AbortError");
|
|
5639
|
+
} finally {
|
|
5640
|
+
cleanup();
|
|
5641
|
+
}
|
|
4732
5642
|
const retryAfterMs = parseRetryAfterMs(response.headers.get("retry-after"));
|
|
4733
5643
|
return retryAfterMs === void 0 ? {
|
|
4734
5644
|
status: response.status,
|
|
@@ -4755,7 +5665,10 @@ function createBlockAssembler() {
|
|
|
4755
5665
|
sawContent: false,
|
|
4756
5666
|
finishReason: void 0,
|
|
4757
5667
|
cliCacheWriteTokens: void 0,
|
|
4758
|
-
openAiToolCalls: []
|
|
5668
|
+
openAiToolCalls: [],
|
|
5669
|
+
messagesBlocks: /* @__PURE__ */ new Map(),
|
|
5670
|
+
messagesUsage: void 0,
|
|
5671
|
+
replayBlocks: []
|
|
4759
5672
|
};
|
|
4760
5673
|
}
|
|
4761
5674
|
function* closeText(asm) {
|
|
@@ -4816,9 +5729,10 @@ function* emitOpenAiToolCalls(asm) {
|
|
|
4816
5729
|
}
|
|
4817
5730
|
asm.openAiToolCalls.length = 0;
|
|
4818
5731
|
}
|
|
4819
|
-
var CommandCodeAdapter = class extends LlmAdapter {
|
|
5732
|
+
var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
4820
5733
|
deps;
|
|
4821
5734
|
catalog = [];
|
|
5735
|
+
catalogState;
|
|
4822
5736
|
fetchImpl;
|
|
4823
5737
|
resolveAttachments;
|
|
4824
5738
|
/**
|
|
@@ -4829,13 +5743,25 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4829
5743
|
billingAccess = /* @__PURE__ */ new Map();
|
|
4830
5744
|
billingAccessInflight = /* @__PURE__ */ new Map();
|
|
4831
5745
|
protocolCache = /* @__PURE__ */ new Map();
|
|
5746
|
+
/** Namespace account-derived facts by gateway without exposing the key. */
|
|
5747
|
+
accountCacheKey(apiBase, apiKey) {
|
|
5748
|
+
return JSON.stringify([apiBase, apiKey]);
|
|
5749
|
+
}
|
|
4832
5750
|
/**
|
|
4833
5751
|
* Consecutive header timeouts for the last request shape, counted ACROSS
|
|
4834
5752
|
* `stream()` calls: dsh-llm-retry re-invokes this same instance, so this is
|
|
4835
|
-
* the only place a streak can live.
|
|
4836
|
-
*
|
|
5753
|
+
* the only place a streak can live. Keyed by `sessionId` (not by `agent`:
|
|
5754
|
+
* `GenerateOptions` carries no agent reference, only `sessionId`) so
|
|
5755
|
+
* concurrent sessions on this one shared adapter instance cannot clear or
|
|
5756
|
+
* inflate each other's count. A call with no `sessionId` (a one-shot,
|
|
5757
|
+
* session-less caller) falls back to {@link GLOBAL_HEADER_TIMEOUT_KEY},
|
|
5758
|
+
* matching the old single-slot behavior for exactly that case. Bounded by
|
|
5759
|
+
* {@link MAX_HEADER_TIMEOUT_STREAK_ENTRIES} with FIFO eviction (`Map`
|
|
5760
|
+
* preserves insertion order) since the key space is per-session rather than
|
|
5761
|
+
* per-account/model, so it is not naturally small like `protocolCache`.
|
|
4837
5762
|
*/
|
|
4838
|
-
|
|
5763
|
+
headerTimeoutStreaks = /* @__PURE__ */ new Map();
|
|
5764
|
+
static MAX_HEADER_TIMEOUT_STREAK_ENTRIES = 200;
|
|
4839
5765
|
constructor(deps) {
|
|
4840
5766
|
super();
|
|
4841
5767
|
this.deps = deps;
|
|
@@ -4848,25 +5774,31 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4848
5774
|
* payload eventually leaves the retryable whitelist.
|
|
4849
5775
|
*
|
|
4850
5776
|
* `providerRetryPolicy()` cannot express this itself — dsh-llm's
|
|
4851
|
-
* `NormalRetryPolicyConfig` carries
|
|
4852
|
-
*
|
|
4853
|
-
* the
|
|
4854
|
-
* carries a real "try again later" signal) while stopping the one code that,
|
|
4855
|
-
* repeated unchanged, only burns the user's session.
|
|
5777
|
+
* `NormalRetryPolicyConfig` carries one `maxRetries` for every listed code.
|
|
5778
|
+
* Escalating here stops repeated identical header timeouts with a specific
|
|
5779
|
+
* diagnosis; the agent listener separately caps ordinary transient errors.
|
|
4856
5780
|
*
|
|
4857
5781
|
* @returns the error to surface: unchanged for any other failure, the
|
|
4858
5782
|
* original timeout below the threshold, and a terminal one at it.
|
|
4859
5783
|
*/
|
|
4860
|
-
gradeHeaderTimeout(error, protocol, body, timeoutMs) {
|
|
5784
|
+
gradeHeaderTimeout(error, protocol, body, timeoutMs, sessionId) {
|
|
4861
5785
|
if (!(error instanceof LlmError && error.code === "TIMEOUT")) return error;
|
|
5786
|
+
const key = sessionId ?? GLOBAL_HEADER_TIMEOUT_KEY;
|
|
4862
5787
|
const fingerprint = `${protocol}:${fingerprintValue(body).sha256}`;
|
|
4863
|
-
const
|
|
4864
|
-
|
|
5788
|
+
const previous = this.headerTimeoutStreaks.get(key);
|
|
5789
|
+
const count = previous?.fingerprint === fingerprint ? previous.count + 1 : 1;
|
|
5790
|
+
if (previous === void 0) {
|
|
5791
|
+
if (this.headerTimeoutStreaks.size >= CommandCodeAdapter.MAX_HEADER_TIMEOUT_STREAK_ENTRIES) {
|
|
5792
|
+
const oldest = this.headerTimeoutStreaks.keys().next().value;
|
|
5793
|
+
if (oldest !== void 0) this.headerTimeoutStreaks.delete(oldest);
|
|
5794
|
+
}
|
|
5795
|
+
} else this.headerTimeoutStreaks.delete(key);
|
|
5796
|
+
this.headerTimeoutStreaks.set(key, {
|
|
4865
5797
|
fingerprint,
|
|
4866
5798
|
count
|
|
4867
|
-
};
|
|
5799
|
+
});
|
|
4868
5800
|
if (count < MAX_CONSECUTIVE_HEADER_TIMEOUTS) return error;
|
|
4869
|
-
this.
|
|
5801
|
+
this.headerTimeoutStreaks.delete(key);
|
|
4870
5802
|
return bilingual("PROVIDER_HTTP_ERROR", `Command Code API stopped retrying: the gateway returned no response headers on ${count} consecutive attempts at the same request, each after the full ${timeoutMs}ms budget, so this is not a network blip — it is provider queueing, a Go-plan capacity limit, or a network/proxy path problem. Resending the identical payload again is unlikely to help; wait for the provider to recover, or switch model or account.`, `Command Code API 已停止重试:同一请求连续 ${count} 次在 ${timeoutMs} 毫秒内都没有收到任何响应头,这不是网络抖动,而是服务商排队、Go 套餐容量限制或网络/代理链路问题。继续原样重发同一个请求通常无济于事——可以等服务商恢复,或换模型/换账号。`);
|
|
4871
5803
|
}
|
|
4872
5804
|
/**
|
|
@@ -4883,12 +5815,11 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4883
5815
|
};
|
|
4884
5816
|
}
|
|
4885
5817
|
/**
|
|
4886
|
-
*
|
|
4887
|
-
*
|
|
4888
|
-
*
|
|
4889
|
-
* `
|
|
4890
|
-
* and
|
|
4891
|
-
* recovers in-session instead of failing after two tries. Permanent failures
|
|
5818
|
+
* The official executor owns backoff and provider reset waits. Its high
|
|
5819
|
+
* route-wide cap lets a confirmed `RATE_LIMIT` window recover in-session;
|
|
5820
|
+
* our request-error listener limits `SERVER`/`TIMEOUT`/`EMPTY_RESPONSE`/
|
|
5821
|
+
* `THROTTLED` to three retries and `TRANSPORT` to its separate budget.
|
|
5822
|
+
* Waits double from 500 ms and cap at 15 minutes (±10% jitter). Permanent failures
|
|
4892
5823
|
* (an invalid key's `INVALID_CREDENTIAL`, `UNSUPPORTED_CONTENT`, plan
|
|
4893
5824
|
* rejections) are absent from the whitelist and surface immediately instead
|
|
4894
5825
|
* of looping. Waits the pool/adapter attach as `providerRetryAfterMs` are
|
|
@@ -4906,6 +5837,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4906
5837
|
retryableCodes: [
|
|
4907
5838
|
"EMPTY_RESPONSE",
|
|
4908
5839
|
"RATE_LIMIT",
|
|
5840
|
+
THROTTLED_CODE,
|
|
4909
5841
|
"SERVER",
|
|
4910
5842
|
"TIMEOUT",
|
|
4911
5843
|
"TRANSPORT"
|
|
@@ -4932,23 +5864,72 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4932
5864
|
const imageCapable = KNOWN_IMAGE_MODELS.has(model);
|
|
4933
5865
|
return { priceImages: (images) => images.map((image) => priceRequestImage(image, model, imageCapable)) };
|
|
4934
5866
|
}
|
|
4935
|
-
/**
|
|
5867
|
+
/** 地址或缓存文件变化时丢弃旧目录,防止旧网关的路由声明影响新请求。 */
|
|
5868
|
+
currentCatalogState(connection = this.deps.options()) {
|
|
5869
|
+
const { apiBase, modelsCachePath } = connection;
|
|
5870
|
+
const source = JSON.stringify([apiBase, modelsCachePath]);
|
|
5871
|
+
if (this.catalogState?.source !== source) {
|
|
5872
|
+
this.catalog = [];
|
|
5873
|
+
this.catalogState = {
|
|
5874
|
+
source,
|
|
5875
|
+
expiresAt: 0
|
|
5876
|
+
};
|
|
5877
|
+
}
|
|
5878
|
+
return this.catalogState;
|
|
5879
|
+
}
|
|
5880
|
+
/** 直接生成也使用本地可信目录,不额外联网;并发冷请求共用一次文件读取。 */
|
|
5881
|
+
async catalogForGenerate(connection) {
|
|
5882
|
+
const state = this.currentCatalogState(connection);
|
|
5883
|
+
if (this.catalog.length > 0 || state.expiresAt > 0) return this.catalog;
|
|
5884
|
+
state.disk ??= readModelsCache(connection.modelsCachePath, connection.apiBase, false).catch(() => []);
|
|
5885
|
+
const cached = await state.disk;
|
|
5886
|
+
if (this.catalogState !== state) return cached;
|
|
5887
|
+
if (this.catalog.length > 0 || state.expiresAt > 0) return this.catalog;
|
|
5888
|
+
if (state.inflight === void 0) this.catalog = cached;
|
|
5889
|
+
return cached;
|
|
5890
|
+
}
|
|
5891
|
+
/** 共享一次刷新;调用者取消只结束自己的等待,不取消其他调用者的请求。 */
|
|
4936
5892
|
async loadCatalog(signal) {
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
5893
|
+
signal?.throwIfAborted();
|
|
5894
|
+
const connection = this.deps.options();
|
|
5895
|
+
const state = this.currentCatalogState(connection);
|
|
5896
|
+
if (Date.now() < state.expiresAt) return this.catalog;
|
|
5897
|
+
const { apiBase, modelsCachePath } = connection;
|
|
5898
|
+
if (!state.inflight) {
|
|
5899
|
+
const fallback = this.catalog;
|
|
5900
|
+
state.inflight = (async () => {
|
|
5901
|
+
let models;
|
|
5902
|
+
let ttl = MODEL_CATALOG_TTL_MS;
|
|
5903
|
+
try {
|
|
5904
|
+
const { status, record } = await this.fetchJson(`${apiBase}/provider/v1/models`, {
|
|
5905
|
+
accept: "application/json",
|
|
5906
|
+
...IDENTITY_ENCODING_HEADER,
|
|
5907
|
+
...attributionHeaders()
|
|
5908
|
+
}, MODELS_TIMEOUT_MS);
|
|
5909
|
+
if (status < 200 || status >= 300) throw new Error(`models endpoint returned ${status}`);
|
|
5910
|
+
models = parseCatalogResponse(record, apiBase);
|
|
5911
|
+
if (this.catalogState === state) await writeModelsCache(modelsCachePath, apiBase, models, () => this.catalogState === state).catch(() => void 0);
|
|
5912
|
+
} catch {
|
|
5913
|
+
ttl = MODEL_CATALOG_RETRY_MS;
|
|
5914
|
+
models = await readModelsCache(modelsCachePath, apiBase).catch(() => fallback);
|
|
5915
|
+
}
|
|
5916
|
+
if (this.catalogState === state) {
|
|
5917
|
+
this.catalog = models;
|
|
5918
|
+
state.expiresAt = Date.now() + ttl;
|
|
5919
|
+
}
|
|
5920
|
+
return models;
|
|
5921
|
+
})().finally(() => {
|
|
5922
|
+
delete state.inflight;
|
|
5923
|
+
});
|
|
4950
5924
|
}
|
|
4951
|
-
return
|
|
5925
|
+
if (!signal) return state.inflight;
|
|
5926
|
+
const inflight = state.inflight;
|
|
5927
|
+
return new Promise((resolve, reject) => {
|
|
5928
|
+
const abort = () => reject(signal.reason);
|
|
5929
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
5930
|
+
inflight.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
|
|
5931
|
+
if (signal.aborted) abort();
|
|
5932
|
+
});
|
|
4952
5933
|
}
|
|
4953
5934
|
async listModels(provider, opts) {
|
|
4954
5935
|
const catalog = await this.loadCatalog();
|
|
@@ -4965,21 +5946,16 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4965
5946
|
if (opts?.unfiltered === true) return catalog.map(toInfo).sort(compareByPlan);
|
|
4966
5947
|
const accesses = this.deps.options().filterModelsByPlan === false ? void 0 : await this.loadPoolBillingAccess();
|
|
4967
5948
|
const visible = this.deps.options().visibleModels;
|
|
4968
|
-
const allow = Array.isArray(visible)
|
|
5949
|
+
const allow = Array.isArray(visible) ? visible.filter((id) => typeof id === "string" && id !== "") : [];
|
|
4969
5950
|
const overrides = this.deps.options().modelVisibility;
|
|
4970
|
-
return catalog.filter((model) => modelVisibleForAnyAccount(model.id, accesses)).filter((model) =>
|
|
4971
|
-
const override = overrides?.[model.id];
|
|
4972
|
-
if (typeof override === "boolean") return override;
|
|
4973
|
-
return allow === void 0 || allow.has(model.id);
|
|
4974
|
-
}).map(toInfo).sort(compareByPlan);
|
|
5951
|
+
return catalog.filter((model) => modelVisibleForAnyAccount(model.id, accesses)).filter((model) => modelIsVisible(model.id, allow, overrides)).map(toInfo).sort(compareByPlan);
|
|
4975
5952
|
}
|
|
4976
5953
|
/**
|
|
4977
5954
|
* The per-model allowance bracket this account POOL falls into, or undefined
|
|
4978
5955
|
* when there is nothing honest to show.
|
|
4979
5956
|
*
|
|
4980
|
-
* The pricing page publishes
|
|
4981
|
-
*
|
|
4982
|
-
* the plans that have them" — so a pool whose highest plan is Go, Provider,
|
|
5957
|
+
* The pricing page publishes per-model monthly allowances for Go, GOAT and
|
|
5958
|
+
* Pro. A pool whose highest plan is Provider,
|
|
4983
5959
|
* Max or Ultra gets undefined rather than a neighbouring tier's figure. Taking
|
|
4984
5960
|
* the HIGHEST tier matches the question the picker already answers for a pool
|
|
4985
5961
|
* (see `modelVisibleForAnyAccount`): what the user can reach, not what the one
|
|
@@ -4999,6 +5975,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
4999
5975
|
return allowanceTierForWeight(highest);
|
|
5000
5976
|
}
|
|
5001
5977
|
async resolveModel(provider, model, signal) {
|
|
5978
|
+
this.currentCatalogState();
|
|
5002
5979
|
const entry = this.catalog.find((m) => m.id === model) ?? (await this.loadCatalog(signal)).find((m) => m.id === model);
|
|
5003
5980
|
const efforts = KNOWN_EFFORTS[model];
|
|
5004
5981
|
const vision = KNOWN_IMAGE_MODELS.has(model);
|
|
@@ -5076,7 +6053,8 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
5076
6053
|
async loadPoolBillingAccess() {
|
|
5077
6054
|
const keys = await this.poolAccountKeys();
|
|
5078
6055
|
if (keys.length === 0) return void 0;
|
|
5079
|
-
|
|
6056
|
+
const apiBase = this.deps.options().apiBase;
|
|
6057
|
+
return Promise.all(keys.map((key) => this.loadBillingAccessForKey(apiBase, key)));
|
|
5080
6058
|
}
|
|
5081
6059
|
/**
|
|
5082
6060
|
* The API keys the picker's plan filter must consult: every account the host
|
|
@@ -5100,21 +6078,22 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
5100
6078
|
* shared across concurrent callers. `undefined` means "unknown — show
|
|
5101
6079
|
* everything" (fail-open).
|
|
5102
6080
|
*/
|
|
5103
|
-
async loadBillingAccessForKey(apiKey) {
|
|
5104
|
-
const
|
|
6081
|
+
async loadBillingAccessForKey(apiBase, apiKey) {
|
|
6082
|
+
const cacheKey = this.accountCacheKey(apiBase, apiKey);
|
|
6083
|
+
const cached = this.billingAccess.get(cacheKey);
|
|
5105
6084
|
if (cached !== void 0 && Date.now() - cached.at < 3e5) return cached.value;
|
|
5106
|
-
const existing = this.billingAccessInflight.get(
|
|
6085
|
+
const existing = this.billingAccessInflight.get(cacheKey);
|
|
5107
6086
|
if (existing !== void 0) return existing;
|
|
5108
|
-
const inflight = this.fetchBillingAccess(apiKey).then((value) => {
|
|
5109
|
-
this.billingAccess.set(
|
|
6087
|
+
const inflight = this.fetchBillingAccess(apiBase, apiKey).then((value) => {
|
|
6088
|
+
this.billingAccess.set(cacheKey, {
|
|
5110
6089
|
value,
|
|
5111
6090
|
at: Date.now()
|
|
5112
6091
|
});
|
|
5113
6092
|
return value;
|
|
5114
6093
|
}).finally(() => {
|
|
5115
|
-
this.billingAccessInflight.delete(
|
|
6094
|
+
this.billingAccessInflight.delete(cacheKey);
|
|
5116
6095
|
});
|
|
5117
|
-
this.billingAccessInflight.set(
|
|
6096
|
+
this.billingAccessInflight.set(cacheKey, inflight);
|
|
5118
6097
|
return inflight;
|
|
5119
6098
|
}
|
|
5120
6099
|
/**
|
|
@@ -5126,12 +6105,10 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
5126
6105
|
* fallback (the CLI stamps plan identity from it too). Any failure resolves
|
|
5127
6106
|
* to `undefined` (fail-open) rather than breaking the picker.
|
|
5128
6107
|
*/
|
|
5129
|
-
async fetchBillingAccess(apiKey) {
|
|
6108
|
+
async fetchBillingAccess(apiBase, apiKey) {
|
|
5130
6109
|
try {
|
|
5131
|
-
const connection = this.deps.options();
|
|
5132
6110
|
const headers = await this.accountHeaders(apiKey);
|
|
5133
|
-
const
|
|
5134
|
-
const getJson = async (path) => (await this.fetchJson(`${base}${path}`, headers)).record;
|
|
6111
|
+
const getJson = async (path) => (await this.fetchJson(`${apiBase}${path}`, headers)).record;
|
|
5135
6112
|
const whoami = await getJson("/alpha/whoami");
|
|
5136
6113
|
const orgData = whoami && isRecord(whoami.org) ? whoami.org : void 0;
|
|
5137
6114
|
const orgId = orgData === void 0 ? void 0 : stringValue(orgData.id);
|
|
@@ -5153,50 +6130,65 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
5153
6130
|
}
|
|
5154
6131
|
}
|
|
5155
6132
|
/** Fresh cached billing tier weight for a key, or undefined when not known. */
|
|
5156
|
-
cachedBillingTierWeight(apiKey) {
|
|
5157
|
-
const hit = this.billingAccess.get(apiKey);
|
|
6133
|
+
cachedBillingTierWeight(apiBase, apiKey) {
|
|
6134
|
+
const hit = this.billingAccess.get(this.accountCacheKey(apiBase, apiKey));
|
|
5158
6135
|
if (hit === void 0 || Date.now() - hit.at >= 3e5) return void 0;
|
|
5159
6136
|
return hit.value?.tierWeight;
|
|
5160
6137
|
}
|
|
5161
6138
|
/** Cached protocol decision for a key, or undefined when expired/unknown. */
|
|
5162
|
-
|
|
5163
|
-
const hit = this.protocolCache.get(apiKey);
|
|
6139
|
+
cachedProtocol(apiBase, apiKey) {
|
|
6140
|
+
const hit = this.protocolCache.get(this.accountCacheKey(apiBase, apiKey));
|
|
5164
6141
|
if (hit === void 0 || Date.now() - hit.at >= 9e5) return void 0;
|
|
5165
|
-
return hit.
|
|
6142
|
+
return hit.protocol;
|
|
5166
6143
|
}
|
|
5167
|
-
rememberProtocol(apiKey,
|
|
5168
|
-
this.protocolCache.set(apiKey, {
|
|
5169
|
-
|
|
6144
|
+
rememberProtocol(apiBase, apiKey, protocol) {
|
|
6145
|
+
this.protocolCache.set(this.accountCacheKey(apiBase, apiKey), {
|
|
6146
|
+
protocol,
|
|
5170
6147
|
at: Date.now()
|
|
5171
6148
|
});
|
|
5172
6149
|
}
|
|
5173
6150
|
/**
|
|
5174
|
-
*
|
|
5175
|
-
*
|
|
5176
|
-
*
|
|
5177
|
-
*
|
|
6151
|
+
* True when the catalog routes `model` to `/provider/v1/messages`.
|
|
6152
|
+
*
|
|
6153
|
+
* The catalog's own `supported_endpoints` is authoritative (it is what
|
|
6154
|
+
* upstream publishes per model); `requiresMessagesEndpoint()` — the
|
|
6155
|
+
* `claude-*` prefix rule — is the fallback for a catalog entry that carries
|
|
6156
|
+
* no route list, which is the shape every pre-1.55 cache file has.
|
|
6157
|
+
*/
|
|
6158
|
+
routesToMessages(model) {
|
|
6159
|
+
const entry = this.catalog.find((candidate) => candidate.id === model);
|
|
6160
|
+
if (entry !== void 0 && entry.supportedEndpoints.length > 0) return entry.supportedEndpoints.includes("/messages");
|
|
6161
|
+
return requiresMessagesEndpoint(model);
|
|
6162
|
+
}
|
|
6163
|
+
/**
|
|
6164
|
+
* Choose the initial protocol for one request.
|
|
5178
6165
|
*
|
|
5179
|
-
* Models the Provider API serves
|
|
5180
|
-
* Claude family
|
|
5181
|
-
*
|
|
5182
|
-
*
|
|
5183
|
-
*
|
|
5184
|
-
*
|
|
5185
|
-
* costs nothing.
|
|
6166
|
+
* Models the Provider API serves only through `/provider/v1/messages` (the
|
|
6167
|
+
* Claude family) take the Messages transport on any tier and even under a
|
|
6168
|
+
* forced `'openai'` preference: that option is documented as "prefer the
|
|
6169
|
+
* Provider API, still fall back", and the 400 these models answer with on
|
|
6170
|
+
* Chat Completions (`Model "<id>" must be called via /provider/v1/messages`)
|
|
6171
|
+
* is a hard refusal, not a preference to honour.
|
|
5186
6172
|
*
|
|
5187
6173
|
* That decision is deliberately NOT written to `protocolCache`, which is
|
|
5188
|
-
* keyed by API key
|
|
5189
|
-
*
|
|
6174
|
+
* keyed by gateway and API key: it depends on the model, not just the account, so
|
|
6175
|
+
* remembering it would pin the whole ACCOUNT to one transport and drag
|
|
6176
|
+
* every other model along (issue #46).
|
|
6177
|
+
*
|
|
6178
|
+
* Otherwise a fresh protocol cache entry wins, then a cached (not
|
|
6179
|
+
* network-fetched) billing tier of Go — the one plan without Provider API
|
|
6180
|
+
* access. Unknown accounts default to Chat Completions and fall back to the
|
|
6181
|
+
* CLI transport only after an `upgrade_required` rejection.
|
|
5190
6182
|
*/
|
|
5191
|
-
resolveProtocol(apiKey, model) {
|
|
6183
|
+
resolveProtocol(apiBase, apiKey, model) {
|
|
5192
6184
|
const forced = this.deps.options().protocol;
|
|
5193
6185
|
if (forced === "cli") return forced;
|
|
5194
|
-
if (
|
|
6186
|
+
if (this.routesToMessages(model)) return "messages";
|
|
5195
6187
|
if (forced === "openai") return forced;
|
|
5196
|
-
const cached = this.
|
|
5197
|
-
if (cached !== void 0) return cached
|
|
5198
|
-
if (this.cachedBillingTierWeight(apiKey) === GO_TIER_WEIGHT) {
|
|
5199
|
-
this.rememberProtocol(apiKey,
|
|
6188
|
+
const cached = this.cachedProtocol(apiBase, apiKey);
|
|
6189
|
+
if (cached !== void 0) return cached;
|
|
6190
|
+
if (this.cachedBillingTierWeight(apiBase, apiKey) === GO_TIER_WEIGHT) {
|
|
6191
|
+
this.rememberProtocol(apiBase, apiKey, "cli");
|
|
5200
6192
|
return "cli";
|
|
5201
6193
|
}
|
|
5202
6194
|
return "openai";
|
|
@@ -5336,6 +6328,59 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
5336
6328
|
}
|
|
5337
6329
|
}
|
|
5338
6330
|
async *stream(options) {
|
|
6331
|
+
const timing = openRequestTiming(options.model, this.deps.onRequestTiming);
|
|
6332
|
+
let outcome = "cancelled";
|
|
6333
|
+
let errorCode;
|
|
6334
|
+
try {
|
|
6335
|
+
for await (const chunk of this.streamRequest(options, timing)) {
|
|
6336
|
+
if (chunk.type === "text-delta" || chunk.type === "reasoning-delta" || chunk.type === "tool-call-delta") timing.first("firstContentMs");
|
|
6337
|
+
if (chunk.type === "finish") outcome = "finished";
|
|
6338
|
+
yield chunk;
|
|
6339
|
+
}
|
|
6340
|
+
} catch (error) {
|
|
6341
|
+
outcome = options.signal?.aborted ? "aborted" : "error";
|
|
6342
|
+
errorCode = error instanceof LlmError ? error.code : "unknown";
|
|
6343
|
+
throw error;
|
|
6344
|
+
} finally {
|
|
6345
|
+
await timing.close(outcome, errorCode);
|
|
6346
|
+
}
|
|
6347
|
+
}
|
|
6348
|
+
/**
|
|
6349
|
+
* One turn, with at most one output-ceiling downshift.
|
|
6350
|
+
*
|
|
6351
|
+
* The ceiling a model accepts is not published anywhere the request can be
|
|
6352
|
+
* built from (see `modelOutputTokenLimit`), and the endpoint states it only
|
|
6353
|
+
* by refusing. That refusal is worth one retry rather than a failed turn: the
|
|
6354
|
+
* harness's own recovery for it — compact the context — cannot help, because
|
|
6355
|
+
* nothing about the context is wrong, and resending the identical request
|
|
6356
|
+
* would be refused identically (issue #71). Reading the stated ceiling off
|
|
6357
|
+
* the rejection turns one wasted turn into one, for good: it is recorded
|
|
6358
|
+
* process-wide, so the next request for this model is built correctly
|
|
6359
|
+
* without spending the round trip again.
|
|
6360
|
+
*
|
|
6361
|
+
* Only BEFORE the first chunk reaches the host. Once a delta has been
|
|
6362
|
+
* delivered the answer is already partly on screen, and a second request
|
|
6363
|
+
* would duplicate it — so from that point the error stands and says what is
|
|
6364
|
+
* actually wrong. One downshift per turn, and a second refusal surfaces.
|
|
6365
|
+
*/
|
|
6366
|
+
async *streamRequest(options, timing) {
|
|
6367
|
+
for (let attempt = 0;; attempt++) {
|
|
6368
|
+
const connection = this.deps.options();
|
|
6369
|
+
let delivered = false;
|
|
6370
|
+
try {
|
|
6371
|
+
for await (const chunk of this.streamAttempt(options, timing, connection)) {
|
|
6372
|
+
delivered = true;
|
|
6373
|
+
yield chunk;
|
|
6374
|
+
}
|
|
6375
|
+
return;
|
|
6376
|
+
} catch (error) {
|
|
6377
|
+
const ceiling = delivered ? void 0 : outputCeilingRefusal(connection.apiBase, options.model, error);
|
|
6378
|
+
if (attempt > 0 || ceiling === void 0) throw error;
|
|
6379
|
+
}
|
|
6380
|
+
}
|
|
6381
|
+
}
|
|
6382
|
+
async *streamAttempt(options, timing, connection) {
|
|
6383
|
+
this.currentCatalogState(connection);
|
|
5339
6384
|
if (options.stop?.length) throw new LlmError("Command Code adapter does not support stop sequences", "UNSUPPORTED_OPTION");
|
|
5340
6385
|
const hasImages = options.messages.some(hasImageContent);
|
|
5341
6386
|
let readImage;
|
|
@@ -5343,39 +6388,47 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
5343
6388
|
if (!KNOWN_IMAGE_MODELS.has(options.model)) throw new LlmError(`Command Code model "${options.model}" does not support image input; switch to a Vision-capable model (see the model registry)`, "UNSUPPORTED_CONTENT");
|
|
5344
6389
|
const attachments = this.resolveAttachments?.();
|
|
5345
6390
|
if (attachments === void 0) throw new LlmError("Command Code image input requires the durable attachment service", "UNSUPPORTED_CONTENT");
|
|
5346
|
-
readImage = (
|
|
6391
|
+
readImage = requestImages(attachments, options.signal, timing);
|
|
5347
6392
|
}
|
|
5348
|
-
const
|
|
5349
|
-
let apiKey
|
|
5350
|
-
|
|
5351
|
-
|
|
6393
|
+
const finishCredentials = timing.phase("credentials");
|
|
6394
|
+
let apiKey;
|
|
6395
|
+
try {
|
|
6396
|
+
apiKey = await this.deps.resolveApiKey(connection, options.model);
|
|
6397
|
+
} finally {
|
|
6398
|
+
finishCredentials();
|
|
6399
|
+
}
|
|
6400
|
+
const catalog = await this.catalogForGenerate(connection);
|
|
6401
|
+
options.signal?.throwIfAborted();
|
|
6402
|
+
const modelEntry = catalog.find((m) => m.id === options.model);
|
|
6403
|
+
const modelMax = Math.min(modelEntry?.maxTokens ?? Infinity, modelOutputTokenLimit(connection.apiBase, options.model));
|
|
5352
6404
|
const requestedMaxTokens = Math.min(options.maxTokens ?? modelMax, modelMax, DEFAULT_GENERATE_MAX_TOKENS);
|
|
5353
|
-
|
|
5354
|
-
let contextBudget = {
|
|
5355
|
-
maxTokens,
|
|
5356
|
-
sizePressure: false
|
|
5357
|
-
};
|
|
6405
|
+
const maxTokens = requestedMaxTokens;
|
|
5358
6406
|
const effort = options.reasoningEffort;
|
|
5359
6407
|
const supported = KNOWN_EFFORTS[options.model];
|
|
5360
6408
|
const reasoningEffort = effort && effort !== "off" && supported?.includes(effort) ? effort : void 0;
|
|
5361
6409
|
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");
|
|
5362
|
-
let protocol = this.resolveProtocol(apiKey, options.model);
|
|
6410
|
+
let protocol = this.resolveProtocol(connection.apiBase, apiKey, options.model);
|
|
5363
6411
|
let requestOptions = withSurfaceOffload(options, this.surfaceOffload, REQUEST_IMAGE_BUDGETS[0], connection.offloadSeenImagesForCache === true && protocol === "cli" ? options.model : void 0);
|
|
5364
6412
|
const buildBody = async (target) => {
|
|
5365
|
-
const
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
6413
|
+
const finishBody = timing.phase("body");
|
|
6414
|
+
try {
|
|
6415
|
+
const facts = {
|
|
6416
|
+
maxTokens: requestedMaxTokens,
|
|
6417
|
+
reasoningEffort,
|
|
6418
|
+
selectableEffort: supported !== void 0 && supported.length > 0,
|
|
6419
|
+
systemText,
|
|
6420
|
+
readImage
|
|
6421
|
+
};
|
|
6422
|
+
const built = target === "cli" ? await buildCliBody(requestOptions, connection, facts) : target === "messages" ? await buildMessagesBody(requestOptions, facts) : await buildOpenAIBody(requestOptions, facts);
|
|
6423
|
+
if (target === "cli") recordOrEmpty(built.params).max_tokens = maxTokens;
|
|
6424
|
+
else built.max_tokens = maxTokens;
|
|
6425
|
+
return built;
|
|
6426
|
+
} finally {
|
|
6427
|
+
finishBody();
|
|
6428
|
+
}
|
|
5377
6429
|
};
|
|
5378
6430
|
let body = await buildBody(protocol);
|
|
6431
|
+
const serializedBodies = /* @__PURE__ */ new WeakMap();
|
|
5379
6432
|
const trace = openStreamTrace(`${options.provider}/${options.model}`);
|
|
5380
6433
|
const tried = /* @__PURE__ */ new Set();
|
|
5381
6434
|
let attemptNumber = 0;
|
|
@@ -5387,15 +6440,16 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
5387
6440
|
options,
|
|
5388
6441
|
connection,
|
|
5389
6442
|
fetchImpl: this.fetchImpl,
|
|
6443
|
+
timing,
|
|
5390
6444
|
onResponse: (response) => {
|
|
5391
|
-
this.
|
|
6445
|
+
this.headerTimeoutStreaks.delete(options.sessionId ?? GLOBAL_HEADER_TIMEOUT_KEY);
|
|
5392
6446
|
responseMetadata = {
|
|
5393
6447
|
protocol,
|
|
5394
6448
|
headers: responseIdentifiers(response.headers)
|
|
5395
6449
|
};
|
|
5396
6450
|
trace.record("response", {
|
|
5397
6451
|
protocol,
|
|
5398
|
-
endpoint: protocol
|
|
6452
|
+
endpoint: protocolEndpoint(protocol, connection.apiBase),
|
|
5399
6453
|
status: response.status,
|
|
5400
6454
|
attempt: attemptNumber,
|
|
5401
6455
|
accountsTried: tried.size,
|
|
@@ -5419,15 +6473,26 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
5419
6473
|
sessionId: options.sessionId,
|
|
5420
6474
|
...requestFingerprint(protocol, body)
|
|
5421
6475
|
});
|
|
5422
|
-
|
|
6476
|
+
let serialized = serializedBodies.get(body);
|
|
6477
|
+
if (serialized === void 0) {
|
|
6478
|
+
const finishSerialization = timing.phase("serialization");
|
|
6479
|
+
try {
|
|
6480
|
+
serialized = JSON.stringify(body, (_key, value) => sanitizeSurrogates(value));
|
|
6481
|
+
} finally {
|
|
6482
|
+
finishSerialization();
|
|
6483
|
+
}
|
|
6484
|
+
serializedBodies.set(body, serialized);
|
|
6485
|
+
}
|
|
6486
|
+
const attempt = await connectGenerate(connectDeps, apiKey, protocol, serialized);
|
|
5423
6487
|
if ("response" in attempt) {
|
|
5424
6488
|
connected = attempt;
|
|
5425
6489
|
break;
|
|
5426
6490
|
}
|
|
5427
6491
|
const providerError = parseProviderError(attempt.errText);
|
|
5428
|
-
if (protocol
|
|
6492
|
+
if (protocol !== "cli" && isUpgradeRequiredError(attempt.status, attempt.errText, providerError)) {
|
|
6493
|
+
const wasMessages = protocol === "messages";
|
|
5429
6494
|
protocol = "cli";
|
|
5430
|
-
this.rememberProtocol(apiKey,
|
|
6495
|
+
if (!wasMessages) this.rememberProtocol(connection.apiBase, apiKey, "cli");
|
|
5431
6496
|
requestOptions = withSurfaceOffload(options, this.surfaceOffload, REQUEST_IMAGE_BUDGETS[0], connection.offloadSeenImagesForCache === true ? options.model : void 0);
|
|
5432
6497
|
body = await buildBody("cli");
|
|
5433
6498
|
continue;
|
|
@@ -5445,17 +6510,26 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
5445
6510
|
});
|
|
5446
6511
|
if (next !== void 0 && !tried.has(next)) {
|
|
5447
6512
|
apiKey = next;
|
|
6513
|
+
const nextProtocol = this.resolveProtocol(connection.apiBase, apiKey, options.model);
|
|
6514
|
+
if (nextProtocol !== protocol) {
|
|
6515
|
+
protocol = nextProtocol;
|
|
6516
|
+
requestOptions = withSurfaceOffload(options, this.surfaceOffload, REQUEST_IMAGE_BUDGETS[0], connection.offloadSeenImagesForCache === true && protocol === "cli" ? options.model : void 0);
|
|
6517
|
+
body = await buildBody(protocol);
|
|
6518
|
+
}
|
|
5448
6519
|
continue;
|
|
5449
6520
|
}
|
|
5450
6521
|
}
|
|
5451
|
-
if (rejection !== void 0 && (rejection.reason === "rate-limit" || rejection.reason === "throttled")
|
|
6522
|
+
if (rejection !== void 0 && (rejection.reason === "rate-limit" || rejection.reason === "throttled")) {
|
|
5452
6523
|
const reset = rejection.resetAtMs;
|
|
5453
|
-
const wait = reset === void 0 ? 0 : Math.min(Math.max(1e3, reset - Date.now()), RETRY_MAX_DELAY_MS);
|
|
6524
|
+
const wait = reset === void 0 ? attempt.retryAfterMs ?? 0 : Math.min(Math.max(1e3, reset - Date.now()), RETRY_MAX_DELAY_MS);
|
|
5454
6525
|
const when = reset === void 0 ? void 0 : new Date(reset).toISOString();
|
|
5455
6526
|
const window = rejection.reason === "rate-limit";
|
|
5456
|
-
throw bilingual("RATE_LIMIT", (window ? "llm-commandcode: the Command Code account is rate limited" : "llm-commandcode: the Command Code account is rate limited (429) — the provider did not report an exhausted usage window") + (when === void 0 ? "" : ` — the provider reports it resets at ${when}`), (window ? "当前 Command Code 账户已被限流" : "当前 Command Code 账户被限流(429),服务商未报告用量窗口用尽") + (when === void 0 ? "" : `,服务商给出的重置时间为 ${when}`),
|
|
6527
|
+
throw bilingual(window ? "RATE_LIMIT" : THROTTLED_CODE, (window ? "llm-commandcode: the Command Code account is rate limited" : "llm-commandcode: the Command Code account is rate limited (429) — the provider did not report an exhausted usage window") + (when === void 0 ? "" : ` — the provider reports it resets at ${when}`), (window ? "当前 Command Code 账户已被限流" : "当前 Command Code 账户被限流(429),服务商未报告用量窗口用尽") + (when === void 0 ? "" : `,服务商给出的重置时间为 ${when}`), {
|
|
6528
|
+
status: attempt.status,
|
|
6529
|
+
...wait > 0 && wait <= 9e5 ? { providerRetryAfterMs: wait } : {}
|
|
6530
|
+
});
|
|
5457
6531
|
}
|
|
5458
|
-
throw generateHttpError(attempt.status, attempt.errText, attempt.retryAfterMs, providerError
|
|
6532
|
+
throw generateHttpError(attempt.status, attempt.errText, attempt.retryAfterMs, providerError);
|
|
5459
6533
|
}
|
|
5460
6534
|
} catch (error) {
|
|
5461
6535
|
trace.record("connect-error", {
|
|
@@ -5464,7 +6538,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
5464
6538
|
});
|
|
5465
6539
|
trace.close();
|
|
5466
6540
|
if (options.signal?.aborted) throw error;
|
|
5467
|
-
throw failureWithRequestId(this.gradeHeaderTimeout(error, protocol, body, connection.requestTimeoutMs), responseMetadata);
|
|
6541
|
+
throw failureWithRequestId(this.gradeHeaderTimeout(error, protocol, body, connection.requestTimeoutMs, options.sessionId), responseMetadata);
|
|
5468
6542
|
}
|
|
5469
6543
|
if (connected === void 0) throw new LlmError("Command Code API connection failed without a response", "TRANSPORT");
|
|
5470
6544
|
const { response, cleanup } = connected;
|
|
@@ -5504,12 +6578,21 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
5504
6578
|
let usage;
|
|
5505
6579
|
let usageEmitted = false;
|
|
5506
6580
|
let endRecorded = false;
|
|
6581
|
+
let protocolStopped = false;
|
|
5507
6582
|
function* handle(event) {
|
|
5508
6583
|
captureResponseIdentifiers(responseMetadata, event);
|
|
6584
|
+
if (protocol === "messages" && isRecord(event) && event.type === "message_stop") protocolStopped = true;
|
|
5509
6585
|
for (const chunk of handleEvent(asm, protocol, event)) if (chunk.type === "finish") finish = chunk;
|
|
5510
6586
|
else if (chunk.type === "usage") usage = chunk.usage;
|
|
5511
6587
|
else yield chunk;
|
|
5512
6588
|
}
|
|
6589
|
+
function* handleLine(line) {
|
|
6590
|
+
if (protocol === "openai" && line.trim().replace(/^data:\s*/, "") === "[DONE]") {
|
|
6591
|
+
protocolStopped = true;
|
|
6592
|
+
return;
|
|
6593
|
+
}
|
|
6594
|
+
yield* handle(parseStreamEventLine(line));
|
|
6595
|
+
}
|
|
5513
6596
|
try {
|
|
5514
6597
|
for (;;) {
|
|
5515
6598
|
let read;
|
|
@@ -5540,10 +6623,11 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
5540
6623
|
buffered: buffer.length
|
|
5541
6624
|
});
|
|
5542
6625
|
if (idleFired) throw bilingual("TIMEOUT", `Command Code API stream from ${connection.apiBase} was idle for ${connection.streamIdleTimeoutMs}ms (no events) and was treated as a dead connection`, `Command Code API 流式响应已 ${connection.streamIdleTimeoutMs} 毫秒无任何事件,被判定为死连接——长思考模型可在设置中调大流空闲超时`);
|
|
5543
|
-
if (buffer.trim()) yield*
|
|
6626
|
+
if (buffer.trim()) yield* handleLine(buffer);
|
|
5544
6627
|
break;
|
|
5545
6628
|
}
|
|
5546
6629
|
const text = decoder.decode(value, { stream: true });
|
|
6630
|
+
if (value.byteLength > 0) timing.first("firstByteMs");
|
|
5547
6631
|
buffer += text;
|
|
5548
6632
|
chunkCount += 1;
|
|
5549
6633
|
totalBytes += value.byteLength;
|
|
@@ -5555,8 +6639,11 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
5555
6639
|
});
|
|
5556
6640
|
const lines = buffer.split("\n");
|
|
5557
6641
|
buffer = lines.pop() ?? "";
|
|
5558
|
-
for (const line of lines)
|
|
5559
|
-
|
|
6642
|
+
for (const line of lines) {
|
|
6643
|
+
yield* handleLine(line);
|
|
6644
|
+
if (protocolStopped) break;
|
|
6645
|
+
}
|
|
6646
|
+
if (protocolStopped || protocol === "cli" && finish !== void 0) break;
|
|
5560
6647
|
}
|
|
5561
6648
|
if (usage !== void 0) {
|
|
5562
6649
|
usageEmitted = true;
|
|
@@ -5581,9 +6668,13 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
5581
6668
|
});
|
|
5582
6669
|
endRecorded = true;
|
|
5583
6670
|
if (failure !== void 0) throw failure;
|
|
5584
|
-
|
|
6671
|
+
const replayBlocks = asm.replayBlocks;
|
|
6672
|
+
yield hasResponseIdentifiers(responseMetadata) || replayBlocks.length > 0 ? {
|
|
5585
6673
|
...finish,
|
|
5586
|
-
replayState: {
|
|
6674
|
+
replayState: {
|
|
6675
|
+
response: responseMetadata,
|
|
6676
|
+
...replayBlocks.length > 0 ? { blocks: replayBlocks } : {}
|
|
6677
|
+
}
|
|
5587
6678
|
} : finish;
|
|
5588
6679
|
} else {
|
|
5589
6680
|
const elapsed = Date.now() - streamStartedAt;
|
|
@@ -5627,40 +6718,69 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
5627
6718
|
}
|
|
5628
6719
|
}
|
|
5629
6720
|
};
|
|
6721
|
+
/**
|
|
6722
|
+
* Unwrap one level of a double-encoded provider message.
|
|
6723
|
+
*
|
|
6724
|
+
* The Messages endpoint sometimes reports a rejection with `error.message`
|
|
6725
|
+
* holding a whole JSON document rather than the sentence
|
|
6726
|
+
* (`{"type":"error","error":{"type":"invalid_request_error","message":"{\"type\":
|
|
6727
|
+
* \"error\",…}"},"request_id":"req_…"}`, measured 2026-09-29). Showing that blob
|
|
6728
|
+
* to the user hides the actual refusal, so one nesting level is peeled; the
|
|
6729
|
+
* outer text is returned unchanged when it is not JSON or carries no message.
|
|
6730
|
+
*/
|
|
6731
|
+
function unwrapProviderMessage(raw) {
|
|
6732
|
+
if (raw === void 0) return void 0;
|
|
6733
|
+
const trimmed = raw.trim();
|
|
6734
|
+
if (!trimmed.startsWith("{")) return raw;
|
|
6735
|
+
try {
|
|
6736
|
+
const inner = JSON.parse(trimmed);
|
|
6737
|
+
if (!isRecord(inner)) return raw;
|
|
6738
|
+
return stringValue(((isRecord(inner.error) ? inner.error : void 0) ?? inner).message) ?? raw;
|
|
6739
|
+
} catch {
|
|
6740
|
+
return raw;
|
|
6741
|
+
}
|
|
6742
|
+
}
|
|
5630
6743
|
/** Parse a rejected body once, retaining the nested/root distinction. */
|
|
5631
6744
|
function parseProviderError(errText) {
|
|
5632
6745
|
try {
|
|
5633
6746
|
const parsed = JSON.parse(errText);
|
|
5634
|
-
if (!isRecord(parsed)) return {};
|
|
6747
|
+
if (!isRecord(parsed)) return { message: errText };
|
|
5635
6748
|
const nested = isRecord(parsed.error) ? parsed.error : void 0;
|
|
5636
6749
|
const body = nested ?? parsed;
|
|
6750
|
+
const rawMessage = stringValue(body.message);
|
|
5637
6751
|
return {
|
|
5638
6752
|
root: parsed,
|
|
5639
6753
|
nested,
|
|
5640
6754
|
code: stringValue(body.code),
|
|
5641
6755
|
type: stringValue(body.type),
|
|
5642
|
-
|
|
6756
|
+
rawMessage,
|
|
6757
|
+
message: unwrapProviderMessage(rawMessage) ?? errText,
|
|
5643
6758
|
rateLimit: isRecord(body.rateLimit) ? body.rateLimit : void 0
|
|
5644
6759
|
};
|
|
5645
6760
|
} catch {
|
|
5646
|
-
return {};
|
|
6761
|
+
return { message: errText };
|
|
5647
6762
|
}
|
|
5648
6763
|
}
|
|
5649
|
-
function generateHttpError(status, errText, retryAfterMs, parsed = parseProviderError(errText)
|
|
6764
|
+
function generateHttpError(status, errText, retryAfterMs, parsed = parseProviderError(errText)) {
|
|
5650
6765
|
const providerCode = parsed.nested === void 0 ? void 0 : parsed.code;
|
|
5651
6766
|
const providerDetail = parsed.nested === void 0 ? "" : [
|
|
5652
6767
|
parsed.code,
|
|
5653
6768
|
parsed.type,
|
|
5654
|
-
parsed.message
|
|
6769
|
+
parsed.rawMessage ?? parsed.message
|
|
5655
6770
|
].filter((part) => part !== void 0 && part !== "").join(" ");
|
|
5656
6771
|
const detail = providerCode ?? `HTTP ${status}`;
|
|
5657
6772
|
const overflowDetail = providerDetail !== "" ? providerDetail : errText.slice(0, 500);
|
|
6773
|
+
if (status < 500 && OUTPUT_LIMIT_REJECTION.test(overflowDetail)) {
|
|
6774
|
+
const stated = /max_tokens:\s*\d+\s*>\s*\d+/.exec(overflowDetail)?.[0] ?? (parsed.message !== void 0 && parsed.message.length <= 200 ? parsed.message : void 0);
|
|
6775
|
+
return bilingual("PROVIDER_HTTP_ERROR", `Command Code API error ${status}: this request's max_tokens is not valid for this model` + (stated ? ` (${stated})` : "") + " — the session is not oversized, so compacting it will not help; lower \"Max output tokens\" or pick a model with a larger output ceiling", `Command Code API 返回 ${status}:本次请求的 max_tokens 对该模型不合法` + (stated ? `(${stated})` : "") + "——会话内容并未超长,压缩上下文无效;请调低「最大输出 token」或改用输出上限更大的模型", { status });
|
|
6776
|
+
}
|
|
5658
6777
|
if (status < 500 && isContextOverflowDetail(overflowDetail)) return bilingual(CONTEXT_WINDOW_EXCEEDED_CODE, `Command Code API error ${status}: the request exceeds the model's context window — this session is being compacted and the request retried`, `Command Code API 返回 ${status}:请求内容超出模型上下文窗口——正在压缩上下文后重试;如仍失败,请新建会话或减少上下文`, { status });
|
|
5659
|
-
if (status === 400 && sizePressure && AMBIGUOUS_SIZE_REJECTION.test(providerDetail) && /\bprovider_error\b/i.test(providerDetail)) return bilingual(CONTEXT_WINDOW_EXCEEDED_CODE, "Command Code API error 400: the provider could not complete a request near the model context limit — compacting the session and retrying", "Command Code API 返回 400:请求接近模型上下文上限,服务商无法完成——正在压缩上下文后重试", { status });
|
|
5660
6778
|
if (status === 422 && isZdrNoProviders(providerCode, providerDetail, errText)) return bilingual("PROVIDER_HTTP_ERROR", `Command Code API error 422 (${detail}): zero data retention is enforced but no ZDR-capable upstream is available for this model — turn the ZDR setting off (or use another model)`, "Command Code API 返回 422:已开启零数据保留(ZDR),但该模型当前没有可用的 ZDR 上游——请关闭 ZDR 开关,或改用其他模型", { status: 422 });
|
|
5661
6779
|
if (status === 401) return bilingual("INVALID_CREDENTIAL", `Command Code API error 401 (${detail}): the API key is missing or invalid — check the key stored for COMMANDCODE_API_KEY (Models page) or the auth file`, "Command Code API 返回 401:API 密钥缺失或无效——请在设置页检查 COMMANDCODE_API_KEY 存储的密钥,或检查 auth 文件", { status: 401 });
|
|
5662
6780
|
if (status === 413) return bilingual("PROVIDER_HTTP_ERROR", "Command Code API error 413: the request body exceeds the provider's size limit (the cap is undocumented, measured at about 50 MB) — the session history is too large to send, usually because of accumulated image attachments. Start a new session, or drop the image-heavy part of this one, and retry", "Command Code API 返回 413:请求体超过服务端的体积上限(官方未公开,实测约 50 MB)——会话历史过大,通常是历史图片累积所致。请新建会话,或移除本会话中图片较多的部分后重试", { status: 413 });
|
|
5663
|
-
|
|
6781
|
+
const diagnosticCode = parsed.code ?? /^\s*(MODEL_NOT_IN_PLAN|INSUFFICIENT_CREDITS|USAGE_EXCEEDED|PREMIUM_CREDITS_EXHAUSTED)\b/i.exec(parsed.message ?? "")?.[1];
|
|
6782
|
+
const diagnostic = diagnosticCode !== void 0 && /^[A-Za-z0-9_.:-]{1,80}$/.test(diagnosticCode) ? `(${diagnosticCode})` : "";
|
|
6783
|
+
return new LlmError(`Command Code 请求失败(HTTP ${status})${diagnostic}:` + (status === 429 ? "请求受到用量或速率限制,请稍后重试并检查账户额度。" : status === 408 ? "服务端处理超时,请稍后重试。" : status >= 500 ? "上游服务暂时无法完成请求,请稍后重试。" : "服务端拒绝了本次请求,请检查模型、套餐和请求设置。"), httpErrorCode(status), {
|
|
5664
6784
|
status,
|
|
5665
6785
|
...retryAfterMs !== void 0 && retryAfterMs > 0 && retryAfterMs <= 9e5 ? { providerRetryAfterMs: retryAfterMs } : {}
|
|
5666
6786
|
});
|
|
@@ -5678,7 +6798,7 @@ function generateHttpError(status, errText, retryAfterMs, parsed = parseProvider
|
|
|
5678
6798
|
* rejection repeats identically on every attempt.
|
|
5679
6799
|
*/
|
|
5680
6800
|
function httpErrorCode(status) {
|
|
5681
|
-
if (status === 429) return
|
|
6801
|
+
if (status === 429) return THROTTLED_CODE;
|
|
5682
6802
|
if (status === 408) return "TIMEOUT";
|
|
5683
6803
|
if (status >= 500) return "SERVER";
|
|
5684
6804
|
return "PROVIDER_HTTP_ERROR";
|
|
@@ -5898,8 +7018,202 @@ function handleOpenAIEvent(asm, event) {
|
|
|
5898
7018
|
return chunks;
|
|
5899
7019
|
}
|
|
5900
7020
|
/** Dispatch one parsed stream event to the active transport's handler. */
|
|
7021
|
+
/**
|
|
7022
|
+
* Messages `usage` → the harness meter.
|
|
7023
|
+
*
|
|
7024
|
+
* The field names are the Anthropic ones, and unlike Chat Completions
|
|
7025
|
+
* `input_tokens` already excludes cache reads and writes, so it is mapped
|
|
7026
|
+
* as-is rather than reduced (the shape `dsh-llm-deepseek` implements).
|
|
7027
|
+
* Measured 2026-09-29: `{ input_tokens, cache_creation_input_tokens,
|
|
7028
|
+
* cache_read_input_tokens, output_tokens, output_tokens_details:
|
|
7029
|
+
* { thinking_tokens } }`, plus gateway additions the meter ignores.
|
|
7030
|
+
*/
|
|
7031
|
+
function mapMessagesUsage(raw, previous) {
|
|
7032
|
+
if (!isRecord(raw)) return void 0;
|
|
7033
|
+
const inputTokens = numberValue(raw.input_tokens) ?? previous?.inputTokens ?? 0;
|
|
7034
|
+
const outputTokens = numberValue(raw.output_tokens) ?? previous?.outputTokens ?? 0;
|
|
7035
|
+
const cacheRead = numberValue(raw.cache_read_input_tokens) ?? previous?.cacheReadTokens ?? 0;
|
|
7036
|
+
const cacheWrite = numberValue(raw.cache_creation_input_tokens) ?? previous?.cacheWriteTokens ?? 0;
|
|
7037
|
+
const reasoningTokens = numberValue((isRecord(raw.output_tokens_details) ? raw.output_tokens_details : void 0)?.thinking_tokens) ?? previous?.reasoningTokens;
|
|
7038
|
+
const usage = {
|
|
7039
|
+
inputTokens: Math.max(0, inputTokens),
|
|
7040
|
+
outputTokens,
|
|
7041
|
+
cacheReadTokens: cacheRead
|
|
7042
|
+
};
|
|
7043
|
+
if (cacheWrite > 0) usage.cacheWriteTokens = cacheWrite;
|
|
7044
|
+
if (reasoningTokens !== void 0) usage.reasoningTokens = reasoningTokens;
|
|
7045
|
+
return usage;
|
|
7046
|
+
}
|
|
7047
|
+
/** The harness block type one Messages content block maps to. */
|
|
7048
|
+
function messagesBlockType(native) {
|
|
7049
|
+
if (native.type === "text") return "text";
|
|
7050
|
+
if (native.type === "thinking") return "reasoning";
|
|
7051
|
+
if (native.type === "tool_use") return "tool-call";
|
|
7052
|
+
}
|
|
7053
|
+
/**
|
|
7054
|
+
* Handle one Messages-transport stream event, appending harness StreamChunks.
|
|
7055
|
+
*
|
|
7056
|
+
* The event sequence is the Anthropic one measured on 2026-09-29:
|
|
7057
|
+
* `message_start`, then per content block `content_block_start` /
|
|
7058
|
+
* `content_block_delta`* / `content_block_stop`, then `message_delta`
|
|
7059
|
+
* (carrying `stop_reason` and the final usage) and `message_stop`. A `ping`
|
|
7060
|
+
* keepalive interleaves freely and carries no content. The terminal chunks are
|
|
7061
|
+
* NOT emitted here: `stream()` holds the success finish until it has validated
|
|
7062
|
+
* the assembled answer, and it attaches the replay envelope.
|
|
7063
|
+
*/
|
|
7064
|
+
function handleMessagesEvent(asm, event) {
|
|
7065
|
+
const chunks = [];
|
|
7066
|
+
if (!isRecord(event)) return chunks;
|
|
7067
|
+
switch (event.type) {
|
|
7068
|
+
case "message_start": {
|
|
7069
|
+
const usage = mapMessagesUsage((isRecord(event.message) ? event.message : void 0)?.usage);
|
|
7070
|
+
if (usage) {
|
|
7071
|
+
asm.messagesUsage = usage;
|
|
7072
|
+
chunks.push({
|
|
7073
|
+
type: "usage",
|
|
7074
|
+
usage
|
|
7075
|
+
});
|
|
7076
|
+
}
|
|
7077
|
+
break;
|
|
7078
|
+
}
|
|
7079
|
+
case "content_block_start": {
|
|
7080
|
+
const wireIndex = numberValue(event.index);
|
|
7081
|
+
const native = isRecord(event.content_block) ? event.content_block : void 0;
|
|
7082
|
+
const type = native ? messagesBlockType(native) : void 0;
|
|
7083
|
+
if (wireIndex === void 0 || type === void 0) break;
|
|
7084
|
+
const chunkIndex = asm.nextIndex++;
|
|
7085
|
+
const state = {
|
|
7086
|
+
type,
|
|
7087
|
+
text: stringValue(native?.text) ?? "",
|
|
7088
|
+
signature: stringValue(native?.signature) ?? "",
|
|
7089
|
+
json: "",
|
|
7090
|
+
id: stringValue(native?.id) ?? "",
|
|
7091
|
+
name: stringValue(native?.name) ?? "",
|
|
7092
|
+
chunkIndex
|
|
7093
|
+
};
|
|
7094
|
+
asm.messagesBlocks.set(wireIndex, state);
|
|
7095
|
+
asm.replayBlocks[chunkIndex] = { type };
|
|
7096
|
+
chunks.push({
|
|
7097
|
+
type: "block-start",
|
|
7098
|
+
index: chunkIndex,
|
|
7099
|
+
blockType: type
|
|
7100
|
+
});
|
|
7101
|
+
if (type === "tool-call" && state.id !== "") chunks.push({
|
|
7102
|
+
type: "tool-call-delta",
|
|
7103
|
+
index: chunkIndex,
|
|
7104
|
+
id: ToolCallId(state.id),
|
|
7105
|
+
name: state.name,
|
|
7106
|
+
argumentsDelta: ""
|
|
7107
|
+
});
|
|
7108
|
+
break;
|
|
7109
|
+
}
|
|
7110
|
+
case "content_block_delta": {
|
|
7111
|
+
const wireIndex = numberValue(event.index);
|
|
7112
|
+
const state = wireIndex === void 0 ? void 0 : asm.messagesBlocks.get(wireIndex);
|
|
7113
|
+
const delta = isRecord(event.delta) ? event.delta : void 0;
|
|
7114
|
+
if (!state || !delta) break;
|
|
7115
|
+
const index = state.chunkIndex;
|
|
7116
|
+
if (delta.type === "text_delta" && state.type === "text") {
|
|
7117
|
+
const text = stringValue(delta.text) ?? "";
|
|
7118
|
+
state.text += text;
|
|
7119
|
+
if (text.trim() !== "") asm.sawContent = true;
|
|
7120
|
+
chunks.push({
|
|
7121
|
+
type: "text-delta",
|
|
7122
|
+
index,
|
|
7123
|
+
text
|
|
7124
|
+
});
|
|
7125
|
+
} else if (delta.type === "thinking_delta" && state.type === "reasoning") {
|
|
7126
|
+
const text = stringValue(delta.thinking) ?? "";
|
|
7127
|
+
state.text += text;
|
|
7128
|
+
chunks.push({
|
|
7129
|
+
type: "reasoning-delta",
|
|
7130
|
+
index,
|
|
7131
|
+
text
|
|
7132
|
+
});
|
|
7133
|
+
} else if (delta.type === "signature_delta" && state.type === "reasoning") {
|
|
7134
|
+
state.signature += stringValue(delta.signature) ?? "";
|
|
7135
|
+
const entry = asm.replayBlocks[index];
|
|
7136
|
+
if (entry) entry.signature = state.signature;
|
|
7137
|
+
} else if (delta.type === "input_json_delta" && state.type === "tool-call") {
|
|
7138
|
+
const fragment = stringValue(delta.partial_json) ?? "";
|
|
7139
|
+
state.json += fragment;
|
|
7140
|
+
chunks.push({
|
|
7141
|
+
type: "tool-call-delta",
|
|
7142
|
+
index,
|
|
7143
|
+
id: ToolCallId(state.id),
|
|
7144
|
+
name: state.name,
|
|
7145
|
+
argumentsDelta: fragment
|
|
7146
|
+
});
|
|
7147
|
+
}
|
|
7148
|
+
break;
|
|
7149
|
+
}
|
|
7150
|
+
case "content_block_stop": {
|
|
7151
|
+
const wireIndex = numberValue(event.index);
|
|
7152
|
+
if (wireIndex === void 0) break;
|
|
7153
|
+
const state = asm.messagesBlocks.get(wireIndex);
|
|
7154
|
+
if (!state) break;
|
|
7155
|
+
asm.messagesBlocks.delete(wireIndex);
|
|
7156
|
+
const index = state.chunkIndex;
|
|
7157
|
+
if (state.type === "text") {
|
|
7158
|
+
if (state.text.trim() !== "") asm.sawContent = true;
|
|
7159
|
+
chunks.push({
|
|
7160
|
+
type: "block-end",
|
|
7161
|
+
index,
|
|
7162
|
+
block: {
|
|
7163
|
+
type: "text",
|
|
7164
|
+
text: state.text
|
|
7165
|
+
}
|
|
7166
|
+
});
|
|
7167
|
+
} else if (state.type === "reasoning") chunks.push({
|
|
7168
|
+
type: "block-end",
|
|
7169
|
+
index,
|
|
7170
|
+
block: {
|
|
7171
|
+
type: "reasoning",
|
|
7172
|
+
text: state.text
|
|
7173
|
+
}
|
|
7174
|
+
});
|
|
7175
|
+
else {
|
|
7176
|
+
if (state.id !== "") asm.sawContent = true;
|
|
7177
|
+
chunks.push({
|
|
7178
|
+
type: "block-end",
|
|
7179
|
+
index,
|
|
7180
|
+
block: {
|
|
7181
|
+
type: "tool-call",
|
|
7182
|
+
id: ToolCallId(state.id),
|
|
7183
|
+
name: state.name,
|
|
7184
|
+
arguments: state.json || "{}"
|
|
7185
|
+
}
|
|
7186
|
+
});
|
|
7187
|
+
}
|
|
7188
|
+
break;
|
|
7189
|
+
}
|
|
7190
|
+
case "message_delta": {
|
|
7191
|
+
const reason = stringValue((isRecord(event.delta) ? event.delta : void 0)?.stop_reason);
|
|
7192
|
+
if (reason !== void 0) asm.finishReason = reason;
|
|
7193
|
+
const usage = mapMessagesUsage(event.usage, asm.messagesUsage);
|
|
7194
|
+
if (usage) {
|
|
7195
|
+
asm.messagesUsage = usage;
|
|
7196
|
+
chunks.push({
|
|
7197
|
+
type: "usage",
|
|
7198
|
+
usage
|
|
7199
|
+
});
|
|
7200
|
+
}
|
|
7201
|
+
break;
|
|
7202
|
+
}
|
|
7203
|
+
case "message_stop":
|
|
7204
|
+
chunks.push({
|
|
7205
|
+
type: "finish",
|
|
7206
|
+
reason: mapFinishReason(asm.finishReason)
|
|
7207
|
+
});
|
|
7208
|
+
break;
|
|
7209
|
+
case "ping": break;
|
|
7210
|
+
case "error": throw streamErrorToLlmError(event.error ?? event, stringValue(event.message));
|
|
7211
|
+
}
|
|
7212
|
+
return chunks;
|
|
7213
|
+
}
|
|
5901
7214
|
function handleEvent(asm, protocol, event) {
|
|
5902
|
-
|
|
7215
|
+
if (protocol === "cli") return handleCliEvent(asm, event);
|
|
7216
|
+
return protocol === "messages" ? handleMessagesEvent(asm, event) : handleOpenAIEvent(asm, event);
|
|
5903
7217
|
}
|
|
5904
7218
|
/**
|
|
5905
7219
|
* Parse an HTTP `Retry-After` value (delay-seconds or an HTTP-date) into
|
|
@@ -5928,7 +7242,7 @@ function parseRetryAfterMs(value, now = Date.now()) {
|
|
|
5928
7242
|
* one function serves both — a new reason cannot drift between them.
|
|
5929
7243
|
*/
|
|
5930
7244
|
function mapFinishReason(reason) {
|
|
5931
|
-
if (reason === "tool-calls" || reason === "tool_calls") return { kind: "tool-calls" };
|
|
7245
|
+
if (reason === "tool-calls" || reason === "tool_calls" || reason === "tool_use") return { kind: "tool-calls" };
|
|
5932
7246
|
if (reason === "length" || reason === "max_tokens" || reason === "max-tokens" || reason === "max_output_tokens") return { kind: "max-tokens" };
|
|
5933
7247
|
return { kind: "stop" };
|
|
5934
7248
|
}
|
|
@@ -6774,7 +8088,7 @@ function applyUsageRemote(ctx, deps) {
|
|
|
6774
8088
|
*
|
|
6775
8089
|
* @module dsh-commandcode-provider/login
|
|
6776
8090
|
*/
|
|
6777
|
-
/**
|
|
8091
|
+
/** 登录总期限包含浏览器回调、密钥验证与存储,不能在收到回调后撤掉保护。 */
|
|
6778
8092
|
const LOGIN_TIMEOUT_MS = 12e4;
|
|
6779
8093
|
/** First local port the flow tries (mirrors the CLI). */
|
|
6780
8094
|
const LOGIN_START_PORT = 5959;
|
|
@@ -6812,7 +8126,7 @@ function studioBaseForApiBase(apiBase) {
|
|
|
6812
8126
|
* tests). Mirrors the CLI's verdicts: 401 → invalid_key, other non-OK →
|
|
6813
8127
|
* server_error, transport failure → network_error.
|
|
6814
8128
|
*/
|
|
6815
|
-
async function validateCommandApiKey(fetchImpl, apiBase, apiKey) {
|
|
8129
|
+
async function validateCommandApiKey(fetchImpl, apiBase, apiKey, signal) {
|
|
6816
8130
|
try {
|
|
6817
8131
|
const response = await fetchImpl(`${apiBase}/alpha/whoami`, {
|
|
6818
8132
|
method: "GET",
|
|
@@ -6820,8 +8134,10 @@ async function validateCommandApiKey(fetchImpl, apiBase, apiKey) {
|
|
|
6820
8134
|
"Content-Type": "application/json",
|
|
6821
8135
|
...IDENTITY_ENCODING_HEADER,
|
|
6822
8136
|
Authorization: `Bearer ${apiKey}`
|
|
6823
|
-
}
|
|
8137
|
+
},
|
|
8138
|
+
...signal === void 0 ? {} : { signal }
|
|
6824
8139
|
});
|
|
8140
|
+
response.body?.cancel().catch(() => void 0);
|
|
6825
8141
|
if (response.status === 401) return {
|
|
6826
8142
|
valid: false,
|
|
6827
8143
|
error: "invalid_key"
|
|
@@ -6867,6 +8183,7 @@ var CommandCodeLoginFlow = class {
|
|
|
6867
8183
|
statusValue = { state: "idle" };
|
|
6868
8184
|
server;
|
|
6869
8185
|
timer;
|
|
8186
|
+
attemptAbort;
|
|
6870
8187
|
/** Settle hooks of the live attempt's callback promise. */
|
|
6871
8188
|
settle;
|
|
6872
8189
|
/**
|
|
@@ -6936,6 +8253,7 @@ var CommandCodeLoginFlow = class {
|
|
|
6936
8253
|
async startAttempt(targetRef) {
|
|
6937
8254
|
this.teardown();
|
|
6938
8255
|
const attempt = ++this.attemptSeq;
|
|
8256
|
+
this.attemptAbort = new AbortController();
|
|
6939
8257
|
const port = await this.findPort();
|
|
6940
8258
|
const expectedState = this.deps.randomToken?.(32) ?? randomBytes(32).toString("base64url");
|
|
6941
8259
|
const settled = new Promise((resolve, reject) => {
|
|
@@ -6965,11 +8283,12 @@ var CommandCodeLoginFlow = class {
|
|
|
6965
8283
|
})
|
|
6966
8284
|
});
|
|
6967
8285
|
this.timer = setTimeout(() => {
|
|
8286
|
+
if (!this.ownsAttempt(attempt)) return;
|
|
6968
8287
|
this.teardown();
|
|
6969
8288
|
this.setStatus({
|
|
6970
8289
|
state: "failed",
|
|
6971
8290
|
reason: "timeout",
|
|
6972
|
-
message: "
|
|
8291
|
+
message: "登录未在规定时间内完成,请重试。"
|
|
6973
8292
|
});
|
|
6974
8293
|
}, this.deps.timeoutMs ?? 12e4);
|
|
6975
8294
|
this.timer.unref?.();
|
|
@@ -7137,7 +8456,8 @@ var CommandCodeLoginFlow = class {
|
|
|
7137
8456
|
settleAttempt(json, code, body, failure, credentials) {
|
|
7138
8457
|
json(code, body);
|
|
7139
8458
|
const settle = this.settle;
|
|
7140
|
-
this.teardown();
|
|
8459
|
+
if (failure !== void 0) this.teardown();
|
|
8460
|
+
else this.closeCallback();
|
|
7141
8461
|
if (settle === void 0) return;
|
|
7142
8462
|
if (failure !== void 0) settle.reject(failure);
|
|
7143
8463
|
else if (credentials !== void 0) settle.resolve(credentials);
|
|
@@ -7154,10 +8474,11 @@ var CommandCodeLoginFlow = class {
|
|
|
7154
8474
|
*/
|
|
7155
8475
|
async complete(attempt, credentials, targetRef) {
|
|
7156
8476
|
if (!this.ownsAttempt(attempt)) return;
|
|
7157
|
-
const validation = await validateCommandApiKey(this.deps.fetchImpl ?? fetch, this.readApiBase(), credentials.apiKey);
|
|
8477
|
+
const validation = await validateCommandApiKey(this.deps.fetchImpl ?? fetch, this.readApiBase(), credentials.apiKey, this.attemptAbort?.signal);
|
|
7158
8478
|
if (!this.ownsAttempt(attempt)) return;
|
|
7159
8479
|
if (!validation.valid) {
|
|
7160
8480
|
const reason = validation.error === "invalid_key" ? "invalid-key" : validation.error === "network_error" ? "network" : "error";
|
|
8481
|
+
this.teardown();
|
|
7161
8482
|
this.setStatus({
|
|
7162
8483
|
state: "failed",
|
|
7163
8484
|
reason,
|
|
@@ -7171,6 +8492,7 @@ var CommandCodeLoginFlow = class {
|
|
|
7171
8492
|
await this.deps.storeKey(credentials, targetRef);
|
|
7172
8493
|
} catch (error) {
|
|
7173
8494
|
if (!this.ownsAttempt(attempt)) return;
|
|
8495
|
+
this.teardown();
|
|
7174
8496
|
this.setStatus({
|
|
7175
8497
|
state: "failed",
|
|
7176
8498
|
reason: "unavailable",
|
|
@@ -7179,7 +8501,7 @@ var CommandCodeLoginFlow = class {
|
|
|
7179
8501
|
return;
|
|
7180
8502
|
}
|
|
7181
8503
|
if (!this.ownsAttempt(attempt)) return;
|
|
7182
|
-
this.
|
|
8504
|
+
this.teardown();
|
|
7183
8505
|
this.setStatus({
|
|
7184
8506
|
state: "success",
|
|
7185
8507
|
userName: credentials.userName,
|
|
@@ -7194,6 +8516,7 @@ var CommandCodeLoginFlow = class {
|
|
|
7194
8516
|
failFrom(attempt, failure) {
|
|
7195
8517
|
if (!(failure instanceof LoginSettleError)) return;
|
|
7196
8518
|
if (!this.ownsAttempt(attempt)) return;
|
|
8519
|
+
this.teardown();
|
|
7197
8520
|
this.setStatus({
|
|
7198
8521
|
state: "failed",
|
|
7199
8522
|
reason: failure.reason,
|
|
@@ -7206,13 +8529,19 @@ var CommandCodeLoginFlow = class {
|
|
|
7206
8529
|
this.timer = void 0;
|
|
7207
8530
|
}
|
|
7208
8531
|
}
|
|
7209
|
-
/**
|
|
7210
|
-
|
|
7211
|
-
this.clearTimer();
|
|
8532
|
+
/** 消费一次回调后关闭监听;保留总期限以约束后续验证。 */
|
|
8533
|
+
closeCallback() {
|
|
7212
8534
|
closeServer(this.server);
|
|
7213
8535
|
this.server = void 0;
|
|
7214
8536
|
this.settle = void 0;
|
|
7215
8537
|
}
|
|
8538
|
+
/** 结束整个尝试,同时中止验证请求,迟到结果由所有权检查拦截。 */
|
|
8539
|
+
teardown() {
|
|
8540
|
+
this.clearTimer();
|
|
8541
|
+
this.attemptAbort?.abort();
|
|
8542
|
+
this.attemptAbort = void 0;
|
|
8543
|
+
this.closeCallback();
|
|
8544
|
+
}
|
|
7216
8545
|
};
|
|
7217
8546
|
/** Close one loopback server without letting "not running"/"already closed" escape. */
|
|
7218
8547
|
function closeServer(server) {
|
|
@@ -7530,9 +8859,8 @@ function buildCommandCodeTuiSection(deps) {
|
|
|
7530
8859
|
* ONE draft: every one of them then parses that same draft on save, all N
|
|
7531
8860
|
* write ops address the same path, and only the LAST field's op survives —
|
|
7532
8861
|
* which silently rewrote the allowlist from the last catalog model instead
|
|
7533
|
-
* of the one that was toggled.
|
|
7534
|
-
*
|
|
7535
|
-
* state leaves no residue.
|
|
8862
|
+
* of the one that was toggled. 保存显式布尔值,不能因为与白名单相同就
|
|
8863
|
+
* 清除用户层:清除后可能重新露出基础配置中的相反开关。
|
|
7536
8864
|
*/
|
|
7537
8865
|
const modelField = (id, hint, group) => ({
|
|
7538
8866
|
path: ["modelVisibility", id],
|
|
@@ -7546,11 +8874,9 @@ function buildCommandCodeTuiSection(deps) {
|
|
|
7546
8874
|
return String(listed.length === 0 || listed.includes(id));
|
|
7547
8875
|
},
|
|
7548
8876
|
parse: (text) => {
|
|
7549
|
-
|
|
7550
|
-
const listed = storedIds(deps.visibleModels());
|
|
7551
|
-
return on === (listed.length === 0 || listed.includes(id)) ? { kind: "clear" } : {
|
|
8877
|
+
return {
|
|
7552
8878
|
kind: "set",
|
|
7553
|
-
value:
|
|
8879
|
+
value: text.trim() === "true"
|
|
7554
8880
|
};
|
|
7555
8881
|
}
|
|
7556
8882
|
});
|
|
@@ -7886,7 +9212,7 @@ function transportBudgetMessage(failureMessage, maxRetries) {
|
|
|
7886
9212
|
* Mark one schema field volatile.
|
|
7887
9213
|
*
|
|
7888
9214
|
* `.volatile()` is unconditional: it is a schemastery 3.18.3 feature and the
|
|
7889
|
-
* only supported engine (`dsh 0.2.0-rc.
|
|
9215
|
+
* only supported engine (`dsh 0.2.0-rc.2`) pins `~3.18.4`, so the method is
|
|
7890
9216
|
* always present. It is reached through a structural member so the caller's
|
|
7891
9217
|
* declared field type survives — the mark changes what the schema PARSES to (a
|
|
7892
9218
|
* live reference), not the plugin-facing `Config` shape.
|
|
@@ -7997,6 +9323,7 @@ const Config = z.object(markVolatileFields({
|
|
|
7997
9323
|
apiKey: z.string().role("secret")
|
|
7998
9324
|
})),
|
|
7999
9325
|
activeAccount: z.string(),
|
|
9326
|
+
credentialCleanupRefs: z.array(z.string().role("credential-ref")),
|
|
8000
9327
|
modelAccountRules: z.array(z.object({
|
|
8001
9328
|
models: z.array(z.string()),
|
|
8002
9329
|
account: z.string()
|
|
@@ -8120,10 +9447,13 @@ function apply(ctx, config) {
|
|
|
8120
9447
|
return Math.min(Math.trunc(value), 50);
|
|
8121
9448
|
};
|
|
8122
9449
|
const sessionAgents = /* @__PURE__ */ new WeakMap();
|
|
8123
|
-
ctx.on("agent/request-error", async ({ agent, failure, signal }, next) => {
|
|
8124
|
-
if (signal.aborted) return next();
|
|
9450
|
+
ctx.on("agent/request-error", async ({ agent, provider, failure, signal }, next) => {
|
|
9451
|
+
if (signal.aborted || provider !== "commandcode") return next();
|
|
8125
9452
|
const session = Reflect.get(agent, "session");
|
|
8126
9453
|
if (typeof session === "object" && session !== null) sessionAgents.set(session, agent);
|
|
9454
|
+
const transient = absorbTransientFailure(agent, failure.code);
|
|
9455
|
+
if (transient === "exhausted") throw new Error(transientBudgetMessage(failure.code, failure.message));
|
|
9456
|
+
if (transient === "retry") return next();
|
|
8127
9457
|
const decision = absorbTransportFailure(agent, failure.code, transportMaxRetries());
|
|
8128
9458
|
if (decision === "ignored") return next();
|
|
8129
9459
|
if (decision === "retry") return { kind: "retry" };
|
|
@@ -8139,15 +9469,21 @@ function apply(ctx, config) {
|
|
|
8139
9469
|
}
|
|
8140
9470
|
if (action !== "reset") return;
|
|
8141
9471
|
const agent = sessionAgents.get(session);
|
|
8142
|
-
if (agent !== void 0)
|
|
9472
|
+
if (agent !== void 0) {
|
|
9473
|
+
resetTransportFailures(agent);
|
|
9474
|
+
resetTransientFailures(agent);
|
|
9475
|
+
}
|
|
8143
9476
|
});
|
|
8144
9477
|
ctx.on("agent/status", ({ agent, status }) => {
|
|
8145
|
-
if (status === "idle")
|
|
9478
|
+
if (status === "idle") {
|
|
9479
|
+
resetTransportFailures(agent);
|
|
9480
|
+
resetTransientFailures(agent);
|
|
9481
|
+
}
|
|
8146
9482
|
});
|
|
8147
9483
|
const usageReports = async () => {
|
|
8148
9484
|
const described = await pool.describeAccounts();
|
|
8149
9485
|
const byId = new Map(described.map((account) => [account.slot.id, account]));
|
|
8150
|
-
const active =
|
|
9486
|
+
const active = await pool.activeAccount();
|
|
8151
9487
|
return { accounts: await Promise.all(slots().map(async (slot) => {
|
|
8152
9488
|
const account = byId.get(slot.id);
|
|
8153
9489
|
let report;
|