@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/CHANGELOG.md +18 -0
- package/README.md +2 -1
- package/README.zh-CN.md +2 -1
- package/lib/client.js +669 -8
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +176 -6
- package/lib/index.js +422 -24
- package/lib/index.js.map +1 -1
- package/package.json +3 -1
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
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
|
429
|
-
*
|
|
430
|
-
*
|
|
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(
|
|
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
|
-
|
|
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
|
-
*
|
|
723
|
-
*
|
|
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
|
|
730
|
-
const
|
|
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}`, {
|
|
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
|