@eddyskywalker/dsh-chatgpt-subscription 0.11.3 → 0.11.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import * as LlmModule from "@deepseek-ai/dsh-llm";
4
- import { HarnessError, LlmAdapter, LlmError, LlmError as LlmError$1, ProviderRequestId, ReasoningEffortId, attributionHeaders, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
4
+ import { HarnessError, LlmAdapter, LlmError, LlmError as LlmError$1, ProviderRequestId, ReasoningEffortId, attributionHeaders, errorChain, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
5
5
  import fs, { chmod, lstat, mkdir, open, readFile, rename, stat, unlink } from "node:fs/promises";
6
6
  import path, { basename, dirname, join, relative, resolve } from "node:path";
7
7
  import os, { homedir } from "node:os";
@@ -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;
@@ -6181,6 +6207,8 @@ var ResponsesClient = class {
6181
6207
  }
6182
6208
  try {
6183
6209
  yield* wrapStreamWithWatchdog((watchdogSignal) => parseResponsesStream(response, watchdogSignal, hiddenSandboxControls), options.signal, 3e5, "LLM_STREAM_IDLE_TIMEOUT", "Codex");
6210
+ } catch (error) {
6211
+ throw streamFailure(error, options.signal);
6184
6212
  } finally {
6185
6213
  this.turnStates.delete(turnKey);
6186
6214
  release?.();
@@ -6335,6 +6363,35 @@ var ResponsesClient = class {
6335
6363
  }
6336
6364
  };
6337
6365
  /**
6366
+ * The failure a stream that died mid-body is reported as.
6367
+ *
6368
+ * Everything above the response body is already typed — the request path raises
6369
+ * NETWORK, `responseError` raises a status verdict, and the parse raises a
6370
+ * protocol or provider one. The body itself was the one gap: a connection that
6371
+ * ends before the response does arrives as undici's `TypeError: terminated`,
6372
+ * which `wrapStreamWithWatchdog` rethrows untouched. That value carries no code
6373
+ * of its own, so the harness normalizes it to UNKNOWN, and UNKNOWN is not in this
6374
+ * route's `retryableCodes` — one severed connection ended the turn with no
6375
+ * second attempt at all. The real reason (ECONNRESET, a socket closed by the far
6376
+ * side, a body timeout) sat on `.cause`, which the harness drops before the
6377
+ * failure is persisted, so the report could not even say what had happened.
6378
+ *
6379
+ * So the transport is named here and its cause chain is kept in the message,
6380
+ * and only the failures something already typed are left alone: an `LlmError` is
6381
+ * this route's own verdict, and a caller abort is a cancellation, not a fault.
6382
+ *
6383
+ * The code is TRANSPORT even once chunks have been yielded. DSH settles a failed
6384
+ * attempt's partial output as a discarded `assistant/attempt` and starts a fresh
6385
+ * attempt on retry, and a tool call is only executed after its stream finishes,
6386
+ * so retrying can neither repeat committed output nor re-run a tool.
6387
+ */
6388
+ function streamFailure(error, signal) {
6389
+ if (error instanceof LlmError) return error;
6390
+ if (signal?.aborted) return new LlmError("Codex request aborted", "ABORTED", { cause: error });
6391
+ const chain = errorChain(error).trim();
6392
+ return new LlmError(`Codex stream failed: ${chain === "" ? "the response stream ended unexpectedly" : chain.slice(0, 500)}`, "TRANSPORT", { cause: error });
6393
+ }
6394
+ /**
6338
6395
  * Prompt cache key for one conversation.
6339
6396
  *
6340
6397
  * The backend keys its prefix cache on this string, so it must be stable for a
@@ -6547,7 +6604,7 @@ async function* parseResponsesStream(response, signal, hiddenSandboxControls = /
6547
6604
  const message = string(error?.message) ?? "Codex generation failed.";
6548
6605
  const rawCode = string(error?.code)?.toLowerCase();
6549
6606
  const isOverload = message.toLowerCase().includes("overload") || message.toLowerCase().includes("server error") || rawCode === "server_error" || rawCode === "service_unavailable" || rawCode === "internal_error";
6550
- 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");
6551
6608
  }
6552
6609
  };
6553
6610
  try {
@@ -6715,6 +6772,7 @@ async function responseError(response) {
6715
6772
  ...requestId ? { requestId: ProviderRequestId(requestId) } : {},
6716
6773
  ...response.status === 429 ? { providerRetryAfterMs: retryAfterMs$1(response.headers) } : {}
6717
6774
  };
6775
+ if (isHttpContextOverflow(response.status, detail)) return new LlmError(`Codex context window exceeded: ${detail}`, CONTEXT_OVERFLOW_CODE, options);
6718
6776
  if (response.status === 401) return new LlmError("ChatGPT sign-in has expired. Sign in again.", "AUTH", options);
6719
6777
  if (response.status === 404) return new LlmError(`Codex model or resource not found (${response.status})${detail ? `: ${detail}` : "."}`, "NOT_FOUND", options);
6720
6778
  if (response.status === 429) return new LlmError("Codex rate limit reached.", "RATE_LIMIT", options);
@@ -9111,6 +9169,8 @@ function processStreamLine$2(line, state) {
9111
9169
  const chunk = safeJsonParse$5(json);
9112
9170
  if (!isRecord$21(chunk)) return [];
9113
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");
9114
9174
  const candidates = Array.isArray(responseData.candidates) ? responseData.candidates : [];
9115
9175
  const candidate = isRecord$21(candidates[0]) ? candidates[0] : void 0;
9116
9176
  const content = isRecord$21(candidate?.content) ? candidate.content : void 0;
@@ -9632,7 +9692,7 @@ var AntigravityAdapter = class extends LlmAdapter {
9632
9692
  const status = response?.status ?? 500;
9633
9693
  const errText = await response?.text().catch(() => "");
9634
9694
  if (status === 429) throw new LlmError(`Antigravity 账号配额已耗尽或请求受限 (429 RESOURCE_EXHAUSTED)。请在插件设置页查看配额剩余百分比及重置倒计时。原始响应: ${errText || "No response"}`, "RATE_LIMIT", { status: 429 });
9635
- 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 });
9636
9696
  }
9637
9697
  if (!response.body) throw new LlmError("Antigravity returned empty response body", "PROVIDER_ERROR");
9638
9698
  const reader = response.body.getReader();
@@ -13095,6 +13155,7 @@ function processOpenAIStreamLine$1(line, state) {
13095
13155
  const chunk = safeJsonParse$4(payload);
13096
13156
  if (!isRecord$19(chunk)) return [];
13097
13157
  const out = [];
13158
+ 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");
13098
13159
  const usage = isRecord$19(chunk.usage) ? chunk.usage : void 0;
13099
13160
  if (usage) {
13100
13161
  state.sawUsage = true;
@@ -13364,7 +13425,10 @@ function processAnthropicStreamLine$1(line, state) {
13364
13425
  state.done = true;
13365
13426
  return closeStream$4(state);
13366
13427
  }
13367
- if (type === "error") throw new LlmError$1(`Command Code stream error: ${asString$12((isRecord$19(event.error) ? event.error : {}).message) ?? "unknown error"}`, "PROVIDER_ERROR");
13428
+ if (type === "error") {
13429
+ const error = isRecord$19(event.error) ? event.error : {};
13430
+ throw new LlmError$1(`Command Code stream error: ${asString$12(error.message) ?? "unknown error"}`, isContextOverflow(error) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
13431
+ }
13368
13432
  return out;
13369
13433
  }
13370
13434
  function tokenUsage$4(state) {
@@ -13475,7 +13539,7 @@ function processResponsesStreamLine(line, state) {
13475
13539
  state.done = true;
13476
13540
  const errorObj = isRecord$19(parsed.error) ? parsed.error : isRecord$19(parsed.response) && isRecord$19(parsed.response.error) ? parsed.response.error : null;
13477
13541
  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 : "");
13478
- throw new LlmError$1(detail === "" ? "Command Code responses stream failed" : `Command Code responses stream failed: ${detail}`, "PROVIDER_ERROR");
13542
+ throw new LlmError$1(detail === "" ? "Command Code responses stream failed" : `Command Code responses stream failed: ${detail}`, isContextOverflow(errorObj ?? detail) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
13479
13543
  }
13480
13544
  return [];
13481
13545
  }
@@ -14075,7 +14139,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
14075
14139
  });
14076
14140
  }
14077
14141
  if (status >= 500) throw new LlmError(`${PROVIDER_NAME$5} upstream server error (${status}): ${detail || "No response"}`, "SERVER", { status });
14078
- throw new LlmError(`${PROVIDER_NAME$5} API error (${status}): ${detail || "No response"}`, "PROVIDER_ERROR", { status });
14142
+ throw new LlmError(`${PROVIDER_NAME$5} API error (${status}): ${detail || "No response"}`, isHttpContextOverflow(status, detail) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR", { status });
14079
14143
  }
14080
14144
  if (response.body === null) throw new LlmError("Command Code returned an empty response body", "PROVIDER_ERROR");
14081
14145
  const reader = response.body.getReader();
@@ -14906,6 +14970,7 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
14906
14970
  ok: true,
14907
14971
  value: await beginWebLogin$3(store, {
14908
14972
  fetchFn,
14973
+ openBrowser: () => void 0,
14909
14974
  ...accountPool === void 0 ? {} : { onSave: (credentials) => accountPool.addAccount(credentials) }
14910
14975
  })
14911
14976
  });
@@ -15828,16 +15893,57 @@ function accumulateToolCall(raw, pending, openaiShape) {
15828
15893
  * making the line unusable.
15829
15894
  */
15830
15895
  async function loadCatalog$1(fetchFn, credentials) {
15831
- const response = await fetchFn(`${CLOUD_BASE_URL}${NATIVE_TAGS_PATH}`, {
15832
- method: "GET",
15833
- headers: headersFor(credentials, "application/json"),
15834
- signal: AbortSignal.timeout(CATALOG_TIMEOUT_MS)
15835
- });
15836
- if (!response.ok) return [];
15837
- const data = await response.json();
15838
- if (typeof data !== "object" || data === null) return [];
15896
+ return (await fetchCatalog(fetchFn, credentials)).models;
15897
+ }
15898
+ /**
15899
+ * The same read as {@link loadCatalog}, keeping the reason it failed.
15900
+ *
15901
+ * Never throws, for the reason its wrapper has always given. What it adds is that
15902
+ * "failed" stops being one opaque state, so the settings card can name the
15903
+ * cause instead of asking the user to guess.
15904
+ */
15905
+ async function fetchCatalog(fetchFn, credentials) {
15906
+ let response;
15907
+ try {
15908
+ response = await fetchFn(`${CLOUD_BASE_URL}${NATIVE_TAGS_PATH}`, {
15909
+ method: "GET",
15910
+ headers: headersFor(credentials, "application/json"),
15911
+ signal: AbortSignal.timeout(CATALOG_TIMEOUT_MS)
15912
+ });
15913
+ } catch {
15914
+ return {
15915
+ models: [],
15916
+ failure: "unreachable"
15917
+ };
15918
+ }
15919
+ if (!response.ok) return response.status === 401 || response.status === 403 ? {
15920
+ models: [],
15921
+ failure: "auth",
15922
+ status: response.status
15923
+ } : {
15924
+ models: [],
15925
+ failure: "upstream",
15926
+ status: response.status
15927
+ };
15928
+ let data;
15929
+ try {
15930
+ data = await response.json();
15931
+ } catch {
15932
+ return {
15933
+ models: [],
15934
+ failure: "malformed"
15935
+ };
15936
+ }
15937
+ if (typeof data !== "object" || data === null) return {
15938
+ models: [],
15939
+ failure: "malformed"
15940
+ };
15839
15941
  const record = data;
15840
- const models = Array.isArray(record.models) ? record.models : [];
15942
+ if (!Array.isArray(record.models)) return {
15943
+ models: [],
15944
+ failure: "malformed"
15945
+ };
15946
+ const models = record.models;
15841
15947
  const result = [];
15842
15948
  for (const entry of models) {
15843
15949
  if (typeof entry !== "object" || entry === null) continue;
@@ -15852,7 +15958,7 @@ async function loadCatalog$1(fetchFn, credentials) {
15852
15958
  fetchedAt: Date.now()
15853
15959
  });
15854
15960
  }
15855
- return result;
15961
+ return { models: result };
15856
15962
  }
15857
15963
  //#endregion
15858
15964
  //#region src/host/ollama/mapper.ts
@@ -16450,6 +16556,8 @@ function textOf$4(content) {
16450
16556
  //#endregion
16451
16557
  //#region src/host/ollama/routes.ts
16452
16558
  const ROUTE_PREFIX$3 = "/ollama/api";
16559
+ /** Where a user creates the keys this line stores. */
16560
+ const OLLAMA_KEYS_URL = "https://ollama.com/settings/keys";
16453
16561
  function sendJson$5(response, status, body) {
16454
16562
  const payload = JSON.stringify(body);
16455
16563
  response.writeHead(status, { "content-type": "application/json; charset=utf-8" });
@@ -16475,6 +16583,22 @@ async function readRequestJson$5(request) {
16475
16583
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("Request body must be a JSON object.");
16476
16584
  return parsed;
16477
16585
  }
16586
+ /**
16587
+ * The sentence the card shows for a catalog read that produced nothing.
16588
+ *
16589
+ * Every one of these used to collapse into "Could not read the model list from
16590
+ * Ollama", which told a user nothing they could act on — the complaint in issue
16591
+ * #36. The four causes have four different remedies, so they are named apart.
16592
+ */
16593
+ function catalogFailureMessage(result) {
16594
+ switch (result.failure) {
16595
+ case "auth": return `Ollama rejected the stored API key (HTTP ${result.status ?? 401}). Create or re-paste a key at ${OLLAMA_KEYS_URL}.`;
16596
+ case "upstream": return `Ollama answered HTTP ${result.status ?? "?"} for ${CLOUD_BASE_URL}/api/tags.`;
16597
+ case "unreachable": return "Could not reach Ollama Cloud. Check the network or proxy, then sync again.";
16598
+ case "malformed": return "Ollama answered with something that is not a /api/tags document (a proxy or filter may be rewriting the reply).";
16599
+ default: return "Could not read the model list from Ollama.";
16600
+ }
16601
+ }
16478
16602
  /** Whether one account can serve a request right now. */
16479
16603
  function isUsable(account, now) {
16480
16604
  if (account.authStatus !== void 0 && account.authStatus !== "ok") return false;
@@ -16525,123 +16649,144 @@ function registerOllamaRoutes(ctx, options) {
16525
16649
  handler: async (request, response) => {
16526
16650
  const path = new URL(request.url || "/", "http://dsh.local").pathname.replace(/^\/ollama\/api\/?/, "");
16527
16651
  const method = request.method ?? "GET";
16528
- if (path === "" || path === "status") return sendJson$5(response, 200, {
16529
- ok: true,
16530
- value: await getOllamaWebStatus(options)
16531
- });
16532
- if (path === "accounts") {
16533
- if (method === "GET") return sendJson$5(response, 200, {
16652
+ try {
16653
+ if (path === "" || path === "status") return sendJson$5(response, 200, {
16534
16654
  ok: true,
16535
16655
  value: await getOllamaWebStatus(options)
16536
16656
  });
16537
- if (method !== "POST") return sendMethodNotAllowed$4(response);
16538
- if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
16539
- ok: false,
16540
- error: "Cross-origin request rejected."
16541
- });
16542
- let body;
16543
- try {
16544
- body = await readRequestJson$5(request);
16545
- } catch (error) {
16546
- return sendJson$5(response, 400, {
16657
+ if (path === "accounts") {
16658
+ if (method === "GET") return sendJson$5(response, 200, {
16659
+ ok: true,
16660
+ value: await getOllamaWebStatus(options)
16661
+ });
16662
+ if (method !== "POST") return sendMethodNotAllowed$4(response);
16663
+ if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
16547
16664
  ok: false,
16548
- error: error instanceof Error ? error.message : String(error)
16665
+ error: "Cross-origin request rejected."
16549
16666
  });
16550
- }
16551
- const action = typeof body.action === "string" ? body.action : "";
16552
- const accountId = typeof body.accountId === "string" ? body.accountId : void 0;
16553
- try {
16554
- if (action === "add") {
16555
- const apiKey = typeof body.apiKey === "string" ? body.apiKey.trim() : "";
16556
- if (apiKey === "") return sendJson$5(response, 400, {
16667
+ let body;
16668
+ try {
16669
+ body = await readRequestJson$5(request);
16670
+ } catch (error) {
16671
+ return sendJson$5(response, 400, {
16557
16672
  ok: false,
16558
- error: "API key is required."
16673
+ error: error instanceof Error ? error.message : String(error)
16559
16674
  });
16560
- const alias = typeof body.alias === "string" && body.alias.trim() !== "" ? body.alias.trim() : void 0;
16561
- const credentials = {
16562
- apiKey,
16563
- addedAt: Date.now()
16564
- };
16565
- if (alias !== void 0) credentials.alias = alias;
16675
+ }
16676
+ const action = typeof body.action === "string" ? body.action : "";
16677
+ const accountId = typeof body.accountId === "string" ? body.accountId : void 0;
16678
+ try {
16679
+ if (action === "add") {
16680
+ const apiKey = typeof body.apiKey === "string" ? body.apiKey.trim() : "";
16681
+ if (apiKey === "") return sendJson$5(response, 400, {
16682
+ ok: false,
16683
+ error: "API key is required."
16684
+ });
16685
+ const alias = typeof body.alias === "string" && body.alias.trim() !== "" ? body.alias.trim() : void 0;
16686
+ const credentials = {
16687
+ apiKey,
16688
+ addedAt: Date.now()
16689
+ };
16690
+ if (alias !== void 0) credentials.alias = alias;
16691
+ return sendJson$5(response, 200, {
16692
+ ok: true,
16693
+ value: { id: (await accountPool.addAccount(credentials, alias)).id }
16694
+ });
16695
+ }
16696
+ if (accountId === void 0) return sendJson$5(response, 400, {
16697
+ ok: false,
16698
+ error: "accountId is required."
16699
+ });
16700
+ if (action === "set-primary") await accountPool.setPrimary(accountId);
16701
+ else if (action === "set-alias" && typeof body.alias === "string") await accountPool.setAlias(accountId, body.alias);
16702
+ else if (action === "delete") await accountPool.deleteAccount(accountId);
16703
+ else if (action === "clear-cooldown") await accountPool.clearCooldown(accountId);
16704
+ else if (action === "strategy" && (body.strategy === "sequential" || body.strategy === "round-robin" || body.strategy === "sticky")) await accountPool.setStrategy(body.strategy);
16705
+ else return sendJson$5(response, 400, {
16706
+ ok: false,
16707
+ error: "Unsupported account action."
16708
+ });
16709
+ } catch (error) {
16710
+ return sendJson$5(response, 400, {
16711
+ ok: false,
16712
+ error: error instanceof Error ? error.message : String(error)
16713
+ });
16714
+ }
16715
+ return sendJson$5(response, 200, {
16716
+ ok: true,
16717
+ value: await getOllamaWebStatus(options)
16718
+ });
16719
+ }
16720
+ if (path === "models") {
16721
+ if (method === "GET") {
16722
+ const settings = await modelSettings.read();
16566
16723
  return sendJson$5(response, 200, {
16567
16724
  ok: true,
16568
- value: { id: (await accountPool.addAccount(credentials, alias)).id }
16725
+ value: {
16726
+ models: settings.catalogModels,
16727
+ enabled: settings.enabled,
16728
+ enabledModelIds: settings.enabledModelIds
16729
+ }
16569
16730
  });
16570
16731
  }
16571
- if (accountId === void 0) return sendJson$5(response, 400, {
16732
+ if (method !== "POST") return sendMethodNotAllowed$4(response);
16733
+ if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
16734
+ ok: false,
16735
+ error: "Cross-origin request rejected."
16736
+ });
16737
+ const body = await readRequestJson$5(request).catch(() => ({}));
16738
+ if (typeof body.enabled === "boolean") await modelSettings.update({ enabled: body.enabled });
16739
+ if (Array.isArray(body.enabledModelIds)) await modelSettings.update({ enabledModelIds: body.enabledModelIds.filter((id) => typeof id === "string") });
16740
+ return sendJson$5(response, 200, {
16741
+ ok: true,
16742
+ value: await modelSettings.read()
16743
+ });
16744
+ }
16745
+ if (path === "catalog/refresh") {
16746
+ if (method !== "POST") return sendMethodNotAllowed$4(response);
16747
+ if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
16572
16748
  ok: false,
16573
- error: "accountId is required."
16749
+ error: "Cross-origin request rejected."
16574
16750
  });
16575
- if (action === "set-primary") await accountPool.setPrimary(accountId);
16576
- else if (action === "set-alias" && typeof body.alias === "string") await accountPool.setAlias(accountId, body.alias);
16577
- else if (action === "delete") await accountPool.deleteAccount(accountId);
16578
- else if (action === "clear-cooldown") await accountPool.clearCooldown(accountId);
16579
- else if (action === "strategy" && (body.strategy === "sequential" || body.strategy === "round-robin" || body.strategy === "sticky")) await accountPool.setStrategy(body.strategy);
16580
- else return sendJson$5(response, 400, {
16751
+ let credentials = null;
16752
+ let poolBusy = false;
16753
+ try {
16754
+ credentials = (await accountPool.getEffectiveCredential(void 0, fetchFn)).credentials;
16755
+ } catch {
16756
+ poolBusy = await accountPool.read().then((data) => data.accounts.length > 0 && data.accounts.every((account) => isUsable(account, Date.now()) === false)).catch(() => false);
16757
+ credentials = null;
16758
+ }
16759
+ if (credentials === null) return sendJson$5(response, 400, {
16581
16760
  ok: false,
16582
- error: "Unsupported account action."
16761
+ 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."
16583
16762
  });
16584
- } catch (error) {
16585
- return sendJson$5(response, 400, {
16763
+ const result = await fetchCatalog(fetchFn, credentials);
16764
+ if (result.models.length === 0) return sendJson$5(response, 502, {
16586
16765
  ok: false,
16587
- error: error instanceof Error ? error.message : String(error)
16766
+ error: catalogFailureMessage(result)
16588
16767
  });
16589
- }
16590
- return sendJson$5(response, 200, {
16591
- ok: true,
16592
- value: await getOllamaWebStatus(options)
16593
- });
16594
- }
16595
- if (path === "models") {
16596
- if (method === "GET") {
16597
- const settings = await modelSettings.read();
16768
+ try {
16769
+ await modelSettings.storeCatalog(result.models);
16770
+ } catch (error) {
16771
+ const detail = error instanceof Error ? error.message : String(error);
16772
+ return sendJson$5(response, 500, {
16773
+ ok: false,
16774
+ error: `Read ${result.models.length} models from Ollama but could not save them locally: ${detail}`
16775
+ });
16776
+ }
16598
16777
  return sendJson$5(response, 200, {
16599
16778
  ok: true,
16600
- value: {
16601
- models: settings.catalogModels,
16602
- enabled: settings.enabled,
16603
- enabledModelIds: settings.enabledModelIds
16604
- }
16779
+ value: { models: result.models }
16605
16780
  });
16606
16781
  }
16607
- if (method !== "POST") return sendMethodNotAllowed$4(response);
16608
- if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
16609
- ok: false,
16610
- error: "Cross-origin request rejected."
16611
- });
16612
- const body = await readRequestJson$5(request).catch(() => ({}));
16613
- if (typeof body.enabled === "boolean") await modelSettings.update({ enabled: body.enabled });
16614
- if (Array.isArray(body.enabledModelIds)) await modelSettings.update({ enabledModelIds: body.enabledModelIds.filter((id) => typeof id === "string") });
16615
- return sendJson$5(response, 200, {
16616
- ok: true,
16617
- value: await modelSettings.read()
16618
- });
16619
- }
16620
- if (path === "catalog/refresh") {
16621
- if (method !== "POST") return sendMethodNotAllowed$4(response);
16622
- if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
16623
- ok: false,
16624
- error: "Cross-origin request rejected."
16625
- });
16626
- let credentials = null;
16627
- try {
16628
- credentials = (await accountPool.getEffectiveCredential(void 0, fetchFn)).credentials;
16629
- } catch {
16630
- credentials = null;
16631
- }
16632
- if (credentials === null) return sendJson$5(response, 400, {
16782
+ return sendJson$5(response, 404, {
16633
16783
  ok: false,
16634
- error: "Add an API key before syncing models."
16784
+ error: "not-found"
16635
16785
  });
16636
- const models = await loadCatalog$1(fetchFn, credentials).catch(() => []);
16637
- if (models.length === 0) return sendJson$5(response, 502, {
16786
+ } catch (error) {
16787
+ return sendJson$5(response, 500, {
16638
16788
  ok: false,
16639
- error: "Could not read the model list from Ollama."
16640
- });
16641
- await modelSettings.storeCatalog(models);
16642
- return sendJson$5(response, 200, {
16643
- ok: true,
16644
- value: { models }
16789
+ error: error instanceof Error ? error.message : String(error)
16645
16790
  });
16646
16791
  }
16647
16792
  }
@@ -20212,7 +20357,7 @@ function processOpenAIStreamLine(line, state) {
20212
20357
  if (!isRecord$16(chunk)) return [];
20213
20358
  const out = [];
20214
20359
  const errorPayload = isRecord$16(chunk.error) ? chunk.error : void 0;
20215
- if (errorPayload !== void 0) throw new LlmError$1(`Kimi Code stream error: ${asString$9(errorPayload.message) ?? "unknown error"}`, "PROVIDER_ERROR");
20360
+ 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");
20216
20361
  const usage = isRecord$16(chunk.usage) ? chunk.usage : void 0;
20217
20362
  if (usage) {
20218
20363
  state.sawUsage = true;
@@ -20479,7 +20624,10 @@ function processAnthropicStreamLine(line, state) {
20479
20624
  state.done = true;
20480
20625
  return closeStream$2(state);
20481
20626
  }
20482
- if (type === "error") throw new LlmError$1(`Kimi Code stream error: ${asString$9((isRecord$16(event.error) ? event.error : {}).message) ?? "unknown error"}`, "PROVIDER_ERROR");
20627
+ if (type === "error") {
20628
+ const error = isRecord$16(event.error) ? event.error : {};
20629
+ throw new LlmError$1(`Kimi Code stream error: ${asString$9(error.message) ?? "unknown error"}`, isContextOverflow(error) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
20630
+ }
20483
20631
  return out;
20484
20632
  }
20485
20633
  /**
@@ -20712,6 +20860,11 @@ function summarizeFailureBody$1(raw) {
20712
20860
  */
20713
20861
  function classifyKimiFailure(status, bodyText) {
20714
20862
  const detail = summarizeFailureBody$1(bodyText);
20863
+ if (isHttpContextOverflow(status, bodyText)) return {
20864
+ code: CONTEXT_OVERFLOW_CODE,
20865
+ retryable: false,
20866
+ message: `${PROVIDER_NAME$3} context window exceeded: ${detail}`
20867
+ };
20715
20868
  if (status === 402) return {
20716
20869
  code: "SERVER",
20717
20870
  retryable: true,
@@ -23347,7 +23500,10 @@ function processMinimaxStreamLine(line, state) {
23347
23500
  state.done = true;
23348
23501
  return closeMinimaxStream(state);
23349
23502
  }
23350
- if (type === "error") throw new LlmError$1("MiniMax Code(编程订阅) stream error: " + (asString$8((isRecord$15(event.error) ? event.error : {}).message) ?? "unknown error"), "PROVIDER_ERROR");
23503
+ if (type === "error") {
23504
+ const error = isRecord$15(event.error) ? event.error : {};
23505
+ throw new LlmError$1("MiniMax Code(编程订阅) stream error: " + (asString$8(error.message) ?? "unknown error"), isContextOverflow(error) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
23506
+ }
23351
23507
  return out;
23352
23508
  }
23353
23509
  //#endregion
@@ -25375,6 +25531,11 @@ function matchesAny(text, patterns) {
25375
25531
  */
25376
25532
  function classifyMinimaxFailure(status, bodyText) {
25377
25533
  const detail = summarizeFailureBody(bodyText);
25534
+ if (isHttpContextOverflow(status, bodyText)) return {
25535
+ code: CONTEXT_OVERFLOW_CODE,
25536
+ retryable: false,
25537
+ message: `${PROVIDER_NAME$2} context window exceeded: ${detail}`
25538
+ };
25378
25539
  if (status === 401 || status === 403) return {
25379
25540
  code: "INVALID_CREDENTIAL",
25380
25541
  retryable: false,
@@ -30020,7 +30181,7 @@ function processStreamLine$1(line, state) {
30020
30181
  if (!isRecord$8(chunk)) return [];
30021
30182
  const out = [];
30022
30183
  const errorPayload = isRecord$8(chunk.error) ? chunk.error : void 0;
30023
- if (errorPayload !== void 0) throw new LlmError$1(`WorkBuddy stream error: ${asString$4(errorPayload.message) ?? "unknown error"}`, "PROVIDER_ERROR");
30184
+ if (errorPayload !== void 0) throw new LlmError$1(`WorkBuddy stream error: ${asString$4(errorPayload.message) ?? "unknown error"}`, isContextOverflow(errorPayload) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
30024
30185
  const usage = isRecord$8(chunk.usage) ? chunk.usage : void 0;
30025
30186
  if (usage) {
30026
30187
  state.sawUsage = true;
@@ -30453,7 +30614,7 @@ function classifyFailure$1(status, detail, headers) {
30453
30614
  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 });
30454
30615
  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 });
30455
30616
  if (status >= 500 || code === 11134) return new LlmError(`${PROVIDER_NAME$1} upstream server error (${status}): ${detail || "No response"}`, "SERVER", { status });
30456
- return new LlmError(`${PROVIDER_NAME$1} API error (${status}): ${detail || "No response"}`, "PROVIDER_ERROR", { status });
30617
+ return new LlmError(`${PROVIDER_NAME$1} API error (${status}): ${detail || "No response"}`, isHttpContextOverflow(status, detail) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR", { status });
30457
30618
  }
30458
30619
  /** Read the subscription's own numeric error code out of a response body. */
30459
30620
  function readErrorCode(detail) {
@@ -34997,7 +35158,7 @@ function processStreamLine(line, state) {
34997
35158
  const error = isRecord$5(event.error) ? event.error : {};
34998
35159
  const message = asString$2(error.message) ?? "unknown error";
34999
35160
  const kind = asString$2(error.type);
35000
- throw new LlmError$1("Claude stream error" + (kind === void 0 ? "" : " (" + kind + ")") + ": " + message, "PROVIDER_ERROR");
35161
+ throw new LlmError$1("Claude stream error" + (kind === void 0 ? "" : " (" + kind + ")") + ": " + message, isContextOverflow(error) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
35001
35162
  }
35002
35163
  return out;
35003
35164
  }
@@ -36722,6 +36883,7 @@ function toLlmError(failure) {
36722
36883
  };
36723
36884
  const detail = failure.message === "" ? "" : " " + failure.message;
36724
36885
  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);
36886
+ if (failure.kind === "request" && isHttpContextOverflow(failure.status, failure.message)) return new LlmError("Claude(订阅) context window exceeded." + detail, CONTEXT_OVERFLOW_CODE, options);
36725
36887
  switch (failure.kind) {
36726
36888
  case "credential": return new LlmError("Claude(订阅) rejected the stored credential. Sign in again from Settings > Claude." + detail, "INVALID_CREDENTIAL", options);
36727
36889
  case "rate_limit_account":
@@ -38325,6 +38487,19 @@ function registerClaudeRoutes(ctx, store, modelSettings, preferences, options =
38325
38487
  clearCachedCatalog();
38326
38488
  }
38327
38489
  });
38490
+ /**
38491
+ * Options for every sign-in this surface starts.
38492
+ *
38493
+ * The CARD opens the authorization page (`window.open`, which the desktop
38494
+ * shell hands to the system browser). The host must not open it as well: it
38495
+ * did, and every sign-in showed the user two identical login pages. A test may
38496
+ * still inject its own opener through `options.login`.
38497
+ */
38498
+ const loginOptions = {
38499
+ openBrowser: () => void 0,
38500
+ ...options.login ?? {},
38501
+ fetchFn
38502
+ };
38328
38503
  const readStatus = (quotaError = null) => activeAccount().then((active) => getClaudeWebStatus(store, modelSettings, preferences, options, quotaError, active));
38329
38504
  return ctx.webServer.register({
38330
38505
  kind: "prefix",
@@ -38365,10 +38540,7 @@ function registerClaudeRoutes(ctx, store, modelSettings, preferences, options =
38365
38540
  const target = typeof body.accountId === "string" && body.accountId !== "" ? body.accountId : void 0;
38366
38541
  return sendJson(response, 200, {
38367
38542
  ok: true,
38368
- value: await beginLogin(loginStore(target), {
38369
- ...options.login ?? {},
38370
- fetchFn
38371
- })
38543
+ value: await beginLogin(loginStore(target), loginOptions)
38372
38544
  });
38373
38545
  }
38374
38546
  if (path === "login/status") {
@@ -38566,10 +38738,7 @@ function registerClaudeRoutes(ctx, store, modelSettings, preferences, options =
38566
38738
  });
38567
38739
  return sendJson(response, 200, {
38568
38740
  ok: true,
38569
- value: await beginLogin(loginStore(accountId), {
38570
- ...options.login ?? {},
38571
- fetchFn
38572
- })
38741
+ value: await beginLogin(loginStore(accountId), loginOptions)
38573
38742
  });
38574
38743
  }
38575
38744
  if (accountId === "" && action !== "strategy") return sendJson(response, 400, {
@@ -1 +1 @@
1
- {"version":3,"file":"ClaudeSection.d.ts","sourceRoot":"","sources":["../../../../src/client/claude/ClaudeSection.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAA;AA4BvE,UAAU,KAAK;IACb,aAAa,CAAC,EAAE,MAAM,IAAI,CAAA;IAC1B,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAA;CAChC;AAoBD,wEAAwE;AACxE,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOlE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIpD;AA+ED,wBAAgB,aAAa,CAAC,EAAE,aAAa,EAAE,kBAAkB,EAAE,EAAE,KAAK,GAAG,KAAK,CAAC,YAAY,CAm7B9F"}
1
+ {"version":3,"file":"ClaudeSection.d.ts","sourceRoot":"","sources":["../../../../src/client/claude/ClaudeSection.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAA;AA4BvE,UAAU,KAAK;IACb,aAAa,CAAC,EAAE,MAAM,IAAI,CAAA;IAC1B,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAA;CAChC;AAoBD,wEAAwE;AACxE,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOlE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIpD;AA+ED,wBAAgB,aAAa,CAAC,EAAE,aAAa,EAAE,kBAAkB,EAAE,EAAE,KAAK,GAAG,KAAK,CAAC,YAAY,CAq7B9F"}