@mars-sea/dsh-commandcode-provider 0.13.1 → 0.13.3

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
  /**
@@ -61,7 +61,7 @@ type CommandCodeProtocol = 'cli' | 'openai' | 'messages';
61
61
  declare const DEFAULT_MAX_OUTPUT_TOKENS = 131072;
62
62
  //#endregion
63
63
  //#region src/adapter.d.ts
64
- declare const COMMAND_CODE_CLI_VERSION = "1.82.1";
64
+ declare const COMMAND_CODE_CLI_VERSION = "1.82.2";
65
65
  declare const DEFAULT_API_BASE = "https://api.commandcode.ai";
66
66
  /**
67
67
  * The output budget this bundle asks for, and the ceiling it will never exceed.
@@ -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
@@ -431,7 +431,7 @@ const KNOWN_EFFORTS = {
431
431
  "xhigh",
432
432
  "max"
433
433
  ],
434
- "inclusionai/ling-3.1-flash:free": [
434
+ "inclusionai/ling-3.1-flash": [
435
435
  "low",
436
436
  "medium",
437
437
  "high"
@@ -711,22 +711,20 @@ function requiresMessagesEndpoint(modelId) {
711
711
  * **Generated — do not hand-edit.** `scripts/sync-output-limits.mjs` rewrites
712
712
  * this literal from models.dev; run it after a catalog sync, and use its
713
713
  * `--check` in CI. Command Code resells these models through their VENDOR's own
714
- * API, so the vendor's published ceiling is the value recorded: 77 of the 89
715
- * catalog ids as of 2026-10-14. Each row carries a comment naming the entry it
714
+ * API, so the vendor's published ceiling is the value recorded: 76 of the 89
715
+ * catalog ids as of 2026-10-15. Each row carries a comment naming the entry it
716
716
  * came from.
717
717
  *
718
- * Two of those 77 rest on more than a vendor lookup, both hand-recorded in the
719
- * script's `MANUAL_CEILINGS`. `gpt-6.1-sol` (128000) comes from OpenAI's own row
720
- * and is corroborated by every other provider carrying it.
721
- * `inclusionai/ling-3.1-flash:free` (32768) has no models.dev row at all, so it
722
- * takes the Ling family's uniform ceiling; `inclusionai/ling-3.0-flash-sante:free`
723
- * (32768) lost both its vendor row and its unanimous third-party vote (vercel
724
- * files 32000), and is pinned so a regeneration cannot drop a live model's
725
- * ceiling back to the default. All are still correctable — a stricter gateway
726
- * costs one refused request, and `streamRequest()` remembers what the endpoint
727
- * said.
728
- *
729
- * The 12 unlisted models are ones no vendor publishes and the aggregators
718
+ * One of those 76 rests on more than a vendor lookup, hand-recorded in the
719
+ * script's `MANUAL_CEILINGS`: `inclusionai/ling-3.0-flash-sante:free` (32768)
720
+ * lost both its vendor row and its unanimous third-party vote (vercel files
721
+ * 32000), and is pinned so a regeneration cannot drop a live model's ceiling
722
+ * back to the default. (`gpt-6.1-sol` (128000) needs no pin — OpenAI's own row
723
+ * carries it and every other provider carrying the id agrees.) All are still
724
+ * correctable — a stricter gateway costs one refused request, and
725
+ * `streamRequest()` remembers what the endpoint said.
726
+ *
727
+ * The 13 unlisted models are ones no vendor publishes and the aggregators
730
728
  * disagree about. They are NOT a gap: a refusal states the ceiling in full, and
731
729
  * `streamRequest()` reads it off, retries once and remembers it for the model
732
730
  * (issue #71). A vendor figure the gateway turns out to be stricter than costs
@@ -772,7 +770,6 @@ const MODEL_OUTPUT_TOKEN_LIMITS = /* @__PURE__ */ new Map([
772
770
  ["gpt-6-sol", 128e3],
773
771
  ["gpt-6.1-sol", 128e3],
774
772
  ["inclusionai/ling-3.0-flash-sante:free", 32768],
775
- ["inclusionai/ling-3.1-flash:free", 32768],
776
773
  ["meituan/LongCat-2.0", 131072],
777
774
  ["meta/muse-spark-1.1", 131072],
778
775
  ["meta/muse-spark-1.2", 131072],
@@ -866,7 +863,7 @@ const KNOWN_PLANS = {
866
863
  "gpt-6-luna": "go",
867
864
  "meituan/LongCat-2.0": "go",
868
865
  "inclusionai/ling-3.0-flash-sante:free": "go",
869
- "inclusionai/ling-3.1-flash:free": "go",
866
+ "inclusionai/ling-3.1-flash": "go",
870
867
  "meta/muse-spark-1.2-contributor": "go",
871
868
  "meta/muse-spark-1.3-contributor": "go",
872
869
  "mistral/mistral-large-4": "go",
@@ -1076,10 +1073,6 @@ const KNOWN_DEALS = {
1076
1073
  label: "FREE",
1077
1074
  free: true
1078
1075
  },
1079
- "inclusionai/ling-3.1-flash:free": {
1080
- label: "FREE",
1081
- free: true
1082
- },
1083
1076
  "stealth/glyph-cluster:free": {
1084
1077
  label: "FREE",
1085
1078
  free: true
@@ -1721,8 +1714,8 @@ const MODEL_PRICE_ROWS = [
1721
1714
  ],
1722
1715
  allowance: {
1723
1716
  go: 10,
1724
- goat: 60,
1725
- pro: 70
1717
+ goat: 100,
1718
+ pro: 100
1726
1719
  }
1727
1720
  },
1728
1721
  {
@@ -2144,6 +2137,19 @@ const MODEL_PRICE_ROWS = [
2144
2137
  pro: 30
2145
2138
  }
2146
2139
  },
2140
+ {
2141
+ id: "ling-3.1-flash",
2142
+ rates: [
2143
+ .3,
2144
+ .9,
2145
+ .06
2146
+ ],
2147
+ allowance: {
2148
+ go: 6,
2149
+ goat: 20,
2150
+ pro: 30
2151
+ }
2152
+ },
2147
2153
  {
2148
2154
  id: "longcat-2.0",
2149
2155
  rates: [
@@ -2554,6 +2560,20 @@ const MODEL_PRICE_ROWS = [
2554
2560
  pro: 30
2555
2561
  }
2556
2562
  },
2563
+ {
2564
+ id: "qwen-3.8-max-prime",
2565
+ rates: [
2566
+ 4,
2567
+ 12,
2568
+ .5,
2569
+ 5
2570
+ ],
2571
+ allowance: {
2572
+ go: 6,
2573
+ goat: 20,
2574
+ pro: 30
2575
+ }
2576
+ },
2557
2577
  {
2558
2578
  id: "qwen-3.8-omni-flash",
2559
2579
  rates: [
@@ -2826,6 +2846,12 @@ function tokensOf$1(value) {
2826
2846
  cacheWriteTokens: u.cacheWriteTokens ?? 0
2827
2847
  };
2828
2848
  }
2849
+ /** The four buckets of an already-parsed record, or undefined when one is missing. */
2850
+ function costTokensOf(value) {
2851
+ const t = record$3(value);
2852
+ if (!t || !COST_TOKEN_KEYS.every((k) => finite(t[k]))) return void 0;
2853
+ return t;
2854
+ }
2829
2855
  /** Current v2 settlements store the last usage in their stream; v1 stores chunk events. */
2830
2856
  function usageOf(type, data) {
2831
2857
  if (type === "assistant/chunk") {
@@ -2846,16 +2872,51 @@ function parseState(value) {
2846
2872
  const s = record$3(value), facts = record$3(s?.facts);
2847
2873
  if (!s || !facts || !finite(facts.pricingKey) || !Array.isArray(facts.groups)) throw new TypeError("invalid commandCodeCost state");
2848
2874
  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]));
2875
+ const g = record$3(v);
2876
+ 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
2877
  };
2852
2878
  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;
2879
+ const summaryTokens = facts.summaryTokens === void 0 ? zeroCostTokens() : costTokensOf(facts.summaryTokens);
2880
+ 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");
2881
+ return {
2882
+ ...value,
2883
+ facts: {
2884
+ ...facts,
2885
+ summaryTokens
2886
+ }
2887
+ };
2855
2888
  }
2856
2889
  /** Optional registry uses only synchronous parse/view; no new runtime module is required. */
2857
2890
  function createCostProjection(table = modelPriceTable()) {
2858
2891
  const version = pricingKey(table);
2892
+ /** Groups that still carry tokens; a retry that replaced a group can drive one to zero. */
2893
+ const liveGroups = (groups) => groups.filter((g) => COST_TOKEN_KEYS.some((k) => g.tokens[k] > 0));
2894
+ /**
2895
+ * Fold one settled usage group into `groups`, first undoing the group a retry
2896
+ * supersedes. Shared by turn/step settlements and summary calls, which have
2897
+ * no turn/step of their own.
2898
+ */
2899
+ const foldGroup = (groups, previous, group, tokens) => {
2900
+ const byKey = /* @__PURE__ */ new Map();
2901
+ groups.forEach((g, i) => {
2902
+ const key = costGroupKey(g, table);
2903
+ if (!byKey.has(key)) byKey.set(key, i);
2904
+ });
2905
+ if (previous) {
2906
+ const old = byKey.get(costGroupKey(previous, table));
2907
+ if (old !== void 0) for (const k of COST_TOKEN_KEYS) groups[old].tokens[k] -= previous.tokens[k];
2908
+ }
2909
+ const targetIdx = byKey.get(costGroupKey(group, table));
2910
+ let target = targetIdx === void 0 ? void 0 : groups[targetIdx];
2911
+ if (!target) {
2912
+ target = {
2913
+ ...group,
2914
+ tokens: zeroCostTokens()
2915
+ };
2916
+ groups.push(target);
2917
+ }
2918
+ for (const k of COST_TOKEN_KEYS) target.tokens[k] += tokens[k];
2919
+ };
2859
2920
  return {
2860
2921
  key: "commandCodeCost",
2861
2922
  stateVersion: version,
@@ -2866,7 +2927,8 @@ function createCostProjection(table = modelPriceTable()) {
2866
2927
  last: null,
2867
2928
  facts: {
2868
2929
  pricingKey: version,
2869
- groups: []
2930
+ groups: [],
2931
+ summaryTokens: zeroCostTokens()
2870
2932
  }
2871
2933
  }),
2872
2934
  apply(state, event) {
@@ -2887,6 +2949,32 @@ function createCostProjection(table = modelPriceTable()) {
2887
2949
  at: finite(event.time) ? event.time : null,
2888
2950
  last: null
2889
2951
  };
2952
+ if (event.type === "compaction/summary") {
2953
+ const summary = tokensOf$1(data.usage);
2954
+ if (summary === void 0) return state;
2955
+ const group = {
2956
+ provider: typeof data.provider === "string" ? data.provider : "",
2957
+ model: typeof data.model === "string" ? data.model : "",
2958
+ at: finite(event.time) ? event.time : null,
2959
+ contextTokens: summary.uncachedInputTokens + summary.cacheReadTokens + summary.cacheWriteTokens,
2960
+ tokens: summary
2961
+ };
2962
+ const groups = state.facts.groups.map((g) => ({
2963
+ ...g,
2964
+ tokens: { ...g.tokens }
2965
+ }));
2966
+ foldGroup(groups, void 0, group, summary);
2967
+ const summaryTokens = { ...state.facts.summaryTokens };
2968
+ for (const k of COST_TOKEN_KEYS) summaryTokens[k] += summary[k];
2969
+ return {
2970
+ ...state,
2971
+ facts: {
2972
+ pricingKey: version,
2973
+ groups: liveGroups(groups),
2974
+ summaryTokens
2975
+ }
2976
+ };
2977
+ }
2890
2978
  const tokens = usageOf(event.type, data);
2891
2979
  if (tokens === void 0 || !finite(data.turn) || !finite(data.step)) return state;
2892
2980
  const previous = state.last?.turn === data.turn && state.last.step === data.step ? state.last.group : void 0;
@@ -2902,26 +2990,7 @@ function createCostProjection(table = modelPriceTable()) {
2902
2990
  ...g,
2903
2991
  tokens: { ...g.tokens }
2904
2992
  }));
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];
2993
+ foldGroup(groups, previous, group, tokens);
2925
2994
  return {
2926
2995
  ...state,
2927
2996
  last: {
@@ -2931,7 +3000,8 @@ function createCostProjection(table = modelPriceTable()) {
2931
3000
  },
2932
3001
  facts: {
2933
3002
  pricingKey: version,
2934
- groups: groups.filter((g) => COST_TOKEN_KEYS.some((k) => g.tokens[k] > 0))
3003
+ groups: liveGroups(groups),
3004
+ summaryTokens: state.facts.summaryTokens
2935
3005
  }
2936
3006
  };
2937
3007
  },
@@ -3009,6 +3079,20 @@ function foldRequests(session, log) {
3009
3079
  }
3010
3080
  continue;
3011
3081
  }
3082
+ if (event.type === "compaction/summary") {
3083
+ if (event.seq >= log.inheritedEventCount && d.provider === "commandcode") {
3084
+ const tokens = tokensOf(d.usage);
3085
+ facts.push({
3086
+ session,
3087
+ model: typeof d.model === "string" ? d.model : "",
3088
+ at: event.time,
3089
+ retry: false,
3090
+ outcome: "completed",
3091
+ ...tokens === void 0 ? {} : { tokens }
3092
+ });
3093
+ }
3094
+ continue;
3095
+ }
3012
3096
  if (event.type === "step/start" || event.type === "llm/retry-started") {
3013
3097
  begin(event, d, event.type === "llm/retry-started");
3014
3098
  continue;
@@ -4157,8 +4241,14 @@ function parseProviderError(errText) {
4157
4241
  * `CONTEXT_WINDOW_EXCEEDED`, because that failure has a recovery the harness
4158
4242
  * performs and a generic provider error does not. Every message built here is
4159
4243
  * bilingual — the harness renders it verbatim in its retry chrome.
4244
+ *
4245
+ * The optional `priorRejection` records a refusal from an earlier transport
4246
+ * attempt in this same call (see {@link isMessagesPermissionRejection}), so a
4247
+ * failed CLI fallback still shows what the Messages surface said first. It is
4248
+ * presentation only: classification, retry budget and failure code all keep
4249
+ * reading the current attempt.
4160
4250
  */
4161
- function generateHttpError(status, errText, retryAfterMs, parsed = parseProviderError(errText)) {
4251
+ function generateHttpError(status, errText, retryAfterMs, parsed = parseProviderError(errText), priorRejection) {
4162
4252
  const providerCode = parsed.nested === void 0 ? void 0 : parsed.code;
4163
4253
  const providerDetail = parsed.nested === void 0 ? "" : [
4164
4254
  parsed.code,
@@ -4175,9 +4265,14 @@ function generateHttpError(status, errText, retryAfterMs, parsed = parseProvider
4175
4265
  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
4266
  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
4267
  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), {
4268
+ const safeIdentifier = (value) => value !== void 0 && /^[A-Za-z0-9_.:-]{1,80}$/.test(value) ? value : void 0;
4269
+ const diagnosticCode = safeIdentifier(parsed.code) ?? /^\s*(MODEL_NOT_IN_PLAN|INSUFFICIENT_CREDITS|USAGE_EXCEEDED|PREMIUM_CREDITS_EXHAUSTED)\b/i.exec(parsed.message ?? "")?.[1];
4270
+ const diagnosticType = safeIdentifier(parsed.type);
4271
+ const identifiers = [diagnosticCode === void 0 ? void 0 : `错误码:${diagnosticCode}`, diagnosticType === void 0 ? void 0 : `错误类型:${diagnosticType}`].filter((value) => value !== void 0);
4272
+ const diagnostic = identifiers.length === 0 ? "" : `(${identifiers.join(";")})`;
4273
+ const priorIdentifier = priorRejection === void 0 ? void 0 : safeIdentifier(priorRejection);
4274
+ const priorNote = priorIdentifier === void 0 ? "" : `此前 Messages 接口以 ${priorIdentifier} 拒绝,已回退 CLI 直连,仍未成功。`;
4275
+ return new LlmError(`Command Code 请求失败(HTTP ${status})${diagnostic}:` + (status === 429 ? "请求受到用量或速率限制,请稍后重试并检查账户额度。" : status === 408 ? "服务端处理超时,请稍后重试。" : status >= 500 ? "上游服务暂时无法完成请求,请稍后重试。" : "服务端拒绝了本次请求,请检查模型、套餐和请求设置。") + priorNote, httpErrorCode(status), {
4181
4276
  status,
4182
4277
  ...retryAfterMs !== void 0 && retryAfterMs > 0 && retryAfterMs <= 9e5 ? { providerRetryAfterMs: retryAfterMs } : {}
4183
4278
  });
@@ -4327,6 +4422,22 @@ function isUpgradeRequiredError(status, errText, parsed = parseProviderError(err
4327
4422
  if (parsed.message?.toLowerCase().includes("go plan") && parsed.message.toLowerCase().includes("api access")) return true;
4328
4423
  return false;
4329
4424
  }
4425
+ /**
4426
+ * True when the Messages surface (`/provider/v1/messages`) refused one request
4427
+ * with Anthropic's `permission_error` (issue #84).
4428
+ *
4429
+ * 职责:把「反代 Claude 链路对该账号不放行」这一种拒绝,从普通权限失败里
4430
+ * 单独认出来,交给适配器决定是否改用 CLI 直连(`/alpha/generate`)。
4431
+ *
4432
+ * 为什么边界如此窄:只认 HTTP 403 加上结构化的 `code`/`type` 等于
4433
+ * `permission_error`,不扫原始正文。正文可能回显输入或返回整页英文错误,
4434
+ * 扫文本会把无关失败也变成一次传输切换;而真正「模型不在套餐内」的拒绝
4435
+ * (`MODEL_NOT_IN_PLAN` 等其它标识)继续按原样上报,不被这条规则吞掉。
4436
+ */
4437
+ function isMessagesPermissionRejection(status, parsed) {
4438
+ if (status !== 403) return false;
4439
+ return (parsed.code ?? parsed.type)?.toLowerCase() === "permission_error";
4440
+ }
4330
4441
  //#endregion
4331
4442
  //#region src/stream-trace.ts
4332
4443
  /**
@@ -5902,7 +6013,7 @@ var GatewayFacts = class {
5902
6013
  * per-request options thunk and an API-key resolver from the plugin entry
5903
6014
  * (src/index.ts), so a settings change reaches the very next request.
5904
6015
  */
5905
- const COMMAND_CODE_CLI_VERSION = "1.82.1";
6016
+ const COMMAND_CODE_CLI_VERSION = "1.82.2";
5906
6017
  const DEFAULT_API_BASE = "https://api.commandcode.ai";
5907
6018
  /**
5908
6019
  * The output budget this bundle asks for, and the ceiling it will never exceed.
@@ -6057,7 +6168,7 @@ function requestFingerprint(protocol, body) {
6057
6168
  };
6058
6169
  }
6059
6170
  /** Parse a billing-period timestamp (ISO string or millis) into millis; 0 when absent/invalid. */
6060
- function periodEndValue(value) {
6171
+ function periodTimestampValue(value) {
6061
6172
  const asNumber = numberValue(value);
6062
6173
  if (asNumber !== void 0) return asNumber;
6063
6174
  const asString = stringValue(value);
@@ -6906,7 +7017,7 @@ const USAGE_ENDPOINT_COUNT = 4;
6906
7017
  */
6907
7018
  function parseUsageTotals(usage) {
6908
7019
  if (usage === void 0) return void 0;
6909
- return {
7020
+ const result = {
6910
7021
  totalCount: numberValue(usage.totalCount) ?? 0,
6911
7022
  totalCost: numberValue(usage.totalCost) ?? 0,
6912
7023
  successRate: numberValue(usage.successRate) ?? 0,
@@ -6915,8 +7026,11 @@ function parseUsageTotals(usage) {
6915
7026
  totalTokensIn: numberValue(usage.totalTokensIn) ?? 0,
6916
7027
  totalTokensOut: numberValue(usage.totalTokensOut) ?? 0,
6917
7028
  totalCredits: numberValue(usage.totalCredits) ?? 0,
6918
- periodBasis: stringValue(usage.periodBasis) ?? "billing-period"
7029
+ periodBasis: stringValue(usage.periodBasis) ?? "unreported"
6919
7030
  };
7031
+ const start = periodTimestampValue(usage.periodStart);
7032
+ if (start > 0 && Number.isFinite(new Date(start).getTime())) result.periodStart = start;
7033
+ return result;
6920
7034
  }
6921
7035
  /**
6922
7036
  * Parse one window block into a window limit, or undefined when the endpoint
@@ -6962,9 +7076,8 @@ function parseCreditLimits(credits) {
6962
7076
  return parsed;
6963
7077
  }
6964
7078
  /**
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.
7079
+ * Read the account identity and organization scope from whoami. Every billing
7080
+ * and usage request must use this same organization, so getUsage reads it first.
6968
7081
  */
6969
7082
  function parseAccountIdentity(whoami) {
6970
7083
  const whoamiData = whoami !== void 0 && isRecord(whoami.user) ? whoami.user : void 0;
@@ -7533,6 +7646,27 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
7533
7646
  const imageCapable = KNOWN_IMAGE_MODELS.has(model);
7534
7647
  return { priceImages: (images) => images.map((image) => priceRequestImage(image, model, imageCapable)) };
7535
7648
  }
7649
+ /**
7650
+ * The dimensions and byte target this route actually puts on the wire.
7651
+ *
7652
+ * The base default answers with the NORMALIZED original (`image.width`,
7653
+ * `image.height`, `image.bytes`), which is right only for an adapter that
7654
+ * does not resize. This route always re-encodes through
7655
+ * `readImageRequest(ref, requestImageTarget(ref))` — long edge 1568, encoded
7656
+ * bytes capped at 1 MiB — so the default would over-report both dimensions
7657
+ * and bytes for every image, most visibly for Retina screenshots.
7658
+ *
7659
+ * `imageRequestPricing` above already prices at this same target; exposing it
7660
+ * here keeps the harness's own readers (the CUA screenshot path calls
7661
+ * `ctx.llm.imageRequestTarget`) consistent with what the gateway receives.
7662
+ * @param _provider - registered provider route; one route per adapter here.
7663
+ * @param _model - exact model id; the target is model-independent on this route.
7664
+ * @param image - normalized attachment whose request projection is resolved.
7665
+ * @returns the same target `readImageRequest` encodes to, before base64 expansion.
7666
+ */
7667
+ imageRequestTarget(_provider, _model, image) {
7668
+ return requestImageTarget(image);
7669
+ }
7536
7670
  async listModels(provider, opts) {
7537
7671
  const connection = { ...this.deps.options() };
7538
7672
  const catalog = await this.gatewayFacts.refresh(connection);
@@ -7879,16 +8013,22 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
7879
8013
  const report = { failures };
7880
8014
  const { account, orgId } = parseAccountIdentity(await getJson("/alpha/whoami"));
7881
8015
  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);
8016
+ const scopedPath = (path, since) => {
8017
+ const params = new URLSearchParams();
8018
+ if (orgId !== void 0) params.set("orgId", orgId);
8019
+ if (since !== void 0) params.set("since", since);
8020
+ const query = params.toString();
8021
+ return query ? `${path}?${query}` : path;
8022
+ };
8023
+ const [credits, subscription] = await Promise.all([getJson(scopedPath("/alpha/billing/credits")), getJson(scopedPath("/alpha/billing/subscriptions"))]);
8024
+ const subData = subscription !== void 0 && isRecord(subscription.data) ? subscription.data : void 0;
8025
+ const rawStart = subData?.currentPeriodStart;
8026
+ const periodStart = periodTimestampValue(rawStart);
8027
+ const validStart = periodStart > 0 && Number.isFinite(new Date(periodStart).getTime());
8028
+ const totals = parseUsageTotals(await getJson(scopedPath("/alpha/usage/summary", validStart ? String(rawStart) : void 0)));
7888
8029
  if (totals !== void 0) report.usage = totals;
7889
8030
  const limits = parseCreditLimits(credits);
7890
8031
  if (limits !== void 0) report.credits = limits;
7891
- const subData = subscription !== void 0 && isRecord(subscription.data) ? subscription.data : void 0;
7892
8032
  const creditsData = credits !== void 0 && isRecord(credits.credits) ? credits.credits : void 0;
7893
8033
  const planId = stringValue(subData?.planId) ?? stringValue(creditsData?.planId);
7894
8034
  if (subData !== void 0 || planId !== void 0) {
@@ -7898,8 +8038,9 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
7898
8038
  name: info?.name ?? planId ?? "",
7899
8039
  status: stringValue(subData?.status) ?? "",
7900
8040
  monthlyCredits: info?.monthlyCredits ?? null,
7901
- currentPeriodEnd: periodEndValue(subData?.currentPeriodEnd)
8041
+ currentPeriodEnd: periodTimestampValue(subData?.currentPeriodEnd)
7902
8042
  };
8043
+ if (validStart) report.plan.currentPeriodStart = periodStart;
7903
8044
  }
7904
8045
  const blocked = classifyTotalFailure(failures, failed);
7905
8046
  if (blocked !== void 0) report.blocked = blocked;
@@ -8053,6 +8194,7 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
8053
8194
  const reasoningEffort = effort === "off" && declared === void 0 ? void 0 : declared;
8054
8195
  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
8196
  let protocol = this.resolveProtocol(connection, apiKey, options.model, modelEntry);
8197
+ let messagesPermissionRejection;
8056
8198
  let requestOptions = withSurfaceOffload(options, this.surfaceOffload, REQUEST_IMAGE_BUDGETS[0], connection.offloadSeenImagesForCache === true && protocol === "cli" ? options.model : void 0);
8057
8199
  const buildBody = async (target) => {
8058
8200
  const finishBody = timing.phase("body");
@@ -8143,8 +8285,10 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
8143
8285
  break;
8144
8286
  }
8145
8287
  const providerError = parseProviderError(attempt.errText);
8146
- if (protocol !== "cli" && isUpgradeRequiredError(attempt.status, attempt.errText, providerError)) {
8288
+ const messagesPermissionDenied = protocol === "messages" && isMessagesPermissionRejection(attempt.status, providerError);
8289
+ if (protocol !== "cli" && (messagesPermissionDenied || isUpgradeRequiredError(attempt.status, attempt.errText, providerError))) {
8147
8290
  const wasMessages = protocol === "messages";
8291
+ if (messagesPermissionDenied) messagesPermissionRejection = providerError.code ?? providerError.type;
8148
8292
  protocol = "cli";
8149
8293
  if (!wasMessages) this.rememberProtocol(connection.apiBase, apiKey, "cli");
8150
8294
  requestOptions = withSurfaceOffload(options, this.surfaceOffload, REQUEST_IMAGE_BUDGETS[0], connection.offloadSeenImagesForCache === true ? options.model : void 0);
@@ -8164,6 +8308,7 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
8164
8308
  });
8165
8309
  if (next !== void 0 && !tried.has(next)) {
8166
8310
  apiKey = next;
8311
+ messagesPermissionRejection = void 0;
8167
8312
  const nextProtocol = this.resolveProtocol(connection, apiKey, options.model, modelEntry);
8168
8313
  if (nextProtocol !== protocol) {
8169
8314
  protocol = nextProtocol;
@@ -8183,7 +8328,7 @@ var CommandCodeAdapter = class CommandCodeAdapter extends LlmAdapter {
8183
8328
  ...wait > 0 && wait <= 9e5 ? { providerRetryAfterMs: wait } : {}
8184
8329
  });
8185
8330
  }
8186
- throw generateHttpError(attempt.status, attempt.errText, attempt.retryAfterMs, providerError);
8331
+ throw generateHttpError(attempt.status, attempt.errText, attempt.retryAfterMs, providerError, messagesPermissionRejection);
8187
8332
  }
8188
8333
  } catch (error) {
8189
8334
  trace.record("connect-error", {
@@ -8365,7 +8510,7 @@ function renderReport(report, locale, title) {
8365
8510
  }
8366
8511
  if (report.usage) {
8367
8512
  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)), "");
8513
+ 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
8514
  }
8370
8515
  if (report.credits) {
8371
8516
  const c = report.credits;
@@ -8486,6 +8631,7 @@ function parseUsageReport(value) {
8486
8631
  totalCredits: numberField(usage, "totalCredits", "usage.totalCredits"),
8487
8632
  periodBasis: stringField$1(usage, "periodBasis", "usage.periodBasis")
8488
8633
  };
8634
+ if (usage.periodStart !== void 0) report.usage.periodStart = numberField(usage, "periodStart", "usage.periodStart");
8489
8635
  }
8490
8636
  if (source.credits !== void 0) {
8491
8637
  const credits = record$1(source.credits, "credits");
@@ -8513,6 +8659,7 @@ function parseUsageReport(value) {
8513
8659
  monthlyCredits: monthly,
8514
8660
  currentPeriodEnd: numberField(plan, "currentPeriodEnd", "plan.currentPeriodEnd")
8515
8661
  };
8662
+ if (plan.currentPeriodStart !== void 0) report.plan.currentPeriodStart = numberField(plan, "currentPeriodStart", "plan.currentPeriodStart");
8516
8663
  }
8517
8664
  return report;
8518
8665
  }
@@ -10756,7 +10903,7 @@ function apply(ctx, config) {
10756
10903
  const resolved = await scopedPool(connection).resolveKey(model === void 0 ? {} : { model });
10757
10904
  if (resolved !== void 0) return assertUsableApiKey(resolved.key, "llm-commandcode", resolved.slot.ref ?? `${resolved.slot.label} (config.apiKey)`);
10758
10905
  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");
10906
+ 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
10907
  };
10761
10908
  const adapter = new CommandCodeAdapter({
10762
10909
  options,