@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/index.js CHANGED
@@ -9,6 +9,7 @@ import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-sett
9
9
  import { existsSync, readFileSync } from "node:fs";
10
10
  import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
11
11
  import { randomUUID } from "node:crypto";
12
+ import { TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
12
13
  //#region src/adapter.ts
13
14
  /**
14
15
  * DeepSeek Harness LLM adapter for the Command Code Provider API.
@@ -359,11 +360,90 @@ function compareByPlan(a, b) {
359
360
  if (nameDiff !== 0) return nameDiff;
360
361
  return a.id.localeCompare(b.id);
361
362
  }
362
- const KNOWN_DEALS = {
363
- "deepseek/deepseek-v4-pro": {
364
- label: "75% off",
365
- expiresAt: "2026-08-16T15:59:59.999Z"
363
+ /**
364
+ * Subscription plan table, synced from the official CLI bundle's plan maps
365
+ * (`Nn`/`$n` in command-code@1.26.0 `dist/cli.mjs`): subscription `planId`
366
+ * prefix → display name and the plan's monthly credit total. This is the
367
+ * account's own subscription (from `/alpha/billing/subscriptions`) — distinct
368
+ * from {@link KNOWN_PLANS}, which maps catalog models to their minimum tier.
369
+ *
370
+ * `tierWeight` is plugin-added (not from the CLI maps): the plan's rank on
371
+ * the {@link PLAN_ORDER} scale, used by the picker's plan filter
372
+ * ({@link modelVisibleInPlan}) to hide models above the account's tier.
373
+ */
374
+ const KNOWN_SUBSCRIPTION_PLANS = {
375
+ "individual-go": {
376
+ name: "Go",
377
+ monthlyCredits: 10,
378
+ tierWeight: 0
379
+ },
380
+ "individual-goat": {
381
+ name: "GOAT",
382
+ monthlyCredits: 70,
383
+ tierWeight: 1
384
+ },
385
+ "individual-pro": {
386
+ name: "Pro",
387
+ monthlyCredits: 30,
388
+ tierWeight: 2
389
+ },
390
+ "individual-pro-v1": {
391
+ name: "Pro",
392
+ monthlyCredits: 80,
393
+ tierWeight: 2
366
394
  },
395
+ "individual-provider": {
396
+ name: "Provider",
397
+ monthlyCredits: 15,
398
+ tierWeight: 3
399
+ },
400
+ "individual-max": {
401
+ name: "Max",
402
+ monthlyCredits: 150,
403
+ tierWeight: 4
404
+ },
405
+ "individual-ultra": {
406
+ name: "Ultra",
407
+ monthlyCredits: 300,
408
+ tierWeight: 4
409
+ },
410
+ "teams-pro": {
411
+ name: "Teams Pro",
412
+ monthlyCredits: 40,
413
+ tierWeight: 2
414
+ }
415
+ };
416
+ /** Plan-id prefixes, longest first — the CLI's prefix-match order. */
417
+ const SUBSCRIPTION_PLAN_PREFIXES = Object.keys(KNOWN_SUBSCRIPTION_PLANS).sort((a, b) => b.length - a.length);
418
+ /**
419
+ * Resolve a subscription `planId` (e.g. `individual-pro-v1`) to its display
420
+ * name and monthly credit total, mirroring the CLI's `getPlanInfo`:
421
+ * normalize (lowercase, `_` → `-`), then longest-prefix match so
422
+ * `individual-pro-v1` wins over `individual-pro`. Unknown ids return
423
+ * `undefined`.
424
+ */
425
+ function subscriptionPlanInfo(planId) {
426
+ const normalized = planId.toLowerCase().replace(/_/g, "-");
427
+ const prefix = SUBSCRIPTION_PLAN_PREFIXES.find((candidate) => normalized.startsWith(candidate));
428
+ return prefix === void 0 ? void 0 : KNOWN_SUBSCRIPTION_PLANS[prefix];
429
+ }
430
+ /**
431
+ * Whether the picker lists `modelId` for an account with the given billing
432
+ * access. Fails open at every uncertainty: no billing data, an unknown plan,
433
+ * or a model outside {@link KNOWN_PLANS} all keep the model visible — the
434
+ * server remains the final gate (`403 MODEL_NOT_IN_PLAN`).
435
+ */
436
+ function modelVisibleInPlan(modelId, access) {
437
+ if (access === void 0) return true;
438
+ if (access.onDemandCredits > 0) return true;
439
+ if (access.tierWeight === void 0) return true;
440
+ const tier = KNOWN_PLANS[modelId];
441
+ if (tier === void 0) return true;
442
+ const weight = PLAN_ORDER[tier];
443
+ if (weight === void 0) return true;
444
+ return weight <= access.tierWeight;
445
+ }
446
+ const KNOWN_DEALS = {
367
447
  "google/gemini-3.7-flash": {
368
448
  label: "50% off",
369
449
  expiresAt: "2026-12-31T23:59:59Z"
@@ -376,10 +456,58 @@ const KNOWN_DEALS = {
376
456
  free: true
377
457
  }
378
458
  };
459
+ /**
460
+ * Models with time-of-day (peak/off-peak) pricing, per the official pricing
461
+ * page (`/docs/resources/pricing-limits`). Since 2026-08-16 16:00 UTC, DeepSeek
462
+ * charges by the hour: peak hours are 01:00–04:00 and 06:00–10:00 UTC (7h/day,
463
+ * full price); the other 17 hours are off-peak at half price. The picker shows
464
+ * the *current* state as a compact label (`Peak`/`Half`) matching the English
465
+ * noun style of the other markers (`Image`, `FREE`), so a developer can tell at
466
+ * a glance whether calling the model right now is cheap or expensive.
467
+ *
468
+ * Keep in sync with the official pricing page when the model set or the peak
469
+ * windows change (see the dsh-commandcode-upstream skill).
470
+ */
471
+ const KNOWN_PEAK_PRICING = /* @__PURE__ */ new Set(["deepseek/deepseek-v4-pro", "deepseek/deepseek-v4-flash"]);
472
+ /** Peak hours (UTC, hour-of-day range end-exclusive): 01–03 and 06–09. */
473
+ const PEAK_HOUR_RANGES = [[1, 4], [6, 10]];
474
+ /**
475
+ * Whether `now` (defaults to `Date.now()`) falls in a peak-pricing hour for
476
+ * time-of-day-priced models. `undefined` for models outside the snapshot.
477
+ */
478
+ function peakPricingState(modelId, now = Date.now()) {
479
+ if (!KNOWN_PEAK_PRICING.has(modelId)) return void 0;
480
+ const hour = new Date(now).getUTCHours();
481
+ return PEAK_HOUR_RANGES.some(([start, end]) => hour >= start && hour < end) ? "peak" : "off-peak";
482
+ }
483
+ /**
484
+ * Compact label for the current peak/off-peak state: `Peak` (full price) or
485
+ * `Half` (off-peak, half price). These English nouns match the picker's other
486
+ * markers (`Go`, `Image`, `FREE`), and since they appear only on time-of-day
487
+ * priced models they double as a "priced by the hour" signal. Returns undefined
488
+ * for models without time-of-day pricing.
489
+ */
490
+ function peakPricingLabel(modelId, now = Date.now()) {
491
+ const state = peakPricingState(modelId, now);
492
+ if (state === void 0) return void 0;
493
+ return state === "peak" ? "Peak" : "Half";
494
+ }
379
495
  const COMMAND_CODE_CLI_VERSION = "1.26.0";
380
496
  const DEFAULT_API_BASE = "https://api.commandcode.ai";
381
497
  const DEFAULT_GENERATE_MAX_TOKENS = 64e3;
382
498
  const DEFAULT_MAX_OUTPUT_TOKENS = 65536;
499
+ const MODELS_TIMEOUT_MS = 1e4;
500
+ /** How long the picker's plan-filter billing facts stay cached before refetching. */
501
+ const BILLING_ACCESS_TTL_MS = 3e5;
502
+ /**
503
+ * Subscription statuses the CLI treats as live (`Mr` in command-code's
504
+ * cli.mjs): the plan gate applies only under one of these.
505
+ */
506
+ const ACTIVE_SUBSCRIPTION_STATUSES = /* @__PURE__ */ new Set([
507
+ "active",
508
+ "trialing",
509
+ "past_due"
510
+ ]);
383
511
  /** Head-of-request timeout: how long to wait for the first response byte. */
384
512
  const DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
385
513
  /** Stream idle timeout: a generation that stalls this long is a dead connection. */
@@ -425,9 +553,10 @@ function formatContext(contextWindow) {
425
553
  }
426
554
  /**
427
555
  * Compact one-line summary for the model picker: plan tier, then any active
428
- * deal (discount or FREE), then `Image` for Vision-capable models, then the
429
- * context window. Text-only models simply omit the Image marker "Text only"
430
- * adds nothing the picker needs to show.
556
+ * deal (discount or FREE), then the current peak/off-peak state (`Peak`/`Half`)
557
+ * for time-of-day-priced models, then `Image` for Vision-capable models, then
558
+ * the context window. Text-only models simply omit the Image marker "Text
559
+ * only" adds nothing the picker needs to show.
431
560
  */
432
561
  function capabilityDescription(modelId, contextWindow, now = Date.now()) {
433
562
  const parts = [];
@@ -435,6 +564,8 @@ function capabilityDescription(modelId, contextWindow, now = Date.now()) {
435
564
  if (plan !== void 0) parts.push(plan);
436
565
  const deal = dealLabel(modelId, now);
437
566
  if (deal !== void 0) parts.push(deal);
567
+ const peak = peakPricingLabel(modelId, now);
568
+ if (peak !== void 0) parts.push(peak);
438
569
  if (KNOWN_IMAGE_MODELS.has(modelId)) parts.push("Image");
439
570
  const ctx = formatContext(contextWindow);
440
571
  if (ctx !== void 0) parts.push(ctx);
@@ -449,6 +580,15 @@ function numberValue(value) {
449
580
  function booleanValue(value) {
450
581
  return typeof value === "boolean" ? value : void 0;
451
582
  }
583
+ /** Parse a billing-period timestamp (ISO string or millis) into millis; 0 when absent/invalid. */
584
+ function periodEndValue(value) {
585
+ const asNumber = numberValue(value);
586
+ if (asNumber !== void 0) return asNumber;
587
+ const asString = stringValue(value);
588
+ if (asString === void 0) return 0;
589
+ const parsed = Date.parse(asString);
590
+ return Number.isNaN(parsed) ? 0 : parsed;
591
+ }
452
592
  /**
453
593
  * Terminal stream-error markers from the official CLI (`Xw` in command-code's
454
594
  * cli.mjs): these always mean "retrying cannot succeed", so the adapter must
@@ -472,7 +612,7 @@ function recordOrEmpty(value) {
472
612
  return {};
473
613
  }
474
614
  function projectSlugFromPath(pathName) {
475
- return pathName.toLowerCase().replace(/^[a-z]:/i, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project";
615
+ return pathName.toLowerCase().replace(/^[a-z]:/i, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|(?<!-)-+$/g, "") || "project";
476
616
  }
477
617
  function parseStreamEventLine(line) {
478
618
  let trimmed = line.trim();
@@ -649,6 +789,8 @@ var CommandCodeAdapter = class extends LlmAdapter {
649
789
  catalog = [];
650
790
  fetchImpl;
651
791
  resolveAttachments;
792
+ billingAccess;
793
+ billingAccessInflight;
652
794
  constructor(deps) {
653
795
  super();
654
796
  this.deps = deps;
@@ -687,7 +829,9 @@ var CommandCodeAdapter = class extends LlmAdapter {
687
829
  return this.catalog;
688
830
  }
689
831
  async listModels(provider) {
690
- return (await this.loadCatalog()).map((model) => {
832
+ const catalog = await this.loadCatalog();
833
+ const access = this.deps.options().filterModelsByPlan === false ? void 0 : await this.loadBillingAccess();
834
+ return catalog.filter((model) => modelVisibleInPlan(model.id, access)).map((model) => {
691
835
  const vision = KNOWN_IMAGE_MODELS.has(model.id);
692
836
  return {
693
837
  provider,
@@ -718,27 +862,96 @@ var CommandCodeAdapter = class extends LlmAdapter {
718
862
  })) } } : {}
719
863
  };
720
864
  }
865
+ /** The headers every authenticated account endpoint shares. */
866
+ async accountHeaders() {
867
+ const connection = this.deps.options();
868
+ return {
869
+ Authorization: `Bearer ${await this.deps.resolveApiKey(connection)}`,
870
+ "x-command-code-version": COMMAND_CODE_CLI_VERSION,
871
+ "x-cli-environment": "production",
872
+ ...attributionHeaders()
873
+ };
874
+ }
721
875
  /**
722
- * Fetch account, usage, and credit state from the Command Code account
723
- * endpoints (`/alpha/whoami`, `/alpha/usage/summary`, `/alpha/billing/credits`).
876
+ * The billing facts behind the picker's plan filter, cached for
877
+ * {@link BILLING_ACCESS_TTL_MS} and shared across concurrent callers.
878
+ * `undefined` means "unknown — show everything" (fail-open).
879
+ */
880
+ async loadBillingAccess() {
881
+ const cached = this.billingAccess;
882
+ if (cached !== void 0 && Date.now() - cached.at < 3e5) return cached.value;
883
+ this.billingAccessInflight ??= this.fetchBillingAccess().then((value) => {
884
+ this.billingAccess = {
885
+ value,
886
+ at: Date.now()
887
+ };
888
+ return value;
889
+ }).finally(() => {
890
+ this.billingAccessInflight = void 0;
891
+ });
892
+ return this.billingAccessInflight;
893
+ }
894
+ /**
895
+ * The billing facts behind the picker's plan filter, mirroring the CLI's
896
+ * `createBilling` flow: whoami yields the org id, then the subscriptions
897
+ * and credits endpoints answer in parallel. The plan id is honored only
898
+ * when the subscription reports an active-ish status (the CLI's rule); when
899
+ * the subscriptions endpoint fails entirely, `credits.planId` is the
900
+ * fallback (the CLI stamps plan identity from it too). Any failure resolves
901
+ * to `undefined` (fail-open) rather than breaking the picker.
902
+ */
903
+ async fetchBillingAccess() {
904
+ try {
905
+ const connection = this.deps.options();
906
+ const headers = await this.accountHeaders();
907
+ const base = connection.apiBase;
908
+ const getJson = async (path) => {
909
+ const response = await this.fetchImpl(`${base}${path}`, {
910
+ headers,
911
+ signal: AbortSignal.timeout(MODELS_TIMEOUT_MS)
912
+ });
913
+ if (!response.ok) return void 0;
914
+ const parsed = await response.json();
915
+ return isRecord(parsed) ? parsed : void 0;
916
+ };
917
+ const whoami = await getJson("/alpha/whoami");
918
+ const orgData = whoami && isRecord(whoami.org) ? whoami.org : void 0;
919
+ const orgId = orgData === void 0 ? void 0 : stringValue(orgData.id);
920
+ const [subscription, credits] = await Promise.all([getJson(orgId === void 0 ? "/alpha/billing/subscriptions" : `/alpha/billing/subscriptions?orgId=${encodeURIComponent(orgId)}`), getJson("/alpha/billing/credits")]);
921
+ const subData = subscription && isRecord(subscription.data) ? subscription.data : void 0;
922
+ const creditsData = credits && isRecord(credits.credits) ? credits.credits : void 0;
923
+ if (subData === void 0 && creditsData === void 0) return void 0;
924
+ let planId;
925
+ if (subData !== void 0) {
926
+ const status = stringValue(subData.status);
927
+ if (status !== void 0 && ACTIVE_SUBSCRIPTION_STATUSES.has(status)) planId = stringValue(subData.planId);
928
+ } else planId = stringValue(creditsData?.planId);
929
+ return {
930
+ tierWeight: planId === void 0 ? void 0 : subscriptionPlanInfo(planId)?.tierWeight,
931
+ onDemandCredits: (numberValue(creditsData?.purchasedCredits) ?? 0) + (numberValue(creditsData?.freeCredits) ?? 0)
932
+ };
933
+ } catch {
934
+ return;
935
+ }
936
+ }
937
+ /**
938
+ * Fetch account, usage, credit, and subscription state from the Command
939
+ * Code account endpoints (`/alpha/whoami`, `/alpha/usage/summary`,
940
+ * `/alpha/billing/credits`, `/alpha/billing/subscriptions`).
724
941
  * Each endpoint degrades independently: a failed one lands in `failures`
725
942
  * while the rest still report, so a transient outage never blanks the whole
726
943
  * view. Requires a usable API key (throws `MISSING_CREDENTIAL` otherwise).
727
944
  */
728
945
  async getUsage() {
729
- const connection = this.deps.options();
730
- const apiKey = await this.deps.resolveApiKey(connection);
731
- const base = connection.apiBase;
732
- const headers = {
733
- Authorization: `Bearer ${apiKey}`,
734
- "x-command-code-version": COMMAND_CODE_CLI_VERSION,
735
- "x-cli-environment": "production",
736
- ...attributionHeaders()
737
- };
946
+ const base = this.deps.options().apiBase;
947
+ const headers = await this.accountHeaders();
738
948
  const failures = [];
739
949
  const getJson = async (path) => {
740
950
  try {
741
- const response = await this.fetchImpl(`${base}${path}`, { headers });
951
+ const response = await this.fetchImpl(`${base}${path}`, {
952
+ headers,
953
+ signal: AbortSignal.timeout(MODELS_TIMEOUT_MS)
954
+ });
742
955
  if (!response.ok) {
743
956
  failures.push(`${path}: HTTP ${response.status}`);
744
957
  return;
@@ -758,6 +971,8 @@ var CommandCodeAdapter = class extends LlmAdapter {
758
971
  name: stringValue(whoamiData.name) ?? "",
759
972
  userName: stringValue(whoamiData.userName) ?? ""
760
973
  };
974
+ const orgData = whoami && isRecord(whoami.org) ? whoami.org : void 0;
975
+ const orgId = orgData === void 0 ? void 0 : stringValue(orgData.id);
761
976
  const usage = await getJson("/alpha/usage/summary");
762
977
  if (usage) report.usage = {
763
978
  totalCount: numberValue(usage.totalCount) ?? 0,
@@ -792,6 +1007,19 @@ var CommandCodeAdapter = class extends LlmAdapter {
792
1007
  resetAt: numberValue(weekly?.resetAt) ?? 0
793
1008
  }
794
1009
  };
1010
+ const subscription = await getJson(orgId === void 0 ? "/alpha/billing/subscriptions" : `/alpha/billing/subscriptions?orgId=${encodeURIComponent(orgId)}`);
1011
+ const subData = subscription && isRecord(subscription.data) ? subscription.data : void 0;
1012
+ const planId = stringValue(subData?.planId) ?? stringValue(creditsData?.planId);
1013
+ if (subData !== void 0 || planId !== void 0) {
1014
+ const info = planId === void 0 ? void 0 : subscriptionPlanInfo(planId);
1015
+ report.plan = {
1016
+ planId: planId ?? "",
1017
+ name: info?.name ?? planId ?? "",
1018
+ status: stringValue(subData?.status) ?? "",
1019
+ monthlyCredits: info?.monthlyCredits ?? null,
1020
+ currentPeriodEnd: periodEndValue(subData?.currentPeriodEnd)
1021
+ };
1022
+ }
795
1023
  return report;
796
1024
  }
797
1025
  async *stream(options) {
@@ -1155,6 +1383,12 @@ function renderReport(report) {
1155
1383
  const lines = [];
1156
1384
  const account = report.account ? ` (${report.account.userName || report.account.name})` : "";
1157
1385
  lines.push(`📊 Command Code 用量${account}`, "");
1386
+ if (report.plan && report.plan.name !== "") {
1387
+ const p = report.plan;
1388
+ const status = p.status !== "" && p.status !== "active" ? ` (${p.status})` : "";
1389
+ const period = p.currentPeriodEnd > 0 ? ` · 账期截止 ${new Date(p.currentPeriodEnd).toLocaleDateString()}` : "";
1390
+ lines.push(` 📦 套餐 ${p.name}${status}${period}`, "");
1391
+ }
1158
1392
  if (report.usage) {
1159
1393
  const u = report.usage;
1160
1394
  lines.push("── 请求 ──────────────────────────────", ` 💬 请求 ${u.completedCount} 次 / 失败 ${u.failedCount} 成功率 ${u.successRate}%`, ` 💰 花费 ${money(u.totalCost)} (${moneyShort(u.totalCredits)} credits)`, ` 🔤 Token ${tokensCompact(u.totalTokensIn)} 入 / ${tokensCompact(u.totalTokensOut)} 出`, "");
@@ -1195,6 +1429,167 @@ function applyCommands(ctx, deps) {
1195
1429
  ctx.commands.register(commandDefinition(deps));
1196
1430
  }
1197
1431
  //#endregion
1432
+ //#region src/usage-wire.ts
1433
+ /** The npm package identity both contribution registrations claim. */
1434
+ const USAGE_REMOTE_PACKAGE = "@mars-sea/dsh-commandcode-provider";
1435
+ /** Canonical `<namespace>/<method>` endpoint of the usage report Remote. */
1436
+ const USAGE_REPORT_ENDPOINT = "commandcode/report";
1437
+ /** Reject one boundary value with a field-naming error. */
1438
+ function reject(field) {
1439
+ throw new TypeError(`commandcode/report result: invalid ${field}`);
1440
+ }
1441
+ /** Read one required finite number field (`field` is the dotted error label). */
1442
+ function numberField(source, key, field) {
1443
+ const value = source[key];
1444
+ if (typeof value !== "number" || !Number.isFinite(value)) reject(field);
1445
+ return value;
1446
+ }
1447
+ /** Read one required string field (`field` is the dotted error label). */
1448
+ function stringField(source, key, field) {
1449
+ const value = source[key];
1450
+ if (typeof value !== "string") reject(field);
1451
+ return value;
1452
+ }
1453
+ /** Read one required boolean field (`field` is the dotted error label). */
1454
+ function booleanField(source, key, field) {
1455
+ const value = source[key];
1456
+ if (typeof value !== "boolean") reject(field);
1457
+ return value;
1458
+ }
1459
+ /** Narrow an unknown value to a plain record, or reject. */
1460
+ function record(value, field) {
1461
+ if (typeof value !== "object" || value === null || Array.isArray(value)) reject(field);
1462
+ return value;
1463
+ }
1464
+ /** Validate one window-limit block (`fiveHour` / `weekly`). */
1465
+ function windowLimit(value, field) {
1466
+ const source = record(value, field);
1467
+ return {
1468
+ used: numberField(source, "used", `${field}.used`),
1469
+ cap: numberField(source, "cap", `${field}.cap`),
1470
+ exceeded: booleanField(source, "exceeded", `${field}.exceeded`),
1471
+ resetAt: numberField(source, "resetAt", `${field}.resetAt`)
1472
+ };
1473
+ }
1474
+ /**
1475
+ * Parse one untrusted boundary value into a {@link CommandCodeUsageReport}.
1476
+ * Optional sections stay optional; every present field is shape-checked so a
1477
+ * malformed frame fails the boundary instead of rendering garbage.
1478
+ */
1479
+ function parseUsageReport(value) {
1480
+ const source = record(value, "report");
1481
+ const failures = source.failures;
1482
+ if (!Array.isArray(failures) || failures.some((entry) => typeof entry !== "string")) reject("failures");
1483
+ const report = { failures };
1484
+ if (source.account !== void 0) {
1485
+ const account = record(source.account, "account");
1486
+ report.account = {
1487
+ id: stringField(account, "id", "account.id"),
1488
+ name: stringField(account, "name", "account.name"),
1489
+ userName: stringField(account, "userName", "account.userName")
1490
+ };
1491
+ }
1492
+ if (source.usage !== void 0) {
1493
+ const usage = record(source.usage, "usage");
1494
+ report.usage = {
1495
+ totalCount: numberField(usage, "totalCount", "usage.totalCount"),
1496
+ totalCost: numberField(usage, "totalCost", "usage.totalCost"),
1497
+ successRate: numberField(usage, "successRate", "usage.successRate"),
1498
+ completedCount: numberField(usage, "completedCount", "usage.completedCount"),
1499
+ failedCount: numberField(usage, "failedCount", "usage.failedCount"),
1500
+ totalTokensIn: numberField(usage, "totalTokensIn", "usage.totalTokensIn"),
1501
+ totalTokensOut: numberField(usage, "totalTokensOut", "usage.totalTokensOut"),
1502
+ totalCredits: numberField(usage, "totalCredits", "usage.totalCredits"),
1503
+ periodBasis: stringField(usage, "periodBasis", "usage.periodBasis")
1504
+ };
1505
+ }
1506
+ if (source.credits !== void 0) {
1507
+ const credits = record(source.credits, "credits");
1508
+ report.credits = {
1509
+ monthlyCredits: numberField(credits, "monthlyCredits", "credits.monthlyCredits"),
1510
+ purchasedCredits: numberField(credits, "purchasedCredits", "credits.purchasedCredits"),
1511
+ freeCredits: numberField(credits, "freeCredits", "credits.freeCredits"),
1512
+ fiveHour: windowLimit(credits.fiveHour, "credits.fiveHour"),
1513
+ weekly: windowLimit(credits.weekly, "credits.weekly")
1514
+ };
1515
+ }
1516
+ if (source.plan !== void 0) {
1517
+ const plan = record(source.plan, "plan");
1518
+ const monthly = plan.monthlyCredits;
1519
+ if (monthly !== null && (typeof monthly !== "number" || !Number.isFinite(monthly))) reject("plan.monthlyCredits");
1520
+ report.plan = {
1521
+ planId: stringField(plan, "planId", "plan.planId"),
1522
+ name: stringField(plan, "name", "plan.name"),
1523
+ status: stringField(plan, "status", "plan.status"),
1524
+ monthlyCredits: monthly,
1525
+ currentPeriodEnd: numberField(plan, "currentPeriodEnd", "plan.currentPeriodEnd")
1526
+ };
1527
+ }
1528
+ return report;
1529
+ }
1530
+ /**
1531
+ * The strict result codec both halves attach to the descriptor. Hand-rolled:
1532
+ * the client bundle may not require a schema library, and `TypertSchema` is
1533
+ * deliberately minimal so one `parse` function satisfies it.
1534
+ */
1535
+ const usageReportSchema = { parse: parseUsageReport };
1536
+ /** The Host-face contribution registered on `ctx.typert`. */
1537
+ const USAGE_HOST_CONTRIBUTION = {
1538
+ package: USAGE_REMOTE_PACKAGE,
1539
+ face: "host",
1540
+ schemas: [],
1541
+ invocations: [{
1542
+ id: `${USAGE_REMOTE_PACKAGE}#${USAGE_REPORT_ENDPOINT}`,
1543
+ service: "commandcodeUsage",
1544
+ namespace: "commandcode",
1545
+ method: "report",
1546
+ invocation: { kind: "direct" },
1547
+ parameters: [],
1548
+ result: {
1549
+ mode: "strict",
1550
+ typeSymbol: `${USAGE_REMOTE_PACKAGE}#CommandCodeUsageReport`,
1551
+ schema: usageReportSchema
1552
+ }
1553
+ }]
1554
+ };
1555
+ //#endregion
1556
+ //#region src/usage-remote.ts
1557
+ /**
1558
+ * The Remote receiver: a Cordis service the Gateway resolves by key
1559
+ * (`commandcodeUsage`) and binds to the wire namespace (`commandcode`). The
1560
+ * base class stamps the `typertRemote` binding the Gateway validates on every
1561
+ * dispatch; no decorators are needed because the descriptor is registered
1562
+ * explicitly (strict path) rather than discovered from source markers.
1563
+ */
1564
+ var CommandCodeUsageService = class extends TypertRemoteService {
1565
+ deps;
1566
+ constructor(ctx, deps) {
1567
+ super(ctx, "commandcodeUsage", { namespace: "commandcode" });
1568
+ this.deps = deps;
1569
+ }
1570
+ /**
1571
+ * Account, usage, and credit state for the settings page's account card.
1572
+ * Degrades per endpoint like the `/commandcode` command (failures land in
1573
+ * `report.failures`); throws `MISSING_CREDENTIAL` when no key resolves, which
1574
+ * the Gateway folds into the failure branch the page renders as a hint.
1575
+ */
1576
+ async report() {
1577
+ return this.deps.adapter.getUsage();
1578
+ }
1579
+ };
1580
+ /**
1581
+ * Provide the usage service and register its Remote descriptor. The registry
1582
+ * contribution is tied to this fiber's lifetime: the registry's own
1583
+ * `register()` effect would otherwise outlive the plugin.
1584
+ */
1585
+ function applyUsageRemote(ctx, deps) {
1586
+ ctx.inject(["typert"], (remoteCtx) => {
1587
+ new CommandCodeUsageService(remoteCtx, deps);
1588
+ const unregister = remoteCtx.typert.register(USAGE_HOST_CONTRIBUTION);
1589
+ remoteCtx.effect(() => () => void unregister(), "dsh-commandcode-provider: usage remote");
1590
+ });
1591
+ }
1592
+ //#endregion
1198
1593
  //#region src/index.ts
1199
1594
  /**
1200
1595
  * dsh-commandcode-provider — DeepSeek Harness LLM provider plugin for Command
@@ -1238,7 +1633,8 @@ const Config = z.object({
1238
1633
  workingDir: z.string(),
1239
1634
  modelsCachePath: z.string(),
1240
1635
  requestTimeoutMs: z.number().min(1).max(MAX_TIMER_DELAY_MS),
1241
- streamIdleTimeoutMs: z.number().min(1).max(MAX_TIMER_DELAY_MS)
1636
+ streamIdleTimeoutMs: z.number().min(1).max(MAX_TIMER_DELAY_MS),
1637
+ filterModelsByPlan: z.boolean()
1242
1638
  });
1243
1639
  /**
1244
1640
  * The one explicit resolve step from raw config to validated connection
@@ -1253,7 +1649,8 @@ function resolveAdapterOptions(config) {
1253
1649
  workingDir: config.workingDir ?? process.cwd(),
1254
1650
  modelsCachePath: config.modelsCachePath ?? DEFAULT_MODELS_CACHE_PATH,
1255
1651
  requestTimeoutMs: config.requestTimeoutMs ?? 6e4,
1256
- streamIdleTimeoutMs: config.streamIdleTimeoutMs ?? 3e5
1652
+ streamIdleTimeoutMs: config.streamIdleTimeoutMs ?? 3e5,
1653
+ filterModelsByPlan: config.filterModelsByPlan ?? true
1257
1654
  };
1258
1655
  }
1259
1656
  function apply(ctx, config) {
@@ -1303,6 +1700,7 @@ function apply(ctx, config) {
1303
1700
  ctx.inject(["commands"], (commandCtx) => {
1304
1701
  applyCommands(commandCtx, { adapter });
1305
1702
  });
1703
+ applyUsageRemote(ctx, { adapter });
1306
1704
  installSettingsSection(ctx, NS, Config, config, {
1307
1705
  setSource: (source) => {
1308
1706
  current = source;
@@ -1311,6 +1709,6 @@ function apply(ctx, config) {
1311
1709
  });
1312
1710
  }
1313
1711
  //#endregion
1314
- export { COMMAND_CODE_CLI_VERSION, CommandCodeAdapter, 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, KNOWN_DEALS, KNOWN_EFFORTS, KNOWN_IMAGE_MODELS, KNOWN_PLANS, KNOWN_THINKING_MODELS, PLAN_LABELS, PLAN_ORDER, PROVIDER, apply, applyCommands, capabilityDescription, commandDefinition, compareByPlan, dealLabel, formatContext, inject, name, planLabel, projectSlugFromPath, resolveAdapterOptions, resolveAuthFileApiKey };
1712
+ export { BILLING_ACCESS_TTL_MS, COMMAND_CODE_CLI_VERSION, CommandCodeAdapter, 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, KNOWN_DEALS, KNOWN_EFFORTS, KNOWN_IMAGE_MODELS, KNOWN_PEAK_PRICING, KNOWN_PLANS, KNOWN_SUBSCRIPTION_PLANS, KNOWN_THINKING_MODELS, PLAN_LABELS, PLAN_ORDER, PROVIDER, USAGE_REPORT_ENDPOINT, apply, applyCommands, applyUsageRemote, capabilityDescription, commandDefinition, compareByPlan, dealLabel, formatContext, inject, modelVisibleInPlan, name, peakPricingLabel, peakPricingState, planLabel, projectSlugFromPath, resolveAdapterOptions, resolveAuthFileApiKey, subscriptionPlanInfo, usageReportSchema };
1315
1713
 
1316
1714
  //# sourceMappingURL=index.js.map