@mars-sea/dsh-commandcode-provider 0.2.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -5,6 +5,7 @@ window.__ModuleLoader__.load({
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
8
+ let react = require("react");
8
9
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
10
  let react_jsx_runtime = require("react/jsx-runtime");
10
11
  //#region src/client/sessions.ts
@@ -59,7 +60,7 @@ window.__ModuleLoader__.load({
59
60
  };
60
61
  }
61
62
  /** A whole-number field; an empty draft clears it, anything non-numeric blocks save. */
62
- function numberField(field) {
63
+ function numberField$1(field) {
63
64
  return {
64
65
  field,
65
66
  format: (value) => typeof value === "number" ? String(value) : "",
@@ -74,12 +75,37 @@ window.__ModuleLoader__.load({
74
75
  }
75
76
  };
76
77
  }
78
+ /**
79
+ * A boolean field, staged as the strings `'true'`/`'false'` (an empty draft
80
+ * clears it). The component renders a toggle and only ever stages these two
81
+ * strings; anything else blocks save.
82
+ */
83
+ function booleanField$1(field) {
84
+ return {
85
+ field,
86
+ format: (value) => typeof value === "boolean" ? String(value) : "",
87
+ parse: (text) => {
88
+ const trimmed = text.trim();
89
+ if (trimmed === "") return { kind: "clear" };
90
+ if (trimmed === "true") return {
91
+ kind: "set",
92
+ value: true
93
+ };
94
+ if (trimmed === "false") return {
95
+ kind: "set",
96
+ value: false
97
+ };
98
+ return { kind: "invalid" };
99
+ }
100
+ };
101
+ }
77
102
  /** The fields this page edits inside the `llm-commandcode` namespace. */
78
103
  const SECTION_FIELDS = [
79
104
  textField("apiBase"),
80
105
  textField("workingDir"),
81
- numberField("requestTimeoutMs"),
82
- numberField("streamIdleTimeoutMs")
106
+ numberField$1("requestTimeoutMs"),
107
+ numberField$1("streamIdleTimeoutMs"),
108
+ booleanField$1("filterModelsByPlan")
83
109
  ];
84
110
  /**
85
111
  * Controller bridging the `llm-commandcode` scope and the credentials domain
@@ -179,6 +205,7 @@ window.__ModuleLoader__.load({
179
205
  defaultWorkingDir: this.defaultWorkingDir,
180
206
  requestTimeoutMs: this.field("requestTimeoutMs"),
181
207
  streamIdleTimeoutMs: this.field("streamIdleTimeoutMs"),
208
+ filterModelsByPlan: this.field("filterModelsByPlan"),
182
209
  dirty: plan.length > 0,
183
210
  invalid: plan.some((item) => item.run === void 0),
184
211
  saving: this.saving,
@@ -361,6 +388,230 @@ window.__ModuleLoader__.load({
361
388
  }
362
389
  };
363
390
  //#endregion
391
+ //#region src/client/usage.ts
392
+ const IDLE = {
393
+ status: "idle",
394
+ report: void 0,
395
+ error: void 0,
396
+ fetchedAt: void 0
397
+ };
398
+ /**
399
+ * Controller bridging the `commandcode/report` Remote onto the card. Public
400
+ * API mirrors {@link CommandCodeSettingsController}: `state()` projections,
401
+ * `subscribe`, and one `refresh()` action.
402
+ */
403
+ var CommandCodeUsageController = class {
404
+ remote;
405
+ listeners = /* @__PURE__ */ new Set();
406
+ current = IDLE;
407
+ generation = 0;
408
+ inFlight = false;
409
+ disposed = false;
410
+ constructor(remote) {
411
+ this.remote = remote;
412
+ }
413
+ /** Release every subscription. Idempotent; in-flight results are dropped. */
414
+ dispose() {
415
+ this.disposed = true;
416
+ this.generation += 1;
417
+ this.listeners.clear();
418
+ }
419
+ /** Subscribe to state projections. @returns the disposer. */
420
+ subscribe(listener) {
421
+ this.listeners.add(listener);
422
+ return () => this.listeners.delete(listener);
423
+ }
424
+ /** The current card state face. */
425
+ state() {
426
+ return this.current;
427
+ }
428
+ /**
429
+ * Fetch (or refetch) the report. Concurrent refreshes collapse onto one
430
+ * request; a superseded fetch's late result is dropped, never published.
431
+ */
432
+ async refresh() {
433
+ if (this.disposed || this.inFlight) return;
434
+ const generation = ++this.generation;
435
+ this.inFlight = true;
436
+ this.current = {
437
+ ...this.current,
438
+ status: "loading",
439
+ error: void 0
440
+ };
441
+ this.publish();
442
+ try {
443
+ const response = await this.remote.report();
444
+ if (this.disposed || generation !== this.generation) return;
445
+ if (response.ok) this.current = {
446
+ status: "ready",
447
+ report: response.value,
448
+ error: void 0,
449
+ fetchedAt: Date.now()
450
+ };
451
+ else this.current = {
452
+ ...this.current,
453
+ status: "error",
454
+ error: response.error.message
455
+ };
456
+ } catch (error) {
457
+ if (this.disposed || generation !== this.generation) return;
458
+ this.current = {
459
+ ...this.current,
460
+ status: "error",
461
+ error: error instanceof Error ? error.message : String(error)
462
+ };
463
+ } finally {
464
+ if (generation === this.generation) this.inFlight = false;
465
+ }
466
+ this.publish();
467
+ }
468
+ publish() {
469
+ if (this.disposed) return;
470
+ for (const listener of this.listeners) listener();
471
+ }
472
+ };
473
+ /** Format a dollar amount compactly (2 decimals). */
474
+ function formatMoney(value) {
475
+ return `$${value.toFixed(2)}`;
476
+ }
477
+ /** Format a dollar amount precisely (4 decimals) for small totals. */
478
+ function formatMoneyExact(value) {
479
+ return `$${value.toFixed(4)}`;
480
+ }
481
+ /** Format a large token count compactly (1.9M style). */
482
+ function formatTokensCompact(value) {
483
+ if (value >= 1e9) return `${(value / 1e9).toFixed(1)}B`;
484
+ if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
485
+ if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
486
+ return String(value);
487
+ }
488
+ /** One window's fill ratio in [0, 1]; 0 when uncapped. */
489
+ function windowRatio(used, cap) {
490
+ if (cap <= 0) return 0;
491
+ return Math.max(0, Math.min(1, used / cap));
492
+ }
493
+ /** Format a millis timestamp as a local short date-time; empty when unset. */
494
+ function formatResetAt(ms) {
495
+ if (ms <= 0) return "";
496
+ return new Date(ms).toLocaleString();
497
+ }
498
+ //#endregion
499
+ //#region src/usage-wire.ts
500
+ /** The npm package identity both contribution registrations claim. */
501
+ const USAGE_REMOTE_PACKAGE = "@mars-sea/dsh-commandcode-provider";
502
+ /** Canonical `<namespace>/<method>` endpoint of the usage report Remote. */
503
+ const USAGE_REPORT_ENDPOINT = "commandcode/report";
504
+ /** Reject one boundary value with a field-naming error. */
505
+ function reject(field) {
506
+ throw new TypeError(`commandcode/report result: invalid ${field}`);
507
+ }
508
+ /** Read one required finite number field (`field` is the dotted error label). */
509
+ function numberField(source, key, field) {
510
+ const value = source[key];
511
+ if (typeof value !== "number" || !Number.isFinite(value)) reject(field);
512
+ return value;
513
+ }
514
+ /** Read one required string field (`field` is the dotted error label). */
515
+ function stringField(source, key, field) {
516
+ const value = source[key];
517
+ if (typeof value !== "string") reject(field);
518
+ return value;
519
+ }
520
+ /** Read one required boolean field (`field` is the dotted error label). */
521
+ function booleanField(source, key, field) {
522
+ const value = source[key];
523
+ if (typeof value !== "boolean") reject(field);
524
+ return value;
525
+ }
526
+ /** Narrow an unknown value to a plain record, or reject. */
527
+ function record(value, field) {
528
+ if (typeof value !== "object" || value === null || Array.isArray(value)) reject(field);
529
+ return value;
530
+ }
531
+ /** Validate one window-limit block (`fiveHour` / `weekly`). */
532
+ function windowLimit(value, field) {
533
+ const source = record(value, field);
534
+ return {
535
+ used: numberField(source, "used", `${field}.used`),
536
+ cap: numberField(source, "cap", `${field}.cap`),
537
+ exceeded: booleanField(source, "exceeded", `${field}.exceeded`),
538
+ resetAt: numberField(source, "resetAt", `${field}.resetAt`)
539
+ };
540
+ }
541
+ /**
542
+ * Parse one untrusted boundary value into a {@link CommandCodeUsageReport}.
543
+ * Optional sections stay optional; every present field is shape-checked so a
544
+ * malformed frame fails the boundary instead of rendering garbage.
545
+ */
546
+ function parseUsageReport(value) {
547
+ const source = record(value, "report");
548
+ const failures = source.failures;
549
+ if (!Array.isArray(failures) || failures.some((entry) => typeof entry !== "string")) reject("failures");
550
+ const report = { failures };
551
+ if (source.account !== void 0) {
552
+ const account = record(source.account, "account");
553
+ report.account = {
554
+ id: stringField(account, "id", "account.id"),
555
+ name: stringField(account, "name", "account.name"),
556
+ userName: stringField(account, "userName", "account.userName")
557
+ };
558
+ }
559
+ if (source.usage !== void 0) {
560
+ const usage = record(source.usage, "usage");
561
+ report.usage = {
562
+ totalCount: numberField(usage, "totalCount", "usage.totalCount"),
563
+ totalCost: numberField(usage, "totalCost", "usage.totalCost"),
564
+ successRate: numberField(usage, "successRate", "usage.successRate"),
565
+ completedCount: numberField(usage, "completedCount", "usage.completedCount"),
566
+ failedCount: numberField(usage, "failedCount", "usage.failedCount"),
567
+ totalTokensIn: numberField(usage, "totalTokensIn", "usage.totalTokensIn"),
568
+ totalTokensOut: numberField(usage, "totalTokensOut", "usage.totalTokensOut"),
569
+ totalCredits: numberField(usage, "totalCredits", "usage.totalCredits"),
570
+ periodBasis: stringField(usage, "periodBasis", "usage.periodBasis")
571
+ };
572
+ }
573
+ if (source.credits !== void 0) {
574
+ const credits = record(source.credits, "credits");
575
+ report.credits = {
576
+ monthlyCredits: numberField(credits, "monthlyCredits", "credits.monthlyCredits"),
577
+ purchasedCredits: numberField(credits, "purchasedCredits", "credits.purchasedCredits"),
578
+ freeCredits: numberField(credits, "freeCredits", "credits.freeCredits"),
579
+ fiveHour: windowLimit(credits.fiveHour, "credits.fiveHour"),
580
+ weekly: windowLimit(credits.weekly, "credits.weekly")
581
+ };
582
+ }
583
+ if (source.plan !== void 0) {
584
+ const plan = record(source.plan, "plan");
585
+ const monthly = plan.monthlyCredits;
586
+ if (monthly !== null && (typeof monthly !== "number" || !Number.isFinite(monthly))) reject("plan.monthlyCredits");
587
+ report.plan = {
588
+ planId: stringField(plan, "planId", "plan.planId"),
589
+ name: stringField(plan, "name", "plan.name"),
590
+ status: stringField(plan, "status", "plan.status"),
591
+ monthlyCredits: monthly,
592
+ currentPeriodEnd: numberField(plan, "currentPeriodEnd", "plan.currentPeriodEnd")
593
+ };
594
+ }
595
+ return report;
596
+ }
597
+ /** The Client-face contribution mounted on `ctx.remote`. */
598
+ const USAGE_REMOTE_CONTRIBUTION = {
599
+ package: USAGE_REMOTE_PACKAGE,
600
+ descriptors: [{
601
+ id: `${USAGE_REMOTE_PACKAGE}#${USAGE_REPORT_ENDPOINT}`,
602
+ service: "commandcodeUsage",
603
+ namespace: "commandcode",
604
+ method: "report",
605
+ invocation: { kind: "direct" },
606
+ parameters: [],
607
+ result: {
608
+ mode: "strict",
609
+ typeSymbol: `${USAGE_REMOTE_PACKAGE}#CommandCodeUsageReport`,
610
+ schema: { parse: parseUsageReport }
611
+ }
612
+ }]
613
+ };
614
+ //#endregion
364
615
  //#region src/client/section.tsx
365
616
  /**
366
617
  * React component for the "Command Code" settings page (browser half).
@@ -418,6 +669,51 @@ window.__ModuleLoader__.load({
418
669
  ]
419
670
  });
420
671
  }
672
+ /**
673
+ * One boolean field row rendered as a toggle. The staged text is `'true'` /
674
+ * `'false'` / `''` (unset → `defaultChecked`); toggling stages the string the
675
+ * boolean field spec parses back into a real boolean on save.
676
+ */
677
+ function ToggleField({ id, label, hint, state, disabled, defaultChecked, onEdit, onReset, t }) {
678
+ const checked = state.text === "" ? defaultChecked : state.text === "true";
679
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
680
+ className: "cc-field",
681
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
682
+ className: "cc-fieldHead",
683
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
684
+ className: "cc-label",
685
+ htmlFor: id,
686
+ children: label
687
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
688
+ className: "cc-badges",
689
+ children: [state.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
690
+ className: "cc-badge",
691
+ children: t("overridden")
692
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
693
+ type: "button",
694
+ className: "cc-reset",
695
+ disabled,
696
+ onClick: onReset,
697
+ children: t("reset")
698
+ })]
699
+ })]
700
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
701
+ className: "cc-toggleRow",
702
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
703
+ id,
704
+ className: "cc-toggle",
705
+ type: "checkbox",
706
+ role: "switch",
707
+ checked,
708
+ disabled,
709
+ onChange: (event) => onEdit(event.target.checked ? "true" : "false")
710
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
711
+ className: "cc-hint",
712
+ children: hint
713
+ })]
714
+ })]
715
+ });
716
+ }
421
717
  /** The API-key control: write-only, reports configured state, never echoes the key. */
