@eddyskywalker/dsh-chatgpt-subscription 0.11.4 → 0.11.7

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +6 -0
  3. package/lib/client.js +96 -56
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +360 -120
  6. package/lib/types/client/antigravity/styles.d.ts.map +1 -1
  7. package/lib/types/client/claude/styles.d.ts.map +1 -1
  8. package/lib/types/client/common/plugin-style.d.ts +23 -0
  9. package/lib/types/client/common/plugin-style.d.ts.map +1 -0
  10. package/lib/types/client/common/styles.d.ts.map +1 -1
  11. package/lib/types/client/kimi-code/styles.d.ts.map +1 -1
  12. package/lib/types/client/mermaid/styles.d.ts.map +1 -1
  13. package/lib/types/client/minimax-code/styles.d.ts.map +1 -1
  14. package/lib/types/client/styles.d.ts +5 -0
  15. package/lib/types/client/styles.d.ts.map +1 -1
  16. package/lib/types/client/workbuddy/styles.d.ts.map +1 -1
  17. package/lib/types/host/antigravity/adapter.d.ts.map +1 -1
  18. package/lib/types/host/antigravity/mapper.d.ts.map +1 -1
  19. package/lib/types/host/claude/adapter.d.ts.map +1 -1
  20. package/lib/types/host/claude/mapper.d.ts.map +1 -1
  21. package/lib/types/host/command-code/adapter.d.ts.map +1 -1
  22. package/lib/types/host/command-code/mapper.d.ts.map +1 -1
  23. package/lib/types/host/common/context-budget-fetch.d.ts +7 -0
  24. package/lib/types/host/common/context-budget-fetch.d.ts.map +1 -0
  25. package/lib/types/host/common/context-overflow.d.ts +7 -0
  26. package/lib/types/host/common/context-overflow.d.ts.map +1 -0
  27. package/lib/types/host/kimi-code/adapter.d.ts.map +1 -1
  28. package/lib/types/host/kimi-code/mapper.d.ts.map +1 -1
  29. package/lib/types/host/minimax-code/adapter.d.ts.map +1 -1
  30. package/lib/types/host/minimax-code/mapper.d.ts.map +1 -1
  31. package/lib/types/host/ollama/client.d.ts +29 -0
  32. package/lib/types/host/ollama/client.d.ts.map +1 -1
  33. package/lib/types/host/ollama/routes.d.ts.map +1 -1
  34. package/lib/types/host/responses-client.d.ts.map +1 -1
  35. package/lib/types/host/workbuddy/adapter.d.ts.map +1 -1
  36. package/lib/types/host/workbuddy/mapper.d.ts.map +1 -1
  37. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -5629,6 +5629,32 @@ function withWritable(value) {
5629
5629
  };
5630
5630
  }
5631
5631
  //#endregion
5632
+ //#region src/host/common/context-overflow.ts
5633
+ /** Stable code understood by Harness overflow recovery, including older hosts. */
5634
+ const CONTEXT_OVERFLOW_CODE = "CONTEXT_WINDOW_EXCEEDED";
5635
+ /** Inspect provider error fields only; never classify request bodies or successful output. */
5636
+ function isContextOverflow(error) {
5637
+ if (typeof error === "string") {
5638
+ try {
5639
+ const parsed = JSON.parse(error);
5640
+ if (typeof parsed === "object" && parsed !== null) return isContextOverflow(parsed);
5641
+ } catch {}
5642
+ return /\bcontext[_ -](?:length|window)[_ -](?:exceeded|overflow|limit[_ -]exceeded)\b/i.test(error) || /\b(?:maximum|max)(?: allowed| supported)? context (?:length|window)\b/i.test(error) && /\b(?:exceed(?:s|ed)?|requested|resulted in|reduce|too (?:long|large))\b/i.test(error) || /\b(?:prompt|input|request) (?:is )?too (?:long|large) for (?:the |this )?(?:model(?:'s)? )?context\b/i.test(error) || /\b(?:prompt|input) (?:is )?too long:\s*[\d,]+ tokens?\s*>\s*[\d,]+\s*(?:tokens?\s*)?maximum\b/i.test(error) || /\brequest exceeded model token limit\b/i.test(error) || /\b(?:input|prompt|request|messages?)\b.{0,40}\bexceed(?:s|ed)?\b.{0,40}\b(?:model(?:'s)? )?context (?:length|window)\b/i.test(error);
5643
+ }
5644
+ if (typeof error !== "object" || error === null || Array.isArray(error)) return false;
5645
+ const fields = error;
5646
+ return [
5647
+ fields.code,
5648
+ fields.type,
5649
+ fields.message,
5650
+ fields.error
5651
+ ].some((value) => typeof value === "string" ? isContextOverflow(value) : value !== error && value !== void 0 && isContextOverflow(value));
5652
+ }
5653
+ /** Only request-rejection statuses can be reclassified as context overflow. */
5654
+ function isHttpContextOverflow(status, error) {
5655
+ return (status === 400 || status === 413 || status === 422) && isContextOverflow(error);
5656
+ }
5657
+ //#endregion
5632
5658
  //#region src/host/common/brand-compat.ts
