@mars-sea/dsh-commandcode-provider 0.9.1 → 0.10.0-alpha.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 +31 -1
- package/README.md +31 -5
- package/README.zh-CN.md +31 -5
- package/lib/client.js +715 -110
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +91 -12
- package/lib/index.js +136 -37
- package/lib/index.js.map +1 -1
- package/package.json +38 -39
package/lib/client.js
CHANGED
|
@@ -4,10 +4,38 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
-
let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
|
|
8
7
|
let react = require("react");
|
|
9
8
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
10
9
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
|
+
//#region src/client/snapshot-store.ts
|
|
11
|
+
/** Notify every subscriber without letting one faulty UI consumer suppress the rest. */
|
|
12
|
+
function notifyListeners(listeners) {
|
|
13
|
+
for (const listener of listeners) try {
|
|
14
|
+
listener();
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.error("[dsh-commandcode-provider] snapshot subscriber failed:", error);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Create one observable snapshot store. */
|
|
20
|
+
function createSnapshotStore(initial) {
|
|
21
|
+
let snapshot = initial;
|
|
22
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
23
|
+
return {
|
|
24
|
+
getSnapshot: () => snapshot,
|
|
25
|
+
subscribe(listener) {
|
|
26
|
+
listeners.add(listener);
|
|
27
|
+
return () => {
|
|
28
|
+
listeners.delete(listener);
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
set(value) {
|
|
32
|
+
if (Object.is(value, snapshot)) return;
|
|
33
|
+
snapshot = value;
|
|
34
|
+
notifyListeners(listeners);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
11
39
|
//#region src/command-locales.ts
|
|
12
40
|
const commandcodeCommand = {
|
|
13
41
|
zh: {
|
|
@@ -137,12 +165,49 @@ window.__ModuleLoader__.load({
|
|
|
137
165
|
}
|
|
138
166
|
};
|
|
139
167
|
}
|
|
140
|
-
/**
|
|
168
|
+
/**
|
|
169
|
+
* Install the friendly-error wrapper when a legacy sessions face is present.
|
|
170
|
+
*
|
|
171
|
+
* @returns whether the wrapper was installed. The rewrite is only UX polish;
|
|
172
|
+
* a 0.1.2 connection has no `api.sessions`, and its absence must never block
|
|
173
|
+
* the plugin from mounting its settings, credential, or usage surfaces.
|
|
174
|
+
*/
|
|
141
175
|
function installFriendlyImageError(connection, getLocale) {
|
|
142
|
-
|
|
176
|
+
const api = connection.api;
|
|
177
|
+
const sessions = api?.sessions;
|
|
178
|
+
if (api === void 0 || sessions === void 0 || typeof sessions.selectModel !== "function") return false;
|
|
179
|
+
api.sessions = withFriendlyImageError(sessions, getLocale);
|
|
180
|
+
return true;
|
|
143
181
|
}
|
|
144
182
|
//#endregion
|
|
145
183
|
//#region src/client/settings.ts
|
|
184
|
+
/**
|
|
185
|
+
* Browser controller for the "Command Code" settings page.
|
|
186
|
+
*
|
|
187
|
+
* The page lives at the same settings-nav level as General / Models / Plugins
|
|
188
|
+
* (a `settings.section` entry, id `commandcode`). It exists because the
|
|
189
|
+
* Models page renders an unknown-adapter-family card for the `commandcode`
|
|
190
|
+
* provider and deliberately disables its submit — the API key cannot be
|
|
191
|
+
* configured there. This page owns the connection facts the plugin resolves
|
|
192
|
+
* per request:
|
|
193
|
+
*
|
|
194
|
+
* - API key -> written through the credentials domain under the reference
|
|
195
|
+
* the plugin resolves (`apiKeyEnv`, default
|
|
196
|
+
* `COMMANDCODE_API_KEY`). The literal never rides a response,
|
|
197
|
+
* so the control only reports whether one is configured.
|
|
198
|
+
* - API base -> the `llm-commandcode` settings namespace (`apiBase`), same
|
|
199
|
+
* namespace the Models page card addresses.
|
|
200
|
+
* - Working dir, request/stream timeouts -> the same namespace.
|
|
201
|
+
*
|
|
202
|
+
* The controller mirrors the plugin-card pattern from the harness's own
|
|
203
|
+
* settings UI: it binds the `llm-commandcode` namespace through the
|
|
204
|
+
* `settingsScope` service, keeps a staged draft of edits, and writes them on
|
|
205
|
+
* save through `scope.set` / the credentials domain. The Host stays the
|
|
206
|
+
* single fact source; the snapshot is republished after each accepted write.
|
|
207
|
+
*
|
|
208
|
+
* This module is deliberately free of JSX — it only produces the state face
|
|
209
|
+
* the React component renders.
|
|
210
|
+
*/
|
|
146
211
|
/** The settings namespace the plugin registers (host half, src/index.ts). */
|
|
147
212
|
const COMMANDCODE_NS = "llm-commandcode";
|
|
148
213
|
/** Default credential reference the plugin resolves when none is named. */
|
|
@@ -239,6 +304,12 @@ window.__ModuleLoader__.load({
|
|
|
239
304
|
booleanField$1("filterModelsByPlan"),
|
|
240
305
|
textField("activeAccount")
|
|
241
306
|
];
|
|
307
|
+
/** Whether two model-id lists are equal as sets (order-insensitive). */
|
|
308
|
+
function sameModels(a, b) {
|
|
309
|
+
if (a.length !== b.length) return false;
|
|
310
|
+
const set = new Set(a);
|
|
311
|
+
return b.every((id) => set.has(id));
|
|
312
|
+
}
|
|
242
313
|
/**
|
|
243
314
|
* Controller bridging the `llm-commandcode` scope and the credentials domain
|
|
244
315
|
* onto the page. Public API mirrors the harness's CardForm actions, so the
|
|
@@ -267,6 +338,15 @@ window.__ModuleLoader__.load({
|
|
|
267
338
|
keyDrafts = /* @__PURE__ */ new Map();
|
|
268
339
|
/** Credential references staged for removal on the next save. */
|
|
269
340
|
keyClears = /* @__PURE__ */ new Set();
|
|
341
|
+
/** Staged model→account routing rules (not yet saved). */
|
|
342
|
+
addedRules = [];
|
|
343
|
+
/** Staged edits to stored routing rules, by stored row id. */
|
|
344
|
+
ruleDrafts = /* @__PURE__ */ new Map();
|
|
345
|
+
/** Stored routing rule rows staged for removal. */
|
|
346
|
+
removedRuleIds = /* @__PURE__ */ new Set();
|
|
347
|
+
/** The catalog the rule editor offers (Host-side). */
|
|
348
|
+
catalogModels = [];
|
|
349
|
+
catalogFailed = false;
|
|
270
350
|
saving = false;
|
|
271
351
|
failed = false;
|
|
272
352
|
savedCount = 0;
|
|
@@ -297,6 +377,7 @@ window.__ModuleLoader__.load({
|
|
|
297
377
|
}
|
|
298
378
|
this.recomputeCredentialRef();
|
|
299
379
|
this.describeAll();
|
|
380
|
+
this.refreshCatalog();
|
|
300
381
|
}
|
|
301
382
|
/** Release every subscription held on external sources. Idempotent. */
|
|
302
383
|
dispose() {
|
|
@@ -353,7 +434,10 @@ window.__ModuleLoader__.load({
|
|
|
353
434
|
activeAccount: this.field("activeAccount"),
|
|
354
435
|
accounts,
|
|
355
436
|
accountsRemoving: [...this.removedRefs],
|
|
356
|
-
|
|
437
|
+
rules: this.effectiveRules(),
|
|
438
|
+
catalogModels: this.catalogModels,
|
|
439
|
+
catalogFailed: this.catalogFailed,
|
|
440
|
+
dirty: plan.length > 0 || this.accountsDirty() || this.rulesDirty(),
|
|
357
441
|
invalid: plan.some((item) => item.run === void 0),
|
|
358
442
|
saving: this.saving,
|
|
359
443
|
failed: this.failed,
|
|
@@ -426,6 +510,64 @@ window.__ModuleLoader__.load({
|
|
|
426
510
|
this.describeAll();
|
|
427
511
|
this.publish();
|
|
428
512
|
}
|
|
513
|
+
/** Stage a new model → account routing rule (saved on the next `save()`). */
|
|
514
|
+
addRule() {
|
|
515
|
+
this.addedRules.push({
|
|
516
|
+
models: [],
|
|
517
|
+
account: "default"
|
|
518
|
+
});
|
|
519
|
+
this.failed = false;
|
|
520
|
+
this.publish();
|
|
521
|
+
}
|
|
522
|
+
/** Stage one routing rule's removal (or drop an unsaved addition). */
|
|
523
|
+
removeRule(id) {
|
|
524
|
+
const addedIndex = this.addedRules.findIndex((_, index) => `new-${index}` === id);
|
|
525
|
+
if (addedIndex >= 0) this.addedRules.splice(addedIndex, 1);
|
|
526
|
+
else this.removedRuleIds.add(id);
|
|
527
|
+
this.ruleDrafts.delete(id);
|
|
528
|
+
this.failed = false;
|
|
529
|
+
this.publish();
|
|
530
|
+
}
|
|
531
|
+
/** Stage one routing rule's selected model ids (multi-select). */
|
|
532
|
+
editRuleModels(id, models) {
|
|
533
|
+
const addedIndex = this.addedRules.findIndex((_, index) => `new-${index}` === id);
|
|
534
|
+
if (addedIndex >= 0) this.addedRules[addedIndex] = {
|
|
535
|
+
...this.addedRules[addedIndex],
|
|
536
|
+
models
|
|
537
|
+
};
|
|
538
|
+
else {
|
|
539
|
+
const current = this.ruleDrafts.get(id) ?? this.storedRules().find((rule) => rule.id === id) ?? {
|
|
540
|
+
models: [],
|
|
541
|
+
account: "default"
|
|
542
|
+
};
|
|
543
|
+
this.ruleDrafts.set(id, {
|
|
544
|
+
...current,
|
|
545
|
+
models
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
this.failed = false;
|
|
549
|
+
this.publish();
|
|
550
|
+
}
|
|
551
|
+
/** Stage one routing rule's target account draft. */
|
|
552
|
+
editRuleAccount(id, text) {
|
|
553
|
+
const addedIndex = this.addedRules.findIndex((_, index) => `new-${index}` === id);
|
|
554
|
+
if (addedIndex >= 0) this.addedRules[addedIndex] = {
|
|
555
|
+
...this.addedRules[addedIndex],
|
|
556
|
+
account: text
|
|
557
|
+
};
|
|
558
|
+
else {
|
|
559
|
+
const current = this.ruleDrafts.get(id) ?? this.storedRules().find((rule) => rule.id === id) ?? {
|
|
560
|
+
models: [],
|
|
561
|
+
account: "default"
|
|
562
|
+
};
|
|
563
|
+
this.ruleDrafts.set(id, {
|
|
564
|
+
...current,
|
|
565
|
+
account: text
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
this.failed = false;
|
|
569
|
+
this.publish();
|
|
570
|
+
}
|
|
429
571
|
/** Stage one field's draft text. */
|
|
430
572
|
edit(field, text) {
|
|
431
573
|
this.staged.set(field, {
|
|
@@ -454,9 +596,10 @@ window.__ModuleLoader__.load({
|
|
|
454
596
|
}
|
|
455
597
|
/** Discard every staged edit. */
|
|
456
598
|
discard() {
|
|
457
|
-
if (this.staged.size === 0 && !this.accountsStaged() && !this.failed) return;
|
|
599
|
+
if (this.staged.size === 0 && !this.accountsStaged() && !this.rulesStaged() && !this.failed) return;
|
|
458
600
|
this.staged.clear();
|
|
459
601
|
this.clearAccountStaging();
|
|
602
|
+
this.clearRuleStaging();
|
|
460
603
|
this.failed = false;
|
|
461
604
|
this.publish();
|
|
462
605
|
}
|
|
@@ -472,7 +615,8 @@ window.__ModuleLoader__.load({
|
|
|
472
615
|
async save() {
|
|
473
616
|
const plan = this.plan();
|
|
474
617
|
const accountRuns = this.accountPlan();
|
|
475
|
-
|
|
618
|
+
const ruleRuns = this.rulesPlan();
|
|
619
|
+
if (plan.length === 0 && accountRuns.length === 0 && ruleRuns.length === 0 || this.saving) return;
|
|
476
620
|
const runs = [];
|
|
477
621
|
for (const item of plan) {
|
|
478
622
|
if (item.run === void 0) return;
|
|
@@ -482,7 +626,11 @@ window.__ModuleLoader__.load({
|
|
|
482
626
|
this.failed = false;
|
|
483
627
|
this.publish();
|
|
484
628
|
let landed = true;
|
|
485
|
-
for (const run of [
|
|
629
|
+
for (const run of [
|
|
630
|
+
...runs,
|
|
631
|
+
...accountRuns,
|
|
632
|
+
...ruleRuns
|
|
633
|
+
]) if (!await run()) {
|
|
486
634
|
landed = false;
|
|
487
635
|
break;
|
|
488
636
|
}
|
|
@@ -492,7 +640,11 @@ window.__ModuleLoader__.load({
|
|
|
492
640
|
this.savedCount += 1;
|
|
493
641
|
this.staged.clear();
|
|
494
642
|
this.clearAccountStaging();
|
|
495
|
-
|
|
643
|
+
this.clearRuleStaging();
|
|
644
|
+
} else {
|
|
645
|
+
this.reconcileAccountStaging();
|
|
646
|
+
this.reconcileRuleStaging();
|
|
647
|
+
}
|
|
496
648
|
this.publish();
|
|
497
649
|
}
|
|
498
650
|
/**
|
|
@@ -607,10 +759,7 @@ window.__ModuleLoader__.load({
|
|
|
607
759
|
/** Write one account's key, then re-read the Host's credential states. */
|
|
608
760
|
async writeKeyTo(ref, value) {
|
|
609
761
|
try {
|
|
610
|
-
if (!(await this.api.credentials.set(
|
|
611
|
-
ref,
|
|
612
|
-
value
|
|
613
|
-
})).result.ok) return false;
|
|
762
|
+
if (!(await this.api.credentials.set(ref, value)).ok) return false;
|
|
614
763
|
} catch {
|
|
615
764
|
return false;
|
|
616
765
|
}
|
|
@@ -626,14 +775,14 @@ window.__ModuleLoader__.load({
|
|
|
626
775
|
];
|
|
627
776
|
let response;
|
|
628
777
|
try {
|
|
629
|
-
response = await this.api.credentials.describe(
|
|
778
|
+
response = await this.api.credentials.describe(refs);
|
|
630
779
|
} catch {
|
|
631
780
|
return;
|
|
632
781
|
}
|
|
633
|
-
if (!response.
|
|
782
|
+
if (!response.ok) return;
|
|
634
783
|
let changed = false;
|
|
635
784
|
for (const ref of refs) {
|
|
636
|
-
const view = response.
|
|
785
|
+
const view = response.value?.[ref];
|
|
637
786
|
const next = {
|
|
638
787
|
configured: view?.configured ?? false,
|
|
639
788
|
writable: view?.writable ?? true
|
|
@@ -646,6 +795,28 @@ window.__ModuleLoader__.load({
|
|
|
646
795
|
}
|
|
647
796
|
if (changed) this.publish();
|
|
648
797
|
}
|
|
798
|
+
/**
|
|
799
|
+
* Fetch the model catalog for the routing-rule editor through the Host
|
|
800
|
+
* Remote. Runs once at construction; call again (e.g. from the client entry
|
|
801
|
+
* once the Remote mount lands) to (re)try — a later success clears a prior
|
|
802
|
+
* failure flag so the editor recovers without a page reload.
|
|
803
|
+
*/
|
|
804
|
+
refreshCatalog() {
|
|
805
|
+
const models = this.api.models;
|
|
806
|
+
if (models === void 0) {
|
|
807
|
+
this.catalogFailed = true;
|
|
808
|
+
this.publish();
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
models().then((response) => {
|
|
812
|
+
if (response.ok && Array.isArray(response.value?.models)) {
|
|
813
|
+
this.catalogModels = response.value.models;
|
|
814
|
+
this.catalogFailed = false;
|
|
815
|
+
} else this.catalogFailed = true;
|
|
816
|
+
}, () => {
|
|
817
|
+
this.catalogFailed = true;
|
|
818
|
+
}).then(() => this.publish());
|
|
819
|
+
}
|
|
649
820
|
/** The stored extra accounts from the settings section (`accounts`). */
|
|
650
821
|
storedExtras() {
|
|
651
822
|
const raw = this.scope.getSnapshot().value?.accounts;
|
|
@@ -711,7 +882,7 @@ window.__ModuleLoader__.load({
|
|
|
711
882
|
/** Unset one stored credential, then re-read the Host's credential states. */
|
|
712
883
|
async unsetKey(ref) {
|
|
713
884
|
try {
|
|
714
|
-
if (!(await this.api.credentials.unset(
|
|
885
|
+
if (!(await this.api.credentials.unset(ref)).ok) return false;
|
|
715
886
|
} catch {
|
|
716
887
|
return false;
|
|
717
888
|
}
|
|
@@ -745,12 +916,140 @@ window.__ModuleLoader__.load({
|
|
|
745
916
|
const after = this.storedExtras();
|
|
746
917
|
return after.length === list.length && list.every((item, index) => after[index]?.ref === item.apiKeyEnv);
|
|
747
918
|
}
|
|
919
|
+
/** The stored routing rules from the settings section (`modelAccountRules`). */
|
|
920
|
+
storedRules() {
|
|
921
|
+
const raw = this.scope.getSnapshot().value?.modelAccountRules;
|
|
922
|
+
if (!Array.isArray(raw)) return [];
|
|
923
|
+
const out = [];
|
|
924
|
+
for (const [index, entry] of raw.entries()) {
|
|
925
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) continue;
|
|
926
|
+
const record = entry;
|
|
927
|
+
const models = record.models;
|
|
928
|
+
const account = record.account;
|
|
929
|
+
const modelsList = Array.isArray(models) && models.every((m) => typeof m === "string") ? models.filter((m) => m !== "") : [];
|
|
930
|
+
if (modelsList.length === 0) continue;
|
|
931
|
+
out.push({
|
|
932
|
+
id: `rule-${index}`,
|
|
933
|
+
models: modelsList,
|
|
934
|
+
account: typeof account === "string" && account !== "" ? account : "default"
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
return out;
|
|
938
|
+
}
|
|
939
|
+
/** Every routing rule row: stored (minus staged removals, with drafts) + staged adds. */
|
|
940
|
+
effectiveRules() {
|
|
941
|
+
const stored = this.storedRules().filter((rule) => !this.removedRuleIds.has(rule.id)).map((rule) => {
|
|
942
|
+
const draft = this.ruleDrafts.get(rule.id);
|
|
943
|
+
return {
|
|
944
|
+
id: rule.id,
|
|
945
|
+
models: draft?.models ?? rule.models,
|
|
946
|
+
account: draft?.account ?? rule.account,
|
|
947
|
+
added: false
|
|
948
|
+
};
|
|
949
|
+
});
|
|
950
|
+
const added = this.addedRules.map((rule, index) => ({
|
|
951
|
+
id: `new-${index}`,
|
|
952
|
+
models: rule.models,
|
|
953
|
+
account: rule.account,
|
|
954
|
+
added: true
|
|
955
|
+
}));
|
|
956
|
+
return [...stored, ...added];
|
|
957
|
+
}
|
|
958
|
+
/** Whether any routing-rule staging (add/remove/edit) exists. */
|
|
959
|
+
rulesStaged() {
|
|
960
|
+
return this.addedRules.length > 0 || this.removedRuleIds.size > 0 || this.ruleDrafts.size > 0;
|
|
961
|
+
}
|
|
962
|
+
/** Whether the staged routing rules differ from the stored section. */
|
|
963
|
+
rulesDirty() {
|
|
964
|
+
if (this.addedRules.length > 0 || this.removedRuleIds.size > 0) return true;
|
|
965
|
+
for (const [id, draft] of this.ruleDrafts) {
|
|
966
|
+
const base = this.storedRules().find((rule) => rule.id === id);
|
|
967
|
+
if (base === void 0) continue;
|
|
968
|
+
if (draft.models.length > 0 && !sameModels(draft.models, base.models)) return true;
|
|
969
|
+
if (draft.account !== base.account) return true;
|
|
970
|
+
}
|
|
971
|
+
return false;
|
|
972
|
+
}
|
|
973
|
+
/** Reset every routing-rule staged edit. */
|
|
974
|
+
clearRuleStaging() {
|
|
975
|
+
this.addedRules = [];
|
|
976
|
+
this.ruleDrafts.clear();
|
|
977
|
+
this.removedRuleIds.clear();
|
|
978
|
+
}
|
|
979
|
+
/** Drop rule staging the stored section already reflects (partial-save retry). */
|
|
980
|
+
reconcileRuleStaging() {
|
|
981
|
+
const storedIds = new Set(this.storedRules().map((rule) => rule.id));
|
|
982
|
+
for (const id of [...this.removedRuleIds]) if (!storedIds.has(id)) this.removedRuleIds.delete(id);
|
|
983
|
+
for (const id of [...this.ruleDrafts.keys()]) if (!storedIds.has(id)) this.ruleDrafts.delete(id);
|
|
984
|
+
}
|
|
985
|
+
/** The routing-rule writes a save performs (empty when nothing staged). */
|
|
986
|
+
rulesPlan() {
|
|
987
|
+
if (!this.rulesDirty()) return [];
|
|
988
|
+
return [() => this.writeRules()];
|
|
989
|
+
}
|
|
990
|
+
/** Persist the staged routing rules into the settings section. */
|
|
991
|
+
async writeRules() {
|
|
992
|
+
const list = [...this.storedRules().filter((rule) => !this.removedRuleIds.has(rule.id)).map((rule) => {
|
|
993
|
+
const draft = this.ruleDrafts.get(rule.id);
|
|
994
|
+
return {
|
|
995
|
+
models: draft !== void 0 && draft.models.length > 0 ? draft.models : rule.models,
|
|
996
|
+
account: draft?.account !== void 0 && draft.account !== "" ? draft.account : rule.account
|
|
997
|
+
};
|
|
998
|
+
}), ...this.addedRules.map((rule) => ({
|
|
999
|
+
models: rule.models,
|
|
1000
|
+
account: rule.account
|
|
1001
|
+
}))].filter((rule) => rule.models.length > 0);
|
|
1002
|
+
await this.scope.set("modelAccountRules", list);
|
|
1003
|
+
const after = this.storedRules();
|
|
1004
|
+
return after.length === list.length && list.every((item, index) => after[index] !== void 0 && sameModels(after[index].models, item.models) && after[index].account === item.account);
|
|
1005
|
+
}
|
|
748
1006
|
publish() {
|
|
749
1007
|
if (this.disposed) return;
|
|
750
1008
|
for (const listener of this.listeners) listener();
|
|
751
1009
|
}
|
|
752
1010
|
};
|
|
753
1011
|
//#endregion
|
|
1012
|
+
//#region src/client/legacy-credentials.ts
|
|
1013
|
+
/** Convert a legacy credentials ApiProxy into the current settings-page face. */
|
|
1014
|
+
function adaptLegacyCredentials(legacy) {
|
|
1015
|
+
if (legacy === void 0) return void 0;
|
|
1016
|
+
return { credentials: {
|
|
1017
|
+
describe: async (refs) => {
|
|
1018
|
+
const response = await legacy.describe({ refs });
|
|
1019
|
+
return response.result.ok ? {
|
|
1020
|
+
ok: true,
|
|
1021
|
+
value: response.result.value.credentials
|
|
1022
|
+
} : {
|
|
1023
|
+
ok: false,
|
|
1024
|
+
error: response.result.error
|
|
1025
|
+
};
|
|
1026
|
+
},
|
|
1027
|
+
set: async (ref, value) => {
|
|
1028
|
+
const response = await legacy.set({
|
|
1029
|
+
ref,
|
|
1030
|
+
value
|
|
1031
|
+
});
|
|
1032
|
+
return response.result.ok ? {
|
|
1033
|
+
ok: true,
|
|
1034
|
+
value: void 0
|
|
1035
|
+
} : {
|
|
1036
|
+
ok: false,
|
|
1037
|
+
error: response.result.error
|
|
1038
|
+
};
|
|
1039
|
+
},
|
|
1040
|
+
unset: async (ref) => {
|
|
1041
|
+
const response = await legacy.unset({ ref });
|
|
1042
|
+
return response.result.ok ? {
|
|
1043
|
+
ok: true,
|
|
1044
|
+
value: void 0
|
|
1045
|
+
} : {
|
|
1046
|
+
ok: false,
|
|
1047
|
+
error: response.result.error
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
} };
|
|
1051
|
+
}
|
|
1052
|
+
//#endregion
|
|
754
1053
|
//#region src/client/usage.ts
|
|
755
1054
|
const IDLE = {
|
|
756
1055
|
status: "idle",
|
|
@@ -1218,6 +1517,39 @@ window.__ModuleLoader__.load({
|
|
|
1218
1517
|
}
|
|
1219
1518
|
}]
|
|
1220
1519
|
};
|
|
1520
|
+
/** Canonical `<namespace>/<method>` endpoint of the model-catalog Remote. */
|
|
1521
|
+
const MODELS_ENDPOINT = "commandcode/models";
|
|
1522
|
+
/** Parse one untrusted boundary value into a {@link CommandCodeCatalogModel}. */
|
|
1523
|
+
function parseCatalogModel(value) {
|
|
1524
|
+
const source = record(value, "model");
|
|
1525
|
+
return {
|
|
1526
|
+
id: stringField(source, "id", "model.id"),
|
|
1527
|
+
name: stringField(source, "name", "model.name")
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
/** Parse the wire result into a {@link CommandCodeCatalog}. */
|
|
1531
|
+
function parseCatalog(value) {
|
|
1532
|
+
const models = record(value, "result").models;
|
|
1533
|
+
if (!Array.isArray(models)) reject$1("models");
|
|
1534
|
+
return { models: models.map(parseCatalogModel) };
|
|
1535
|
+
}
|
|
1536
|
+
/** The Client-face contribution for the model-catalog endpoint. */
|
|
1537
|
+
const MODELS_REMOTE_CONTRIBUTION = {
|
|
1538
|
+
package: USAGE_REMOTE_PACKAGE,
|
|
1539
|
+
descriptors: [{
|
|
1540
|
+
id: `${USAGE_REMOTE_PACKAGE}#${MODELS_ENDPOINT}`,
|
|
1541
|
+
service: "commandcodeUsage",
|
|
1542
|
+
namespace: "commandcode",
|
|
1543
|
+
method: "models",
|
|
1544
|
+
invocation: { kind: "direct" },
|
|
1545
|
+
parameters: [],
|
|
1546
|
+
result: {
|
|
1547
|
+
mode: "strict",
|
|
1548
|
+
typeSymbol: `${USAGE_REMOTE_PACKAGE}#CommandCodeCatalog`,
|
|
1549
|
+
schema: { parse: parseCatalog }
|
|
1550
|
+
}
|
|
1551
|
+
}]
|
|
1552
|
+
};
|
|
1221
1553
|
//#endregion
|
|
1222
1554
|
//#region src/login-wire.ts
|
|
1223
1555
|
/** The canonical endpoint paths of the three login Remotes. */
|
|
@@ -1299,7 +1631,7 @@ window.__ModuleLoader__.load({
|
|
|
1299
1631
|
};
|
|
1300
1632
|
//#endregion
|
|
1301
1633
|
//#region package.json
|
|
1302
|
-
var version = "0.
|
|
1634
|
+
var version = "0.10.0-alpha.2";
|
|
1303
1635
|
var repository = {
|
|
1304
1636
|
"type": "git",
|
|
1305
1637
|
"url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
|
|
@@ -2361,6 +2693,173 @@ window.__ModuleLoader__.load({
|
|
|
2361
2693
|
]
|
|
2362
2694
|
});
|
|
2363
2695
|
}
|
|
2696
|
+
/** One model → account routing rule row. */
|
|
2697
|
+
function RuleRow({ rule, accounts, catalog, disabled, t, onModels, onAccount, onRemove }) {
|
|
2698
|
+
const targets = [{
|
|
2699
|
+
value: "default",
|
|
2700
|
+
label: t("accountDefault")
|
|
2701
|
+
}, ...accounts.filter((account) => !account.added).map((account) => ({
|
|
2702
|
+
value: account.ref,
|
|
2703
|
+
label: account.label
|
|
2704
|
+
}))];
|
|
2705
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2706
|
+
className: "cc-field",
|
|
2707
|
+
children: [
|
|
2708
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2709
|
+
className: "cc-fieldHead",
|
|
2710
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
2711
|
+
className: "cc-label",
|
|
2712
|
+
htmlFor: `cc-rule-model-${rule.id}`,
|
|
2713
|
+
children: t("ruleModel")
|
|
2714
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2715
|
+
className: "cc-badges",
|
|
2716
|
+
children: [rule.added ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2717
|
+
className: "cc-badge",
|
|
2718
|
+
children: t("unsaved")
|
|
2719
|
+
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2720
|
+
type: "button",
|
|
2721
|
+
className: "cc-reset",
|
|
2722
|
+
disabled,
|
|
2723
|
+
onClick: onRemove,
|
|
2724
|
+
children: t("ruleRemove")
|
|
2725
|
+
})]
|
|
2726
|
+
})]
|
|
2727
|
+
}),
|
|
2728
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMultiSelect, {
|
|
2729
|
+
id: `cc-rule-model-${rule.id}`,
|
|
2730
|
+
selected: rule.models,
|
|
2731
|
+
catalog,
|
|
2732
|
+
disabled,
|
|
2733
|
+
t,
|
|
2734
|
+
onSelect: onModels
|
|
2735
|
+
}),
|
|
2736
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
2737
|
+
id: `cc-rule-account-${rule.id}`,
|
|
2738
|
+
className: "cc-input",
|
|
2739
|
+
value: rule.account,
|
|
2740
|
+
disabled,
|
|
2741
|
+
onChange: (event) => onAccount(event.target.value),
|
|
2742
|
+
children: targets.map((target) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2743
|
+
value: target.value,
|
|
2744
|
+
children: target.label
|
|
2745
|
+
}, target.value))
|
|
2746
|
+
}),
|
|
2747
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2748
|
+
className: "cc-hint",
|
|
2749
|
+
children: t("ruleHint")
|
|
2750
|
+
})
|
|
2751
|
+
]
|
|
2752
|
+
});
|
|
2753
|
+
}
|
|
2754
|
+
/**
|
|
2755
|
+
* A checkbox multi-select dropdown for routing-rule models. The trigger shows
|
|
2756
|
+
* the selection count; the anchored Menu lists every catalog model with a
|
|
2757
|
+
* checkbox, toggled by clicking the row. Selected ids the catalog no longer
|
|
2758
|
+
* carries still render so a saved rule never silently loses a selection.
|
|
2759
|
+
*/
|
|
2760
|
+
function ModelMultiSelect({ id, selected, catalog, disabled, t, onSelect }) {
|
|
2761
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
2762
|
+
const options = [...catalog.map((model) => ({
|
|
2763
|
+
value: model.id,
|
|
2764
|
+
label: model.name
|
|
2765
|
+
})), ...selected.filter((id) => !catalog.some((model) => model.id === id)).map((id) => ({
|
|
2766
|
+
value: id,
|
|
2767
|
+
label: id
|
|
2768
|
+
}))];
|
|
2769
|
+
const selectedSet = new Set(selected);
|
|
2770
|
+
const items = options.map((option) => ({
|
|
2771
|
+
id: option.value,
|
|
2772
|
+
label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2773
|
+
className: "cc-checkRow",
|
|
2774
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2775
|
+
type: "checkbox",
|
|
2776
|
+
className: "cc-check",
|
|
2777
|
+
checked: selectedSet.has(option.value),
|
|
2778
|
+
readOnly: true,
|
|
2779
|
+
tabIndex: -1
|
|
2780
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2781
|
+
className: "cc-checkName",
|
|
2782
|
+
children: option.label
|
|
2783
|
+
})]
|
|
2784
|
+
})
|
|
2785
|
+
}));
|
|
2786
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
2787
|
+
open,
|
|
2788
|
+
onClose: () => setOpen(false),
|
|
2789
|
+
onSelect: (modelId) => {
|
|
2790
|
+
onSelect(selectedSet.has(modelId) ? selected.filter((value) => value !== modelId) : [...selected, modelId]);
|
|
2791
|
+
},
|
|
2792
|
+
selectedIds: selected,
|
|
2793
|
+
items,
|
|
2794
|
+
portal: true,
|
|
2795
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2796
|
+
id,
|
|
2797
|
+
type: "button",
|
|
2798
|
+
className: "cc-input cc-ruleTrigger",
|
|
2799
|
+
disabled: disabled || catalog.length === 0,
|
|
2800
|
+
onClick: () => setOpen((value) => !value),
|
|
2801
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2802
|
+
className: "cc-ruleTriggerText",
|
|
2803
|
+
children: selected.length === 0 ? t("ruleModelPick") : t("ruleModelCount", { count: selected.length })
|
|
2804
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2805
|
+
className: "cc-ruleCaret",
|
|
2806
|
+
"aria-hidden": "true"
|
|
2807
|
+
})]
|
|
2808
|
+
})
|
|
2809
|
+
});
|
|
2810
|
+
}
|
|
2811
|
+
/** The model → account routing card: rules in list order (first match wins). */
|
|
2812
|
+
function RulesCard({ t, state, disabled, onAdd, onRemove, onModels, onAccount }) {
|
|
2813
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2814
|
+
className: "cc-card",
|
|
2815
|
+
"aria-label": t("rulesTitle"),
|
|
2816
|
+
children: [
|
|
2817
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2818
|
+
className: "cc-field",
|
|
2819
|
+
children: [
|
|
2820
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2821
|
+
className: "cc-fieldHead",
|
|
2822
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
2823
|
+
className: "cc-label",
|
|
2824
|
+
children: t("rulesTitle")
|
|
2825
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2826
|
+
className: "cc-badges",
|
|
2827
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2828
|
+
type: "button",
|
|
2829
|
+
className: "cc-reset",
|
|
2830
|
+
disabled,
|
|
2831
|
+
onClick: onAdd,
|
|
2832
|
+
children: t("ruleAdd")
|
|
2833
|
+
})
|
|
2834
|
+
})]
|
|
2835
|
+
}),
|
|
2836
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2837
|
+
className: "cc-hint",
|
|
2838
|
+
children: t("rulesHint")
|
|
2839
|
+
}),
|
|
2840
|
+
state.catalogFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2841
|
+
className: "cc-invalid",
|
|
2842
|
+
children: t("rulesCatalogFailed")
|
|
2843
|
+
}) : null
|
|
2844
|
+
]
|
|
2845
|
+
}),
|
|
2846
|
+
state.rules.map((rule) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RuleRow, {
|
|
2847
|
+
rule,
|
|
2848
|
+
accounts: state.accounts,
|
|
2849
|
+
catalog: state.catalogModels,
|
|
2850
|
+
disabled,
|
|
2851
|
+
t,
|
|
2852
|
+
onModels: (ids) => onModels(rule.id, ids),
|
|
2853
|
+
onAccount: (text) => onAccount(rule.id, text),
|
|
2854
|
+
onRemove: () => onRemove(rule.id)
|
|
2855
|
+
}, rule.id)),
|
|
2856
|
+
state.rules.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2857
|
+
className: "cc-hint",
|
|
2858
|
+
children: t("rulesEmpty")
|
|
2859
|
+
}) : null
|
|
2860
|
+
]
|
|
2861
|
+
});
|
|
2862
|
+
}
|
|
2364
2863
|
/**
|
|
2365
2864
|
* Show the "Saved ✓" affordance for a short window after each accepted save.
|
|
2366
2865
|
* The controller only counts saves (`savedCount`); the flash timing lives
|
|
@@ -2448,6 +2947,15 @@ window.__ModuleLoader__.load({
|
|
|
2448
2947
|
onActive: (text) => props.edit("activeAccount", text),
|
|
2449
2948
|
onActiveReset: () => props.resetField("activeAccount")
|
|
2450
2949
|
}),
|
|
2950
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RulesCard, {
|
|
2951
|
+
t,
|
|
2952
|
+
state,
|
|
2953
|
+
disabled,
|
|
2954
|
+
onAdd: props.addRule,
|
|
2955
|
+
onRemove: props.removeRule,
|
|
2956
|
+
onModels: props.editRuleModels,
|
|
2957
|
+
onAccount: props.editRuleAccount
|
|
2958
|
+
}),
|
|
2451
2959
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2452
2960
|
className: "cc-card",
|
|
2453
2961
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SecretKeyField, {
|
|
@@ -2533,35 +3041,23 @@ window.__ModuleLoader__.load({
|
|
|
2533
3041
|
]
|
|
2534
3042
|
});
|
|
2535
3043
|
}
|
|
2536
|
-
//#endregion
|
|
2537
|
-
//#region src/client/card.tsx
|
|
2538
3044
|
/**
|
|
2539
|
-
*
|
|
2540
|
-
*
|
|
2541
|
-
*
|
|
2542
|
-
*
|
|
2543
|
-
*
|
|
2544
|
-
*
|
|
2545
|
-
*
|
|
2546
|
-
*
|
|
2547
|
-
* from this plugin's `CommandCodeSettingsController` shared with the dedicated
|
|
2548
|
-
* settings page, so the two surfaces can never disagree about whether a key
|
|
2549
|
-
* is stored.
|
|
2550
|
-
*
|
|
2551
|
-
* Not configured: an "Unconfigured" badge, a paste-a-key field, and the
|
|
2552
|
-
* official sign-in button — the whole flow a first-run user needs, inline.
|
|
2553
|
-
* Configured: a green "Configured" badge plus a pointer to the full
|
|
2554
|
-
* Command Code settings page for rotation, usage, and connection facts.
|
|
2555
|
-
*
|
|
2556
|
-
* The card renders nothing until the shared controller's first snapshot is
|
|
2557
|
-
* ready (`available`), which also keeps the stale-facts window of the older
|
|
2558
|
-
* join from ever being visible. A controller-less render (card mounted before
|
|
2559
|
-
* the section registered its inject face — the composition runs one apply)
|
|
2560
|
-
* degrades to the stateless registration-notice form.
|
|
2561
|
-
*
|
|
2562
|
-
* Styles ride the page stylesheet the client entry injects once (`cc-`
|
|
2563
|
-
* prefixed classes); the card adds no CSS of its own.
|
|
3045
|
+
* Find the official editor card among the slot outlet's siblings, or null
|
|
3046
|
+
* while it is closed. The Models page renders the editor as an immediate
|
|
3047
|
+
* sibling of the outlet wrapper — after it in a provider row (the target of
|
|
3048
|
+
* the row's 编辑 toggle), before it in the first-run setup card and the
|
|
3049
|
+
* add-provider card, where it is always open. The editor is the only such
|
|
3050
|
+
* sibling whose CSS module class carries the `editor` stem
|
|
3051
|
+
* (`<hash>_editor`); the row header and the add card's provider select
|
|
3052
|
+
* never do, so the lookup needs no hash knowledge.
|
|
2564
3053
|
*/
|
|
3054
|
+
function adjacentEditorCard(wrapper) {
|
|
3055
|
+
if (wrapper === null) return null;
|
|
3056
|
+
for (const sibling of [wrapper.previousElementSibling, wrapper.nextElementSibling]) if (sibling !== null && typeof sibling.className === "string" && sibling.className.includes("editor")) return sibling;
|
|
3057
|
+
return null;
|
|
3058
|
+
}
|
|
3059
|
+
/** The closed-panel style: the outlet stays mounted as the detection anchor. */
|
|
3060
|
+
const HIDDEN_STYLE = { display: "none" };
|
|
2565
3061
|
/** Decide the card's posture from the injected face and the owner facts. */
|
|
2566
3062
|
function cardMode(props) {
|
|
2567
3063
|
if (props.useCommandCodeSettings === void 0) return { kind: "registration" };
|
|
@@ -2692,20 +3188,18 @@ window.__ModuleLoader__.load({
|
|
|
2692
3188
|
]
|
|
2693
3189
|
});
|
|
2694
3190
|
}
|
|
2695
|
-
/** One "Configured" badge + pointer to the full settings page (footer area). */
|
|
2696
|
-
function CardConfiguredBody({ t }) {
|
|
2697
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2698
|
-
className: "cc-field",
|
|
2699
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2700
|
-
className: "cc-hint",
|
|
2701
|
-
children: t("cardConfiguredHint")
|
|
2702
|
-
})
|
|
2703
|
-
});
|
|
2704
|
-
}
|
|
2705
3191
|
/**
|
|
2706
3192
|
* The slot component body. Dispatched on every Command Code provider card of
|
|
2707
3193
|
* the Models page (saved row, first-run setup posture, and add-provider
|
|
2708
3194
|
* draft).
|
|
3195
|
+
*
|
|
3196
|
+
* Closed (the official 编辑 toggle off) the panel renders nothing: the row
|
|
3197
|
+
* head the Models page owns already names the provider and shows the
|
|
3198
|
+
* credential dot, so a page full of providers stays compact. Opening the
|
|
3199
|
+
* official editor mounts the editor shell as the outlet's sibling; the panel
|
|
3200
|
+
* watches for it, hides the shell (it carries only the settings.yaml hint and
|
|
3201
|
+
* a disabled apply for this namespace), and shows the real controls — badges,
|
|
3202
|
+
* API-key field, sign-in, discard/save.
|
|
2709
3203
|
*/
|
|
2710
3204
|
function CommandCodeProviderCard(props) {
|
|
2711
3205
|
const { t } = props;
|
|
@@ -2720,14 +3214,50 @@ window.__ModuleLoader__.load({
|
|
|
2720
3214
|
const configured = mode.kind === "live" && mode.ready ? mode.controllerConfigured : props.keyConfigured;
|
|
2721
3215
|
const disabled = mode.kind === "live" && (!mode.writable || state !== void 0 && !mode.apiKeyWritable);
|
|
2722
3216
|
const showBody = mode.kind === "live" && mode.ready && state !== void 0;
|
|
3217
|
+
const rootRef = (0, react.useRef)(null);
|
|
3218
|
+
const [editorOpen, setEditorOpen] = (0, react.useState)(false);
|
|
3219
|
+
(0, react.useEffect)(() => {
|
|
3220
|
+
const root = rootRef.current;
|
|
3221
|
+
if (root === null || typeof MutationObserver === "undefined") return;
|
|
3222
|
+
const wrapper = root.closest(`[data-slot="settings.models.provider-card"]`) ?? root.parentElement;
|
|
3223
|
+
if (wrapper === null) return;
|
|
3224
|
+
const row = wrapper.parentElement;
|
|
3225
|
+
if (row === null) return;
|
|
3226
|
+
let hiddenEditor = null;
|
|
3227
|
+
const sync = () => {
|
|
3228
|
+
const editor = adjacentEditorCard(wrapper);
|
|
3229
|
+
setEditorOpen(editor !== null);
|
|
3230
|
+
if (editor !== null) {
|
|
3231
|
+
editor.style.display = "none";
|
|
3232
|
+
hiddenEditor = editor;
|
|
3233
|
+
}
|
|
3234
|
+
};
|
|
3235
|
+
sync();
|
|
3236
|
+
const observer = new MutationObserver(sync);
|
|
3237
|
+
observer.observe(row, { childList: true });
|
|
3238
|
+
return () => {
|
|
3239
|
+
observer.disconnect();
|
|
3240
|
+
if (hiddenEditor !== null) hiddenEditor.style.display = "";
|
|
3241
|
+
};
|
|
3242
|
+
}, []);
|
|
2723
3243
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3244
|
+
ref: rootRef,
|
|
2724
3245
|
className: "cc-providerCard",
|
|
2725
3246
|
"data-cc-models-card": "true",
|
|
3247
|
+
style: editorOpen ? void 0 : HIDDEN_STYLE,
|
|
2726
3248
|
children: [
|
|
2727
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
2728
|
-
className: "cc-
|
|
2729
|
-
children:
|
|
2730
|
-
|
|
3249
|
+
editorOpen && mode.kind === "registration" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3250
|
+
className: "cc-hint",
|
|
3251
|
+
children: t("cardRegistrationHint")
|
|
3252
|
+
}) : null,
|
|
3253
|
+
editorOpen && mode.kind === "live" && !mode.ready ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3254
|
+
className: "cc-hint",
|
|
3255
|
+
children: t("cardLoadingHint")
|
|
3256
|
+
}) : null,
|
|
3257
|
+
editorOpen && showBody ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
3258
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3259
|
+
className: "cc-field",
|
|
3260
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2731
3261
|
className: "cc-fieldHead",
|
|
2732
3262
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2733
3263
|
className: "cc-label",
|
|
@@ -2743,18 +3273,8 @@ window.__ModuleLoader__.load({
|
|
|
2743
3273
|
children: t("cardRouteActive")
|
|
2744
3274
|
}) : null]
|
|
2745
3275
|
})]
|
|
2746
|
-
})
|
|
2747
|
-
|
|
2748
|
-
className: "cc-hint",
|
|
2749
|
-
children: t("cardRegistrationHint")
|
|
2750
|
-
}) : null,
|
|
2751
|
-
mode.kind === "live" && !mode.ready ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2752
|
-
className: "cc-hint",
|
|
2753
|
-
children: t("cardLoadingHint")
|
|
2754
|
-
}) : null
|
|
2755
|
-
]
|
|
2756
|
-
}),
|
|
2757
|
-
showBody && !configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
3276
|
+
})
|
|
3277
|
+
}),
|
|
2758
3278
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardKeyField, {
|
|
2759
3279
|
state: state.apiKey,
|
|
2760
3280
|
disabled,
|
|
@@ -2770,20 +3290,29 @@ window.__ModuleLoader__.load({
|
|
|
2770
3290
|
}) : null,
|
|
2771
3291
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2772
3292
|
className: "cc-footer",
|
|
2773
|
-
children: [
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
3293
|
+
children: [
|
|
3294
|
+
failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3295
|
+
className: "cc-failed",
|
|
3296
|
+
role: "status",
|
|
3297
|
+
children: t("saveFailed")
|
|
3298
|
+
}) : null,
|
|
3299
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3300
|
+
type: "button",
|
|
3301
|
+
className: "cc-reset",
|
|
3302
|
+
disabled: !dirty || saving,
|
|
3303
|
+
onClick: props.discard,
|
|
3304
|
+
children: t("discard")
|
|
3305
|
+
}),
|
|
3306
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3307
|
+
type: "button",
|
|
3308
|
+
className: "cc-reset",
|
|
3309
|
+
disabled: savingBlocked || saving,
|
|
3310
|
+
onClick: props.save,
|
|
3311
|
+
children: t(saving ? "saving" : "save")
|
|
3312
|
+
})
|
|
3313
|
+
]
|
|
2784
3314
|
})
|
|
2785
|
-
] }) : null
|
|
2786
|
-
showBody && configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardConfiguredBody, { t }) : null
|
|
3315
|
+
] }) : null
|
|
2787
3316
|
]
|
|
2788
3317
|
});
|
|
2789
3318
|
}
|
|
@@ -2824,6 +3353,17 @@ window.__ModuleLoader__.load({
|
|
|
2824
3353
|
activeAccount: "当前使用账户",
|
|
2825
3354
|
activeAccountAuto: "自动(第一个可用账户)",
|
|
2826
3355
|
activeAccountHint: "手动指定优先使用的账户,保存后下次请求即生效;所选账户耗尽时仍会自动切换到其他可用账户。",
|
|
3356
|
+
rulesTitle: "按模型切换账户",
|
|
3357
|
+
rulesHint: "选择模型并路由到某个账户(可多选)。命中规则的模型且该账户可用时优先使用;账户耗尽或密钥失效时仍自动回落到其他账户。规则按列表顺序匹配,第一条命中生效。",
|
|
3358
|
+
rulesEmpty: "尚未配置规则。",
|
|
3359
|
+
rulesCatalogFailed: "模型目录获取失败,暂时无法选择模型;已保存的规则仍会生效。",
|
|
3360
|
+
ruleAdd: "添加规则",
|
|
3361
|
+
ruleRemove: "移除",
|
|
3362
|
+
ruleModel: "模型",
|
|
3363
|
+
ruleModelPick: "选择模型…",
|
|
3364
|
+
ruleModelCount: "已选 {count} 个模型",
|
|
3365
|
+
ruleAccount: "目标账户",
|
|
3366
|
+
ruleHint: "从下拉列表勾选要路由的模型(可多选),再选择目标账户。",
|
|
2827
3367
|
overridden: "已覆盖",
|
|
2828
3368
|
reset: "重置",
|
|
2829
3369
|
invalidNumber: "无效数字",
|
|
@@ -2893,11 +3433,10 @@ window.__ModuleLoader__.load({
|
|
|
2893
3433
|
loginStoreFailed: "密钥无法写入本机凭据服务,请手动粘贴。",
|
|
2894
3434
|
loginCancelled: "登录已取消。",
|
|
2895
3435
|
loginFailedGeneric: "登录失败,请重试或手动粘贴密钥。",
|
|
2896
|
-
cardTitle: "Command Code
|
|
3436
|
+
cardTitle: "Command Code",
|
|
2897
3437
|
cardRouteActive: "已启用",
|
|
2898
3438
|
cardLoadingHint: "正在读取 Command Code 配置…",
|
|
2899
|
-
cardRegistrationHint: "此卡片随 Command Code 插件注册,需要较新版本的 DeepSeek Harness 才会显示完整内容。"
|
|
2900
|
-
cardConfiguredHint: "API 密钥已就绪。如需更换密钥、添加多账户轮换、查看用量或修改 API 地址,请前往设置中的「Command Code」页。"
|
|
3439
|
+
cardRegistrationHint: "此卡片随 Command Code 插件注册,需要较新版本的 DeepSeek Harness 才会显示完整内容。"
|
|
2901
3440
|
};
|
|
2902
3441
|
const en = {
|
|
2903
3442
|
nav: "Command Code",
|
|
@@ -2934,6 +3473,17 @@ window.__ModuleLoader__.load({
|
|
|
2934
3473
|
activeAccount: "Active account",
|
|
2935
3474
|
activeAccountAuto: "Auto (first usable account)",
|
|
2936
3475
|
activeAccountHint: "Pin the preferred account; applies to the next request after saving. If the selected account is exhausted, requests still rotate to another usable account.",
|
|
3476
|
+
rulesTitle: "Route models to accounts",
|
|
3477
|
+
rulesHint: "Pick models (multi-select) and route them to an account. When the request’s model is in a rule and that account is usable, it serves; an exhausted or invalid routed account falls back to the normal rotation. Rules match in list order — the first hit wins.",
|
|
3478
|
+
rulesEmpty: "No rules yet.",
|
|
3479
|
+
rulesCatalogFailed: "Could not load the model catalog — selecting models is unavailable; saved rules still apply.",
|
|
3480
|
+
ruleAdd: "Add rule",
|
|
3481
|
+
ruleRemove: "Remove",
|
|
3482
|
+
ruleModel: "Models",
|
|
3483
|
+
ruleModelPick: "Select models…",
|
|
3484
|
+
ruleModelCount: "{count} model(s) selected",
|
|
3485
|
+
ruleAccount: "Target account",
|
|
3486
|
+
ruleHint: "Check the models to route from the dropdown (multi-select), then pick the target account.",
|
|
2937
3487
|
overridden: "Overridden",
|
|
2938
3488
|
reset: "Reset",
|
|
2939
3489
|
invalidNumber: "Invalid number",
|
|
@@ -3003,11 +3553,10 @@ window.__ModuleLoader__.load({
|
|
|
3003
3553
|
loginStoreFailed: "The key could not be stored in the local credential service; paste it manually.",
|
|
3004
3554
|
loginCancelled: "Sign-in cancelled.",
|
|
3005
3555
|
loginFailedGeneric: "Sign-in failed; try again or paste the key manually.",
|
|
3006
|
-
cardTitle: "Command Code
|
|
3556
|
+
cardTitle: "Command Code",
|
|
3007
3557
|
cardRouteActive: "Active",
|
|
3008
3558
|
cardLoadingHint: "Loading the Command Code configuration…",
|
|
3009
|
-
cardRegistrationHint: "This card is contributed by the Command Code plugin; a newer DeepSeek Harness is needed to show the full controls."
|
|
3010
|
-
cardConfiguredHint: "The API key is ready. To replace it, add account rotation, review usage, or change the API base, open the \"Command Code\" page in Settings."
|
|
3559
|
+
cardRegistrationHint: "This card is contributed by the Command Code plugin; a newer DeepSeek Harness is needed to show the full controls."
|
|
3011
3560
|
};
|
|
3012
3561
|
//#endregion
|
|
3013
3562
|
//#region src/client/index.ts
|
|
@@ -3031,6 +3580,19 @@ window.__ModuleLoader__.load({
|
|
|
3031
3580
|
.cc-input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}
|
|
3032
3581
|
.cc-input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}
|
|
3033
3582
|
.cc-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
|
|
3583
|
+
/* The routing-rule model multi-select: a button trigger that opens an
|
|
3584
|
+
* anchored Menu of checkbox rows. The trigger mirrors .cc-input sizing so it
|
|
3585
|
+
* sits flush with the sibling account select. */
|
|
3586
|
+
.cc-ruleTrigger{align-items:center;gap:8px;display:flex;width:100%;text-align:left;cursor:pointer}
|
|
3587
|
+
.cc-ruleTrigger:disabled{cursor:default}
|
|
3588
|
+
.cc-ruleTriggerText{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
3589
|
+
.cc-ruleCaret{flex-shrink:0;border-right:1.5px solid var(--dsw-alias-label-tertiary);border-bottom:1.5px solid var(--dsw-alias-label-tertiary);width:6px;height:6px;margin-right:4px;margin-bottom:2px;transform:rotate(45deg)}
|
|
3590
|
+
.cc-checkRow{align-items:center;gap:8px;display:inline-flex;min-width:0}
|
|
3591
|
+
.cc-checkRow:hover{cursor:pointer}
|
|
3592
|
+
.cc-check{appearance:none;flex-shrink:0;width:15px;height:15px;margin:0;border:1px solid var(--dsw-alias-border-l2);border-radius:4px;background:var(--dsw-alias-bg-layer-1);position:relative}
|
|
3593
|
+
.cc-check:checked{background:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}
|
|
3594
|
+
.cc-check:checked::after{content:'';position:absolute;top:2px;left:5px;width:3px;height:7px;border:solid #fff;border-width:0 1.5px 1.5px 0;transform:rotate(45deg)}
|
|
3595
|
+
.cc-checkName{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
3034
3596
|
/* Selects need their own treatment to sit flush with the text inputs:
|
|
3035
3597
|
* the UA stylesheet renders <select> border-box (34px total vs the inputs'
|
|
3036
3598
|
* 36px) and forces its own menulist text metrics, so drop the native
|
|
@@ -3128,30 +3690,55 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
3128
3690
|
document.head.appendChild(tag);
|
|
3129
3691
|
}
|
|
3130
3692
|
/**
|
|
3131
|
-
* Client plugin body. Gates on the services
|
|
3132
|
-
* (`slots`, `locale`, `connection`, `remote`, `settingsScope`)
|
|
3133
|
-
*
|
|
3134
|
-
*
|
|
3693
|
+
* Client plugin body. Gates on the services shared by both client generations
|
|
3694
|
+
* (`slots`, `locale`, `connection`, `remote`, `settingsScope`). Current clients
|
|
3695
|
+
* mount the page after `remote.credentials` appears; legacy clients mount it
|
|
3696
|
+
* from the connection ApiProxy credential face.
|
|
3135
3697
|
*/
|
|
3136
3698
|
function apply(ctx) {
|
|
3137
3699
|
injectPageCss();
|
|
3138
3700
|
const connection = ctx.get("connection");
|
|
3139
|
-
if (connection !== void 0) installFriendlyImageError(connection, () => ctx.locale.getLocale().active);
|
|
3701
|
+
if (connection !== void 0) installFriendlyImageError(connection, () => ctx.locale.getLocale().active === "zh" ? "zh" : "en");
|
|
3140
3702
|
ctx.effect(() => ctx.locale.register("settings.commandcode", {
|
|
3141
3703
|
zh,
|
|
3142
3704
|
en
|
|
3143
3705
|
}), "dsh-commandcode-provider: page copy");
|
|
3144
|
-
const
|
|
3145
|
-
|
|
3146
|
-
|
|
3706
|
+
const legacyApi = adaptLegacyCredentials(connection?.api?.credentials);
|
|
3707
|
+
if (legacyApi !== void 0) {
|
|
3708
|
+
applyClientSurfaces(ctx, legacyApi, connection?.hostDescription);
|
|
3709
|
+
return;
|
|
3710
|
+
}
|
|
3711
|
+
ctx.inject(["remote.credentials"], (remoteCtx) => {
|
|
3712
|
+
const credentials = remoteCtx.remote.credentials;
|
|
3713
|
+
applyClientSurfaces(remoteCtx, { credentials });
|
|
3714
|
+
});
|
|
3715
|
+
}
|
|
3716
|
+
/** Mount the one shared UI implementation over either credential transport. */
|
|
3717
|
+
function applyClientSurfaces(ctx, api, hostDescription) {
|
|
3718
|
+
const scope = ctx.settingsScope.bind({ namespace: COMMANDCODE_NS });
|
|
3719
|
+
let modelsRemote;
|
|
3720
|
+
const controller = new CommandCodeSettingsController(scope, {
|
|
3721
|
+
...api,
|
|
3722
|
+
models: () => modelsRemote?.() ?? Promise.resolve({
|
|
3723
|
+
ok: false,
|
|
3724
|
+
error: { message: "commandcode/models remote is not mounted" }
|
|
3725
|
+
})
|
|
3726
|
+
}, hostDescription);
|
|
3147
3727
|
ctx.effect(() => () => controller.dispose(), "dsh-commandcode-provider: settings controller");
|
|
3148
|
-
const store =
|
|
3728
|
+
const store = createSnapshotStore(controller.state());
|
|
3149
3729
|
controller.subscribe(() => store.set(controller.state()));
|
|
3730
|
+
ctx.effect(() => ctx.remote.$on("credentials/reference-updated", () => {
|
|
3731
|
+
controller.refreshCredentials();
|
|
3732
|
+
}), "dsh-commandcode-provider: credential invalidations");
|
|
3150
3733
|
let usageNamespace;
|
|
3151
3734
|
let usageMountError;
|
|
3152
3735
|
const contribution = {
|
|
3153
3736
|
package: USAGE_REMOTE_CONTRIBUTION.package,
|
|
3154
|
-
descriptors: [
|
|
3737
|
+
descriptors: [
|
|
3738
|
+
...USAGE_REMOTE_CONTRIBUTION.descriptors,
|
|
3739
|
+
...MODELS_REMOTE_CONTRIBUTION.descriptors,
|
|
3740
|
+
...LOGIN_REMOTE_CONTRIBUTION.descriptors
|
|
3741
|
+
]
|
|
3155
3742
|
};
|
|
3156
3743
|
ctx.effect(() => {
|
|
3157
3744
|
let cancelled = false;
|
|
@@ -3164,6 +3751,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
3164
3751
|
unmount = dispose;
|
|
3165
3752
|
ctx.inject(["remote.commandcode"], (namespaceCtx) => {
|
|
3166
3753
|
usageNamespace = namespaceCtx.remote.commandcode;
|
|
3754
|
+
controller.refreshCatalog();
|
|
3167
3755
|
namespaceCtx.effect(() => () => {
|
|
3168
3756
|
usageNamespace = void 0;
|
|
3169
3757
|
}, "dsh-commandcode-provider: usage namespace");
|
|
@@ -3177,16 +3765,28 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
3177
3765
|
if (unmount !== void 0) unmount();
|
|
3178
3766
|
};
|
|
3179
3767
|
}, "dsh-commandcode-provider: usage remote");
|
|
3180
|
-
const
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3768
|
+
const usageRemote = {
|
|
3769
|
+
report: async () => {
|
|
3770
|
+
const namespace = usageNamespace;
|
|
3771
|
+
if (namespace === void 0) return {
|
|
3772
|
+
ok: false,
|
|
3773
|
+
error: { message: usageMountError ?? "commandcode/report remote is not mounted" }
|
|
3774
|
+
};
|
|
3775
|
+
return namespace.report();
|
|
3776
|
+
},
|
|
3777
|
+
models: async () => {
|
|
3778
|
+
const namespace = usageNamespace;
|
|
3779
|
+
if (namespace === void 0) return {
|
|
3780
|
+
ok: false,
|
|
3781
|
+
error: { message: usageMountError ?? "commandcode/models remote is not mounted" }
|
|
3782
|
+
};
|
|
3783
|
+
return namespace.models();
|
|
3784
|
+
}
|
|
3785
|
+
};
|
|
3786
|
+
modelsRemote = () => usageRemote.models();
|
|
3787
|
+
const usageController = new CommandCodeUsageController(usageRemote);
|
|
3188
3788
|
ctx.effect(() => () => usageController.dispose(), "dsh-commandcode-provider: usage controller");
|
|
3189
|
-
const usageStore =
|
|
3789
|
+
const usageStore = createSnapshotStore(usageController.state());
|
|
3190
3790
|
usageController.subscribe(() => usageStore.set(usageController.state()));
|
|
3191
3791
|
const loginRemote = {
|
|
3192
3792
|
loginBegin: async () => {
|
|
@@ -3213,7 +3813,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
3213
3813
|
};
|
|
3214
3814
|
const loginController = new CommandCodeLoginController(() => loginRemote);
|
|
3215
3815
|
ctx.effect(() => () => loginController.dispose(), "dsh-commandcode-provider: login controller");
|
|
3216
|
-
const loginStore =
|
|
3816
|
+
const loginStore = createSnapshotStore(loginController.state());
|
|
3217
3817
|
let lastLoginPhase = loginController.state().phase;
|
|
3218
3818
|
loginController.subscribe(() => {
|
|
3219
3819
|
const phase = loginController.state().phase;
|
|
@@ -3244,7 +3844,11 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
3244
3844
|
removeAccount: (id) => controller.removeAccount(id),
|
|
3245
3845
|
editAccountLabel: (id, text) => controller.editAccountLabel(id, text),
|
|
3246
3846
|
editAccountKey: (id, text) => controller.editAccountKey(id, text),
|
|
3247
|
-
toggleKeyClear: (id) => controller.toggleKeyClear(id)
|
|
3847
|
+
toggleKeyClear: (id) => controller.toggleKeyClear(id),
|
|
3848
|
+
addRule: () => controller.addRule(),
|
|
3849
|
+
removeRule: (id) => controller.removeRule(id),
|
|
3850
|
+
editRuleModels: (id, ids) => controller.editRuleModels(id, ids),
|
|
3851
|
+
editRuleAccount: (id, text) => controller.editRuleAccount(id, text)
|
|
3248
3852
|
});
|
|
3249
3853
|
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
3250
3854
|
name: "settings.section",
|
|
@@ -3268,6 +3872,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
3268
3872
|
const settled = controller.state();
|
|
3269
3873
|
if (!settled.failed && settled.anyAccountConfigured) usageController.refresh();
|
|
3270
3874
|
}),
|
|
3875
|
+
discard: () => controller.discard(),
|
|
3271
3876
|
beginLogin: () => void loginController.begin(),
|
|
3272
3877
|
cancelLogin: () => void loginController.cancel()
|
|
3273
3878
|
})
|