422
718
  function SecretKeyField({ label, hint, state, disabled, configured, configuredLabel, unconfiguredLabel, onEdit }) {
423
719
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -453,10 +749,227 @@ window.__ModuleLoader__.load({
453
749
  ]
454
750
  });
455
751
  }
752
+ /** One stat tile in the account card's summary grid. */
753
+ function UsageStat({ label, value, sub }) {
754
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
755
+ className: "cc-usageStat",
756
+ children: [
757
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
758
+ className: "cc-usageStatLabel",
759
+ children: label
760
+ }),
761
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
762
+ className: "cc-usageStatValue",
763
+ children: value
764
+ }),
765
+ sub !== void 0 && sub !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
766
+ className: "cc-usageStatSub",
767
+ children: sub
768
+ }) : null
769
+ ]
770
+ });
771
+ }
772
+ /** One window-limit row: label, used/cap, a fill bar, and the reset time. */
773
+ function UsageWindow({ label, limit: { used, cap, exceeded, resetAt }, t }) {
774
+ const ratio = windowRatio(used, cap);
775
+ const reset = formatResetAt(resetAt);
776
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
777
+ className: "cc-usageWindow",
778
+ children: [
779
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
780
+ className: "cc-usageWindowHead",
781
+ children: [
782
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
783
+ className: "cc-usageWindowLabel",
784
+ children: label
785
+ }),
786
+ exceeded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
787
+ className: "cc-usageExceeded",
788
+ children: t("usageExceeded")
789
+ }) : null,
790
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
791
+ className: "cc-usageWindowValue",
792
+ children: cap > 0 ? `${formatMoney(used)} / ${formatMoney(cap)}` : formatMoney(used)
793
+ })
794
+ ]
795
+ }),
796
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
797
+ className: "cc-usageBar",
798
+ role: "progressbar",
799
+ "aria-valuemin": 0,
800
+ "aria-valuemax": 100,
801
+ "aria-valuenow": Math.round(ratio * 100),
802
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
803
+ className: exceeded ? "cc-usageBarFill cc-usageBarFillWarn" : "cc-usageBarFill",
804
+ style: { width: `${ratio * 100}%` }
805
+ })
806
+ }),
807
+ reset !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
808
+ className: "cc-usageWindowReset",
809
+ children: [
810
+ t("usageReset"),
811
+ " ",
812
+ reset
813
+ ]
814
+ }) : null
815
+ ]
816
+ });
817
+ }
818
+ /**
819
+ * The account-usage card: the `/commandcode` dashboard's facts (account,
820
+ * totals, credits, window limits) rendered as a native settings card. Data
821
+ * arrives through the `commandcode/report` Remote; the API key never leaves
822
+ * the Host.
823
+ */
824
+ function UsageCard({ t, usage, apiKeyConfigured, onRefresh }) {
825
+ (0, react.useEffect)(() => {
826
+ if (apiKeyConfigured && usage.status === "idle") onRefresh();
827
+ }, [
828
+ apiKeyConfigured,
829
+ usage.status,
830
+ onRefresh
831
+ ]);
832
+ const loading = usage.status === "loading";
833
+ const report = usage.report;
834
+ const account = report?.account;
835
+ const accountName = account === void 0 ? "" : account.userName || account.name;
836
+ const credits = report?.credits;
837
+ const plan = report?.plan;
838
+ const planName = plan?.name ?? "";
839
+ const planStatus = plan !== void 0 && plan.status !== "" && plan.status !== "active" ? plan.status : "";
840
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
841
+ className: "cc-usageCard",
842
+ "aria-label": t("usageTitle"),
843
+ children: [
844
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
845
+ className: "cc-usageHead",
846
+ children: [
847
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
848
+ className: "cc-usageTitle",
849
+ children: t("usageTitle")
850
+ }),
851
+ accountName !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
852
+ className: "cc-usageAccount",
853
+ children: accountName
854
+ }) : null,
855
+ planName !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
856
+ className: "cc-usagePlan",
857
+ children: planName
858
+ }) : null,
859
+ planStatus !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
860
+ className: "cc-usagePlanStatus",
861
+ children: planStatus
862
+ }) : null,
863
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
864
+ type: "button",
865
+ className: "cc-usageRefresh",
866
+ disabled: loading || !apiKeyConfigured,
867
+ onClick: onRefresh,
868
+ children: loading ? t("usageRefreshing") : t("usageRefresh")
869
+ })
870
+ ]
871
+ }),
872
+ !apiKeyConfigured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
873
+ className: "cc-usageHint",
874
+ children: t("usageNoKey")
875
+ }) : null,
876
+ apiKeyConfigured && report === void 0 && loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
877
+ className: "cc-usageHint",
878
+ children: t("usageLoading")
879
+ }) : null,
880
+ usage.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
881
+ className: "cc-usageError",
882
+ role: "status",
883
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [t("usageError"), usage.error !== void 0 && usage.error !== "" ? ` — ${usage.error}` : ""] })
884
+ }) : null,
885
+ report?.usage !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
886
+ className: "cc-usageStats",
887
+ children: [
888
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
889
+ label: t("usageRequests"),
890
+ value: String(report.usage.completedCount),
891
+ sub: `${t("usageFailed")} ${report.usage.failedCount}`
892
+ }),
893
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
894
+ label: t("usageSuccessRate"),
895
+ value: `${report.usage.successRate}%`
896
+ }),
897
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
898
+ label: t("usageCost"),
899
+ value: formatMoneyExact(report.usage.totalCost),
900
+ sub: `${formatMoney(report.usage.totalCredits)} credits`
901
+ }),
902
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
903
+ label: t("usageTokens"),
904
+ value: formatTokensCompact(report.usage.totalTokensIn + report.usage.totalTokensOut),
905
+ sub: `${formatTokensCompact(report.usage.totalTokensIn)} ${t("usageTokensIn")} / ${formatTokensCompact(report.usage.totalTokensOut)} ${t("usageTokensOut")}`
906
+ })
907
+ ]
908
+ }) : null,
909
+ credits !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
910
+ className: "cc-usageStats",
911
+ children: [
912
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
913
+ label: t("usageMonthly"),
914
+ value: formatMoney(credits.monthlyCredits)
915
+ }),
916
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
917
+ label: t("usagePurchased"),
918
+ value: formatMoney(credits.purchasedCredits)
919
+ }),
920
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
921
+ label: t("usageFree"),
922
+ value: formatMoney(credits.freeCredits)
923
+ })
924
+ ]
925
+ }) : null,
926
+ credits !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
927
+ className: "cc-usageWindows",
928
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageWindow, {
929
+ label: t("usageFiveHour"),
930
+ limit: credits.fiveHour,
931
+ t
932
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageWindow, {
933
+ label: t("usageWeekly"),
934
+ limit: credits.weekly,
935
+ t
936
+ })]
937
+ }) : null,
938
+ report !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
939
+ className: "cc-usageMeta",
940
+ children: [
941
+ plan !== void 0 && plan.currentPeriodEnd > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
942
+ className: "cc-usageUpdated",
943
+ children: [
944
+ t("usagePeriodEnd"),
945
+ " ",
946
+ new Date(plan.currentPeriodEnd).toLocaleDateString()
947
+ ]
948
+ }) : null,
949
+ usage.fetchedAt !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
950
+ className: "cc-usageUpdated",
951
+ children: [
952
+ t("usageUpdated"),
953
+ " ",
954
+ new Date(usage.fetchedAt).toLocaleTimeString()
955
+ ]
956
+ }) : null,
957
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-usageMetaSpacer" }),
958
+ report.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
959
+ className: "cc-usagePartial",
960
+ title: report.failures.join("; "),
961
+ children: t("usagePartial")
962
+ }) : null
963
+ ]
964
+ }) : null
965
+ ]
966
+ });
967
+ }
456
968
  /** The settings page body: connection facts for the Command Code provider. */