5633
5659
  const toToolCallId = (id) => {
5634
5660
  const mod = LlmModule;
@@ -6578,7 +6604,7 @@ async function* parseResponsesStream(response, signal, hiddenSandboxControls = /
6578
6604
  const message = string(error?.message) ?? "Codex generation failed.";
6579
6605
  const rawCode = string(error?.code)?.toLowerCase();
6580
6606
  const isOverload = message.toLowerCase().includes("overload") || message.toLowerCase().includes("server error") || rawCode === "server_error" || rawCode === "service_unavailable" || rawCode === "internal_error";
6581
- throw new LlmError(message, message.toLowerCase().includes("rate limit") || rawCode === "rate_limit" ? "RATE_LIMIT" : isOverload ? "SERVER_ERROR" : string(error?.code)?.toUpperCase() ?? "PROVIDER_ERROR");
6607
+ throw new LlmError(message, message.toLowerCase().includes("rate limit") || rawCode === "rate_limit" ? "RATE_LIMIT" : isOverload ? "SERVER_ERROR" : isContextOverflow(error) ? CONTEXT_OVERFLOW_CODE : string(error?.code)?.toUpperCase() ?? "PROVIDER_ERROR");
6582
6608
  }
6583
6609
  };
6584
6610
  try {
@@ -6746,6 +6772,7 @@ async function responseError(response) {
6746
6772
  ...requestId ? { requestId: ProviderRequestId(requestId) } : {},
6747
6773
  ...response.status === 429 ? { providerRetryAfterMs: retryAfterMs$1(response.headers) } : {}
6748
6774
  };
6775
+ if (isHttpContextOverflow(response.status, detail)) return new LlmError(`Codex context window exceeded: ${detail}`, CONTEXT_OVERFLOW_CODE, options);
6749
6776
  if (response.status === 401) return new LlmError("ChatGPT sign-in has expired. Sign in again.", "AUTH", options);
6750
6777
  if (response.status === 404) return new LlmError(`Codex model or resource not found (${response.status})${detail ? `: ${detail}` : "."}`, "NOT_FOUND", options);
6751
6778
  if (response.status === 429) return new LlmError("Codex rate limit reached.", "RATE_LIMIT", options);
@@ -9142,6 +9169,8 @@ function processStreamLine$2(line, state) {
9142
9169
  const chunk = safeJsonParse$5(json);
9143
9170
  if (!isRecord$21(chunk)) return [];
9144
9171
  const responseData = isRecord$21(chunk.response) ? chunk.response : chunk;
9172
+ const error = isRecord$21(chunk.error) ? chunk.error : isRecord$21(responseData.error) ? responseData.error : void 0;
9173
+ if (error !== void 0) throw new LlmError$1(`Antigravity stream error: ${asString$14(error.message) ?? "unknown error"}`, isContextOverflow(error) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
9145
9174
  const candidates = Array.isArray(responseData.candidates) ? responseData.candidates : [];
9146
9175
  const candidate = isRecord$21(candidates[0]) ? candidates[0] : void 0;
9147
9176
  const content = isRecord$21(candidate?.content) ? candidate.content : void 0;
@@ -9663,7 +9692,7 @@ var AntigravityAdapter = class extends LlmAdapter {
9663
9692
  const status = response?.status ?? 500;
9664
9693
  const errText = await response?.text().catch(() => "");
9665
9694
  if (status === 429) throw new LlmError(`Antigravity 账号配额已耗尽或请求受限 (429 RESOURCE_EXHAUSTED)。请在插件设置页查看配额剩余百分比及重置倒计时。原始响应: ${errText || "No response"}`, "RATE_LIMIT", { status: 429 });
9666
- throw new LlmError(`Antigravity API error (${status}): ${errText || "No response"}`, "PROVIDER_ERROR", { status });
9695
+ throw new LlmError(`Antigravity API error (${status}): ${errText || "No response"}`, isHttpContextOverflow(status, errText) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR", { status });
9667
9696
  }
9668
9697
  if (!response.body) throw new LlmError("Antigravity returned empty response body", "PROVIDER_ERROR");
9669
9698
  const reader = response.body.getReader();
@@ -9950,6 +9979,103 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
9950
9979
  });
9951
9980
  }
9952
9981
  //#endregion
9982
+ //#region src/host/common/context-budget-fetch.ts
9983
+ /**
9984
+ * Recover a rejected input+output budget without dropping model-visible history.
9985
+ * Only explicit wire output controls and provider-confirmed counts qualify.
9986
+ * HTTP success/SSE, auth, quota, and byte-limit failures retain their old paths.
9987
+ */
9988
+ function fetchWithContextBudgetRecovery(fetchFn) {
9989
+ return async (input, init) => {
9990
+ const response = await fetchFn(input, init);
9991
+ if (response.status !== 400 && response.status !== 422) return response;
9992
+ if (typeof init?.body !== "string") return response;
9993
+ let body;
9994
+ try {
9995
+ const parsed = JSON.parse(init.body);
9996
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return response;
9997
+ body = parsed;
9998
+ } catch {
9999
+ return response;
10000
+ }
10001
+ const keys = [
10002
+ "max_tokens",
10003
+ "max_completion_tokens",
10004
+ "max_output_tokens"
10005
+ ].filter((key) => key in body);
10006
+ if (keys.length !== 1) return response;
10007
+ const key = keys[0];
10008
+ const requested = body[key];
10009
+ if (typeof requested !== "number" || !Number.isSafeInteger(requested) || requested <= 0) return response;
10010
+ const reduced = reducedOutputBudget(await boundedDiagnostic(response), requested);
10011
+ if (reduced === void 0) return response;
10012
+ const thinking = body.thinking;
10013
+ if (typeof thinking === "object" && thinking !== null) {
10014
+ const budget = thinking.budget_tokens;
10015
+ if (typeof budget === "number" && reduced <= budget) return response;
10016
+ }
10017
+ init.signal?.throwIfAborted();
10018
+ await response.body?.cancel().catch(() => void 0);
10019
+ return fetchFn(input, {
10020
+ ...init,
10021
+ body: JSON.stringify({
10022
+ ...body,
10023
+ [key]: reduced
10024
+ })
10025
+ });
10026
+ };
10027
+ }
10028
+ /** Read a bounded clone so a non-recoverable refusal remains readable by its owner. */
10029
+ async function boundedDiagnostic(response) {
10030
+ const reader = response.clone().body?.getReader();
10031
+ if (reader === void 0) return "";
10032
+ const decoder = new TextDecoder();
10033
+ let size = 0;
10034
+ let text = "";
10035
+ try {
10036
+ for (;;) {
10037
+ const { done, value } = await reader.read();
10038
+ if (done) return text + decoder.decode();
10039
+ size += value.byteLength;
10040
+ if (size > 16384) return "";
10041
+ text += decoder.decode(value, { stream: true });
10042
+ }
10043
+ } catch {
10044
+ return "";
10045
+ } finally {
10046
+ reader.cancel().catch(() => void 0);
10047
+ }
10048
+ }
10049
+ function diagnosticMessage(value, depth = 0) {
10050
+ if (depth > 4) return void 0;
10051
+ if (typeof value === "string") return value;
10052
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
10053
+ const fields = value;
10054
+ return diagnosticMessage(fields.error, depth + 1) ?? diagnosticMessage(fields.message, depth + 1);
10055
+ }
10056
+ /** Never infer capacity from a model name or from an echoed request. */
10057
+ function reducedOutputBudget(detail, requested) {
10058
+ let message;
10059
+ try {
10060
+ message = diagnosticMessage(JSON.parse(detail));
10061
+ } catch {
10062
+ message = detail;
10063
+ }
10064
+ if (message === void 0) return void 0;
10065
+ const match = /maximum context length is ([\d,]+) tokens[\s\S]*?requested ([\d,]+) tokens[\s\S]*?\(([\d,]+) in the messages, ([\d,]+) in the completion\)/i.exec(message);
10066
+ if (match === null) return void 0;
10067
+ const [window, total, prompt, completion] = match.slice(1).map((value) => Number(value.replaceAll(",", "")));
10068
+ if (![
10069
+ window,
10070
+ total,
10071
+ prompt,
10072
+ completion
10073
+ ].every((value) => Number.isSafeInteger(value) && value > 0)) return void 0;
10074
+ if (completion !== requested || prompt + completion !== total || total <= window) return void 0;
10075
+ const available = window - prompt - 1024;
10076
+ return available >= 1024 && available < requested ? available : void 0;
10077
+ }
10078
+ //#endregion
9953
10079
  //#region src/host/command-code/model-catalog.ts
9954
10080
  /** Every model the Command Code registry describes, in registry order. */
9955
10081
  const COMMAND_CODE_MODELS = [
@@ -13126,6 +13252,7 @@ function processOpenAIStreamLine$1(line, state) {
13126
13252
  const chunk = safeJsonParse$4(payload);
13127
13253
  if (!isRecord$19(chunk)) return [];
13128
13254
  const out = [];
13255
+ if (isRecord$19(chunk.error)) throw new LlmError$1(`Command Code stream error: ${asString$12(chunk.error.message) ?? "unknown error"}`, isContextOverflow(chunk.error) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
13129
13256
  const usage = isRecord$19(chunk.usage) ? chunk.usage : void 0;
13130
13257
  if (usage) {
13131
13258
  state.sawUsage = true;
@@ -13395,7 +13522,10 @@ function processAnthropicStreamLine$1(line, state) {
13395
13522
  state.done = true;
13396
13523
  return closeStream$4(state);
13397
13524
  }
13398
- if (type === "error") throw new LlmError$1(`Command Code stream error: ${asString$12((isRecord$19(event.error) ? event.error : {}).message) ?? "unknown error"}`, "PROVIDER_ERROR");
13525
+ if (type === "error") {
13526
+ const error = isRecord$19(event.error) ? event.error : {};
13527
+ throw new LlmError$1(`Command Code stream error: ${asString$12(error.message) ?? "unknown error"}`, isContextOverflow(error) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
13528
+ }
13399
13529
  return out;
13400
13530
  }
13401
13531
  function tokenUsage$4(state) {
@@ -13506,7 +13636,7 @@ function processResponsesStreamLine(line, state) {
13506
13636
  state.done = true;
13507
13637
  const errorObj = isRecord$19(parsed.error) ? parsed.error : isRecord$19(parsed.response) && isRecord$19(parsed.response.error) ? parsed.response.error : null;
13508
13638
  const detail = (typeof parsed.message === "string" ? parsed.message : "") || (errorObj && typeof errorObj.message === "string" ? errorObj.message : "") || (isRecord$19(parsed.response) && typeof parsed.response.error === "string" ? parsed.response.error : typeof parsed.error === "string" ? parsed.error : "");
13509
- throw new LlmError$1(detail === "" ? "Command Code responses stream failed" : `Command Code responses stream failed: ${detail}`, "PROVIDER_ERROR");
13639
+ throw new LlmError$1(detail === "" ? "Command Code responses stream failed" : `Command Code responses stream failed: ${detail}`, isContextOverflow(errorObj ?? detail) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
13510
13640
  }
13511
13641
  return [];
13512
13642
  }
@@ -14059,7 +14189,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
14059
14189
  "anthropic-version": "2023-06-01"
14060
14190
  } : baseHeaders;
14061
14191
  try {
14062
- response = await fetchFn(endpoint, {
14192
+ response = await fetchWithContextBudgetRecovery(fetchFn)(endpoint, {
14063
14193
  method: "POST",
14064
14194
  headers,
14065
14195
  body,
@@ -14106,7 +14236,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
14106
14236
  });
14107
14237
  }
14108
14238
  if (status >= 500) throw new LlmError(`${PROVIDER_NAME$5} upstream server error (${status}): ${detail || "No response"}`, "SERVER", { status });
14109
- throw new LlmError(`${PROVIDER_NAME$5} API error (${status}): ${detail || "No response"}`, "PROVIDER_ERROR", { status });
14239
+ throw new LlmError(`${PROVIDER_NAME$5} API error (${status}): ${detail || "No response"}`, isHttpContextOverflow(status, detail) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR", { status });
14110
14240
  }
14111
14241
  if (response.body === null) throw new LlmError("Command Code returned an empty response body", "PROVIDER_ERROR");
14112
14242
  const reader = response.body.getReader();
@@ -15860,16 +15990,57 @@ function accumulateToolCall(raw, pending, openaiShape) {
15860
15990
  * making the line unusable.
15861
15991
  */
15862
15992
  async function loadCatalog$1(fetchFn, credentials) {
15863
- const response = await fetchFn(`${CLOUD_BASE_URL}${NATIVE_TAGS_PATH}`, {
15864
- method: "GET",
15865
- headers: headersFor(credentials, "application/json"),
15866
- signal: AbortSignal.timeout(CATALOG_TIMEOUT_MS)
15867
- });
15868
- if (!response.ok) return [];
15869
- const data = await response.json();
15870
- if (typeof data !== "object" || data === null) return [];
15993
+ return (await fetchCatalog(fetchFn, credentials)).models;
15994
+ }
15995
+ /**
15996
+ * The same read as {@link loadCatalog}, keeping the reason it failed.
15997
+ *
15998
+ * Never throws, for the reason its wrapper has always given. What it adds is that
15999
+ * "failed" stops being one opaque state, so the settings card can name the
16000
+ * cause instead of asking the user to guess.
16001
+ */
16002
+ async function fetchCatalog(fetchFn, credentials) {
16003
+ let response;
16004
+ try {
16005
+ response = await fetchFn(`${CLOUD_BASE_URL}${NATIVE_TAGS_PATH}`, {
16006
+ method: "GET",
16007
+ headers: headersFor(credentials, "application/json"),
16008
+ signal: AbortSignal.timeout(CATALOG_TIMEOUT_MS)
16009
+ });
16010
+ } catch {
16011
+ return {
16012
+ models: [],
16013
+ failure: "unreachable"
16014
+ };
16015
+ }
16016
+ if (!response.ok) return response.status === 401 || response.status === 403 ? {
16017
+ models: [],
16018
+ failure: "auth",
16019
+ status: response.status
16020
+ } : {
16021
+ models: [],
16022
+ failure: "upstream",
16023
+ status: response.status
16024
+ };
16025
+ let data;
16026
+ try {
16027
+ data = await response.json();
16028
+ } catch {
16029
+ return {
16030
+ models: [],
16031
+ failure: "malformed"
16032
+ };
16033
+ }
16034
+ if (typeof data !== "object" || data === null) return {
16035
+ models: [],
16036
+ failure: "malformed"
16037
+ };
15871
16038
  const record = data;
15872
- const models = Array.isArray(record.models) ? record.models : [];
16039
+ if (!Array.isArray(record.models)) return {
16040
+ models: [],
16041
+ failure: "malformed"
16042
+ };
16043
+ const models = record.models;
15873
16044
  const result = [];
15874
16045
  for (const entry of models) {
15875
16046
  if (typeof entry !== "object" || entry === null) continue;
@@ -15884,7 +16055,7 @@ async function loadCatalog$1(fetchFn, credentials) {
15884
16055
  fetchedAt: Date.now()
15885
16056
  });
15886
16057
  }
15887
- return result;
16058
+ return { models: result };
15888
16059
  }
15889
16060
  //#endregion
15890
16061
  //#region src/host/ollama/mapper.ts
@@ -16482,6 +16653,8 @@ function textOf$4(content) {
16482
16653
  //#endregion
16483
16654
  //#region src/host/ollama/routes.ts
16484
16655
  const ROUTE_PREFIX$3 = "/ollama/api";
16656
+ /** Where a user creates the keys this line stores. */
16657
+ const OLLAMA_KEYS_URL = "https://ollama.com/settings/keys";
16485
16658
  function sendJson$5(response, status, body) {
16486
16659
  const payload = JSON.stringify(body);
16487
16660
  response.writeHead(status, { "content-type": "application/json; charset=utf-8" });
@@ -16507,6 +16680,22 @@ async function readRequestJson$5(request) {
16507
16680
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("Request body must be a JSON object.");
16508
16681
  return parsed;
16509
16682
  }
16683
+ /**
16684
+ * The sentence the card shows for a catalog read that produced nothing.
16685
+ *
16686
+ * Every one of these used to collapse into "Could not read the model list from
16687
+ * Ollama", which told a user nothing they could act on — the complaint in issue
16688
+ * #36. The four causes have four different remedies, so they are named apart.
16689
+ */
16690
+ function catalogFailureMessage(result) {
16691
+ switch (result.failure) {
16692
+ case "auth": return `Ollama rejected the stored API key (HTTP ${result.status ?? 401}). Create or re-paste a key at ${OLLAMA_KEYS_URL}.`;
16693
+ case "upstream": return `Ollama answered HTTP ${result.status ?? "?"} for ${CLOUD_BASE_URL}/api/tags.`;
16694
+ case "unreachable": return "Could not reach Ollama Cloud. Check the network or proxy, then sync again.";
16695
+ case "malformed": return "Ollama answered with something that is not a /api/tags document (a proxy or filter may be rewriting the reply).";
16696
+ default: return "Could not read the model list from Ollama.";
16697
+ }
16698
+ }
16510
16699
  /** Whether one account can serve a request right now. */
16511
16700
  function isUsable(account, now) {
16512
16701
  if (account.authStatus !== void 0 && account.authStatus !== "ok") return false;
@@ -16557,123 +16746,144 @@ function registerOllamaRoutes(ctx, options) {
16557
16746
  handler: async (request, response) => {
16558
16747
  const path = new URL(request.url || "/", "http://dsh.local").pathname.replace(/^\/ollama\/api\/?/, "");
16559
16748
  const method = request.method ?? "GET";
16560
- if (path === "" || path === "status") return sendJson$5(response, 200, {
16561
- ok: true,
16562
- value: await getOllamaWebStatus(options)
16563
- });
16564
- if (path === "accounts") {
16565
- if (method === "GET") return sendJson$5(response, 200, {
16749
+ try {
16750
+ if (path === "" || path === "status") return sendJson$5(response, 200, {
16566
16751
  ok: true,
16567
16752
  value: await getOllamaWebStatus(options)
16568
16753
  });
16569
- if (method !== "POST") return sendMethodNotAllowed$4(response);
16570
- if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
16571
- ok: false,
16572
- error: "Cross-origin request rejected."
16573
- });
16574
- let body;
16575
- try {
16576
- body = await readRequestJson$5(request);
16577
- } catch (error) {
16578
- return sendJson$5(response, 400, {
16754
+ if (path === "accounts") {
16755
+ if (method === "GET") return sendJson$5(response, 200, {
16756
+ ok: true,
16757
+ value: await getOllamaWebStatus(options)
16758
+ });
16759
+ if (method !== "POST") return sendMethodNotAllowed$4(response);
16760
+ if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
16579
16761
  ok: false,
16580
- error: error instanceof Error ? error.message : String(error)
16762
+ error: "Cross-origin request rejected."
16581
16763
  });
16582
- }
16583
- const action = typeof body.action === "string" ? body.action : "";
16584
- const accountId = typeof body.accountId === "string" ? body.accountId : void 0;
16585
- try {
16586
- if (action === "add") {
16587
- const apiKey = typeof body.apiKey === "string" ? body.apiKey.trim() : "";
16588
- if (apiKey === "") return sendJson$5(response, 400, {
16764
+ let body;
16765
+ try {
16766
+ body = await readRequestJson$5(request);
16767
+ } catch (error) {
16768
+ return sendJson$5(response, 400, {
16589
16769
  ok: false,
16590
- error: "API key is required."
16770
+ error: error instanceof Error ? error.message : String(error)
16591
16771
  });
16592
- const alias = typeof body.alias === "string" && body.alias.trim() !== "" ? body.alias.trim() : void 0;
16593
- const credentials = {
16594
- apiKey,
16595
- addedAt: Date.now()
16596
- };
16597
- if (alias !== void 0) credentials.alias = alias;
16772
+ }
16773
+ const action = typeof body.action === "string" ? body.action : "";
16774
+ const accountId = typeof body.accountId === "string" ? body.accountId : void 0;
16775
+ try {
16776
+ if (action === "add") {
16777
+ const apiKey = typeof body.apiKey === "string" ? body.apiKey.trim() : "";
16778
+ if (apiKey === "") return sendJson$5(response, 400, {
16779
+ ok: false,
16780
+ error: "API key is required."
16781
+ });
16782
+ const alias = typeof body.alias === "string" && body.alias.trim() !== "" ? body.alias.trim() : void 0;
16783
+ const credentials = {
16784
+ apiKey,
16785
+ addedAt: Date.now()
16786
+ };
16787
+ if (alias !== void 0) credentials.alias = alias;
16788
+ return sendJson$5(response, 200, {
16789
+ ok: true,
16790
+ value: { id: (await accountPool.addAccount(credentials, alias)).id }
16791
+ });
16792
+ }
16793
+ if (accountId === void 0) return sendJson$5(response, 400, {
16794
+ ok: false,
16795
+ error: "accountId is required."
16796
+ });
16797
+ if (action === "set-primary") await accountPool.setPrimary(accountId);
16798
+ else if (action === "set-alias" && typeof body.alias === "string") await accountPool.setAlias(accountId, body.alias);
16799
+ else if (action === "delete") await accountPool.deleteAccount(accountId);
16800
+ else if (action === "clear-cooldown") await accountPool.clearCooldown(accountId);
16801
+ else if (action === "strategy" && (body.strategy === "sequential" || body.strategy === "round-robin" || body.strategy === "sticky")) await accountPool.setStrategy(body.strategy);
16802
+ else return sendJson$5(response, 400, {
16803
+ ok: false,
16804
+ error: "Unsupported account action."
16805
+ });
16806
+ } catch (error) {
16807
+ return sendJson$5(response, 400, {
16808
+ ok: false,
16809
+ error: error instanceof Error ? error.message : String(error)
16810
+ });
16811
+ }
16812
+ return sendJson$5(response, 200, {
16813
+ ok: true,
16814
+ value: await getOllamaWebStatus(options)
16815
+ });
16816
+ }
16817
+ if (path === "models") {
16818
+ if (method === "GET") {
16819
+ const settings = await modelSettings.read();
16598
16820
  return sendJson$5(response, 200, {
16599
16821
  ok: true,
16600
- value: { id: (await accountPool.addAccount(credentials, alias)).id }
16822
+ value: {
16823
+ models: settings.catalogModels,
16824
+ enabled: settings.enabled,
16825
+ enabledModelIds: settings.enabledModelIds
16826
+ }
16601
16827
  });
16602
16828
  }
16603
- if (accountId === void 0) return sendJson$5(response, 400, {
16829
+ if (method !== "POST") return sendMethodNotAllowed$4(response);
16830
+ if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
16604
16831
  ok: false,
16605
- error: "accountId is required."
16832
+ error: "Cross-origin request rejected."
16606
16833
  });
16607
- if (action === "set-primary") await accountPool.setPrimary(accountId);
16608
- else if (action === "set-alias" && typeof body.alias === "string") await accountPool.setAlias(accountId, body.alias);
16609
- else if (action === "delete") await accountPool.deleteAccount(accountId);
16610
- else if (action === "clear-cooldown") await accountPool.clearCooldown(accountId);
16611
- else if (action === "strategy" && (body.strategy === "sequential" || body.strategy === "round-robin" || body.strategy === "sticky")) await accountPool.setStrategy(body.strategy);
16612
- else return sendJson$5(response, 400, {
16834
+ const body = await readRequestJson$5(request).catch(() => ({}));
16835
+ if (typeof body.enabled === "boolean") await modelSettings.update({ enabled: body.enabled });
16836
+ if (Array.isArray(body.enabledModelIds)) await modelSettings.update({ enabledModelIds: body.enabledModelIds.filter((id) => typeof id === "string") });
16837
+ return sendJson$5(response, 200, {
16838
+ ok: true,
16839
+ value: await modelSettings.read()
16840
+ });
16841
+ }
16842
+ if (path === "catalog/refresh") {
16843
+ if (method !== "POST") return sendMethodNotAllowed$4(response);
16844
+ if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
16613
16845
  ok: false,
16614
- error: "Unsupported account action."
16846
+ error: "Cross-origin request rejected."
16615
16847
  });
16616
- } catch (error) {
16617
- return sendJson$5(response, 400, {
16848
+ let credentials = null;
16849
+ let poolBusy = false;
16850
+ try {
16851
+ credentials = (await accountPool.getEffectiveCredential(void 0, fetchFn)).credentials;
16852
+ } catch {
16853
+ poolBusy = await accountPool.read().then((data) => data.accounts.length > 0 && data.accounts.every((account) => isUsable(account, Date.now()) === false)).catch(() => false);
16854
+ credentials = null;
16855
+ }
16856
+ if (credentials === null) return sendJson$5(response, 400, {
16618
16857
  ok: false,
16619
- error: error instanceof Error ? error.message : String(error)
16858
+ error: poolBusy ? "Every stored Ollama key is cooling down right now. Wait for the cooldown, then sync again." : "Add an API key before syncing models."
16620
16859
  });
16621
- }
16622
- return sendJson$5(response, 200, {
16623
- ok: true,
16624
- value: await getOllamaWebStatus(options)
16625
- });
16626
- }
16627
- if (path === "models") {
16628
- if (method === "GET") {
16629
- const settings = await modelSettings.read();
16860
+ const result = await fetchCatalog(fetchFn, credentials);
16861
+ if (result.models.length === 0) return sendJson$5(response, 502, {
16862
+ ok: false,
16863
+ error: catalogFailureMessage(result)
16864
+ });
16865
+ try {
16866
+ await modelSettings.storeCatalog(result.models);
16867
+ } catch (error) {
16868
+ const detail = error instanceof Error ? error.message : String(error);
16869
+ return sendJson$5(response, 500, {
16870
+ ok: false,
16871
+ error: `Read ${result.models.length} models from Ollama but could not save them locally: ${detail}`
16872
+ });
16873
+ }
16630
16874
  return sendJson$5(response, 200, {
16631
16875
  ok: true,
16632
- value: {
16633
- models: settings.catalogModels,
16634
- enabled: settings.enabled,
16635
- enabledModelIds: settings.enabledModelIds
16636
- }
16876
+ value: { models: result.models }
16637
16877
  });
16638
16878
  }
16639
- if (method !== "POST") return sendMethodNotAllowed$4(response);
16640
- if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
16641
- ok: false,
16642
- error: "Cross-origin request rejected."
16643
- });
16644
- const body = await readRequestJson$5(request).catch(() => ({}));
16645
- if (typeof body.enabled === "boolean") await modelSettings.update({ enabled: body.enabled });
16646
- if (Array.isArray(body.enabledModelIds)) await modelSettings.update({ enabledModelIds: body.enabledModelIds.filter((id) => typeof id === "string") });
16647
- return sendJson$5(response, 200, {
16648
- ok: true,
16649
- value: await modelSettings.read()
16650
- });
16651
- }
16652
- if (path === "catalog/refresh") {
16653
- if (method !== "POST") return sendMethodNotAllowed$4(response);
16654
- if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
16879
+ return sendJson$5(response, 404, {
16655
16880
  ok: false,
16656
- error: "Cross-origin request rejected."
16657
- });
16658
- let credentials = null;
16659
- try {
16660
- credentials = (await accountPool.getEffectiveCredential(void 0, fetchFn)).credentials;
16661
- } catch {
16662
- credentials = null;
16663
- }
16664
- if (credentials === null) return sendJson$5(response, 400, {
16665
- ok: false,
16666
- error: "Add an API key before syncing models."
16881
+ error: "not-found"
16667
16882
  });
16668
- const models = await loadCatalog$1(fetchFn, credentials).catch(() => []);
16669
- if (models.length === 0) return sendJson$5(response, 502, {
16883
+ } catch (error) {
16884
+ return sendJson$5(response, 500, {
16670
16885
  ok: false,
16671
- error: "Could not read the model list from Ollama."
16672
- });
16673
- await modelSettings.storeCatalog(models);
16674
- return sendJson$5(response, 200, {
16675
- ok: true,
16676
- value: { models }
16886
+ error: error instanceof Error ? error.message : String(error)
16677
16887
  });
16678
16888
  }
16679
16889
  }
@@ -19810,7 +20020,20 @@ function estimatedInputTokens(options) {
19810
20020
  characters += JSON.stringify(tool.parameters).length;
19811
20021
  } catch {}
19812
20022
  }
19813
- for (const message of options.messages) characters += textOf$3(message.content).length;
20023
+ const contentCharacters = (content) => {
20024
+ if (!Array.isArray(content)) return typeof content === "string" ? content.length : 0;
20025
+ return content.reduce((total, block) => {
20026
+ if (!isRecord$16(block)) return total;
20027
+ if ((block.type === "text" || block.type === "reasoning") && typeof block.text === "string") return total + block.text.length;
20028
+ if (block.type === "tool-result") return total + contentCharacters(block.content);
20029
+ if (block.type === "tool-call") {
20030
+ const args = toolCallArguments$3(block.arguments);
20031
+ return total + String(block.name ?? "").length + args.length;
20032
+ }
20033
+ return total;
20034
+ }, 0);
20035
+ };
20036
+ for (const message of options.messages) characters += contentCharacters(message.content);
19814
20037
  if (characters === 0) return void 0;
19815
20038
  return Math.ceil(characters / 4);
19816
20039
  }
@@ -20244,7 +20467,7 @@ function processOpenAIStreamLine(line, state) {
20244
20467
  if (!isRecord$16(chunk)) return [];
20245
20468
  const out = [];
20246
20469
  const errorPayload = isRecord$16(chunk.error) ? chunk.error : void 0;
20247
- if (errorPayload !== void 0) throw new LlmError$1(`Kimi Code stream error: ${asString$9(errorPayload.message) ?? "unknown error"}`, "PROVIDER_ERROR");
20470
+ if (errorPayload !== void 0) throw new LlmError$1(`Kimi Code stream error: ${asString$9(errorPayload.message) ?? "unknown error"}`, isContextOverflow(errorPayload) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
20248
20471
  const usage = isRecord$16(chunk.usage) ? chunk.usage : void 0;
20249
20472
  if (usage) {
20250
20473
  state.sawUsage = true;
@@ -20511,7 +20734,10 @@ function processAnthropicStreamLine(line, state) {
20511
20734
  state.done = true;
20512
20735
  return closeStream$2(state);
20513
20736
  }
20514
- if (type === "error") throw new LlmError$1(`Kimi Code stream error: ${asString$9((isRecord$16(event.error) ? event.error : {}).message) ?? "unknown error"}`, "PROVIDER_ERROR");
20737
+ if (type === "error") {
20738
+ const error = isRecord$16(event.error) ? event.error : {};
20739
+ throw new LlmError$1(`Kimi Code stream error: ${asString$9(error.message) ?? "unknown error"}`, isContextOverflow(error) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
20740
+ }
20515
20741
  return out;
20516
20742
  }
20517
20743
  /**
@@ -20744,6 +20970,11 @@ function summarizeFailureBody$1(raw) {
20744
20970
  */
20745
20971
  function classifyKimiFailure(status, bodyText) {
20746
20972
  const detail = summarizeFailureBody$1(bodyText);
20973
+ if (isHttpContextOverflow(status, bodyText)) return {
20974
+ code: CONTEXT_OVERFLOW_CODE,
20975
+ retryable: false,
20976
+ message: `${PROVIDER_NAME$3} context window exceeded: ${detail}`
20977
+ };
20747
20978
  if (status === 402) return {
20748
20979
  code: "SERVER",
20749
20980
  retryable: true,
@@ -20961,7 +21192,7 @@ var KimiCodeAdapter = class extends LlmAdapter {
20961
21192
  const endpoint = wire === "anthropic" ? `${base}/v1/messages?beta=true` : `${base}/v1/chat/completions`;
20962
21193
  const headers = await modelRequestHeaders$1(apiCredentials.accessToken, wire);
20963
21194
  try {
20964
- response = await fetchFn(endpoint, {
21195
+ response = await fetchWithContextBudgetRecovery(fetchFn)(endpoint, {
20965
21196
  method: "POST",
20966
21197
  headers,
20967
21198
  body,
@@ -23379,7 +23610,10 @@ function processMinimaxStreamLine(line, state) {
23379
23610
  state.done = true;
23380
23611
  return closeMinimaxStream(state);
23381
23612
  }
23382
- if (type === "error") throw new LlmError$1("MiniMax Code(编程订阅) stream error: " + (asString$8((isRecord$15(event.error) ? event.error : {}).message) ?? "unknown error"), "PROVIDER_ERROR");
23613
+ if (type === "error") {
23614
+ const error = isRecord$15(event.error) ? event.error : {};
23615
+ throw new LlmError$1("MiniMax Code(编程订阅) stream error: " + (asString$8(error.message) ?? "unknown error"), isContextOverflow(error) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
23616
+ }
23383
23617
  return out;
23384
23618
  }
23385
23619
  //#endregion
@@ -25407,6 +25641,11 @@ function matchesAny(text, patterns) {
25407
25641
  */
25408
25642
  function classifyMinimaxFailure(status, bodyText) {
25409
25643
  const detail = summarizeFailureBody(bodyText);
25644
+ if (isHttpContextOverflow(status, bodyText)) return {
25645
+ code: CONTEXT_OVERFLOW_CODE,
25646
+ retryable: false,
25647
+ message: `${PROVIDER_NAME$2} context window exceeded: ${detail}`
25648
+ };
25410
25649
  if (status === 401 || status === 403) return {
25411
25650
  code: "INVALID_CREDENTIAL",
25412
25651
  retryable: false,
@@ -25670,7 +25909,7 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
25670
25909
  let response;
25671
25910
  for (let attempt = 0;; attempt += 1) {
25672
25911
  try {
25673
- response = await fetchFn(messagesUrl(credentials.region), {
25912
+ response = await fetchWithContextBudgetRecovery(fetchFn)(messagesUrl(credentials.region), {
25674
25913
  method: "POST",
25675
25914
  headers: modelRequestHeaders(credentials.accessToken),
25676
25915
  body,
@@ -30052,7 +30291,7 @@ function processStreamLine$1(line, state) {
30052
30291
  if (!isRecord$8(chunk)) return [];
30053
30292
  const out = [];
30054
30293
  const errorPayload = isRecord$8(chunk.error) ? chunk.error : void 0;
30055
- if (errorPayload !== void 0) throw new LlmError$1(`WorkBuddy stream error: ${asString$4(errorPayload.message) ?? "unknown error"}`, "PROVIDER_ERROR");
30294
+ if (errorPayload !== void 0) throw new LlmError$1(`WorkBuddy stream error: ${asString$4(errorPayload.message) ?? "unknown error"}`, isContextOverflow(errorPayload) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
30056
30295
  const usage = isRecord$8(chunk.usage) ? chunk.usage : void 0;
30057
30296
  if (usage) {
30058
30297
  state.sawUsage = true;
@@ -30410,7 +30649,7 @@ var WorkBuddyAdapter = class extends LlmAdapter {
30410
30649
  credentials = effective.credentials;
30411
30650
  }
30412
30651
  try {
30413
- response = await fetchFn(`${credentials.backend}${CHAT_PATH}`, {
30652
+ response = await fetchWithContextBudgetRecovery(fetchFn)(`${credentials.backend}${CHAT_PATH}`, {
30414
30653
  method: "POST",
30415
30654
  headers: workBuddyHeaders(credentials, { accept: "text/event-stream" }),
30416
30655
  body,
@@ -30485,7 +30724,7 @@ function classifyFailure$1(status, detail, headers) {
30485
30724
  if (code === 11135 || code === 11133) return new LlmError(`${PROVIDER_NAME$1} rejected the request parameters (${code}). An image may be unsupported by this model or unreadable.${detail ? ` ${detail}` : ""}`, "PROVIDER_ERROR", { status });
30486
30725
  if (code === 11128) return new LlmError(`${PROVIDER_NAME$1} rejected the message history: the conversation must open with a system prompt.${detail ? ` ${detail}` : ""}`, "PROVIDER_ERROR", { status });
30487
30726
  if (status >= 500 || code === 11134) return new LlmError(`${PROVIDER_NAME$1} upstream server error (${status}): ${detail || "No response"}`, "SERVER", { status });
30488
- return new LlmError(`${PROVIDER_NAME$1} API error (${status}): ${detail || "No response"}`, "PROVIDER_ERROR", { status });
30727
+ return new LlmError(`${PROVIDER_NAME$1} API error (${status}): ${detail || "No response"}`, isHttpContextOverflow(status, detail) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR", { status });
30489
30728
  }
30490
30729
  /** Read the subscription's own numeric error code out of a response body. */
30491
30730
  function readErrorCode(detail) {
@@ -35029,7 +35268,7 @@ function processStreamLine(line, state) {
35029
35268
  const error = isRecord$5(event.error) ? event.error : {};
35030
35269
  const message = asString$2(error.message) ?? "unknown error";
35031
35270
  const kind = asString$2(error.type);
35032
- throw new LlmError$1("Claude stream error" + (kind === void 0 ? "" : " (" + kind + ")") + ": " + message, "PROVIDER_ERROR");
35271
+ throw new LlmError$1("Claude stream error" + (kind === void 0 ? "" : " (" + kind + ")") + ": " + message, isContextOverflow(error) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
35033
35272
  }
35034
35273
  return out;
35035
35274
  }
@@ -36558,7 +36797,7 @@ var ClaudeAdapter = class ClaudeAdapter extends LlmAdapter {
36558
36797
  cacheTtl
36559
36798
  });
36560
36799
  try {
36561
- return await fetchFn("https://api.anthropic.com/v1/messages?beta=true", {
36800
+ return await fetchWithContextBudgetRecovery(fetchFn)("https://api.anthropic.com/v1/messages?beta=true", {
36562
36801
  method: "POST",
36563
36802
  headers: buildClaudeHeaders(credentials.accessToken, {
36564
36803
  model: requestOptions.model,
@@ -36754,6 +36993,7 @@ function toLlmError(failure) {
36754
36993
  };
36755
36994
  const detail = failure.message === "" ? "" : " " + failure.message;
36756
36995
  if (failure.clientVersionTooOld) return new LlmError("Claude(订阅) refused this request because the client version it was told is below the server's floor (claude_code_version_too_old). Raise it with the DSH_CLAUDE_CLI_VERSION environment variable (or a setClaudeCliVersion pin), then retry — the stored sign-in is still valid." + detail, "PROVIDER_ERROR", options);
36996
+ if (failure.kind === "request" && isHttpContextOverflow(failure.status, failure.message)) return new LlmError("Claude(订阅) context window exceeded." + detail, CONTEXT_OVERFLOW_CODE, options);
36757
36997
  switch (failure.kind) {
36758
36998
  case "credential": return new LlmError("Claude(订阅) rejected the stored credential. Sign in again from Settings > Claude." + detail, "INVALID_CREDENTIAL", options);
36759
36999
  case "rate_limit_account":