@mars-sea/dsh-commandcode-provider 0.13.0 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.d.ts CHANGED
@@ -4,7 +4,7 @@ import { CredentialRef } from "@deepseek-ai/dsh-credentials";
4
4
  import { TypertRemoteService, TypertSchema } from "@deepseek-ai/dsh-typert-protocol";
5
5
  import { WebRuntime, WebSearchProvider, WebSearchRequest, WebSearchResult } from "@deepseek-ai/dsh-web";
6
6
  import { Context } from "@deepseek-ai/cordis";
7
- import { AttachmentStore } from "@deepseek-ai/dsh-attachment";
7
+ import { AttachmentStore, ImageAttachmentRef, ImageRequestTarget } from "@deepseek-ai/dsh-attachment";
8
8
  import { CommandDefinition } from "@deepseek-ai/dsh-commands";
9
9
  //#region src/provider-errors.d.ts
10
10
  /**
@@ -284,6 +284,8 @@ interface CommandCodeUsage {
284
284
  totalTokensOut: number;
285
285
  totalCredits: number;
286
286
  periodBasis: string;
287
+ /** 官方汇总返回的统计起点;缺失时不把查询参数当作已确认的统计范围。 */
288
+ periodStart?: number;
287
289
  }
288
290
  /** Credit/limit state from `/alpha/billing/credits`. */
289
291
  interface CommandCodeCredits {
@@ -338,6 +340,8 @@ interface CommandCodePlan {
338
340
  monthlyCredits: number | null;
339
341
  /** Billing period end in millis; 0 when the endpoint did not report one. */
340
342
  currentPeriodEnd: number;
343
+ /** 官方订阅账期起点,毫秒;旧宿主或未报告该字段时缺省。 */
344
+ currentPeriodStart?: number;
341
345
  }
342
346
  /**
343
347
  * Why every account endpoint failed at once (the report then carries no data
@@ -443,6 +447,25 @@ declare class CommandCodeAdapter<C extends CommandCodeConnectionOptions = Comman
443
447
  * requirement — the meter throws when the counts differ.
444
448
  */
445
449
  imageRequestPricing(_provider: string, model: string): LlmImageRequestPricing | undefined;
450
+ /**
451
+ * The dimensions and byte target this route actually puts on the wire.
452
+ *
453
+ * The base default answers with the NORMALIZED original (`image.width`,
454
+ * `image.height`, `image.bytes`), which is right only for an adapter that
455
+ * does not resize. This route always re-encodes through
456
+ * `readImageRequest(ref, requestImageTarget(ref))` — long edge 1568, encoded
457
+ * bytes capped at 1 MiB — so the default would over-report both dimensions
458
+ * and bytes for every image, most visibly for Retina screenshots.
459
+ *
460
+ * `imageRequestPricing` above already prices at this same target; exposing it
461
+ * here keeps the harness's own readers (the CUA screenshot path calls
462
+ * `ctx.llm.imageRequestTarget`) consistent with what the gateway receives.
463
+ * @param _provider - registered provider route; one route per adapter here.
464
+ * @param _model - exact model id; the target is model-independent on this route.
465
+ * @param image - normalized attachment whose request projection is resolved.
466
+ * @returns the same target `readImageRequest` encodes to, before base64 expansion.
467
+ */
468
+ imageRequestTarget(_provider: string, _model: string, image: ImageAttachmentRef): ImageRequestTarget;
446
469
  listModels(provider: string, opts?: {
447
470
  unfiltered?: boolean;
448
471
  }): Promise<readonly LlmModelInfo[]>;
package/lib/index.js CHANGED
@@ -2826,6 +2826,12 @@ function tokensOf$1(value) {
2826
2826
  cacheWriteTokens: u.cacheWriteTokens ?? 0
2827
2827
  };
2828
2828
  }
2829
+ /** The four buckets of an already-parsed record, or undefined when one is missing. */
2830
+ function costTokensOf(value) {
2831
+ const t = record$3(value);
2832
+ if (!t || !COST_TOKEN_KEYS.every((k) => finite(t[k]))) return void 0;
2833
+ return t;
2834
+ }
2829
2835
  /** Current v2 settlements store the last usage in their stream; v1 stores chunk events. */