457
969
  function CommandCodeSettingsPage(props) {
458
970
  const { t } = props;
459
971
  const state = props.useCommandCodeSettings((snapshot) => snapshot);
972
+ const usage = props.useCommandCodeUsage((snapshot) => snapshot);
460
973
  const disabled = !state.writable;
461
974
  const keyLocked = !state.apiKeyWritable;
462
975
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
@@ -476,6 +989,12 @@ window.__ModuleLoader__.load({
476
989
  role: "status",
477
990
  children: t("readOnly")
478
991
  }) : null,
992
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageCard, {
993
+ t,
994
+ usage,
995
+ apiKeyConfigured: state.apiKeyConfigured,
996
+ onRefresh: props.refreshUsage
997
+ }),
479
998
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
480
999
  className: "cc-card",
481
1000
  children: [
@@ -531,6 +1050,17 @@ window.__ModuleLoader__.load({
531
1050
  onEdit: (text) => props.edit("streamIdleTimeoutMs", text),
532
1051
  onReset: () => props.resetField("streamIdleTimeoutMs"),
533
1052
  t
1053
+ }),
1054
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
1055
+ id: "cc-filter-models-by-plan",
1056
+ label: t("filterModelsByPlan"),
1057
+ hint: t("filterModelsByPlanHint"),
1058
+ state: state.filterModelsByPlan,
1059
+ disabled,
1060
+ defaultChecked: true,
1061
+ onEdit: (text) => props.edit("filterModelsByPlan", text),
1062
+ onReset: () => props.resetField("filterModelsByPlan"),
1063
+ t
534
1064
  })
