@mars-sea/dsh-commandcode-provider 0.13.4 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/README.md +31 -14
- package/README.zh-CN.md +19 -6
- package/lib/client.js +273 -64
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +173 -66
- package/lib/index.js +689 -178
- package/lib/index.js.map +1 -1
- package/package.json +42 -42
package/lib/index.js
CHANGED
|
@@ -103,7 +103,7 @@ const PLAN_ORDER = Object.fromEntries(PLAN_TIER_ORDER.map((tier, index) => [tier
|
|
|
103
103
|
* subscription-plan labels, deals, and hourly (peak/off-peak) pricing.
|
|
104
104
|
*
|
|
105
105
|
* Everything here is synced from official sources — the command-code CLI
|
|
106
|
-
* bundle's model table (`dist/cli.mjs`, re-verified at command-code@1.
|
|
106
|
+
* bundle's model table (`dist/cli.mjs`, re-verified at command-code@1.84.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
|
|
@@ -315,6 +315,13 @@ const KNOWN_EFFORTS = {
|
|
|
315
315
|
"medium",
|
|
316
316
|
"high"
|
|
317
317
|
],
|
|
318
|
+
"reflection/beam:free": [
|
|
319
|
+
"low",
|
|
320
|
+
"medium",
|
|
321
|
+
"high",
|
|
322
|
+
"xhigh",
|
|
323
|
+
"max"
|
|
324
|
+
],
|
|
318
325
|
"sakana/fugu-ultra": ["high", "xhigh"],
|
|
319
326
|
"stepfun/Step-5-Preview": [
|
|
320
327
|
"low",
|
|
@@ -622,9 +629,13 @@ const KNOWN_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
|
622
629
|
* what the current CLI says.
|
|
623
630
|
*
|
|
624
631
|
* 1.82.4 then reversed 1.74.1's removal of `meituan/LongCat-2.0:free` and added
|
|
625
|
-
* the `stealth/cat-jam:free` preview, taking this set to 24 members
|
|
626
|
-
*
|
|
627
|
-
*
|
|
632
|
+
* the `stealth/cat-jam:free` preview, taking this set to 24 members. The 1.84.0
|
|
633
|
+
* sync (2026-10-16) moves it to 25 with no verdict flip: LongCat's exclusion
|
|
634
|
+
* followed its rename to the paid id, and the free `reflection/beam:free`
|
|
635
|
+
* preview joined on catalog evidence alone. The authoritative membership is the
|
|
636
|
+
* UNION of the artifact's exclusion set and the catalog's `zdr:false` list —
|
|
637
|
+
* neither source alone is complete, which is why this set is not simply "what
|
|
638
|
+
* the CLI says".
|
|
628
639
|
*/
|
|
629
640
|
const KNOWN_NON_ZDR_MODELS = /* @__PURE__ */ new Set([
|
|
630
641
|
"MiniMaxAI/MiniMax-M3",
|
|
@@ -636,9 +647,10 @@ const KNOWN_NON_ZDR_MODELS = /* @__PURE__ */ new Set([
|
|
|
636
647
|
"meta/muse-spark-1.3",
|
|
637
648
|
"meta/muse-spark-1.3-contributor",
|
|
638
649
|
"meta/muse-wonder-1.0",
|
|
639
|
-
"meituan/LongCat-2.0
|
|
650
|
+
"meituan/LongCat-2.0",
|
|
640
651
|
"minimax/minimax-m3-free",
|
|
641
652
|
"poolside/laguna-s-2.1-free",
|
|
653
|
+
"reflection/beam:free",
|
|
642
654
|
"sakana/fugu-ultra",
|
|
643
655
|
"stealth/glyph-cluster:free",
|
|
644
656
|
"stealth/cat-jam:free",
|
|
@@ -792,6 +804,7 @@ const MODEL_OUTPUT_TOKEN_LIMITS = /* @__PURE__ */ new Map([
|
|
|
792
804
|
["claude-sonnet-5", 128e3],
|
|
793
805
|
["claude-sonnet-5-5", 128e3],
|
|
794
806
|
["deepseek/deepseek-v4-flash", 393216],
|
|
807
|
+
["deepseek/deepseek-v4-flash-fast", 384e3],
|
|
795
808
|
["deepseek/deepseek-v4-flash-vision-exp", 393216],
|
|
796
809
|
["deepseek/deepseek-v4-pro", 393216],
|
|
797
810
|
["google/gemini-3.1-flash-lite", 65536],
|
|
@@ -835,11 +848,13 @@ const MODEL_OUTPUT_TOKEN_LIMITS = /* @__PURE__ */ new Map([
|
|
|
835
848
|
["Qwen/Qwen3.7-Plus", 131072],
|
|
836
849
|
["Qwen/Qwen3.8-Flash", 131072],
|
|
837
850
|
["Qwen/Qwen3.8-Max", 131072],
|
|
851
|
+
["Qwen/Qwen3.8-Max-0902", 131072],
|
|
838
852
|
["Qwen/Qwen3.8-Omni-Flash", 131072],
|
|
839
853
|
["sakana/fugu-ultra", 1e6],
|
|
840
854
|
["stepfun/Step-3.5-Flash", 256e3],
|
|
841
855
|
["stepfun/Step-3.7-Flash", 256e3],
|
|
842
856
|
["stepfun/Step-5-Preview", 65536],
|
|
857
|
+
["tencent/hy3-paid", 128e3],
|
|
843
858
|
["thinkingmachines/inkling", 65536],
|
|
844
859
|
["xai/grok-4.5", 5e5],
|
|
845
860
|
["xai/grok-4.6", 5e5],
|
|
@@ -923,6 +938,7 @@ const KNOWN_PLANS = {
|
|
|
923
938
|
"nvidia/nemotron-3-ultra-550b-a55b": "go",
|
|
924
939
|
"nvidia/nemotron-3.5-super-vl": "go",
|
|
925
940
|
"poolside/laguna-s-2.1-free": "go",
|
|
941
|
+
"reflection/beam:free": "go",
|
|
926
942
|
"stepfun/Step-3.5-Flash": "go",
|
|
927
943
|
"stepfun/Step-3.7-Flash": "go",
|
|
928
944
|
"stepfun/Step-5-Preview": "go",
|
|
@@ -1119,6 +1135,10 @@ const KNOWN_DEALS = {
|
|
|
1119
1135
|
label: "FREE",
|
|
1120
1136
|
free: true
|
|
1121
1137
|
},
|
|
1138
|
+
"reflection/beam:free": {
|
|
1139
|
+
label: "FREE",
|
|
1140
|
+
free: true
|
|
1141
|
+
},
|
|
1122
1142
|
"stealth/cat-jam:free": {
|
|
1123
1143
|
label: "FREE",
|
|
1124
1144
|
free: true
|
|
@@ -1563,8 +1583,8 @@ const MODEL_PRICE_ROWS = [
|
|
|
1563
1583
|
],
|
|
1564
1584
|
allowance: {
|
|
1565
1585
|
go: 10,
|
|
1566
|
-
goat:
|
|
1567
|
-
pro:
|
|
1586
|
+
goat: 80,
|
|
1587
|
+
pro: 90
|
|
1568
1588
|
}
|
|
1569
1589
|
},
|
|
1570
1590
|
{
|
|
@@ -1764,8 +1784,8 @@ const MODEL_PRICE_ROWS = [
|
|
|
1764
1784
|
],
|
|
1765
1785
|
allowance: {
|
|
1766
1786
|
go: 10,
|
|
1767
|
-
goat:
|
|
1768
|
-
pro:
|
|
1787
|
+
goat: 80,
|
|
1788
|
+
pro: 90
|
|
1769
1789
|
}
|
|
1770
1790
|
},
|
|
1771
1791
|
{
|
|
@@ -3676,6 +3696,140 @@ function effectiveContextWindow(catalog, model, options) {
|
|
|
3676
3696
|
if (isValidContextWindow(options.contextWindow)) return options.contextWindow;
|
|
3677
3697
|
return catalog;
|
|
3678
3698
|
}
|
|
3699
|
+
//#endregion
|
|
3700
|
+
//#region src/output-budget.ts
|
|
3701
|
+
/**
|
|
3702
|
+
* Output-budget bounds shared by the Host, the browser settings page and the
|
|
3703
|
+
* adapter (issue #74).
|
|
3704
|
+
*
|
|
3705
|
+
* The module stays pure (no node imports) so the client bundle can inline it,
|
|
3706
|
+
* exactly like `context-window.ts`. The report behind issue #74 asked for a
|
|
3707
|
+
* knob that stops the adapter from writing `max_tokens` at all; that is the
|
|
3708
|
+
* `0` sentinel here rather than a separate `null` spelling, because the value
|
|
3709
|
+
* travels through one numeric settings field on both surfaces.
|
|
3710
|
+
*/
|
|
3711
|
+
/**
|
|
3712
|
+
* The output budget this bundle asks for, and the ceiling it will never exceed.
|
|
3713
|
+
*
|
|
3714
|
+
* 131 072, chosen against a measured provider limit rather than a round guess.
|
|
3715
|
+
* Probing `/alpha/generate` directly on 2026-09-28: 64 000 → 200, 131 072 → 200,
|
|
3716
|
+
* 196 608 → 200, **200 000 → 200, 200 704 → 400**, 1 000 000 → 400. The server
|
|
3717
|
+
* therefore caps `max_tokens` at 200 000, and it does so identically for a
|
|
3718
|
+
* 262 144-window model (`stealth/pixel-canary`, retired by Command Code on
|
|
3719
|
+
* 2026-10-01 and named here only as the probe's subject) and a 1 000 000-window
|
|
3720
|
+
* one (`stealth/space-bunny-alpha`) — the cap is global, not per model. 131 072
|
|
3721
|
+
* keeps a 35 % margin under it while covering the real output ceiling of the
|
|
3722
|
+
* models that have one (Claude 64 K, Gemini 64 K, several at 128 K).
|
|
3723
|
+
*
|
|
3724
|
+
* The old 64 000 was never a provider requirement: it cost nothing in latency
|
|
3725
|
+
* (a 64 000 request answered headers in 1.28 s against 1.57 s for 131 072) but
|
|
3726
|
+
* it did cap what a model could ever say in one turn.
|
|
3727
|
+
*
|
|
3728
|
+
* No client-side clamp is applied on top of this: an earlier version pre-shrank
|
|
3729
|
+
* `max_tokens` from an estimate of the prompt already sent (`requestContextBudget()`,
|
|
3730
|
+
* issue #67), but that estimate over-corrected on ordinary long conversations —
|
|
3731
|
+
* exactly the "output gets cut short as the conversation grows" symptom issue #74
|
|
3732
|
+
* reported — while a live A/B probe (2026-09-30, `stealth/space-bunny-alpha`,
|
|
3733
|
+
* ~19.6% over its 1,000,000-token window with no client-side shrink) showed the
|
|
3734
|
+
* endpoint answers a genuine overflow with an unambiguous `400 … exceeds the
|
|
3735
|
+
* model's context window`, which `isContextOverflowDetail()` classifies as
|
|
3736
|
+
* `CONTEXT_WINDOW_EXCEEDED` independently of this constant.
|
|
3737
|
+
*
|
|
3738
|
+
* A user-set positive budget can only TIGHTEN this, never raise it — a figure
|
|
3739
|
+
* above the cap would be refused by the gateway anyway, so the settings field
|
|
3740
|
+
* refuses it at the input instead of spending a round trip to learn that.
|
|
3741
|
+
*
|
|
3742
|
+
* Lives here rather than in `adapter.ts` so the browser settings page can bound
|
|
3743
|
+
* its input by the same figure the Host enforces; `adapter.ts` re-exports it
|
|
3744
|
+
* under its historical name.
|
|
3745
|
+
*/
|
|
3746
|
+
const MAX_OUTPUT_TOKEN_LIMIT = 131072;
|
|
3747
|
+
/**
|
|
3748
|
+
* Whether a value can serve as a user output budget: the omit sentinel, or a
|
|
3749
|
+
* whole token count inside the gateway's own ceiling.
|
|
3750
|
+
*
|
|
3751
|
+
* Programmatically built connections can bypass schemastery normalization, so
|
|
3752
|
+
* the resolve step re-judges every value here and drops an out-of-range one
|
|
3753
|
+
* back to "unset" instead of letting the request carry a budget the gateway
|
|
3754
|
+
* refuses.
|
|
3755
|
+
*/
|
|
3756
|
+
function isValidMaxOutputTokens(value) {
|
|
3757
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= 131072;
|
|
3758
|
+
}
|
|
3759
|
+
/**
|
|
3760
|
+
* Apply a user output budget to a derived one.
|
|
3761
|
+
*
|
|
3762
|
+
* `undefined` means the user set nothing, so the derived ceiling stands
|
|
3763
|
+
* unchanged — the default path, and the one every existing profile takes.
|
|
3764
|
+
* {@link OMIT_OUTPUT_TOKENS} is a wire instruction rather than a number, so it
|
|
3765
|
+
* also leaves the derived budget alone; {@link omitOutputTokens} is what turns
|
|
3766
|
+
* it into an absent field.
|
|
3767
|
+
*/
|
|
3768
|
+
function applyMaxOutputTokens(derived, configured) {
|
|
3769
|
+
if (configured === void 0 || configured === 0) return derived;
|
|
3770
|
+
return Math.min(derived, configured);
|
|
3771
|
+
}
|
|
3772
|
+
/**
|
|
3773
|
+
* Whether this attempt must send no `max_tokens` field at all.
|
|
3774
|
+
*
|
|
3775
|
+
* Two guards narrow this beyond the user's own setting, and both are
|
|
3776
|
+
* correctness requirements rather than conveniences:
|
|
3777
|
+
*
|
|
3778
|
+
* - The Messages route is excluded deliberately: the endpoint rejects a body
|
|
3779
|
+
* without the field (`Invalid input: expected number, received undefined at
|
|
3780
|
+
* max_tokens`), so omitting it there would not lengthen an answer, it would
|
|
3781
|
+
* make the whole Claude family unusable. The CLI and OpenAI transports accept
|
|
3782
|
+
* an absent field, which is where the setting does what it promises.
|
|
3783
|
+
* - An explicit per-call budget from the host is an instruction, not a default,
|
|
3784
|
+
* so it wins over the plugin-wide setting. `session-title` is the concrete
|
|
3785
|
+
* case: it asks for 64 tokens precisely because its output must stay bounded,
|
|
3786
|
+
* and dropping the field there would let a title generation run long instead
|
|
3787
|
+
* of making a long answer fit (issue #88's fix depends on that bound).
|
|
3788
|
+
*/
|
|
3789
|
+
function omitOutputTokens(configured, protocol, hostRequestedTokens) {
|
|
3790
|
+
if (configured !== 0) return false;
|
|
3791
|
+
if (protocol === "messages") return false;
|
|
3792
|
+
return hostRequestedTokens === void 0;
|
|
3793
|
+
}
|
|
3794
|
+
//#endregion
|
|
3795
|
+
//#region src/readable-error.ts
|
|
3796
|
+
/**
|
|
3797
|
+
* Readable error text for the settings page AND the Host-side failure paths.
|
|
3798
|
+
*
|
|
3799
|
+
* @module dsh-commandcode-provider/readable-error
|
|
3800
|
+
*/
|
|
3801
|
+
/**
|
|
3802
|
+
* Turn a caught value into readable text.
|
|
3803
|
+
*
|
|
3804
|
+
* `error instanceof Error ? error.message : String(error)` degrades to the
|
|
3805
|
+
* literal string `[object Object]` for a plain object with no `Error`
|
|
3806
|
+
* prototype — which is exactly what a Remote call across the webview/host
|
|
3807
|
+
* boundary can hand back: `dsh-typert-protocol` serializes a rejection for
|
|
3808
|
+
* the wire, and a structurally-cloned value loses its prototype chain even
|
|
3809
|
+
* when the host threw a real `Error` (issue #74). Reading a `message` string
|
|
3810
|
+
* off the plain object first recovers the original text in that case; only a
|
|
3811
|
+
* value with neither an `Error` prototype nor a usable `message` field falls
|
|
3812
|
+
* through to a JSON dump.
|
|
3813
|
+
*
|
|
3814
|
+
* Shared by both halves on purpose. The browser client needs it for rejections
|
|
3815
|
+
* that crossed the RPC boundary, and the Host needs it for values that reached
|
|
3816
|
+
* it from a transport, a JSON body, or a user-thrown non-Error — the same
|
|
3817
|
+
* degradation, one owner.
|
|
3818
|
+
*/
|
|
3819
|
+
function readableErrorText(error) {
|
|
3820
|
+
if (error instanceof Error) return error.message;
|
|
3821
|
+
if (typeof error === "string") return error;
|
|
3822
|
+
if (error !== null && typeof error === "object") {
|
|
3823
|
+
const message = error.message;
|
|
3824
|
+
if (typeof message === "string" && message !== "") return message;
|
|
3825
|
+
try {
|
|
3826
|
+
return JSON.stringify(error);
|
|
3827
|
+
} catch {
|
|
3828
|
+
return Object.prototype.toString.call(error);
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
3831
|
+
return String(error);
|
|
3832
|
+
}
|
|
3679
3833
|
const THROTTLED_CODE = "THROTTLED";
|
|
3680
3834
|
const boundedCodes = /* @__PURE__ */ new Set([
|
|
3681
3835
|
"EMPTY_RESPONSE",
|
|
@@ -6144,37 +6298,15 @@ var GatewayFacts = class {
|
|
|
6144
6298
|
* per-request options thunk and an API-key resolver from the plugin entry
|
|
6145
6299
|
* (src/index.ts), so a settings change reaches the very next request.
|
|
6146
6300
|
*/
|
|
6147
|
-
const COMMAND_CODE_CLI_VERSION = "1.
|
|
6301
|
+
const COMMAND_CODE_CLI_VERSION = "1.84.0";
|
|
6148
6302
|
const DEFAULT_API_BASE = "https://api.commandcode.ai";
|
|
6149
6303
|
/**
|
|
6150
6304
|
* The output budget this bundle asks for, and the ceiling it will never exceed.
|
|
6151
|
-
*
|
|
6152
|
-
*
|
|
6153
|
-
*
|
|
6154
|
-
* 196 608 → 200, **200 000 → 200, 200 704 → 400**, 1 000 000 → 400. The server
|
|
6155
|
-
* therefore caps `max_tokens` at 200 000, and it does so identically for a
|
|
6156
|
-
* 262 144-window model (`stealth/pixel-canary`, retired by Command Code on
|
|
6157
|
-
* 2026-10-01 and named here only as the probe's subject) and a 1 000 000-window
|
|
6158
|
-
* one (`stealth/space-bunny-alpha`) — the cap is global, not per model. 131 072
|
|
6159
|
-
* keeps a 35 % margin under it while covering the real output ceiling of the
|
|
6160
|
-
* models that have one (Claude 64 K, Gemini 64 K, several at 128 K).
|
|
6161
|
-
*
|
|
6162
|
-
* The old 64 000 was never a provider requirement: it cost nothing in latency
|
|
6163
|
-
* (a 64 000 request answered headers in 1.28 s against 1.57 s for 131 072) but
|
|
6164
|
-
* it did cap what a model could ever say in one turn.
|
|
6165
|
-
*
|
|
6166
|
-
* No client-side clamp is applied on top of this: an earlier version pre-shrank
|
|
6167
|
-
* `max_tokens` from an estimate of the prompt already sent (`requestContextBudget()`,
|
|
6168
|
-
* issue #67), but that estimate over-corrected on ordinary long conversations —
|
|
6169
|
-
* exactly the "output gets cut short as the conversation grows" symptom issue #74
|
|
6170
|
-
* reported — while a live A/B probe (2026-09-30, `stealth/space-bunny-alpha`,
|
|
6171
|
-
* ~19.6% over its 1,000,000-token window with no client-side shrink) showed the
|
|
6172
|
-
* endpoint answers a genuine overflow with an unambiguous `400 … exceeds the
|
|
6173
|
-
* model's context window`, which `isContextOverflowDetail()` below already
|
|
6174
|
-
* classifies as `CONTEXT_WINDOW_EXCEEDED` independently of this constant. See
|
|
6175
|
-
* [决策记录](../docs/决策记录.md).
|
|
6305
|
+
* The figure and its probe evidence live in `./output-budget.ts` — the module
|
|
6306
|
+
* the browser settings page also bounds its input by — and are re-exported here
|
|
6307
|
+
* under the historical name so existing importers are unaffected.
|
|
6176
6308
|
*/
|
|
6177
|
-
const DEFAULT_GENERATE_MAX_TOKENS =
|
|
6309
|
+
const DEFAULT_GENERATE_MAX_TOKENS = MAX_OUTPUT_TOKEN_LIMIT;
|
|
6178
6310
|
const MODELS_TIMEOUT_MS = 1e4;
|
|
6179
6311
|
/** How long the picker's plan-filter billing facts stay cached before refetching. */
|
|
6180
6312
|
const BILLING_ACCESS_TTL_MS = 3e5;
|
|
@@ -7405,7 +7537,7 @@ async function buildCliBody(options, connection, facts) {
|
|
|
7405
7537
|
input_schema: toolParametersSchema(tool.parameters)
|
|
7406
7538
|
})),
|
|
7407
7539
|
system: cliSystem(facts.systemText),
|
|
7408
|
-
max_tokens: facts.maxTokens,
|
|
7540
|
+
...facts.omitMaxTokens ? {} : { max_tokens: facts.maxTokens },
|
|
7409
7541
|
temperature: options.temperature ?? .3,
|
|
7410
7542
|
stream: true,
|
|
7411
7543
|
...facts.reasoningEffort ? { reasoning_effort: facts.reasoningEffort } : {}
|
|
@@ -7431,7 +7563,7 @@ async function buildOpenAIBody(options, facts) {
|
|
|
7431
7563
|
model: options.model,
|
|
7432
7564
|
messages: openAiMessages,
|
|
7433
7565
|
...openAiTools.length > 0 ? { tools: openAiTools } : {},
|
|
7434
|
-
max_tokens: facts.maxTokens,
|
|
7566
|
+
...facts.omitMaxTokens ? {} : { max_tokens: facts.maxTokens },
|
|
7435
7567
|
temperature: options.temperature ?? .3,
|
|
7436
7568
|
stream: true,
|
|
7437
7569
|
...facts.reasoningEffort ? { reasoning_effort: facts.reasoningEffort } : {}
|
|
@@ -7494,6 +7626,16 @@ async function buildMessagesBody(options, facts) {
|
|
|
7494
7626
|
return body;
|
|
7495
7627
|
}
|
|
7496
7628
|
/**
|
|
7629
|
+
* Write the output budget into an already-built body, each transport in its own
|
|
7630
|
+
* shape (the CLI route nests it under `params`). Used to keep a REBUILT body's
|
|
7631
|
+
* figure in step with the budget without duplicating the nesting knowledge, and
|
|
7632
|
+
* only called when the field is not being omitted (issue #74).
|
|
7633
|
+
*/
|
|
7634
|
+
function assignMaxTokens(body, target, maxTokens) {
|
|
7635
|
+
if (target === "cli") recordOrEmpty(body.params).max_tokens = maxTokens;
|
|
7636
|
+
else body.max_tokens = maxTokens;
|
|
7637
|
+
}
|
|
7638
|
+
/**
|
|
7497
7639
|
* One connect attempt's inputs: everything `connectGenerate` needs beyond
|
|
7498
7640
|
* the per-attempt key and protocol. Passed explicitly (not closed over) so
|
|
7499
7641
|
* the rotation loop's mutation of `protocol`/`body` stays visible at the
|
|
@@ -7857,7 +7999,7 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
7857
7999
|
toolUpdate: "addition-only",
|
|
7858
8000
|
...entry !== void 0 && contextWindow !== void 0 ? {
|
|
7859
8001
|
context: { contextWindow },
|
|
7860
|
-
defaultMaxTokens: Math.min(catalogModelMaxTokens(connection.apiBase, entry, contextWindow), DEFAULT_GENERATE_MAX_TOKENS)
|
|
8002
|
+
defaultMaxTokens: applyMaxOutputTokens(Math.min(catalogModelMaxTokens(connection.apiBase, entry, contextWindow), DEFAULT_GENERATE_MAX_TOKENS), connection.maxOutputTokens)
|
|
7861
8003
|
} : {},
|
|
7862
8004
|
...efforts ? { reasoning: { efforts: efforts.map((effort) => ({
|
|
7863
8005
|
id: ReasoningEffortId(effort),
|
|
@@ -8137,7 +8279,7 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
8137
8279
|
}
|
|
8138
8280
|
return record;
|
|
8139
8281
|
} catch (error) {
|
|
8140
|
-
failures.push(`${path}: ${
|
|
8282
|
+
failures.push(`${path}: ${readableErrorText(error)}`);
|
|
8141
8283
|
failed.push({ kind: "transport" });
|
|
8142
8284
|
return;
|
|
8143
8285
|
}
|
|
@@ -8281,7 +8423,7 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
8281
8423
|
options.signal?.throwIfAborted();
|
|
8282
8424
|
const modelEntry = catalog.find((row) => row.id === options.model);
|
|
8283
8425
|
const modelMax = modelEntry === void 0 ? facts.fallbackLimit : catalogModelMaxTokens(connection.apiBase, modelEntry, effectiveContextWindow(modelEntry.contextWindow, options.model, connection));
|
|
8284
|
-
const initialMax = Math.min(options.maxTokens ?? Infinity, modelMax, DEFAULT_GENERATE_MAX_TOKENS);
|
|
8426
|
+
const initialMax = applyMaxOutputTokens(Math.min(options.maxTokens ?? Infinity, modelMax, DEFAULT_GENERATE_MAX_TOKENS), connection.maxOutputTokens);
|
|
8285
8427
|
const budget = {
|
|
8286
8428
|
max: initialMax,
|
|
8287
8429
|
sent: initialMax
|
|
@@ -8332,17 +8474,16 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
8332
8474
|
const buildBody = async (target) => {
|
|
8333
8475
|
const finishBody = timing.phase("body");
|
|
8334
8476
|
try {
|
|
8477
|
+
const omitMaxTokens = omitOutputTokens(connection.maxOutputTokens, target, options.maxTokens);
|
|
8335
8478
|
const facts = {
|
|
8336
8479
|
maxTokens,
|
|
8480
|
+
omitMaxTokens,
|
|
8337
8481
|
reasoningEffort,
|
|
8338
8482
|
selectableEffort: supported !== void 0 && supported.length > 0,
|
|
8339
8483
|
systemText,
|
|
8340
8484
|
readImage
|
|
8341
8485
|
};
|
|
8342
|
-
|
|
8343
|
-
if (target === "cli") recordOrEmpty(built.params).max_tokens = maxTokens;
|
|
8344
|
-
else built.max_tokens = maxTokens;
|
|
8345
|
-
return built;
|
|
8486
|
+
return target === "cli" ? await buildCliBody(requestOptions, connection, facts) : target === "messages" ? await buildMessagesBody(requestOptions, facts) : await buildOpenAIBody(requestOptions, facts);
|
|
8346
8487
|
} finally {
|
|
8347
8488
|
finishBody();
|
|
8348
8489
|
}
|
|
@@ -8392,8 +8533,7 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
|
|
|
8392
8533
|
if (lower < maxTokens) {
|
|
8393
8534
|
maxTokens = lower;
|
|
8394
8535
|
budget.max = Math.min(budget.max, lower);
|
|
8395
|
-
if (protocol
|
|
8396
|
-
else body.max_tokens = lower;
|
|
8536
|
+
if (!omitOutputTokens(connection.maxOutputTokens, protocol, options.maxTokens)) assignMaxTokens(body, protocol, lower);
|
|
8397
8537
|
serializedBodies.delete(body);
|
|
8398
8538
|
}
|
|
8399
8539
|
budget.sent = maxTokens;
|
|
@@ -8707,7 +8847,7 @@ function commandDefinition(deps) {
|
|
|
8707
8847
|
text: renderReport(await adapter.getUsage(), locale)
|
|
8708
8848
|
};
|
|
8709
8849
|
} catch (error) {
|
|
8710
|
-
const message =
|
|
8850
|
+
const message = readableErrorText(error);
|
|
8711
8851
|
return {
|
|
8712
8852
|
kind: "error",
|
|
8713
8853
|
text: commandCopy(locale, "errorText").replace("{message}", message)
|
|
@@ -9025,6 +9165,30 @@ const LOGIN_HOST_CONTRIBUTION = {
|
|
|
9025
9165
|
};
|
|
9026
9166
|
//#endregion
|
|
9027
9167
|
//#region src/enrollment-wire.ts
|
|
9168
|
+
const ENROLLMENT_MESSAGE_KEYS = [
|
|
9169
|
+
"enrollmentCreating",
|
|
9170
|
+
"enrollmentWaiting",
|
|
9171
|
+
"enrollmentWriting",
|
|
9172
|
+
"enrollmentNaming",
|
|
9173
|
+
"enrollmentNameFailed",
|
|
9174
|
+
"enrollmentFinished",
|
|
9175
|
+
"enrollmentCancelling",
|
|
9176
|
+
"enrollmentCancelled",
|
|
9177
|
+
"enrollmentCancelledCleaned",
|
|
9178
|
+
"enrollmentFailed",
|
|
9179
|
+
"enrollmentFailedCleaned",
|
|
9180
|
+
"enrollmentCleanupPending",
|
|
9181
|
+
"enrollmentCleanupFailed",
|
|
9182
|
+
"enrollmentRecordNaming",
|
|
9183
|
+
"enrollmentRecordCleanup",
|
|
9184
|
+
"enrollmentBeginFailed",
|
|
9185
|
+
"enrollmentCancelUnconfirmed",
|
|
9186
|
+
"enrollmentNameRequestFailed",
|
|
9187
|
+
"enrollmentCleanupRequestFailed",
|
|
9188
|
+
"enrollmentDisconnected",
|
|
9189
|
+
"enrollmentHostUnavailable",
|
|
9190
|
+
"enrollmentStatusUnavailable"
|
|
9191
|
+
];
|
|
9028
9192
|
const guard = makeBoundaryValidator("commandcode/enrollment");
|
|
9029
9193
|
const phases = [
|
|
9030
9194
|
"creating",
|
|
@@ -9041,11 +9205,13 @@ function parseEnrollmentState(value) {
|
|
|
9041
9205
|
const source = guard.record(value, "state");
|
|
9042
9206
|
const phase = source.phase;
|
|
9043
9207
|
if (!phases.includes(phase)) return guard.reject("phase");
|
|
9208
|
+
const messageKey = source.messageKey;
|
|
9209
|
+
if (!ENROLLMENT_MESSAGE_KEYS.includes(messageKey)) return guard.reject("messageKey");
|
|
9044
9210
|
const state = {
|
|
9045
9211
|
id: guard.stringField(source, "id", "id"),
|
|
9046
9212
|
ref: guard.stringField(source, "ref", "ref"),
|
|
9047
9213
|
phase,
|
|
9048
|
-
|
|
9214
|
+
messageKey
|
|
9049
9215
|
};
|
|
9050
9216
|
for (const field of ["authUrl", "suggestedName"]) if (source[field] !== void 0) state[field] = guard.stringField(source, field, field);
|
|
9051
9217
|
return state;
|
|
@@ -9737,7 +9903,7 @@ var CommandCodeLoginFlow = class {
|
|
|
9737
9903
|
this.setStatus({
|
|
9738
9904
|
state: "failed",
|
|
9739
9905
|
reason: "unavailable",
|
|
9740
|
-
message:
|
|
9906
|
+
message: readableErrorText(error)
|
|
9741
9907
|
});
|
|
9742
9908
|
return;
|
|
9743
9909
|
}
|
|
@@ -9846,7 +10012,7 @@ var AccountEnrollmentManager = class {
|
|
|
9846
10012
|
id: input.id,
|
|
9847
10013
|
ref: "",
|
|
9848
10014
|
phase: "creating",
|
|
9849
|
-
|
|
10015
|
+
messageKey: "enrollmentCreating"
|
|
9850
10016
|
}
|
|
9851
10017
|
};
|
|
9852
10018
|
this.tasks.set(input.id, task);
|
|
@@ -9854,7 +10020,7 @@ var AccountEnrollmentManager = class {
|
|
|
9854
10020
|
task.state = {
|
|
9855
10021
|
...task.state,
|
|
9856
10022
|
phase: "cleanup-needed",
|
|
9857
|
-
|
|
10023
|
+
messageKey: "enrollmentCleanupPending"
|
|
9858
10024
|
};
|
|
9859
10025
|
});
|
|
9860
10026
|
return task.state;
|
|
@@ -9884,7 +10050,7 @@ var AccountEnrollmentManager = class {
|
|
|
9884
10050
|
id,
|
|
9885
10051
|
ref: "",
|
|
9886
10052
|
phase: "cancelled",
|
|
9887
|
-
|
|
10053
|
+
messageKey: "enrollmentCancelled"
|
|
9888
10054
|
}
|
|
9889
10055
|
};
|
|
9890
10056
|
this.tasks.set(id, task);
|
|
@@ -9897,7 +10063,7 @@ var AccountEnrollmentManager = class {
|
|
|
9897
10063
|
task.state = {
|
|
9898
10064
|
...task.state,
|
|
9899
10065
|
phase: "cancelling",
|
|
9900
|
-
|
|
10066
|
+
messageKey: "enrollmentCancelling"
|
|
9901
10067
|
};
|
|
9902
10068
|
await task.flow?.cancelAndDrain();
|
|
9903
10069
|
await task.work;
|
|
@@ -9934,14 +10100,14 @@ var AccountEnrollmentManager = class {
|
|
|
9934
10100
|
id,
|
|
9935
10101
|
ref: record.ref,
|
|
9936
10102
|
phase: "finished",
|
|
9937
|
-
|
|
10103
|
+
messageKey: "enrollmentFinished"
|
|
9938
10104
|
});
|
|
9939
10105
|
} catch {
|
|
9940
10106
|
return this.publish(task, {
|
|
9941
10107
|
id,
|
|
9942
10108
|
ref: record.ref,
|
|
9943
10109
|
phase: "naming",
|
|
9944
|
-
|
|
10110
|
+
messageKey: "enrollmentNameFailed",
|
|
9945
10111
|
...name ? { suggestedName: name } : {}
|
|
9946
10112
|
});
|
|
9947
10113
|
}
|
|
@@ -9967,7 +10133,7 @@ var AccountEnrollmentManager = class {
|
|
|
9967
10133
|
task.state = {
|
|
9968
10134
|
...task.state,
|
|
9969
10135
|
phase: "cleanup-needed",
|
|
9970
|
-
|
|
10136
|
+
messageKey: "enrollmentCleanupFailed"
|
|
9971
10137
|
};
|
|
9972
10138
|
});
|
|
9973
10139
|
await task.work;
|
|
@@ -10035,7 +10201,7 @@ var AccountEnrollmentManager = class {
|
|
|
10035
10201
|
if (!task.cancelled && status.state === "waiting") task.state = {
|
|
10036
10202
|
...task.state,
|
|
10037
10203
|
phase: "waiting",
|
|
10038
|
-
|
|
10204
|
+
messageKey: "enrollmentWaiting",
|
|
10039
10205
|
...status.authUrl ? { authUrl: status.authUrl } : {}
|
|
10040
10206
|
};
|
|
10041
10207
|
if (task.cancelled) flow.cancelAndDrain();
|
|
@@ -10051,7 +10217,7 @@ var AccountEnrollmentManager = class {
|
|
|
10051
10217
|
task.state = {
|
|
10052
10218
|
...task.state,
|
|
10053
10219
|
phase: "naming",
|
|
10054
|
-
|
|
10220
|
+
messageKey: "enrollmentNaming",
|
|
10055
10221
|
...suggested ? { suggestedName: suggested } : {}
|
|
10056
10222
|
};
|
|
10057
10223
|
try {
|
|
@@ -10064,7 +10230,7 @@ var AccountEnrollmentManager = class {
|
|
|
10064
10230
|
if (task.state.phase === "finished") return;
|
|
10065
10231
|
task.state = {
|
|
10066
10232
|
...task.state,
|
|
10067
|
-
|
|
10233
|
+
messageKey: "enrollmentNameFailed"
|
|
10068
10234
|
};
|
|
10069
10235
|
return;
|
|
10070
10236
|
}
|
|
@@ -10076,7 +10242,7 @@ var AccountEnrollmentManager = class {
|
|
|
10076
10242
|
else task.state = {
|
|
10077
10243
|
...task.state,
|
|
10078
10244
|
phase: task.cancelled ? "cancelled" : "failed",
|
|
10079
|
-
|
|
10245
|
+
messageKey: task.cancelled ? "enrollmentCancelled" : "enrollmentFailed"
|
|
10080
10246
|
};
|
|
10081
10247
|
} finally {
|
|
10082
10248
|
task.flow?.dispose();
|
|
@@ -10087,7 +10253,7 @@ var AccountEnrollmentManager = class {
|
|
|
10087
10253
|
task.state = {
|
|
10088
10254
|
...task.state,
|
|
10089
10255
|
phase: "writing",
|
|
10090
|
-
|
|
10256
|
+
messageKey: "enrollmentWriting"
|
|
10091
10257
|
};
|
|
10092
10258
|
task.writeStarted = true;
|
|
10093
10259
|
await this.deps.set(task.state.ref, key);
|
|
@@ -10128,7 +10294,7 @@ var AccountEnrollmentManager = class {
|
|
|
10128
10294
|
id,
|
|
10129
10295
|
ref,
|
|
10130
10296
|
phase: task.cancelled ? "cancelled" : "failed",
|
|
10131
|
-
|
|
10297
|
+
messageKey: task.cancelled ? "enrollmentCancelledCleaned" : "enrollmentFailedCleaned"
|
|
10132
10298
|
};
|
|
10133
10299
|
}
|
|
10134
10300
|
settings() {
|
|
@@ -10177,12 +10343,134 @@ var AccountEnrollmentManager = class {
|
|
|
10177
10343
|
id: record.id,
|
|
10178
10344
|
ref: record.ref,
|
|
10179
10345
|
phase: record.phase === "naming" ? "naming" : "cleanup-needed",
|
|
10180
|
-
|
|
10346
|
+
messageKey: record.phase === "naming" ? "enrollmentRecordNaming" : "enrollmentRecordCleanup",
|
|
10181
10347
|
...record.phase === "naming" ? { suggestedName: record.label } : {}
|
|
10182
10348
|
};
|
|
10183
10349
|
}
|
|
10184
10350
|
};
|
|
10185
10351
|
//#endregion
|
|
10352
|
+
//#region src/config-volatile.ts
|
|
10353
|
+
/**
|
|
10354
|
+
* Volatile-config helpers for dsh 0.1.7's schema-derived profile Config.
|
|
10355
|
+
*
|
|
10356
|
+
* 0.1.7's `settings.describe()` projects each entry's schema through
|
|
10357
|
+
* `volatileForm()`, so a form contains ONLY nodes carrying `meta.volatile`
|
|
10358
|
+
* and a form edit is refused unless its path lies beneath a marked node. The
|
|
10359
|
+
* loader then resolves each marked field into a frozen `{ get() }` reference
|
|
10360
|
+
* that is written IN PLACE (no remount) and announced with
|
|
10361
|
+
* `loader/volatile-update`. Two helpers keep the rest of the plugin reading a
|
|
10362
|
+
* PLAIN `Config`: `markVolatile()` applies the mark, and
|
|
10363
|
+
* `unwrapVolatileConfig()` reads every top-level field through its reference
|
|
10364
|
+
* when one is present — FRESH per call, because a reference's identity is
|
|
10365
|
+
* stable while its value changes.
|
|
10366
|
+
*
|
|
10367
|
+
* Only TOP-LEVEL fields are marked (a form write names a top-level path such
|
|
10368
|
+
* as `accounts` or `modelVisibility.<id>`), so one unwrap level is complete.
|
|
10369
|
+
* The literal `apiKey` secret stays unmarked on purpose: no settings surface
|
|
10370
|
+
* writes it — page and TUI both write keys through the credentials seam — so a
|
|
10371
|
+
* config-file edit to it reloading the fiber is correct for a secret literal.
|
|
10372
|
+
*
|
|
10373
|
+
* `volatileWrite()` is the third helper and the only WRITE in this module: it
|
|
10374
|
+
* commits into a reference that belongs to ANOTHER plugin entry (`ctx.web`'s
|
|
10375
|
+
* `searchProvider`), which no host API addresses directly. It is the degraded
|
|
10376
|
+
* path behind `ctx.settings.update()`; see its own note for why the disk-backed
|
|
10377
|
+
* path is preferred.
|
|
10378
|
+
*
|
|
10379
|
+
* @module dsh-commandcode-provider/config-volatile
|
|
10380
|
+
*/
|
|
10381
|
+
/**
|
|
10382
|
+
* Mark one schema field volatile.
|
|
10383
|
+
*
|
|
10384
|
+
* `.volatile()` is unconditional: it is a schemastery 3.18.3 feature and the
|
|
10385
|
+
* supported engine ships `~3.18.5`, so the
|
|
10386
|
+
* method is always present. It is reached through a structural member so the caller's
|
|
10387
|
+
* declared field type survives — the mark changes what the schema PARSES to (a
|
|
10388
|
+
* live reference), not the plugin-facing `Config` shape.
|
|
10389
|
+
*/
|
|
10390
|
+
function markVolatile(schema) {
|
|
10391
|
+
return schema.volatile();
|
|
10392
|
+
}
|
|
10393
|
+
/**
|
|
10394
|
+
* Mark every field of a Config field dict volatile, except the named
|
|
10395
|
+
* composition-only secrets (`apiKey` here, which no settings surface writes).
|
|
10396
|
+
*/
|
|
10397
|
+
function markVolatileFields(fields, skip = []) {
|
|
10398
|
+
return Object.fromEntries(Object.entries(fields).map(([name, schema]) => [name, skip.includes(name) ? schema : markVolatile(schema)]));
|
|
10399
|
+
}
|
|
10400
|
+
/**
|
|
10401
|
+
* Whether a resolved config value is a volatile reference.
|
|
10402
|
+
*
|
|
10403
|
+
* Duck-typed on the two properties `createVolatile` produces (frozen object
|
|
10404
|
+
* with a `get` method) rather than cosmokit's private `write` symbol, because
|
|
10405
|
+
* cosmokit is not a peer of this bundle. Config values are plain parsed JSON
|
|
10406
|
+
* (or `!!jsExpr` wrappers), which are never frozen, so the test cannot
|
|
10407
|
+
* misfire on an ordinary field.
|
|
10408
|
+
*/
|
|
10409
|
+
function isVolatileRef(value) {
|
|
10410
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.isFrozen(value) && typeof value.get === "function";
|
|
10411
|
+
}
|
|
10412
|
+
/**
|
|
10413
|
+
* The commit protocol cosmokit's `createVolatile` installs on every reference.
|
|
10414
|
+
*
|
|
10415
|
+
* `Symbol.for` — not a local `Symbol()` — because cosmokit registers it
|
|
10416
|
+
* globally precisely so copies of the shared library loaded twice (ESM/CJS, two
|
|
10417
|
+
* node_modules trees) still recognize each other's references. Reaching it
|
|
10418
|
+
* through the global registry is what lets this bundle commit a value without
|
|
10419
|
+
* taking `@deepseek-ai/cosmokit` as a dependency.
|
|
10420
|
+
*/
|
|
10421
|
+
const VOLATILE_WRITE = Symbol.for("cosmokit.volatile.write");
|
|
10422
|
+
/**
|
|
10423
|
+
* Commit a value into a RUNNING volatile reference and verify it landed.
|
|
10424
|
+
*
|
|
10425
|
+
* This is the DEGRADED write path, and it is deliberately second choice: the
|
|
10426
|
+
* reference is the live config the owner reads per call, but the value never
|
|
10427
|
+
* reaches the profile document, so the next loader commit for that entry (any
|
|
10428
|
+
* `loader/volatile-update`, including one caused by an unrelated field of the
|
|
10429
|
+
* same entry) re-resolves the raw config and overwrites it. The official
|
|
10430
|
+
* `ctx.settings.update()` path exists to avoid exactly that; this one keeps the
|
|
10431
|
+
* CURRENT process coherent when settings is unavailable or refuses the write.
|
|
10432
|
+
*
|
|
10433
|
+
* Reading back is not optional: the write method is an internal protocol, so a
|
|
10434
|
+
* future cosmokit that renames the symbol, hides it, or changes the value shape
|
|
10435
|
+
* must degrade to "not applied" instead of silently pretending success.
|
|
10436
|
+
*
|
|
10437
|
+
* @param ref - Candidate volatile reference; anything else returns `false`.
|
|
10438
|
+
* @param value - Value to commit.
|
|
10439
|
+
* @returns Whether the reference now reads back as `value`.
|
|
10440
|
+
*/
|
|
10441
|
+
function volatileWrite(ref, value) {
|
|
10442
|
+
if (!isVolatileRef(ref)) return false;
|
|
10443
|
+
const write = ref[VOLATILE_WRITE];
|
|
10444
|
+
if (typeof write !== "function") return false;
|
|
10445
|
+
try {
|
|
10446
|
+
write.call(ref, value);
|
|
10447
|
+
} catch {
|
|
10448
|
+
return false;
|
|
10449
|
+
}
|
|
10450
|
+
try {
|
|
10451
|
+
return Object.is(ref.get(), value);
|
|
10452
|
+
} catch {
|
|
10453
|
+
return false;
|
|
10454
|
+
}
|
|
10455
|
+
}
|
|
10456
|
+
/**
|
|
10457
|
+
* Read one config object as PLAIN values.
|
|
10458
|
+
*
|
|
10459
|
+
* Every top-level field found to be a volatile reference is read through
|
|
10460
|
+
* `.get()` — freshly on every call, so a live update lands on the very next
|
|
10461
|
+
* read (the loader resolves EVERY marked node into a reference, unset fields
|
|
10462
|
+
* included, so a loader-parsed config always yields a fresh object here). The
|
|
10463
|
+
* pass-through for a field that is not a reference is what keeps the unmarked
|
|
10464
|
+
* `apiKey` literal readable.
|
|
10465
|
+
*/
|
|
10466
|
+
function unwrapVolatileConfig(config) {
|
|
10467
|
+
const source = config;
|
|
10468
|
+
if (typeof source !== "object" || source === null) return config;
|
|
10469
|
+
const plain = {};
|
|
10470
|
+
for (const [field, value] of Object.entries(source)) plain[field] = isVolatileRef(value) ? value.get() : value;
|
|
10471
|
+
return plain;
|
|
10472
|
+
}
|
|
10473
|
+
//#endregion
|
|
10186
10474
|
//#region src/web-search.ts
|
|
10187
10475
|
/**
|
|
10188
10476
|
* Command Code web-search provider over `ctx.web`.
|
|
@@ -10195,6 +10483,15 @@ var AccountEnrollmentManager = class {
|
|
|
10195
10483
|
* therefore reuses the plugin's existing credential chain and `apiBase`, so
|
|
10196
10484
|
* the model-facing `web_search` tool needs no separate configuration.
|
|
10197
10485
|
*
|
|
10486
|
+
* The second half of this module is the SELECTION seam: which registered search
|
|
10487
|
+
* backend `ctx.web` actually runs. That seam has TWO engine shapes, and both are
|
|
10488
|
+
* supported. dsh 0.2.1-rc.2 turned the choice into a per-call `Volatile` field of
|
|
10489
|
+
* the `web` profile entry, so it is changed through the host settings service
|
|
10490
|
+
* rather than by assigning a runtime field. Engines up to and including
|
|
10491
|
+
* 0.2.1-alpha.4 kept it in the private instance field `searchProviderId`, which
|
|
10492
|
+
* has no settings surface and can only be assigned in memory — see
|
|
10493
|
+
* {@link createCommandCodeSearchSelection}.
|
|
10494
|
+
*
|
|
10198
10495
|
* @module dsh-commandcode-provider/web-search
|
|
10199
10496
|
*/
|
|
10200
10497
|
/** Stable id this provider registers under in `ctx.web`. */
|
|
@@ -10206,54 +10503,326 @@ const COMMANDCODE_SEARCH_PROVIDER_ID = "commandcode";
|
|
|
10206
10503
|
* PREVIOUSLY selected backend, never this default (issue #26).
|
|
10207
10504
|
*/
|
|
10208
10505
|
const DEFAULT_WEB_SEARCH_PROVIDER_ID = "deepseek-official";
|
|
10506
|
+
/**
|
|
10507
|
+
* Profile entry id whose volatile `searchProvider` selects the search backend.
|
|
10508
|
+
*
|
|
10509
|
+
* The hosting profile declares it in `dsh-base`'s cordis patch as
|
|
10510
|
+
* `- id: web` / `name: '@deepseek-ai/dsh-web'`, and the shipped web-search
|
|
10511
|
+
* settings page addresses the very same namespace — that page and this plugin
|
|
10512
|
+
* are therefore two writers of ONE field (see
|
|
10513
|
+
* {@link createCommandCodeSearchSelection} for how they avoid fighting).
|
|
10514
|
+
*/
|
|
10515
|
+
const WEB_SELECTION_NAMESPACE = "web";
|
|
10209
10516
|
/** Fresh selection state: the plugin starts out not owning the selection. */
|
|
10210
10517
|
function commandCodeSearchSelection() {
|
|
10211
10518
|
return {
|
|
10212
10519
|
owner: false,
|
|
10213
|
-
|
|
10214
|
-
|
|
10520
|
+
preexisting: false,
|
|
10521
|
+
foreign: void 0,
|
|
10522
|
+
displaced: void 0
|
|
10215
10523
|
};
|
|
10216
10524
|
}
|
|
10525
|
+
/** Read one member of a plain object or array without trusting its shape. */
|
|
10526
|
+
function member(value, key) {
|
|
10527
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
10528
|
+
return value[key];
|
|
10529
|
+
}
|
|
10530
|
+
/** A string member, or `undefined` for anything else (including `null`). */
|
|
10531
|
+
function text(value) {
|
|
10532
|
+
return typeof value === "string" ? value : void 0;
|
|
10533
|
+
}
|
|
10534
|
+
/** The settings row describing the `web` entry, when the host serves one. */
|
|
10535
|
+
function selectionRow(settings) {
|
|
10536
|
+
let rows;
|
|
10537
|
+
try {
|
|
10538
|
+
rows = settings.describe();
|
|
10539
|
+
} catch {
|
|
10540
|
+
return;
|
|
10541
|
+
}
|
|
10542
|
+
if (!Array.isArray(rows)) return void 0;
|
|
10543
|
+
return rows.find((row) => text(member(row, "ns")) === "web");
|
|
10544
|
+
}
|
|
10217
10545
|
/**
|
|
10218
|
-
*
|
|
10219
|
-
* providers (issue #26).
|
|
10546
|
+
* The volatile reference backing `web.searchProvider`.
|
|
10220
10547
|
*
|
|
10221
|
-
*
|
|
10222
|
-
*
|
|
10223
|
-
*
|
|
10224
|
-
*
|
|
10225
|
-
|
|
10226
|
-
|
|
10227
|
-
|
|
10228
|
-
|
|
10229
|
-
|
|
10230
|
-
*
|
|
10548
|
+
* `config` is a TypeScript-private parameter property on `WebRuntime`, so at
|
|
10549
|
+
* runtime it is an ordinary own field — the only handle to a reference the host
|
|
10550
|
+
* exposes no public accessor for. Reading it structurally means a hardened
|
|
10551
|
+
* runtime degrades to "not writable" instead of throwing.
|
|
10552
|
+
*/
|
|
10553
|
+
function searchProviderRef(web) {
|
|
10554
|
+
return member(member(web, "config"), "searchProvider");
|
|
10555
|
+
}
|
|
10556
|
+
/**
|
|
10557
|
+
* The pre-rc.2 instance field, or `undefined` when this runtime does not carry
|
|
10558
|
+
* it.
|
|
10231
10559
|
*
|
|
10232
|
-
*
|
|
10233
|
-
*
|
|
10560
|
+
* An OWN property is the discriminator, not a value test: a runtime of the
|
|
10561
|
+
* reference shape never has `searchProviderId` at all, so the two shapes cannot
|
|
10562
|
+
* both answer and neither needs to be preferred over the other.
|
|
10234
10563
|
*/
|
|
10235
|
-
function
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
|
|
10239
|
-
|
|
10240
|
-
|
|
10564
|
+
function legacySearchField(web) {
|
|
10565
|
+
if (!Object.prototype.hasOwnProperty.call(web, "searchProviderId")) return void 0;
|
|
10566
|
+
const shaped = web;
|
|
10567
|
+
return {
|
|
10568
|
+
read: () => text(shaped.searchProviderId),
|
|
10569
|
+
write: (value) => {
|
|
10570
|
+
try {
|
|
10571
|
+
shaped.searchProviderId = value;
|
|
10572
|
+
return true;
|
|
10573
|
+
} catch {
|
|
10574
|
+
return false;
|
|
10575
|
+
}
|
|
10576
|
+
}
|
|
10577
|
+
};
|
|
10578
|
+
}
|
|
10579
|
+
/** Observe the live selection; `undefined` when nothing can be observed. */
|
|
10580
|
+
function readView(deps) {
|
|
10581
|
+
const settings = deps.settings();
|
|
10582
|
+
if (settings !== void 0) {
|
|
10583
|
+
const row = selectionRow(settings);
|
|
10584
|
+
if (row !== void 0) {
|
|
10585
|
+
const user = member(row, "user");
|
|
10586
|
+
return {
|
|
10587
|
+
configured: text(member(member(row, "value"), "searchProvider")),
|
|
10588
|
+
pinned: text(member(user, "searchProvider")),
|
|
10589
|
+
base: text(member(member(row, "base"), "searchProvider")),
|
|
10590
|
+
user: typeof user === "object" && user !== null && !Array.isArray(user) ? { ...user } : {},
|
|
10591
|
+
persistent: true
|
|
10592
|
+
};
|
|
10593
|
+
}
|
|
10594
|
+
}
|
|
10595
|
+
const ref = searchProviderRef(deps.web);
|
|
10596
|
+
if (isVolatileRef(ref)) {
|
|
10597
|
+
let current;
|
|
10598
|
+
try {
|
|
10599
|
+
current = ref.get();
|
|
10600
|
+
} catch {
|
|
10601
|
+
return;
|
|
10602
|
+
}
|
|
10603
|
+
if (current !== void 0 && typeof current !== "string") return void 0;
|
|
10604
|
+
return {
|
|
10605
|
+
configured: current,
|
|
10606
|
+
pinned: void 0,
|
|
10607
|
+
base: void 0,
|
|
10608
|
+
user: {},
|
|
10609
|
+
persistent: false
|
|
10610
|
+
};
|
|
10611
|
+
}
|
|
10612
|
+
const legacy = legacySearchField(deps.web);
|
|
10613
|
+
if (legacy === void 0) return void 0;
|
|
10614
|
+
return {
|
|
10615
|
+
configured: legacy.read(),
|
|
10616
|
+
pinned: void 0,
|
|
10617
|
+
base: void 0,
|
|
10618
|
+
user: {},
|
|
10619
|
+
persistent: false
|
|
10620
|
+
};
|
|
10621
|
+
}
|
|
10622
|
+
/**
|
|
10623
|
+
* The selection seam over `ctx.web`.
|
|
10624
|
+
*
|
|
10625
|
+
* ## Why settings rather than the runtime instance
|
|
10626
|
+
*
|
|
10627
|
+
* 0.2.1-rc.2's `WebRuntime` reads `this.config.searchProvider.get()` on EVERY
|
|
10628
|
+
* search and fetch, and the loader commits a settings write into that same
|
|
10629
|
+
* reference in place (`Entry._commitVolatile`) without remounting the entry.
|
|
10630
|
+
* Writing the reference directly therefore works, but leaves the value out of
|
|
10631
|
+
* the profile: the next commit for that entry — including one caused by an
|
|
10632
|
+
* unrelated field such as `fetchProvider` — re-resolves the raw config and
|
|
10633
|
+
* overwrites it. `ctx.settings.update()` / `replace()` is the documented path
|
|
10634
|
+
* and the one the shipped web-search settings page uses, so this plugin takes
|
|
10635
|
+
* it first and falls back to the reference only when settings is unavailable or
|
|
10636
|
+
* refuses (see `volatileWrite`).
|
|
10637
|
+
*
|
|
10638
|
+
* ## Two engine shapes
|
|
10639
|
+
*
|
|
10640
|
+
* Everything above describes an rc.2+ runtime, where the selection is a
|
|
10641
|
+
* reference. PRE-rc.2 engines (0.2.0-rc.2 and the 0.2.1 alphas) have neither
|
|
10642
|
+
* that reference nor a settings row for `web`: there the selection is the
|
|
10643
|
+
* private instance field `searchProviderId`, memory-only by nature. Those
|
|
10644
|
+
* engines therefore run the degraded path BY CONSTRUCTION — no profile write
|
|
10645
|
+
* exists for it and the user layer is unreadable, so a foreign pin cannot be
|
|
10646
|
+
* seen and the takeover/restore semantics are the ones this plugin shipped
|
|
10647
|
+
* before rc.2: remember the displaced backend, hand back only what we took. The
|
|
10648
|
+
* shape is resolved per observation, so one build serves both.
|
|
10649
|
+
*
|
|
10650
|
+
* ## Two writers, one field
|
|
10651
|
+
*
|
|
10652
|
+
* The shipped web-search settings page writes `web.searchProvider` too
|
|
10653
|
+
* (`deepseek-search-api` / `deepseek-official`). This seam never displaces a
|
|
10654
|
+
* pin it did not write: a non-`commandcode` value in the USER LAYER stops the
|
|
10655
|
+
* takeover, the value is recorded on {@link CommandCodeSearchSelection.foreign}
|
|
10656
|
+
* and named in one `info` line, and the provider stays registered but
|
|
10657
|
+
* unselected. A value the user layer does not carry is the deployment's own
|
|
10658
|
+
* default, and taking it over is the toggle's whole purpose. A user-layer
|
|
10659
|
+
* `commandcode` is treated as ours: nothing else writes that id, so it can only
|
|
10660
|
+
* be a previous run of this plugin or a deliberate manual pin, and turning the
|
|
10661
|
+
* toggle off restores the composed default in both cases.
|
|
10662
|
+
*
|
|
10663
|
+
* @param deps - the running `web` service, a settings resolver, and a log sink.
|
|
10664
|
+
* @returns a synchronous handle; failures are logged, never thrown.
|
|
10665
|
+
*/
|
|
10666
|
+
function createCommandCodeSearchSelection(deps) {
|
|
10667
|
+
const state = commandCodeSearchSelection();
|
|
10668
|
+
let pending;
|
|
10669
|
+
let activeKey;
|
|
10670
|
+
let running = false;
|
|
10671
|
+
let closed = false;
|
|
10672
|
+
let waiters = [];
|
|
10673
|
+
const warn = (message) => {
|
|
10674
|
+
try {
|
|
10675
|
+
deps.logger.warn(message);
|
|
10676
|
+
} catch {}
|
|
10677
|
+
};
|
|
10678
|
+
const info = (message) => {
|
|
10679
|
+
try {
|
|
10680
|
+
deps.logger.info(message);
|
|
10681
|
+
} catch {}
|
|
10682
|
+
};
|
|
10683
|
+
/**
|
|
10684
|
+
* Commit the selection in memory; the last resort before giving up.
|
|
10685
|
+
*
|
|
10686
|
+
* The volatile reference is tried first because on an rc.2+ runtime that is
|
|
10687
|
+
* what `search()` reads. A PRE-rc.2 runtime carries no reference at all, and
|
|
10688
|
+
* there the private instance field IS the seam — the fallback is a different
|
|
10689
|
+
* engine shape, not a second-best copy of the same write.
|
|
10690
|
+
*/
|
|
10691
|
+
const memoryWrite = (value) => {
|
|
10692
|
+
if (volatileWrite(searchProviderRef(deps.web), value)) return true;
|
|
10693
|
+
if (legacySearchField(deps.web)?.write(value) === true) return true;
|
|
10694
|
+
warn("llm-commandcode: web search selection could not be applied (settings refused the write and the running web runtime is not writable); the provider stays registered but unselected");
|
|
10695
|
+
return false;
|
|
10696
|
+
};
|
|
10697
|
+
/** Run one intent against a FRESH observation; never throws. */
|
|
10698
|
+
const perform = async (intent) => {
|
|
10699
|
+
let view;
|
|
10700
|
+
try {
|
|
10701
|
+
view = readView(deps);
|
|
10702
|
+
} catch {
|
|
10703
|
+
view = void 0;
|
|
10704
|
+
}
|
|
10705
|
+
if (view === void 0) return;
|
|
10706
|
+
const settings = view.persistent ? deps.settings() : void 0;
|
|
10707
|
+
if (intent.kind === "take") {
|
|
10708
|
+
if (view.pinned !== void 0 && view.pinned !== "commandcode") return;
|
|
10709
|
+
if (settings !== void 0) try {
|
|
10710
|
+
await settings.update("web", { searchProvider: COMMANDCODE_SEARCH_PROVIDER_ID });
|
|
10241
10711
|
return;
|
|
10712
|
+
} catch (error) {
|
|
10713
|
+
warn(`llm-commandcode: writing web.searchProvider through settings failed (${error instanceof Error ? error.message : String(error)}); falling back to the running config`);
|
|
10242
10714
|
}
|
|
10243
|
-
|
|
10244
|
-
state.preexisting = prior === COMMANDCODE_SEARCH_PROVIDER_ID;
|
|
10245
|
-
state.displaced = state.preexisting ? void 0 : prior;
|
|
10246
|
-
field.searchProviderId = COMMANDCODE_SEARCH_PROVIDER_ID;
|
|
10247
|
-
state.owner = true;
|
|
10715
|
+
memoryWrite(COMMANDCODE_SEARCH_PROVIDER_ID);
|
|
10248
10716
|
return;
|
|
10249
10717
|
}
|
|
10250
|
-
if (
|
|
10718
|
+
if (view.pinned !== void 0 && view.pinned !== "commandcode") return;
|
|
10719
|
+
if (settings !== void 0) {
|
|
10720
|
+
const section = { ...view.user };
|
|
10721
|
+
delete section.searchProvider;
|
|
10722
|
+
if (view.base !== void 0) section.searchProvider = view.base;
|
|
10723
|
+
try {
|
|
10724
|
+
await settings.replace("web", section);
|
|
10725
|
+
return;
|
|
10726
|
+
} catch (error) {
|
|
10727
|
+
warn(`llm-commandcode: restoring web.searchProvider through settings failed (${error instanceof Error ? error.message : String(error)}); falling back to the running config`);
|
|
10728
|
+
}
|
|
10729
|
+
}
|
|
10730
|
+
memoryWrite(intent.displaced);
|
|
10731
|
+
};
|
|
10732
|
+
const flush = () => {
|
|
10733
|
+
const done = waiters;
|
|
10734
|
+
waiters = [];
|
|
10735
|
+
for (const resolve of done) resolve();
|
|
10736
|
+
};
|
|
10737
|
+
const drain = async () => {
|
|
10738
|
+
running = true;
|
|
10739
|
+
try {
|
|
10740
|
+
while (pending !== void 0 && !closed) {
|
|
10741
|
+
const item = pending;
|
|
10742
|
+
pending = void 0;
|
|
10743
|
+
activeKey = item.key;
|
|
10744
|
+
await perform(item.intent);
|
|
10745
|
+
}
|
|
10746
|
+
} finally {
|
|
10747
|
+
running = false;
|
|
10748
|
+
activeKey = void 0;
|
|
10749
|
+
flush();
|
|
10750
|
+
}
|
|
10751
|
+
};
|
|
10752
|
+
/**
|
|
10753
|
+
* Queue one intent, skipping an identical one already queued or in flight. A
|
|
10754
|
+
* later DIFFERENT intent replaces the queued one: the toggle is a state, not a
|
|
10755
|
+
* log, so only the newest position has to land.
|
|
10756
|
+
*/
|
|
10757
|
+
const submit = (key, intent) => {
|
|
10758
|
+
if (closed) return;
|
|
10759
|
+
if (key === activeKey && pending === void 0) return;
|
|
10760
|
+
if (pending?.key === key) return;
|
|
10761
|
+
pending = {
|
|
10762
|
+
key,
|
|
10763
|
+
intent
|
|
10764
|
+
};
|
|
10765
|
+
if (!running) drain();
|
|
10766
|
+
};
|
|
10767
|
+
const apply = (enable) => {
|
|
10768
|
+
try {
|
|
10769
|
+
const view = readView(deps);
|
|
10770
|
+
if (view === void 0) return;
|
|
10771
|
+
if (!enable) {
|
|
10772
|
+
if (!state.owner) return;
|
|
10773
|
+
state.owner = false;
|
|
10774
|
+
if (state.preexisting) {
|
|
10775
|
+
state.preexisting = false;
|
|
10776
|
+
return;
|
|
10777
|
+
}
|
|
10778
|
+
submit("restore", {
|
|
10779
|
+
kind: "restore",
|
|
10780
|
+
displaced: state.displaced
|
|
10781
|
+
});
|
|
10782
|
+
return;
|
|
10783
|
+
}
|
|
10784
|
+
if (view.pinned !== void 0 && view.pinned !== "commandcode") {
|
|
10785
|
+
if (state.foreign !== view.pinned) {
|
|
10786
|
+
state.foreign = view.pinned;
|
|
10787
|
+
info(`llm-commandcode: web search backend "${view.pinned}" was chosen in the web-search settings page; leaving that explicit choice in place, so the commandcode provider stays registered but unselected`);
|
|
10788
|
+
}
|
|
10789
|
+
state.owner = false;
|
|
10790
|
+
state.preexisting = false;
|
|
10791
|
+
return;
|
|
10792
|
+
}
|
|
10793
|
+
state.foreign = void 0;
|
|
10794
|
+
if (view.configured === "commandcode") {
|
|
10795
|
+
state.owner = view.pinned === "commandcode" || !view.persistent && state.owner;
|
|
10796
|
+
state.preexisting = !state.owner;
|
|
10797
|
+
if (state.owner && state.displaced === void 0) state.displaced = view.base;
|
|
10798
|
+
return;
|
|
10799
|
+
}
|
|
10800
|
+
state.displaced = view.configured;
|
|
10801
|
+
state.preexisting = false;
|
|
10802
|
+
state.owner = true;
|
|
10803
|
+
submit("take", {
|
|
10804
|
+
kind: "take",
|
|
10805
|
+
displaced: view.configured
|
|
10806
|
+
});
|
|
10807
|
+
} catch (error) {
|
|
10808
|
+
warn(`llm-commandcode: aligning the web search selection failed (${error instanceof Error ? error.message : String(error)})`);
|
|
10809
|
+
}
|
|
10810
|
+
};
|
|
10811
|
+
return {
|
|
10812
|
+
state,
|
|
10813
|
+
apply,
|
|
10814
|
+
settled: () => running || pending !== void 0 ? new Promise((resolve) => {
|
|
10815
|
+
waiters.push(resolve);
|
|
10816
|
+
}) : Promise.resolve(),
|
|
10817
|
+
release: () => {
|
|
10818
|
+
closed = true;
|
|
10819
|
+
pending = void 0;
|
|
10820
|
+
if (state.owner && !state.preexisting) memoryWrite(state.displaced);
|
|
10251
10821
|
state.owner = false;
|
|
10252
|
-
if (!state.preexisting) field.searchProviderId = state.displaced;
|
|
10253
10822
|
state.preexisting = false;
|
|
10254
|
-
|
|
10823
|
+
if (!running) flush();
|
|
10255
10824
|
}
|
|
10256
|
-
}
|
|
10825
|
+
};
|
|
10257
10826
|
}
|
|
10258
10827
|
/** Command Code's lower/upper bound on `numResults` (from the CLI's `web_search` schema). */
|
|
10259
10828
|
const MIN_NUM_RESULTS = 1;
|
|
@@ -10337,7 +10906,7 @@ var CommandCodeSearchProvider = class {
|
|
|
10337
10906
|
});
|
|
10338
10907
|
} catch (error) {
|
|
10339
10908
|
if (signal?.aborted === true || isAbortError(error)) throw searchAborted(signal, error);
|
|
10340
|
-
throw new WebError(`Command Code web search request failed: ${
|
|
10909
|
+
throw new WebError(`Command Code web search request failed: ${readableErrorText(error)}`, "WEB_PROVIDER_ERROR", { cause: error });
|
|
10341
10910
|
}
|
|
10342
10911
|
if (!response.ok) {
|
|
10343
10912
|
let message = `Command Code web search failed (HTTP ${response.status})`;
|
|
@@ -10391,7 +10960,7 @@ var CommandCodeSearchProvider = class {
|
|
|
10391
10960
|
if (code === "MISSING_CREDENTIAL") throw new WebError(error.message, "WEB_PROVIDER_CREDENTIAL_MISSING", { cause: error });
|
|
10392
10961
|
if (code === "INVALID_CREDENTIAL" || code === "RATE_LIMIT") throw new WebError(error.message, "WEB_PROVIDER_ERROR", { cause: error });
|
|
10393
10962
|
}
|
|
10394
|
-
throw new WebError(`Command Code web search credential resolution failed: ${
|
|
10963
|
+
throw new WebError(`Command Code web search credential resolution failed: ${readableErrorText(error)}`, "WEB_PROVIDER_ERROR", { cause: error });
|
|
10395
10964
|
}
|
|
10396
10965
|
if (key === void 0 || key.length === 0) throw new WebError("Command Code web search has no API key; store COMMANDCODE_API_KEY through the credentials service (the web Models page writes it), export it in the launching environment, set config.apiKey, or run `command-code login` to write ~/.commandcode/auth.json", "WEB_PROVIDER_CREDENTIAL_MISSING");
|
|
10397
10966
|
return key;
|
|
@@ -10722,7 +11291,7 @@ function applyCommandCodeTuiSettings(ctx, deps) {
|
|
|
10722
11291
|
dispose: service.register(section)
|
|
10723
11292
|
};
|
|
10724
11293
|
} catch (error) {
|
|
10725
|
-
ctx.logger?.warn(`llm-commandcode: could not register the dsh-TUI settings section: ${
|
|
11294
|
+
ctx.logger?.warn(`llm-commandcode: could not register the dsh-TUI settings section: ${readableErrorText(error)}`);
|
|
10726
11295
|
}
|
|
10727
11296
|
};
|
|
10728
11297
|
refresh();
|
|
@@ -10805,78 +11374,6 @@ function transportBudgetMessage(failureMessage, maxRetries) {
|
|
|
10805
11374
|
return `${failureMessage};Command Code API 连接在连续重试后仍失败——已停止重试以免整轮卡死。插件已自动重试 ${maxRetries} 次。通常说明当前网络到 api.commandcode.ai 的连接不通,常见原因是代理未生效:DSH 只读取环境变量 HTTPS_PROXY/HTTP_PROXY(系统代理/PAC 不会被读取),请确认启动 DSH 的终端里已导出代理,或先关闭代理直连后重试。网络恢复后直接重发即可 — Command Code API transport still failed after ${maxRetries} automatic retries, which were stopped so the turn could not stall for minutes. This is a connectivity problem between this machine and api.commandcode.ai, not a context-window or request-size rejection. Common cause: the proxy is not reaching the harness — DSH reads HTTPS_PROXY/HTTP_PROXY from the environment only (a Windows system proxy/PAC setting is not consulted), so export it in the launching shell, or turn the proxy off and retry. Resend when the network recovers; the budget is per request and has been reset`;
|
|
10806
11375
|
}
|
|
10807
11376
|
//#endregion
|
|
10808
|
-
//#region src/config-volatile.ts
|
|
10809
|
-
/**
|
|
10810
|
-
* Volatile-config helpers for dsh 0.1.7's schema-derived profile Config.
|
|
10811
|
-
*
|
|
10812
|
-
* 0.1.7's `settings.describe()` projects each entry's schema through
|
|
10813
|
-
* `volatileForm()`, so a form contains ONLY nodes carrying `meta.volatile`
|
|
10814
|
-
* and a form edit is refused unless its path lies beneath a marked node. The
|
|
10815
|
-
* loader then resolves each marked field into a frozen `{ get() }` reference
|
|
10816
|
-
* that is written IN PLACE (no remount) and announced with
|
|
10817
|
-
* `loader/volatile-update`. Two helpers keep the rest of the plugin reading a
|
|
10818
|
-
* PLAIN `Config`: `markVolatile()` applies the mark, and
|
|
10819
|
-
* `unwrapVolatileConfig()` reads every top-level field through its reference
|
|
10820
|
-
* when one is present — FRESH per call, because a reference's identity is
|
|
10821
|
-
* stable while its value changes.
|
|
10822
|
-
*
|
|
10823
|
-
* Only TOP-LEVEL fields are marked (a form write names a top-level path such
|
|
10824
|
-
* as `accounts` or `modelVisibility.<id>`), so one unwrap level is complete.
|
|
10825
|
-
* The literal `apiKey` secret stays unmarked on purpose: no settings surface
|
|
10826
|
-
* writes it — page and TUI both write keys through the credentials seam — so a
|
|
10827
|
-
* config-file edit to it reloading the fiber is correct for a secret literal.
|
|
10828
|
-
*
|
|
10829
|
-
* @module dsh-commandcode-provider/config-volatile
|
|
10830
|
-
*/
|
|
10831
|
-
/**
|
|
10832
|
-
* Mark one schema field volatile.
|
|
10833
|
-
*
|
|
10834
|
-
* `.volatile()` is unconditional: it is a schemastery 3.18.3 feature and every
|
|
10835
|
-
* supported engine ships `~3.18.4` or `~3.18.5-alpha.1`, so the
|
|
10836
|
-
* method is always present. It is reached through a structural member so the caller's
|
|
10837
|
-
* declared field type survives — the mark changes what the schema PARSES to (a
|
|
10838
|
-
* live reference), not the plugin-facing `Config` shape.
|
|
10839
|
-
*/
|
|
10840
|
-
function markVolatile(schema) {
|
|
10841
|
-
return schema.volatile();
|
|
10842
|
-
}
|
|
10843
|
-
/**
|
|
10844
|
-
* Mark every field of a Config field dict volatile, except the named
|
|
10845
|
-
* composition-only secrets (`apiKey` here, which no settings surface writes).
|
|
10846
|
-
*/
|
|
10847
|
-
function markVolatileFields(fields, skip = []) {
|
|
10848
|
-
return Object.fromEntries(Object.entries(fields).map(([name, schema]) => [name, skip.includes(name) ? schema : markVolatile(schema)]));
|
|
10849
|
-
}
|
|
10850
|
-
/**
|
|
10851
|
-
* Whether a resolved config value is a volatile reference.
|
|
10852
|
-
*
|
|
10853
|
-
* Duck-typed on the two properties `createVolatile` produces (frozen object
|
|
10854
|
-
* with a `get` method) rather than cosmokit's private `write` symbol, because
|
|
10855
|
-
* cosmokit is not a peer of this bundle. Config values are plain parsed JSON
|
|
10856
|
-
* (or `!!jsExpr` wrappers), which are never frozen, so the test cannot
|
|
10857
|
-
* misfire on an ordinary field.
|
|
10858
|
-
*/
|
|
10859
|
-
function isVolatileRef(value) {
|
|
10860
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.isFrozen(value) && typeof value.get === "function";
|
|
10861
|
-
}
|
|
10862
|
-
/**
|
|
10863
|
-
* Read one config object as PLAIN values.
|
|
10864
|
-
*
|
|
10865
|
-
* Every top-level field found to be a volatile reference is read through
|
|
10866
|
-
* `.get()` — freshly on every call, so a live update lands on the very next
|
|
10867
|
-
* read (the loader resolves EVERY marked node into a reference, unset fields
|
|
10868
|
-
* included, so a loader-parsed config always yields a fresh object here). The
|
|
10869
|
-
* pass-through for a field that is not a reference is what keeps the unmarked
|
|
10870
|
-
* `apiKey` literal readable.
|
|
10871
|
-
*/
|
|
10872
|
-
function unwrapVolatileConfig(config) {
|
|
10873
|
-
const source = config;
|
|
10874
|
-
if (typeof source !== "object" || source === null) return config;
|
|
10875
|
-
const plain = {};
|
|
10876
|
-
for (const [field, value] of Object.entries(source)) plain[field] = isVolatileRef(value) ? value.get() : value;
|
|
10877
|
-
return plain;
|
|
10878
|
-
}
|
|
10879
|
-
//#endregion
|
|
10880
11377
|
//#region src/index.ts
|
|
10881
11378
|
/**
|
|
10882
11379
|
* dsh-commandcode-provider — DeepSeek Harness LLM provider plugin for Command
|
|
@@ -10936,6 +11433,7 @@ const Config = z.object(markVolatileFields({
|
|
|
10936
11433
|
modelVisibility: z.dict(z.boolean()),
|
|
10937
11434
|
contextWindow: z.number().min(1).max(CONTEXT_WINDOW_MAX).step(1),
|
|
10938
11435
|
contextWindowOverrides: z.dict(z.number().min(1).max(CONTEXT_WINDOW_MAX).step(1)),
|
|
11436
|
+
maxOutputTokens: z.number().min(0).max(MAX_OUTPUT_TOKEN_LIMIT).step(1),
|
|
10939
11437
|
webSearch: z.boolean().default(true),
|
|
10940
11438
|
showSidebarQuota: z.boolean().default(false),
|
|
10941
11439
|
accounts: z.array(z.object({
|
|
@@ -10983,6 +11481,7 @@ function resolveAdapterOptions(config) {
|
|
|
10983
11481
|
modelVisibility: readModelVisibility(config.modelVisibility),
|
|
10984
11482
|
contextWindow: isValidContextWindow(config.contextWindow) ? config.contextWindow : void 0,
|
|
10985
11483
|
contextWindowOverrides: readContextWindowOverrides(config.contextWindowOverrides),
|
|
11484
|
+
maxOutputTokens: isValidMaxOutputTokens(config.maxOutputTokens) ? config.maxOutputTokens : void 0,
|
|
10986
11485
|
zdr: config.zdr === true
|
|
10987
11486
|
};
|
|
10988
11487
|
}
|
|
@@ -11143,7 +11642,7 @@ function apply(ctx, config) {
|
|
|
11143
11642
|
else try {
|
|
11144
11643
|
report = await adapter.getUsage(account.key, connection);
|
|
11145
11644
|
} catch (error) {
|
|
11146
|
-
report = { failures: [
|
|
11645
|
+
report = { failures: [readableErrorText(error)] };
|
|
11147
11646
|
}
|
|
11148
11647
|
const state = account?.state;
|
|
11149
11648
|
const usable = accountUsable(state);
|
|
@@ -11225,13 +11724,24 @@ function apply(ctx, config) {
|
|
|
11225
11724
|
listModels: catalogForEditors,
|
|
11226
11725
|
analytics: createAnalyticsReader(ctx, adapter)
|
|
11227
11726
|
});
|
|
11228
|
-
const
|
|
11727
|
+
const selectionLogger = {
|
|
11728
|
+
warn: (message) => {
|
|
11729
|
+
ctx.logger.warn(message);
|
|
11730
|
+
},
|
|
11731
|
+
info: (message) => {
|
|
11732
|
+
ctx.logger.info(message);
|
|
11733
|
+
}
|
|
11734
|
+
};
|
|
11735
|
+
let searchSelection;
|
|
11229
11736
|
const applySearchSelection = (enabled) => {
|
|
11230
|
-
|
|
11737
|
+
searchSelection?.apply(enabled);
|
|
11231
11738
|
};
|
|
11232
|
-
let webRuntime;
|
|
11233
11739
|
ctx.inject(["web"], (webCtx) => {
|
|
11234
|
-
|
|
11740
|
+
searchSelection = createCommandCodeSearchSelection({
|
|
11741
|
+
web: webCtx.web,
|
|
11742
|
+
settings: () => ctx.get("settings"),
|
|
11743
|
+
logger: selectionLogger
|
|
11744
|
+
});
|
|
11235
11745
|
webCtx.web.registerSearchProvider(new CommandCodeSearchProvider({
|
|
11236
11746
|
resolveKey: async (apiBase) => {
|
|
11237
11747
|
const connection = options();
|
|
@@ -11243,8 +11753,8 @@ function apply(ctx, config) {
|
|
|
11243
11753
|
}));
|
|
11244
11754
|
applySearchSelection(current().webSearch ?? true);
|
|
11245
11755
|
webCtx.effect(() => () => {
|
|
11246
|
-
|
|
11247
|
-
|
|
11756
|
+
searchSelection?.release();
|
|
11757
|
+
searchSelection = void 0;
|
|
11248
11758
|
}, "dsh-commandcode-provider: web search selection");
|
|
11249
11759
|
});
|
|
11250
11760
|
let refreshTuiSettings;
|
|
@@ -11289,6 +11799,7 @@ function apply(ctx, config) {
|
|
|
11289
11799
|
dispose();
|
|
11290
11800
|
};
|
|
11291
11801
|
}, "dsh-commandcode-provider: settings auto-form policy");
|
|
11802
|
+
applySearchSelection(current().webSearch ?? true);
|
|
11292
11803
|
});
|
|
11293
11804
|
const loaderEvents = ctx;
|
|
11294
11805
|
ctx.effect(() => {
|
|
@@ -11302,6 +11813,6 @@ function apply(ctx, config) {
|
|
|
11302
11813
|
}, "dsh-commandcode-provider: volatile config updates");
|
|
11303
11814
|
}
|
|
11304
11815
|
//#endregion
|
|
11305
|
-
export { ACTIVE_ACCOUNT_AUTO, BILLING_ACCESS_TTL_MS, COMMANDCODE_SEARCH_PROVIDER_ID, COMMAND_CODE_CLI_VERSION, CommandCodeAccountPool, CommandCodeAdapter, CommandCodeLoginFlow, CommandCodeSearchProvider, CommandCodeUsageService, Config, DEFAULT_API_BASE, DEFAULT_GENERATE_MAX_TOKENS, DEFAULT_MAX_OUTPUT_TOKENS, DEFAULT_MODELS_CACHE_PATH, DEFAULT_REQUEST_TIMEOUT_MS, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DEFAULT_WEB_SEARCH_PROVIDER_ID, KNOWN_DEALS, KNOWN_EFFORTS, KNOWN_IMAGE_MODELS, KNOWN_NON_ZDR_MODELS, KNOWN_PEAK_PRICING, KNOWN_PLANS, KNOWN_SUBSCRIPTION_PLANS, KNOWN_THINKING_MODELS, LANG_AUTO, LOGIN_ALLOWED_ORIGINS, LOGIN_BEGIN_ENDPOINT, LOGIN_BODY_LIMIT_BYTES, LOGIN_CANCEL_ENDPOINT, LOGIN_MAX_PORT_ATTEMPTS, LOGIN_START_PORT, LOGIN_STATUS_ENDPOINT, LOGIN_TIMEOUT_MS, PLAN_LABELS, PLAN_ORDER, PROVIDER, USAGE_REPORT_ENDPOINT, accountUsable, apply,
|
|
11816
|
+
export { ACTIVE_ACCOUNT_AUTO, BILLING_ACCESS_TTL_MS, COMMANDCODE_SEARCH_PROVIDER_ID, COMMAND_CODE_CLI_VERSION, CommandCodeAccountPool, CommandCodeAdapter, CommandCodeLoginFlow, CommandCodeSearchProvider, CommandCodeUsageService, Config, DEFAULT_API_BASE, DEFAULT_GENERATE_MAX_TOKENS, DEFAULT_MAX_OUTPUT_TOKENS, DEFAULT_MODELS_CACHE_PATH, DEFAULT_REQUEST_TIMEOUT_MS, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DEFAULT_WEB_SEARCH_PROVIDER_ID, KNOWN_DEALS, KNOWN_EFFORTS, KNOWN_IMAGE_MODELS, KNOWN_NON_ZDR_MODELS, KNOWN_PEAK_PRICING, KNOWN_PLANS, KNOWN_SUBSCRIPTION_PLANS, KNOWN_THINKING_MODELS, LANG_AUTO, LOGIN_ALLOWED_ORIGINS, LOGIN_BEGIN_ENDPOINT, LOGIN_BODY_LIMIT_BYTES, LOGIN_CANCEL_ENDPOINT, LOGIN_MAX_PORT_ATTEMPTS, LOGIN_START_PORT, LOGIN_STATUS_ENDPOINT, LOGIN_TIMEOUT_MS, PLAN_LABELS, PLAN_ORDER, PROVIDER, USAGE_REPORT_ENDPOINT, WEB_SELECTION_NAMESPACE, accountUsable, apply, applyCommandCodeTuiSettings, applyCommands, applyUsageRemote, buildCommandAuthUrl, buildCommandCodeTuiSection, capabilityDescription, commandCodeSearchSelection, commandDefinition, compareByPlan, createCommandCodeSearchSelection, dealLabel, formatContext, inject, loginStatusSchema, matchModelRule, modelVisibleInPlan, name, parseLoginStatus, peakPricingLabel, peakPricingState, planLabel, projectSlugFromPath, resolveAdapterOptions, resolveAuthFileApiKey, selectAccountForModel, selectActiveAccount, studioBaseForApiBase, subscriptionPlanInfo, supportsZeroDataRetention, usageReportSchema, validateCommandApiKey };
|
|
11306
11817
|
|
|
11307
11818
|
//# sourceMappingURL=index.js.map
|