2830
2836
  function usageOf(type, data) {
2831
2837
  if (type === "assistant/chunk") {
@@ -2846,16 +2852,51 @@ function parseState(value) {
2846
2852
  const s = record$3(value), facts = record$3(s?.facts);
2847
2853
  if (!s || !facts || !finite(facts.pricingKey) || !Array.isArray(facts.groups)) throw new TypeError("invalid commandCodeCost state");
2848
2854
  const group = (v) => {
2849
- const g = record$3(v), t = record$3(g?.tokens);
2850
- return !!g && typeof g.provider === "string" && typeof g.model === "string" && (g.at === null || finite(g.at)) && finite(g.contextTokens) && !!t && COST_TOKEN_KEYS.every((k) => finite(t[k]));
2855
+ const g = record$3(v);
2856
+ return !!g && typeof g.provider === "string" && typeof g.model === "string" && (g.at === null || finite(g.at)) && finite(g.contextTokens) && costTokensOf(g.tokens) !== void 0;
2851
2857
  };
2852
2858
  const sel = record$3(s.selection), last = record$3(s.last);
2853
- if (!(s.selection === null || sel && typeof sel.provider === "string" && typeof sel.model === "string") || !(s.at === null || finite(s.at)) || !facts.groups.every(group) || !(s.last === null || last && finite(last.turn) && finite(last.step) && group(last.group))) throw new TypeError("invalid commandCodeCost state");
2854
- return value;
2859
+ const summaryTokens = facts.summaryTokens === void 0 ? zeroCostTokens() : costTokensOf(facts.summaryTokens);
2860
+ if (summaryTokens === void 0 || !(s.selection === null || sel && typeof sel.provider === "string" && typeof sel.model === "string") || !(s.at === null || finite(s.at)) || !facts.groups.every(group) || !(s.last === null || last && finite(last.turn) && finite(last.step) && group(last.group))) throw new TypeError("invalid commandCodeCost state");
2861
+ return {
2862
+ ...value,
2863
+ facts: {
2864
+ ...facts,
2865
+ summaryTokens
2866
+ }
2867
+ };
2855
2868
  }
2856
2869
  /** Optional registry uses only synchronous parse/view; no new runtime module is required. */
2857
2870
  function createCostProjection(table = modelPriceTable()) {
2858
2871
  const version = pricingKey(table);
2872
+ /** Groups that still carry tokens; a retry that replaced a group can drive one to zero. */
2873
+ const liveGroups = (groups) => groups.filter((g) => COST_TOKEN_KEYS.some((k) => g.tokens[k] > 0));
2874
+ /**
2875
+ * Fold one settled usage group into `groups`, first undoing the group a retry
2876
+ * supersedes. Shared by turn/step settlements and summary calls, which have
2877
+ * no turn/step of their own.
2878
+ */
2879
+ const foldGroup = (groups, previous, group, tokens) => {
2880
+ const byKey = /* @__PURE__ */ new Map();
2881
+ groups.forEach((g, i) => {
2882
+ const key = costGroupKey(g, table);
2883
+ if (!byKey.has(key)) byKey.set(key, i);
2884
+ });
2885
+ if (previous) {
2886
+ const old = byKey.get(costGroupKey(previous, table));
2887
+ if (old !== void 0) for (const k of COST_TOKEN_KEYS) groups[old].tokens[k] -= previous.tokens[k];
2888
+ }
2889
+ const targetIdx = byKey.get(costGroupKey(group, table));
2890
+ let target = targetIdx === void 0 ? void 0 : groups[targetIdx];
2891
+ if (!target) {
2892
+ target = {
2893
+ ...group,
2894
+ tokens: zeroCostTokens()
2895
+ };
2896
+ groups.push(target);
2897
+ }
2898
+ for (const k of COST_TOKEN_KEYS) target.tokens[k] += tokens[k];
2899
+ };
2859
2900
  return {
2860
2901
  key: "commandCodeCost",
2861
2902
  stateVersion: version,
@@ -2866,7 +2907,8 @@ function createCostProjection(table = modelPriceTable()) {
2866
2907
  last: null,
2867
2908
  facts: {
2868
2909
  pricingKey: version,
2869
- groups: []
2910
+ groups: [],
2911
+ summaryTokens: zeroCostTokens()
2870
2912
  }
2871
2913
  }),
2872
2914
  apply(state, event) {
@@ -2887,6 +2929,32 @@ function createCostProjection(table = modelPriceTable()) {
2887
2929
  at: finite(event.time) ? event.time : null,
2888
2930
  last: null
2889
2931
  };
2932
+ if (event.type === "compaction/summary") {
2933
+ const summary = tokensOf$1(data.usage);
2934
+ if (summary === void 0) return state;
2935
+ const group = {
2936
+ provider: typeof data.provider === "string" ? data.provider : "",
2937
+ model: typeof data.model === "string" ? data.model : "",
2938
+ at: finite(event.time) ? event.time : null,
2939
+ contextTokens: summary.uncachedInputTokens + summary.cacheReadTokens + summary.cacheWriteTokens,
2940
+ tokens: summary
2941
+ };
2942
+ const groups = state.facts.groups.map((g) => ({
2943
+ ...g,
2944
+ tokens: { ...g.tokens }
2945
+ }));
2946
+ foldGroup(groups, void 0, group, summary);
2947
+ const summaryTokens = { ...state.facts.summaryTokens };
2948
+ for (const k of COST_TOKEN_KEYS) summaryTokens[k] += summary[k];
2949
+ return {
2950
+ ...state,
2951
+ facts: {
2952
+ pricingKey: version,
2953
+ groups: liveGroups(groups),
2954
+ summaryTokens
2955
+ }
2956
+ };
2957
+ }
2890
2958
  const tokens = usageOf(event.type, data);
2891
2959
  if (tokens === void 0 || !finite(data.turn) || !finite(data.step)) return state;
2892
2960
  const previous = state.last?.turn === data.turn && state.last.step === data.step ? state.last.group : void 0;
@@ -2902,26 +2970,7 @@ function createCostProjection(table = modelPriceTable()) {
2902
2970
  ...g,
2903
2971
  tokens: { ...g.tokens }
2904
2972
  }));
2905
- const byKey = /* @__PURE__ */ new Map();
2906
- groups.forEach((g, i) => {
2907
- const key = costGroupKey(g, table);
2908
- if (!byKey.has(key)) byKey.set(key, i);
2909
- });
2910
- const groupKey = costGroupKey(group, table);
2911
- if (previous) {
2912
- const old = byKey.get(costGroupKey(previous, table));
2913
- if (old !== void 0) for (const k of COST_TOKEN_KEYS) groups[old].tokens[k] -= previous.tokens[k];
2914
- }
2915
- const targetIdx = byKey.get(groupKey);
2916
- let target = targetIdx === void 0 ? void 0 : groups[targetIdx];
2917
- if (!target) {
2918
- target = {
2919
- ...group,
2920
- tokens: zeroCostTokens()
2921
- };
2922
- groups.push(target);
2923
- }
2924
- for (const k of COST_TOKEN_KEYS) target.tokens[k] += tokens[k];
2973
+ foldGroup(groups, previous, group, tokens);
2925
2974
  return {
2926
2975
  ...state,
2927
2976
  last: {
@@ -2931,7 +2980,8 @@ function createCostProjection(table = modelPriceTable()) {
2931
2980
  },
2932
2981
  facts: {
2933
2982
  pricingKey: version,
2934
- groups: groups.filter((g) => COST_TOKEN_KEYS.some((k) => g.tokens[k] > 0))
2983
+ groups: liveGroups(groups),
2984
+ summaryTokens: state.facts.summaryTokens
2935
2985
  }
2936
2986
  };
2937
2987
  },
@@ -3009,6 +3059,20 @@ function foldRequests(session, log) {
3009
3059
  }
3010
3060
  continue;
3011
3061
  }
3062
+ if (event.type === "compaction/summary") {
3063
+ if (event.seq >= log.inheritedEventCount && d.provider === "commandcode") {
3064
+ const tokens = tokensOf(d.usage);
3065
+ facts.push({
3066
+ session,
3067
+ model: typeof d.model === "string" ? d.model : "",
3068
+ at: event.time,
3069
+ retry: false,
3070
+ outcome: "completed",
3071
+ ...tokens === void 0 ? {} : { tokens }
3072
+ });
3073
+ }
3074
+ continue;
3075
+ }
3012
3076
  if (event.type === "step/start" || event.type === "llm/retry-started") {
3013
3077
  begin(event, d, event.type === "llm/retry-started");
3014
3078
  continue;
@@ -4157,8 +4221,14 @@ function parseProviderError(errText) {
4157
4221
  * `CONTEXT_WINDOW_EXCEEDED`, because that failure has a recovery the harness
4158
4222
  * performs and a generic provider error does not. Every message built here is
4159
4223
  * bilingual — the harness renders it verbatim in its retry chrome.
4224
+ *
4225
+ * The optional `priorRejection` records a refusal from an earlier transport
4226
+ * attempt in this same call (see {@link isMessagesPermissionRejection}), so a
4227
+ * failed CLI fallback still shows what the Messages surface said first. It is
4228
+ * presentation only: classification, retry budget and failure code all keep
4229
+ * reading the current attempt.
4160
4230
  */
4161
- function generateHttpError(status, errText, retryAfterMs, parsed = parseProviderError(errText)) {
4231
+ function generateHttpError(status, errText, retryAfterMs, parsed = parseProviderError(errText), priorRejection) {
4162
4232
  const providerCode = parsed.nested === void 0 ? void 0 : parsed.code;
4163
4233
  const providerDetail = parsed.nested === void 0 ? "" : [
4164
4234
  parsed.code,
@@ -4175,9 +4245,14 @@ function generateHttpError(status, errText, retryAfterMs, parsed = parseProvider
4175
4245
  if (status === 422 && isZdrNoProviders(providerCode, providerDetail, errText)) return bilingual("PROVIDER_HTTP_ERROR", `Command Code API error 422 (${detail}): zero data retention is enforced but no ZDR-capable upstream is available for this model — turn the ZDR setting off (or use another model)`, "Command Code API 返回 422:已开启零数据保留(ZDR),但该模型当前没有可用的 ZDR 上游——请关闭 ZDR 开关,或改用其他模型", { status: 422 });
4176
4246
  if (status === 401) return bilingual("INVALID_CREDENTIAL", `Command Code API error 401 (${detail}): the API key is missing or invalid — check the key stored for COMMANDCODE_API_KEY (Models page) or the auth file`, "Command Code API 返回 401:API 密钥缺失或无效——请在设置页检查 COMMANDCODE_API_KEY 存储的密钥,或检查 auth 文件", { status: 401 });
4177
4247
  if (status === 413) return bilingual("PROVIDER_HTTP_ERROR", "Command Code API error 413: the request body exceeds the provider's size limit (the cap is undocumented, measured at about 50 MB) — the session history is too large to send, usually because of accumulated image attachments. Start a new session, or drop the image-heavy part of this one, and retry", "Command Code API 返回 413:请求体超过服务端的体积上限(官方未公开,实测约 50 MB)——会话历史过大,通常是历史图片累积所致。请新建会话,或移除本会话中图片较多的部分后重试", { status: 413 });
4178
- const diagnosticCode = parsed.code ?? /^\s*(MODEL_NOT_IN_PLAN|INSUFFICIENT_CREDITS|USAGE_EXCEEDED|PREMIUM_CREDITS_EXHAUSTED)\b/i.exec(parsed.message ?? "")?.[1];
4179
- const diagnostic = diagnosticCode !== void 0 && /^[A-Za-z0-9_.:-]{1,80}$/.test(diagnosticCode) ? `(${diagnosticCode})` : "";
4180
- return new LlmError(`Command Code 请求失败(HTTP ${status})${diagnostic}:` + (status === 429 ? "请求受到用量或速率限制,请稍后重试并检查账户额度。" : status === 408 ? "服务端处理超时,请稍后重试。" : status >= 500 ? "上游服务暂时无法完成请求,请稍后重试。" : "服务端拒绝了本次请求,请检查模型、套餐和请求设置。"), httpErrorCode(status), {
4248
+ const safeIdentifier = (value) => value !== void 0 && /^[A-Za-z0-9_.:-]{1,80}$/.test(value) ? value : void 0;
4249
+ const diagnosticCode = safeIdentifier(parsed.code) ?? /^\s*(MODEL_NOT_IN_PLAN|INSUFFICIENT_CREDITS|USAGE_EXCEEDED|PREMIUM_CREDITS_EXHAUSTED)\b/i.exec(parsed.message ?? "")?.[1];
4250
+ const diagnosticType = safeIdentifier(parsed.type);
4251
+ const identifiers = [diagnosticCode === void 0 ? void 0 : `错误码:${diagnosticCode}`, diagnosticType === void 0 ? void 0 : `错误类型:${diagnosticType}`].filter((value) => value !== void 0);
4252
+ const diagnostic = identifiers.length === 0 ? "" : `(${identifiers.join(";")})`;
4253
+ const priorIdentifier = priorRejection === void 0 ? void 0 : safeIdentifier(priorRejection);
4254
+ const priorNote = priorIdentifier === void 0 ? "" : `此前 Messages 接口以 ${priorIdentifier} 拒绝,已回退 CLI 直连,仍未成功。`;
4255
+ return new LlmError(`Command Code 请求失败(HTTP ${status})${diagnostic}:` + (status === 429 ? "请求受到用量或速率限制,请稍后重试并检查账户额度。" : status === 408 ? "服务端处理超时,请稍后重试。" : status >= 500 ? "上游服务暂时无法完成请求,请稍后重试。" : "服务端拒绝了本次请求,请检查模型、套餐和请求设置。") + priorNote, httpErrorCode(status), {
4181
4256
  status,
4182
4257
  ...retryAfterMs !== void 0 && retryAfterMs > 0 && retryAfterMs <= 9e5 ? { providerRetryAfterMs: retryAfterMs } : {}
4183
4258
  });
@@ -4327,6 +4402,22 @@ function isUpgradeRequiredError(status, errText, parsed = parseProviderError(err
4327
4402
  if (parsed.message?.toLowerCase().includes("go plan") && parsed.message.toLowerCase().includes("api access")) return true;
4328
4403
  return false;
4329
4404
  }
4405
+ /**
4406
+ * True when the Messages surface (`/provider/v1/messages`) refused one request
4407
+ * with Anthropic's `permission_error` (issue #84).
4408
+ *
4409
+ * 职责:把「反代 Claude 链路对该账号不放行」这一种拒绝,从普通权限失败里
4410
+ * 单独认出来,交给适配器决定是否改用 CLI 直连(`/alpha/generate`)。
4411
+ *
4412
+ * 为什么边界如此窄:只认 HTTP 403 加上结构化的 `code`/`type` 等于
4413
+ * `permission_error`,不扫原始正文。正文可能回显输入或返回整页英文错误,
4414
+ * 扫文本会把无关失败也变成一次传输切换;而真正「模型不在套餐内」的拒绝
4415
+ * (`MODEL_NOT_IN_PLAN` 等其它标识)继续按原样上报,不被这条规则吞掉。
4416
+ */
4417
+ function isMessagesPermissionRejection(status, parsed) {
4418
+ if (status !== 403) return false;
4419
+ return (parsed.code ?? parsed.type)?.toLowerCase() === "permission_error";
4420
+ }
4330
4421
  //#endregion
4331
4422
  //#region src/stream-trace.ts
4332
4423
  /**
@@ -6057,7 +6148,7 @@ function requestFingerprint(protocol, body) {
6057
6148
  };
6058
6149
  }
6059
6150
  /** Parse a billing-period timestamp (ISO string or millis) into millis; 0 when absent/invalid. */
6060
- function periodEndValue(value) {
6151
+ function periodTimestampValue(value) {
6061
6152
  const asNumber = numberValue(value);
6062
6153
  if (asNumber !== void 0) return asNumber;
6063
6154
  const asString = stringValue(value);
@@ -6906,7 +6997,7 @@ const USAGE_ENDPOINT_COUNT = 4;
6906
6997
  */
6907
6998
  function parseUsageTotals(usage) {
6908
6999
  if (usage === void 0) return void 0;
6909
- return {
7000
+ const result = {
6910
7001
  totalCount: numberValue(usage.totalCount) ?? 0,
6911
7002
  totalCost: numberValue(usage.totalCost) ?? 0,
6912
7003
  successRate: numberValue(usage.successRate) ?? 0,
@@ -6915,8 +7006,11 @@ function parseUsageTotals(usage) {
6915
7006
  totalTokensIn: numberValue(usage.totalTokensIn) ?? 0,
6916
7007
  totalTokensOut: numberValue(usage.totalTokensOut) ?? 0,
6917
7008
  totalCredits: numberValue(usage.totalCredits) ?? 0,
6918
- periodBasis: stringValue(usage.periodBasis) ?? "billing-period"
7009
+ periodBasis: stringValue(usage.periodBasis) ?? "unreported"
6919
7010
  };
7011
+ const start = periodTimestampValue(usage.periodStart);
7012
+ if (start > 0 && Number.isFinite(new Date(start).getTime())) result.periodStart = start;
7013
+ return result;
6920
7014
  }
6921
7015
  /**
6922
7016
  * Parse one window block into a window limit, or undefined when the endpoint
@@ -6962,9 +7056,8 @@ function parseCreditLimits(credits) {
6962
7056
  return parsed;
6963
7057
  }
6964
7058
  /**
6965
- * Parse the account identity from whoami plus the plan identity from the
6966
- * subscriptions/credits payloads. Returns the org id for the subscriptions
6967
- * query alongside, so getUsage fetches whoami first and the rest in parallel.
7059
+ * Read the account identity and organization scope from whoami. Every billing
7060
+ * and usage request must use this same organization, so getUsage reads it first.
6968
7061
  */
6969
7062
  function parseAccountIdentity(whoami) {
6970
7063
  const whoamiData = whoami !== void 0 && isRecord(whoami.user) ? whoami.user : void 0;
@@ -7533,6 +7626,27 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
7533
7626
  const imageCapable = KNOWN_IMAGE_MODELS.has(model);
7534
7627
  return { priceImages: (images) => images.map((image) => priceRequestImage(image, model, imageCapable)) };
7535
7628
  }
7629
+ /**
7630
+ * The dimensions and byte target this route actually puts on the wire.
7631
+ *
7632
+ * The base default answers with the NORMALIZED original (`image.width`,
7633
+ * `image.height`, `image.bytes`), which is right only for an adapter that
7634
+ * does not resize. This route always re-encodes through
7635
+ * `readImageRequest(ref, requestImageTarget(ref))` — long edge 1568, encoded
7636
+ * bytes capped at 1 MiB — so the default would over-report both dimensions
7637
+ * and bytes for every image, most visibly for Retina screenshots.
7638
+ *
7639
+ * `imageRequestPricing` above already prices at this same target; exposing it
7640
+ * here keeps the harness's own readers (the CUA screenshot path calls
7641
+ * `ctx.llm.imageRequestTarget`) consistent with what the gateway receives.
7642
+ * @param _provider - registered provider route; one route per adapter here.
7643
+ * @param _model - exact model id; the target is model-independent on this route.
7644
+ * @param image - normalized attachment whose request projection is resolved.
7645
+ * @returns the same target `readImageRequest` encodes to, before base64 expansion.
7646
+ */
7647
+ imageRequestTarget(_provider, _model, image) {
7648
+ return requestImageTarget(image);
7649
+ }
7536
7650
  async listModels(provider, opts) {
7537
7651
  const connection = { ...this.deps.options() };
7538
7652
  const catalog = await this.gatewayFacts.refresh(connection);
@@ -7879,16 +7993,22 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
7879
7993
  const report = { failures };
7880
7994
  const { account, orgId } = parseAccountIdentity(await getJson("/alpha/whoami"));
7881
7995
  if (account !== void 0) report.account = account;
7882
- const [usage, credits, subscription] = await Promise.all([
7883
- getJson("/alpha/usage/summary"),
7884
- getJson("/alpha/billing/credits"),
7885
- getJson(orgId === void 0 ? "/alpha/billing/subscriptions" : `/alpha/billing/subscriptions?orgId=${encodeURIComponent(orgId)}`)
7886
- ]);
7887
- const totals = parseUsageTotals(usage);
7996
+ const scopedPath = (path, since) => {
7997
+ const params = new URLSearchParams();
7998
+ if (orgId !== void 0) params.set("orgId", orgId);
7999
+ if (since !== void 0) params.set("since", since);
8000
+ const query = params.toString();
8001
+ return query ? `${path}?${query}` : path;
8002
+ };
8003
+ const [credits, subscription] = await Promise.all([getJson(scopedPath("/alpha/billing/credits")), getJson(scopedPath("/alpha/billing/subscriptions"))]);
8004
+ const subData = subscription !== void 0 && isRecord(subscription.data) ? subscription.data : void 0;
8005
+ const rawStart = subData?.currentPeriodStart;
8006
+ const periodStart = periodTimestampValue(rawStart);
8007
+ const validStart = periodStart > 0 && Number.isFinite(new Date(periodStart).getTime());
8008
+ const totals = parseUsageTotals(await getJson(scopedPath("/alpha/usage/summary", validStart ? String(rawStart) : void 0)));
7888
8009
  if (totals !== void 0) report.usage = totals;
7889
8010
  const limits = parseCreditLimits(credits);
7890
8011
  if (limits !== void 0) report.credits = limits;
7891
- const subData = subscription !== void 0 && isRecord(subscription.data) ? subscription.data : void 0;
7892
8012
  const creditsData = credits !== void 0 && isRecord(credits.credits) ? credits.credits : void 0;
7893
8013
  const planId = stringValue(subData?.planId) ?? stringValue(creditsData?.planId);
7894
8014
  if (subData !== void 0 || planId !== void 0) {
@@ -7898,8 +8018,9 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
7898
8018
  name: info?.name ?? planId ?? "",
7899
8019
  status: stringValue(subData?.status) ?? "",
7900
8020
  monthlyCredits: info?.monthlyCredits ?? null,
7901
- currentPeriodEnd: periodEndValue(subData?.currentPeriodEnd)
8021
+ currentPeriodEnd: periodTimestampValue(subData?.currentPeriodEnd)
7902
8022
  };
8023
+ if (validStart) report.plan.currentPeriodStart = periodStart;
7903
8024
  }
7904
8025
  const blocked = classifyTotalFailure(failures, failed);
7905
8026
  if (blocked !== void 0) report.blocked = blocked;
@@ -8053,6 +8174,7 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
8053
8174
  const reasoningEffort = effort === "off" && declared === void 0 ? void 0 : declared;
8054
8175
  const systemText = [options.system ?? "", ...options.messages.filter((m) => m.role === "system").map((m) => m.content.map(blockText).filter(Boolean).join("\n"))].filter(Boolean).join("\n\n");
8055
8176
  let protocol = this.resolveProtocol(connection, apiKey, options.model, modelEntry);
8177
+ let messagesPermissionRejection;
8056
8178
  let requestOptions = withSurfaceOffload(options, this.surfaceOffload, REQUEST_IMAGE_BUDGETS[0], connection.offloadSeenImagesForCache === true && protocol === "cli" ? options.model : void 0);
8057
8179
  const buildBody = async (target) => {
8058
8180
  const finishBody = timing.phase("body");
@@ -8143,8 +8265,10 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
8143
8265
  break;
8144
8266
  }
8145
8267
  const providerError = parseProviderError(attempt.errText);
8146
- if (protocol !== "cli" && isUpgradeRequiredError(attempt.status, attempt.errText, providerError)) {
8268
+ const messagesPermissionDenied = protocol === "messages" && isMessagesPermissionRejection(attempt.status, providerError);
8269
+ if (protocol !== "cli" && (messagesPermissionDenied || isUpgradeRequiredError(attempt.status, attempt.errText, providerError))) {
8147
8270
  const wasMessages = protocol === "messages";
8271
+ if (messagesPermissionDenied) messagesPermissionRejection = providerError.code ?? providerError.type;
8148
8272
  protocol = "cli";
8149
8273
  if (!wasMessages) this.rememberProtocol(connection.apiBase, apiKey, "cli");
8150
8274
  requestOptions = withSurfaceOffload(options, this.surfaceOffload, REQUEST_IMAGE_BUDGETS[0], connection.offloadSeenImagesForCache === true ? options.model : void 0);
@@ -8164,6 +8288,7 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
8164
8288
  });
8165
8289
  if (next !== void 0 && !tried.has(next)) {
8166
8290
  apiKey = next;
8291
+ messagesPermissionRejection = void 0;
8167
8292
  const nextProtocol = this.resolveProtocol(connection, apiKey, options.model, modelEntry);
8168
8293
  if (nextProtocol !== protocol) {
8169
8294
  protocol = nextProtocol;
@@ -8183,7 +8308,7 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
8183
8308
  ...wait > 0 && wait <= 9e5 ? { providerRetryAfterMs: wait } : {}
8184
8309
  });
8185
8310
  }
8186
- throw generateHttpError(attempt.status, attempt.errText, attempt.retryAfterMs, providerError);
8311
+ throw generateHttpError(attempt.status, attempt.errText, attempt.retryAfterMs, providerError, messagesPermissionRejection);
8187
8312
  }
8188
8313
  } catch (error) {
8189
8314
  trace.record("connect-error", {
@@ -8365,7 +8490,7 @@ function renderReport(report, locale, title) {
8365
8490
  }
8366
8491
  if (report.usage) {
8367
8492
  const u = report.usage;
8368
- lines.push(commandCopy(locale, "usageHeader"), commandCopy(locale, "requestsLine").replace("{n}", String(u.completedCount)).replace("{f}", String(u.failedCount)).replace("{r}", successRateText(u.successRate)), commandCopy(locale, "costLine").replace("{money}", money(u.totalCost)).replace("{credits}", moneyShort(u.totalCredits)), commandCopy(locale, "tokensLine").replace("{in}", tokensCompact(u.totalTokensIn)).replace("{out}", tokensCompact(u.totalTokensOut)), "");
8493
+ lines.push(commandCopy(locale, "usageHeader"), commandCopy(locale, "requestsLine").replace("{n}", String(u.totalCount)).replace("{f}", String(u.failedCount)).replace("{r}", successRateText(u.successRate)), commandCopy(locale, "costLine").replace("{money}", money(u.totalCost)).replace("{credits}", moneyShort(u.totalCredits)), commandCopy(locale, "tokensLine").replace("{in}", tokensCompact(u.totalTokensIn)).replace("{out}", tokensCompact(u.totalTokensOut)), "");
8369
8494
  }
8370
8495
  if (report.credits) {
8371
8496
  const c = report.credits;
@@ -8486,6 +8611,7 @@ function parseUsageReport(value) {
8486
8611
  totalCredits: numberField(usage, "totalCredits", "usage.totalCredits"),
8487
8612
  periodBasis: stringField$1(usage, "periodBasis", "usage.periodBasis")
8488
8613
  };
8614
+ if (usage.periodStart !== void 0) report.usage.periodStart = numberField(usage, "periodStart", "usage.periodStart");
8489
8615
  }
8490
8616
  if (source.credits !== void 0) {
8491
8617
  const credits = record$1(source.credits, "credits");
@@ -8513,6 +8639,7 @@ function parseUsageReport(value) {
8513
8639
  monthlyCredits: monthly,
8514
8640
  currentPeriodEnd: numberField(plan, "currentPeriodEnd", "plan.currentPeriodEnd")
8515
8641
  };
8642
+ if (plan.currentPeriodStart !== void 0) report.plan.currentPeriodStart = numberField(plan, "currentPeriodStart", "plan.currentPeriodStart");
8516
8643
  }
8517
8644
  return report;
8518
8645
  }
@@ -10756,7 +10883,7 @@ function apply(ctx, config) {
10756
10883
  const resolved = await scopedPool(connection).resolveKey(model === void 0 ? {} : { model });
10757
10884
  if (resolved !== void 0) return assertUsableApiKey(resolved.key, "llm-commandcode", resolved.slot.ref ?? `${resolved.slot.label} (config.apiKey)`);
10758
10885
  const ref = connection.apiKeyEnv;
10759
- throw new LlmError(`llm-commandcode: no API key for provider route "${PROVIDER}"; store ${ref} through the credentials service (the web Models page writes it), export it in the launching environment, set config.apiKey, or run \`command-code login\` to write ~/.commandcode/auth.json`, "MISSING_CREDENTIAL");
10886
+ throw new LlmError(`llm-commandcode: no API key for provider route "${PROVIDER}"; store ${ref} through the credentials service (the web Models page writes it), export it in the launching environment, set config.apiKey, or run \`command-code login\` to write ~/.commandcode/auth.json. The headless profile mounts no credentials service, so a key saved only in the store never reaches it: export the variable or put it in a .env file for those runs.`, "MISSING_CREDENTIAL");
10760
10887
  };
10761
10888
  const adapter = new CommandCodeAdapter({
10762
10889
  options,