535
1065
  ]
536
1066
  }),
@@ -580,6 +1110,8 @@ window.__ModuleLoader__.load({
580
1110
  requestTimeoutMsHint: "等待响应首个字节的超时;默认 60000。",
581
1111
  streamIdleTimeoutMs: "流空闲超时(毫秒)",
582
1112
  streamIdleTimeoutMsHint: "生成流停滞多久视为断连;默认 300000(长思考模型可静默数分钟,默认值刻意放宽)。",
1113
+ filterModelsByPlan: "隐藏套餐外模型",
1114
+ filterModelsByPlanHint: "开启后,模型选择器只列出当前套餐可用的模型;账户持有按需余额时会显示全部。",
583
1115
  overridden: "已覆盖",
584
1116
  reset: "重置",
585
1117
  invalidNumber: "无效数字",
@@ -589,7 +1121,30 @@ window.__ModuleLoader__.load({
589
1121
  saving: "保存中",
590
1122
  saveFailed: "保存失败,请重试。",
591
1123
  discard: "放弃",
592
- cancel: "取消"
1124
+ cancel: "取消",
1125
+ usageTitle: "账户用量",
1126
+ usageRefresh: "刷新",
1127
+ usageRefreshing: "刷新中…",
1128
+ usageLoading: "正在获取账户用量…",
1129
+ usageNoKey: "配置 API 密钥后,这里会显示账户的用量与额度状态。",
1130
+ usageError: "用量获取失败",
1131
+ usageRequests: "请求",
1132
+ usageFailed: "失败",
1133
+ usageSuccessRate: "成功率",
1134
+ usageCost: "花费",
1135
+ usageTokens: "Token",
1136
+ usageTokensIn: "入",
1137
+ usageTokensOut: "出",
1138
+ usageMonthly: "月额度",
1139
+ usagePurchased: "已购",
1140
+ usageFree: "赠送",
1141
+ usageFiveHour: "5 小时窗口",
1142
+ usageWeekly: "每周窗口",
1143
+ usageExceeded: "已超限",
1144
+ usageReset: "重置于",
1145
+ usagePartial: "部分端点数据不可用",
1146
+ usageUpdated: "更新于",
1147
+ usagePeriodEnd: "账期截止"
593
1148
  };
594
1149
  const en = {
595
1150
  nav: "Command Code",
@@ -608,6 +1163,8 @@ window.__ModuleLoader__.load({
608
1163
  requestTimeoutMsHint: "Time to wait for the first response byte; default 60000.",
609
1164
  streamIdleTimeoutMs: "Stream idle timeout (ms)",
610
1165
  streamIdleTimeoutMsHint: "How long a stalled stream is treated as dead; default 300000 (deliberately generous — long-thinking models can stay silent for minutes).",
1166
+ filterModelsByPlan: "Hide out-of-plan models",
1167
+ filterModelsByPlanHint: "When on, the model picker lists only models your subscription includes; any on-demand credit balance shows the full catalog.",
611
1168
  overridden: "Overridden",
612
1169
  reset: "Reset",
613
1170
  invalidNumber: "Invalid number",
@@ -617,7 +1174,30 @@ window.__ModuleLoader__.load({
617
1174
  saving: "Saving",
618
1175
  saveFailed: "Save failed, please retry.",
619
1176
  discard: "Discard",
620
- cancel: "Cancel"
1177
+ cancel: "Cancel",
1178
+ usageTitle: "Account usage",
1179
+ usageRefresh: "Refresh",
1180
+ usageRefreshing: "Refreshing…",
1181
+ usageLoading: "Fetching account usage…",
1182
+ usageNoKey: "Configure an API key to see this account’s usage and credit state here.",
1183
+ usageError: "Could not fetch usage",
1184
+ usageRequests: "Requests",
1185
+ usageFailed: "failed",
1186
+ usageSuccessRate: "Success rate",
1187
+ usageCost: "Spend",
1188
+ usageTokens: "Tokens",
1189
+ usageTokensIn: "in",
1190
+ usageTokensOut: "out",
1191
+ usageMonthly: "Monthly",
1192
+ usagePurchased: "Purchased",
1193
+ usageFree: "Free",
1194
+ usageFiveHour: "5-hour window",
1195
+ usageWeekly: "Weekly window",
1196
+ usageExceeded: "Exceeded",
1197
+ usageReset: "Resets",
1198
+ usagePartial: "Some endpoint data unavailable",
1199
+ usageUpdated: "Updated",
1200
+ usagePeriodEnd: "Period ends"
621
1201
  };
622
1202
  //#endregion
623
1203
  //#region src/client/index.ts
@@ -645,7 +1225,44 @@ window.__ModuleLoader__.load({
645
1225
  .cc-invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}
646
1226
  .cc-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
647
1227
  .cc-footer{justify-content:flex-end;align-items:center;gap:8px;display:flex}
1228
+ .cc-toggleRow{align-items:center;gap:8px;cursor:pointer;display:flex}
1229
+ .cc-toggleRow:has(.cc-toggle:disabled){cursor:default}
1230
+ .cc-toggle{appearance:none;flex-shrink:0;background:var(--dsw-alias-border-l2);border-radius:999px;width:30px;height:18px;margin:0;cursor:pointer;position:relative;transition:background .15s ease}
1231
+ .cc-toggle:checked{background:var(--dsw-alias-brand-primary)}
1232
+ .cc-toggle::after{content:'';background:#fff;border-radius:50%;width:14px;height:14px;position:absolute;top:2px;left:2px;transition:left .15s ease}
1233
+ .cc-toggle:checked::after{left:14px}
1234
+ .cc-toggle:disabled{cursor:default;opacity:.5}
648
1235
  .cc-failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}
1236
+ .cc-usageCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:14px 16px;flex-direction:column;gap:12px;display:flex}
1237
+ .cc-usageHead{align-items:center;gap:8px;display:flex}
1238
+ .cc-usageTitle{color:var(--dsw-alias-label-primary);flex:1;margin:0;font-size:13px;font-weight:600;line-height:1.5}
1239
+ .cc-usageAccount{max-width:40%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
1240
+ .cc-usagePlan{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-brand-primary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:600;line-height:17px}
1241
+ .cc-usagePlanStatus{white-space:nowrap;color:var(--dsw-alias-label-error);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
1242
+ .cc-usageRefresh{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}
1243
+ .cc-usageRefresh:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
1244
+ .cc-usageRefresh:disabled{cursor:default;opacity:.5}
1245
+ .cc-usageHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
1246
+ .cc-usageError{align-items:center;gap:8px;color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5;display:flex}
1247
+ .cc-usageStats{grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:8px;display:grid}
1248
+ .cc-usageStat{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:8px;padding:8px 10px;flex-direction:column;gap:2px;display:flex}
1249
+ .cc-usageStatLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5}
1250
+ .cc-usageStatValue{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}
1251
+ .cc-usageStatSub{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5}
1252
+ .cc-usageWindows{flex-direction:column;gap:16px;display:flex}
1253
+ .cc-usageWindow{flex-direction:column;gap:6px;display:flex}
1254
+ .cc-usageWindowHead{align-items:baseline;gap:8px;display:flex}
1255
+ .cc-usageWindowLabel{color:var(--dsw-alias-label-secondary);flex:1;font-size:12px;font-weight:500;line-height:1.5}
1256
+ .cc-usageWindowValue{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:500;line-height:1.5}
1257
+ .cc-usageExceeded{color:var(--dsw-alias-label-error);font-size:11px;font-weight:500;line-height:1.5}
1258
+ .cc-usageBar{overflow:hidden;background:var(--dsw-alias-bg-layer-1);border-radius:999px;height:6px}
1259
+ .cc-usageBarFill{background:var(--dsw-alias-brand-primary);border-radius:999px;height:100%;transition:width .3s ease}
1260
+ .cc-usageBarFillWarn{background:var(--dsw-alias-label-error)}
1261
+ .cc-usageWindowReset{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}
1262
+ .cc-usageMeta{align-items:center;gap:8px;display:flex}
1263
+ .cc-usageMetaSpacer{flex:1}
1264
+ .cc-usageUpdated{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}
1265
+ .cc-usagePartial{color:var(--dsw-alias-label-error);margin:0;font-size:11px;line-height:1.5}
649
1266
  `;
650
1267
  /** Inject the page stylesheet once (idempotent per tag). */
651
1268
  function injectPageCss() {
@@ -678,12 +1295,56 @@ window.__ModuleLoader__.load({
678
1295
  ctx.effect(() => () => controller.dispose(), "dsh-commandcode-provider: settings controller");
679
1296
  const store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(controller.state());
680
1297
  controller.subscribe(() => store.set(controller.state()));
1298
+ let usageNamespace;
1299
+ let usageMountError;
1300
+ ctx.effect(() => {
1301
+ let cancelled = false;
1302
+ let unmount;
1303
+ ctx.remote.$mount(USAGE_REMOTE_CONTRIBUTION).then((dispose) => {
1304
+ if (cancelled) {
1305
+ dispose();
1306
+ return;
1307
+ }
1308
+ unmount = dispose;
1309
+ ctx.inject(["remote.commandcode"], (namespaceCtx) => {
1310
+ usageNamespace = namespaceCtx.remote.commandcode;
1311
+ namespaceCtx.effect(() => () => {
1312
+ usageNamespace = void 0;
1313
+ }, "dsh-commandcode-provider: usage namespace");
1314
+ });
1315
+ }, (error) => {
1316
+ usageMountError = error instanceof Error ? error.message : String(error);
1317
+ });
1318
+ return () => {
1319
+ cancelled = true;
1320
+ usageNamespace = void 0;
1321
+ if (unmount !== void 0) unmount();
1322
+ };
1323
+ }, "dsh-commandcode-provider: usage remote");
1324
+ const usageController = new CommandCodeUsageController({ report: async () => {
1325
+ const namespace = usageNamespace;
1326
+ if (namespace === void 0) return {
1327
+ ok: false,
1328
+ error: { message: usageMountError ?? "commandcode/report remote is not mounted" }
1329
+ };
1330
+ return namespace.report();
1331
+ } });
1332
+ ctx.effect(() => () => usageController.dispose(), "dsh-commandcode-provider: usage controller");
1333
+ const usageStore = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(usageController.state());
1334
+ usageController.subscribe(() => usageStore.set(usageController.state()));
681
1335
  const injected = () => ({
682
- hooks: { commandCodeSettings: store },
1336
+ hooks: {
1337
+ commandCodeSettings: store,
1338
+ commandCodeUsage: usageStore
1339
+ },
683
1340
  edit: (field, text) => controller.edit(field, text),
684
1341
  resetField: (field) => controller.resetField(field),
685
- save: () => void controller.save(),
686
- discard: () => controller.discard()
1342
+ save: () => void controller.save().then(() => {
1343
+ const settled = controller.state();
1344
+ if (!settled.failed && settled.apiKeyConfigured) usageController.refresh();
1345
+ }),
1346
+ discard: () => controller.discard(),
1347
+ refreshUsage: () => void usageController.refresh()
687
1348
  });
688
1349
  ctx.slots.inject("settings.section", () => ctx.slots.register({
689
1350
  name: "settings.section",