@eddyskywalker/dsh-chatgpt-subscription 0.10.15 → 0.10.21

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 (63) hide show
  1. package/CHANGELOG.md +45 -1
  2. package/README.md +8 -4
  3. package/lib/client.js +641 -121
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +2086 -306
  6. package/lib/types/client/ProviderHubSection.d.ts.map +1 -1
  7. package/lib/types/client/command-code/CommandCodeSection.d.ts.map +1 -1
  8. package/lib/types/client/index.d.ts +1 -0
  9. package/lib/types/client/index.d.ts.map +1 -1
  10. package/lib/types/client/locales.d.ts +4 -4
  11. package/lib/types/client/ollama/OllamaSection.d.ts +25 -0
  12. package/lib/types/client/ollama/OllamaSection.d.ts.map +1 -0
  13. package/lib/types/client/ollama/api.d.ts +36 -0
  14. package/lib/types/client/ollama/api.d.ts.map +1 -0
  15. package/lib/types/client/ollama/locales.d.ts +413 -0
  16. package/lib/types/client/ollama/locales.d.ts.map +1 -0
  17. package/lib/types/host/antigravity/adapter.d.ts.map +1 -1
  18. package/lib/types/host/claude/adapter.d.ts +3 -5
  19. package/lib/types/host/claude/adapter.d.ts.map +1 -1
  20. package/lib/types/host/claude/model-catalog.d.ts +30 -16
  21. package/lib/types/host/claude/model-catalog.d.ts.map +1 -1
  22. package/lib/types/host/command-code/adapter.d.ts.map +1 -1
  23. package/lib/types/host/command-code/mapper.d.ts.map +1 -1
  24. package/lib/types/host/command-code/model-catalog.d.ts +19 -2
  25. package/lib/types/host/command-code/model-catalog.d.ts.map +1 -1
  26. package/lib/types/host/command-code/types.d.ts +29 -1
  27. package/lib/types/host/command-code/types.d.ts.map +1 -1
  28. package/lib/types/host/common/output-reservation.d.ts +11 -0
  29. package/lib/types/host/common/output-reservation.d.ts.map +1 -0
  30. package/lib/types/host/kimi-code/adapter.d.ts.map +1 -1
  31. package/lib/types/host/kimi-code/model-catalog.d.ts +1 -1
  32. package/lib/types/host/kimi-code/model-catalog.d.ts.map +1 -1
  33. package/lib/types/host/minimax-code/adapter.d.ts +2 -3
  34. package/lib/types/host/minimax-code/adapter.d.ts.map +1 -1
  35. package/lib/types/host/model-catalog.d.ts.map +1 -1
  36. package/lib/types/host/ollama/account-pool.d.ts +65 -0
  37. package/lib/types/host/ollama/account-pool.d.ts.map +1 -0
  38. package/lib/types/host/ollama/adapter.d.ts +47 -0
  39. package/lib/types/host/ollama/adapter.d.ts.map +1 -0
  40. package/lib/types/host/ollama/client.d.ts +101 -0
  41. package/lib/types/host/ollama/client.d.ts.map +1 -0
  42. package/lib/types/host/ollama/mapper.d.ts +44 -0
  43. package/lib/types/host/ollama/mapper.d.ts.map +1 -0
  44. package/lib/types/host/ollama/routes.d.ts +22 -0
  45. package/lib/types/host/ollama/routes.d.ts.map +1 -0
  46. package/lib/types/host/ollama/token-store.d.ts +73 -0
  47. package/lib/types/host/ollama/token-store.d.ts.map +1 -0
  48. package/lib/types/host/ollama/types.d.ts +115 -0
  49. package/lib/types/host/ollama/types.d.ts.map +1 -0
  50. package/lib/types/host/reasoning-collapse-guard/index.d.ts.map +1 -1
  51. package/lib/types/host/responses-mapper.d.ts +0 -2
  52. package/lib/types/host/responses-mapper.d.ts.map +1 -1
  53. package/lib/types/host/search-provider-switcher.d.ts +12 -0
  54. package/lib/types/host/search-provider-switcher.d.ts.map +1 -1
  55. package/lib/types/host/workbuddy/adapter.d.ts.map +1 -1
  56. package/lib/types/index.d.ts.map +1 -1
  57. package/lib/types/shared/command-code-contracts.d.ts +10 -4
  58. package/lib/types/shared/command-code-contracts.d.ts.map +1 -1
  59. package/lib/types/shared/model-catalog.d.ts +58 -1
  60. package/lib/types/shared/model-catalog.d.ts.map +1 -1
  61. package/lib/types/shared/ollama-contracts.d.ts +36 -0
  62. package/lib/types/shared/ollama-contracts.d.ts.map +1 -0
  63. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -13,12 +13,27 @@ import http, { createServer } from "node:http";
13
13
  import { WebError } from "@deepseek-ai/dsh-web";
14
14
  import { lookup } from "node:dns/promises";
15
15
  import { isIP } from "node:net";
16
- import { createUserMessage } from "@deepseek-ai/dsh-llm/message";
16
+ import { boundContextSummary, createUserMessage } from "@deepseek-ai/dsh-llm/message";
17
17
  import { defineTool } from "@deepseek-ai/dsh-tools";
18
18
  import { ProxyAgent, fetch as fetch$1 } from "undici";
19
19
  import * as SettingsModule from "@deepseek-ai/dsh-settings";
20
20
  import { idleWatchdog, timeoutOf } from "@deepseek-ai/dsh-timeout";
21
21
  import { URL as URL$1, URLSearchParams as URLSearchParams$1, fileURLToPath } from "node:url";
22
+ //#region src/host/common/output-reservation.ts
23
+ /**
24
+ * Keep fixed defaults only when they fit DSH compaction-basic's default policy.
25
+ * This is host metadata, NEVER a wire cap. Callers must retain their original
26
+ * wire fallback when the metadata is omitted. Explicit request caps still win.
27
+ * Custom compaction policies and windows too small even at zero reservation
28
+ * require deployment configuration; do not invent a larger context window.
29
+ */
30
+ function outputReservation(contextWindow, maxTokens) {
31
+ const messageBudget = contextWindow - maxTokens;
32
+ const threshold = Math.floor(Math.min(contextWindow * .8, messageBudget - 65536));
33
+ const retain = Math.floor(messageBudget * .16);
34
+ return Number.isSafeInteger(maxTokens) && maxTokens > 0 && threshold > 0 && retain < threshold ? { defaultMaxTokens: maxTokens } : {};
35
+ }
36
+ //#endregion
22
37
  //#region src/shared/minimax-code-contracts.ts
23
38
  const MINIMAX_CODE_PROVIDER_ID = "minimax-code";
24
39
  const MINIMAX_CODE_PROVIDER_NAME = "MiniMax Code(编程订阅)";
@@ -78,7 +93,7 @@ const CODEX_ORIGINATOR = "opencode";
78
93
  /** OAuth presents the same identity the request path does (see above). */
79
94
  const OAUTH_ORIGINATOR = CODEX_ORIGINATOR;
80
95
  const TOKEN_REFRESH_MARGIN_MS = 6e4;
81
- const ROUTE_PREFIX$4 = "/api/dsh-chatgpt-subscription";
96
+ const ROUTE_PREFIX$5 = "/api/dsh-chatgpt-subscription";
82
97
  const PLUGIN_VERSION = "0.1.0-alpha.0";
83
98
  const CODEX_CHATGPT_PROVIDER_ID = "codex-chatgpt";
84
99
  const CODEX_API_BASE = "https://chatgpt.com/backend-api/codex";
@@ -122,7 +137,9 @@ const CODEX_MODEL_CATALOG = [
122
137
  defaultReasoningEffort: "medium",
123
138
  reasoningProfile: "gpt-5.6",
124
139
  supportsReasoningSummary: true,
125
- fallbackModelId: "gpt-5.6-terra"
140
+ fallbackModelId: "gpt-5.6-terra",
141
+ supportsOutputVerbosity: true,
142
+ defaultOutputVerbosity: "low"
126
143
  },
127
144
  {
128
145
  id: "gpt-6-astra",
@@ -132,7 +149,9 @@ const CODEX_MODEL_CATALOG = [
132
149
  defaultReasoningEffort: "medium",
133
150
  reasoningProfile: "gpt-6",
134
151
  supportsReasoningSummary: true,
135
- maxTokens: GPT_6_MAX_TOKENS
152
+ maxTokens: GPT_6_MAX_TOKENS,
153
+ supportsOutputVerbosity: true,
154
+ defaultOutputVerbosity: "low"
136
155
  },
137
156
  {
138
157
  id: "gpt-6.1-sol",
@@ -142,7 +161,9 @@ const CODEX_MODEL_CATALOG = [
142
161
  defaultReasoningEffort: "medium",
143
162
  reasoningProfile: "gpt-6",
144
163
  supportsReasoningSummary: true,
145
- maxTokens: GPT_6_MAX_TOKENS
164
+ maxTokens: GPT_6_MAX_TOKENS,
165
+ supportsOutputVerbosity: true,
166
+ defaultOutputVerbosity: "low"
146
167
  },
147
168
  {
148
169
  id: "gpt-6-sol",
@@ -152,7 +173,9 @@ const CODEX_MODEL_CATALOG = [
152
173
  defaultReasoningEffort: "medium",
153
174
  reasoningProfile: "gpt-6",
154
175
  supportsReasoningSummary: true,
155
- maxTokens: GPT_6_MAX_TOKENS
176
+ maxTokens: GPT_6_MAX_TOKENS,
177
+ supportsOutputVerbosity: true,
178
+ defaultOutputVerbosity: "low"
156
179
  },
157
180
  {
158
181
  id: "gpt-6-luna",
@@ -162,7 +185,9 @@ const CODEX_MODEL_CATALOG = [
162
185
  defaultReasoningEffort: "medium",
163
186
  reasoningProfile: "gpt-6",
164
187
  supportsReasoningSummary: true,
165
- maxTokens: GPT_6_MAX_TOKENS
188
+ maxTokens: GPT_6_MAX_TOKENS,
189
+ supportsOutputVerbosity: true,
190
+ defaultOutputVerbosity: "low"
166
191
  },
167
192
  {
168
193
  id: "gpt-5.6-terra",
@@ -172,7 +197,9 @@ const CODEX_MODEL_CATALOG = [
172
197
  defaultReasoningEffort: "medium",
173
198
  reasoningProfile: "gpt-5.6",
174
199
  supportsReasoningSummary: true,
175
- fallbackModelId: "gpt-5.5"
200
+ fallbackModelId: "gpt-5.5",
201
+ supportsOutputVerbosity: true,
202
+ defaultOutputVerbosity: "low"
176
203
  },
177
204
  {
178
205
  id: "gpt-5.6-luna",
@@ -182,7 +209,9 @@ const CODEX_MODEL_CATALOG = [
182
209
  defaultReasoningEffort: "medium",
183
210
  reasoningProfile: "gpt-5.6",
184
211
  supportsReasoningSummary: true,
185
- fallbackModelId: "gpt-5.5"
212
+ fallbackModelId: "gpt-5.5",
213
+ supportsOutputVerbosity: true,
214
+ defaultOutputVerbosity: "low"
186
215
  },
187
216
  {
188
217
  id: "gpt-5.5",
@@ -191,7 +220,9 @@ const CODEX_MODEL_CATALOG = [
191
220
  inputModalities: ["text", "image"],
192
221
  defaultReasoningEffort: "medium",
193
222
  reasoningProfile: "standard",
194
- supportsReasoningSummary: true
223
+ supportsReasoningSummary: true,
224
+ supportsOutputVerbosity: true,
225
+ defaultOutputVerbosity: "low"
195
226
  },
196
227
  {
197
228
  id: "gpt-5.4",
@@ -275,7 +306,44 @@ function codexModelSupportsImageInput(model) {
275
306
  function codexModelSupportsReasoningSummary(model) {
276
307
  return resolveCodexCatalogEntry(model).supportsReasoningSummary;
277
308
  }
278
- /** Per-request output cap for one model, or the pre-GPT-6 default when it declares none. */
309
+ /**
310
+ * One catalog entry by exact id, or undefined when this table has no record.
311
+ *
312
+ * Deliberately NOT {@link resolveCodexCatalogEntry}, which answers an unknown id
313
+ * with the default entry: that substitution is the right guide for a model whose
314
+ * capabilities only need a sensible floor, but it cannot tell "this model
315
+ * declares verbosity support" apart from "nobody said". A wire field must not be
316
+ * invented from silence.
317
+ */
318
+ function codexCatalogEntryById(model) {
319
+ return CODEX_MODEL_CATALOG.find((entry) => entry.id === model);
320
+ }
321
+ /**
322
+ * Verbosity to send when the user has not chosen one.
323
+ *
324
+ * "Follow the provider" has to mean what the provider's own client sends. The
325
+ * official catalog declares `default_verbosity: "low"` for every model it
326
+ * lists, so the answer is low — not the server's implicit medium, which is what
327
+ * omitting the field gets.
328
+ *
329
+ * @returns the verbosity to send, or `undefined` to omit the field entirely.
330
+ */
331
+ function codexDefaultOutputVerbosity(model) {
332
+ const entry = codexCatalogEntryById(model);
333
+ if (entry?.supportsOutputVerbosity !== true) return void 0;
334
+ return entry.defaultOutputVerbosity ?? "low";
335
+ }
336
+ /**
337
+ * Declared output ceiling for one model, or the pre-GPT-6 default when it
338
+ * declares none.
339
+ *
340
+ * This is NOT a request field. The subscription Responses endpoint rejects
341
+ * `max_output_tokens` outright (400 Unsupported parameter, issue #29 — and the
342
+ * official CLI's own request struct has no such field), so nothing is sent to
343
+ * the wire from here. The value reaches DSH only as the adapter's
344
+ * `defaultMaxTokens`, which compaction uses as a local completion reservation
345
+ * when it plans a fold; that number never leaves this process.
346
+ */
279
347
  function codexModelMaxTokens(model) {
280
348
  return resolveCodexCatalogEntry(model).maxTokens ?? 32768;
281
349
  }
@@ -296,8 +364,8 @@ function resolveCodexFallbackModel(model) {
296
364
  }
297
365
  //#endregion
298
366
  //#region src/host/model-catalog.ts
299
- const PROVIDER_ID$6 = CODEX_CHATGPT_PROVIDER_ID;
300
- const PROVIDER_NAME$6 = "Codex(ChatGPT 订阅)";
367
+ const PROVIDER_ID$7 = CODEX_CHATGPT_PROVIDER_ID;
368
+ const PROVIDER_NAME$7 = "Codex(ChatGPT 订阅)";
301
369
  function listCodexModels(preferences, live) {
302
370
  const status = preferences?.status();
303
371
  if (status?.enabled === false) return [];
@@ -309,7 +377,7 @@ function listCodexModels(preferences, live) {
309
377
  const visible = new Set(status?.visibleModelIds ?? (listed ?? shippedModels()).map((entry) => entry.id));
310
378
  const selected = (listed ?? shippedModels()).filter((entry) => visible.has(entry.id));
311
379
  return (listed === void 0 || selected.length > 0 ? selected : shippedModels().filter((entry) => visible.has(entry.id))).map((entry) => ({
312
- provider: PROVIDER_ID$6,
380
+ provider: PROVIDER_ID$7,
313
381
  id: entry.id,
314
382
  name: entry.name,
315
383
  inputModalities: [...entry.inputModalities]
@@ -332,12 +400,12 @@ function resolveCodexModel(model, preferences, live) {
332
400
  const statedDefault = liveEntry?.defaultReasoningEffort ?? entry.defaultReasoningEffort;
333
401
  const defaultEffort = isCodexReasoningEffort(statedDefault) && efforts.includes(statedDefault) ? statedDefault : efforts[0];
334
402
  return {
335
- provider: PROVIDER_ID$6,
403
+ provider: PROVIDER_ID$7,
336
404
  id: model,
337
405
  name: liveEntry?.name ?? (entry.id === model ? entry.name : model),
338
406
  inputModalities: [...liveEntry?.inputModalities ?? entry.inputModalities],
339
407
  context: { contextWindow: configuredContextWindow ?? liveEntry?.contextWindow ?? entry.contextWindow },
340
- defaultMaxTokens: codexModelMaxTokens(model),
408
+ ...outputReservation(configuredContextWindow ?? liveEntry?.contextWindow ?? entry.contextWindow, codexModelMaxTokens(model)),
341
409
  reasoning: {
342
410
  efforts: efforts.map((effort) => ({
343
411
  id: ReasoningEffortId(effort),
@@ -349,7 +417,7 @@ function resolveCodexModel(model, preferences, live) {
349
417
  }
350
418
  //#endregion
351
419
  //#region src/host/adapter.ts
352
- const RETRY_POLICY$5 = resolveRetryPolicy({
420
+ const RETRY_POLICY$6 = resolveRetryPolicy({
353
421
  mode: "normal",
354
422
  maxRetries: 3,
355
423
  retryableCodes: [
@@ -379,11 +447,11 @@ var CodexChatGptAdapter = class extends LlmAdapter {
379
447
  providerInfo(provider) {
380
448
  return {
381
449
  id: provider,
382
- name: PROVIDER_NAME$6
450
+ name: PROVIDER_NAME$7
383
451
  };
384
452
  }
385
453
  providerRetryPolicy() {
386
- return RETRY_POLICY$5;
454
+ return RETRY_POLICY$6;
387
455
  }
388
456
  imageRequestPricing(_provider, _model) {}
389
457
  async listModels() {
@@ -2630,7 +2698,7 @@ async function oauthErrorIdentifier(response) {
2630
2698
  function codexPoolPath() {
2631
2699
  return path.join(dshHomeDir(), "storages", "codex-pool.json");
2632
2700
  }
2633
- function optionalString$7(record, key) {
2701
+ function optionalString$9(record, key) {
2634
2702
  const value = record[key];
2635
2703
  if (value === void 0 || value === null) return void 0;
2636
2704
  if (typeof value !== "string") throw new Error("ChatGPT pool account field is invalid");
@@ -2653,11 +2721,11 @@ function parseCodexPoolData(value) {
2653
2721
  addedAt: typeof raw.addedAt === "number" ? raw.addedAt : Date.now(),
2654
2722
  isPrimary: raw.isPrimary === true
2655
2723
  };
2656
- const email = optionalString$7(raw, "email") ?? credentials.email;
2724
+ const email = optionalString$9(raw, "email") ?? credentials.email;
2657
2725
  if (email !== void 0) account.email = email;
2658
- const planType = optionalString$7(raw, "planType") ?? credentials.planType;
2726
+ const planType = optionalString$9(raw, "planType") ?? credentials.planType;
2659
2727
  if (planType !== void 0) account.planType = planType;
2660
- const accountId = optionalString$7(raw, "accountId") ?? credentials.accountId;
2728
+ const accountId = optionalString$9(raw, "accountId") ?? credentials.accountId;
2661
2729
  if (accountId !== void 0) account.accountId = accountId;
2662
2730
  if (typeof raw.lastUsedAt === "number") account.lastUsedAt = raw.lastUsedAt;
2663
2731
  if (typeof raw.cooldownUntil === "number") account.cooldownUntil = raw.cooldownUntil;
@@ -3414,8 +3482,7 @@ function sendSearch(fetchFn, credentials, query, model, id, signal) {
3414
3482
  settings: {
3415
3483
  allowed_callers: ["direct"],
3416
3484
  external_web_access: true
3417
- },
3418
- max_output_tokens: 4096
3485
+ }
3419
3486
  }),
3420
3487
  signal
3421
3488
  });
@@ -3678,8 +3745,8 @@ var ProxyManager = class {
3678
3745
  };
3679
3746
  //#endregion
3680
3747
  //#region src/host/antigravity/types.ts
3681
- const PROVIDER_NAME$5 = "Antigravity";
3682
- const PROVIDER_ID$5 = "antigravity";
3748
+ const PROVIDER_NAME$6 = "Antigravity";
3749
+ const PROVIDER_ID$6 = "antigravity";
3683
3750
  const STREAM_IDLE_TIMEOUT_MS$5 = 3e5;
3684
3751
  const STREAM_IDLE_TIMEOUT_CODE$6 = "LLM_STREAM_IDLE_TIMEOUT";
3685
3752
  const DISCOVERY_TIMEOUT_MS$4 = 8e3;
@@ -4143,13 +4210,13 @@ function parseAntigravityCredentials(value) {
4143
4210
  if (!(credentials.access || credentials.access_token || credentials.refresh || credentials.refresh_token)) throw new Error("Antigravity credential tokens are missing");
4144
4211
  return credentials;
4145
4212
  }
4146
- function credentialAccount$3(filePath) {
4213
+ function credentialAccount$4(filePath) {
4147
4214
  return createHash("sha256").update(path.resolve(filePath)).digest("hex");
4148
4215
  }
4149
- function createCredentialBackend$3(filePath) {
4216
+ function createCredentialBackend$4(filePath) {
4150
4217
  if (process.platform === "win32") return new WindowsDpapiCredentialStore(`${filePath}.dpapi`, parseAntigravityCredentials);
4151
- if (process.platform === "darwin") return new MacKeychainCredentialStore("dsh-antigravity", credentialAccount$3(filePath), parseAntigravityCredentials);
4152
- if (process.platform === "linux") return new SecretServiceCredentialStore("dsh-antigravity", credentialAccount$3(filePath), parseAntigravityCredentials);
4218
+ if (process.platform === "darwin") return new MacKeychainCredentialStore("dsh-antigravity", credentialAccount$4(filePath), parseAntigravityCredentials);
4219
+ if (process.platform === "linux") return new SecretServiceCredentialStore("dsh-antigravity", credentialAccount$4(filePath), parseAntigravityCredentials);
4153
4220
  throw new Error("Antigravity encrypted credential storage requires Windows, macOS, or Linux.");
4154
4221
  }
4155
4222
  const credentialOperations$3 = /* @__PURE__ */ new Map();
@@ -4157,13 +4224,13 @@ const credentialOperations$3 = /* @__PURE__ */ new Map();
4157
4224
  var FileCredentialStore$2 = class {
4158
4225
  filePath;
4159
4226
  backend;
4160
- constructor(filePath = credentialPath$2(), backend = createCredentialBackend$3(filePath)) {
4227
+ constructor(filePath = credentialPath$2(), backend = createCredentialBackend$4(filePath)) {
4161
4228
  this.filePath = filePath;
4162
4229
  this.backend = backend;
4163
4230
  }
4164
4231
  path() {
4165
4232
  if (process.platform === "win32") return `${this.filePath}.dpapi`;
4166
- return `${process.platform === "darwin" ? "Keychain" : "Secret Service"}: dsh-antigravity/${credentialAccount$3(this.filePath)}`;
4233
+ return `${process.platform === "darwin" ? "Keychain" : "Secret Service"}: dsh-antigravity/${credentialAccount$4(this.filePath)}`;
4167
4234
  }
4168
4235
  serialize(operation) {
4169
4236
  const key = path.resolve(this.filePath);
@@ -4877,15 +4944,15 @@ async function buildResponsesPayload(options, attachments, localRawImages = {},
4877
4944
  payload.tool_choice = "auto";
4878
4945
  payload.parallel_tool_calls = true;
4879
4946
  }
4880
- if (outputVerbosity !== null) payload.text = { verbosity: outputVerbosity };
4947
+ const providerVerbosity = codexDefaultOutputVerbosity(options.model);
4948
+ if (providerVerbosity !== void 0) payload.text = { verbosity: outputVerbosity ?? providerVerbosity };
4881
4949
  if (fastMode) payload.service_tier = "priority";
4882
- const modelMaxTokens = codexModelMaxTokens(options.model);
4883
- payload.max_output_tokens = options.maxTokens === void 0 ? modelMaxTokens : Math.min(options.maxTokens, modelMaxTokens);
4884
4950
  if (options.reasoningEffort !== void 0) {
4885
4951
  const effort = codexWireReasoningEffort(options.model, options.reasoningEffort);
4886
- payload.reasoning = codexModelSupportsReasoningSummary(options.model) ? {
4952
+ const summary = reasoningSummary === null || reasoningSummary === "none" ? void 0 : reasoningSummary;
4953
+ payload.reasoning = summary !== void 0 && codexModelSupportsReasoningSummary(options.model) ? {
4887
4954
  effort,
4888
- summary: reasoningSummary ?? "auto"
4955
+ summary
4889
4956
  } : { effort };
4890
4957
  }
4891
4958
  return payload;
@@ -6408,13 +6475,13 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
6408
6475
  }
6409
6476
  try {
6410
6477
  switch (url.pathname) {
6411
- case `${ROUTE_PREFIX$4}/login/start`:
6478
+ case `${ROUTE_PREFIX$5}/login/start`:
6412
6479
  json(response, {
6413
6480
  ok: true,
6414
6481
  value: await oauth.startLogin()
6415
6482
  });
6416
6483
  return;
6417
- case `${ROUTE_PREFIX$4}/login/cancel`: {
6484
+ case `${ROUTE_PREFIX$5}/login/cancel`: {
6418
6485
  const loginId = field(body, "loginId");
6419
6486
  if (loginId === null) throw new Error("missing loginId");
6420
6487
  oauth.cancelLogin(loginId);
@@ -6424,7 +6491,7 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
6424
6491
  });
6425
6492
  return;
6426
6493
  }
6427
- case `${ROUTE_PREFIX$4}/accounts`: {
6494
+ case `${ROUTE_PREFIX$5}/accounts`: {
6428
6495
  if (accountPool === void 0) throw new Error("The ChatGPT account pool is not installed.");
6429
6496
  const action = field(body, "action");
6430
6497
  const accountId = field(body, "accountId");
@@ -6446,7 +6513,7 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
6446
6513
  });
6447
6514
  return;
6448
6515
  }
6449
- case `${ROUTE_PREFIX$4}/logout`:
6516
+ case `${ROUTE_PREFIX$5}/logout`:
6450
6517
  await oauth.logout(field(body, "accountId") ?? void 0);
6451
6518
  usage.clear();
6452
6519
  json(response, {
@@ -6454,7 +6521,7 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
6454
6521
  value: { authenticated: false }
6455
6522
  });
6456
6523
  return;
6457
- case `${ROUTE_PREFIX$4}/token/refresh`: {
6524
+ case `${ROUTE_PREFIX$5}/token/refresh`: {
6458
6525
  const oauthStatus = await oauth.refresh();
6459
6526
  json(response, {
6460
6527
  ok: true,
@@ -6466,27 +6533,27 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
6466
6533
  });
6467
6534
  return;
6468
6535
  }
6469
- case `${ROUTE_PREFIX$4}/quota/refresh`:
6536
+ case `${ROUTE_PREFIX$5}/quota/refresh`:
6470
6537
  if (!(await oauth.status()).authenticated) throw new Error("not authenticated");
6471
6538
  json(response, {
6472
6539
  ok: true,
6473
6540
  value: await usage.status(true, true)
6474
6541
  });
6475
6542
  return;
6476
- case `${ROUTE_PREFIX$4}/quota/reset-credit/use`:
6543
+ case `${ROUTE_PREFIX$5}/quota/reset-credit/use`:
6477
6544
  if (!(await oauth.status()).authenticated) throw new Error("not authenticated");
6478
6545
  json(response, {
6479
6546
  ok: true,
6480
6547
  value: await usage.consumeResetCredit()
6481
6548
  });
6482
6549
  return;
6483
- case `${ROUTE_PREFIX$4}/connection/test`:
6550
+ case `${ROUTE_PREFIX$5}/connection/test`:
6484
6551
  json(response, {
6485
6552
  ok: true,
6486
6553
  value: await usage.testConnection()
6487
6554
  });
6488
6555
  return;
6489
- case `${ROUTE_PREFIX$4}/preferences/update`: {
6556
+ case `${ROUTE_PREFIX$5}/preferences/update`: {
6490
6557
  const patch = readPreferencesUpdate(body, preferences.status());
6491
6558
  const value = await preferences.update(patch);
6492
6559
  if (patch.visibleModelIds !== void 0 || patch.enabled !== void 0 || patch.contextWindowOverrides !== void 0) ctx.emit?.("llm/adapters-updated");
@@ -6563,11 +6630,11 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
6563
6630
  };
6564
6631
  const disposers = [ctx.webServer.register({
6565
6632
  kind: "prefix",
6566
- path: ROUTE_PREFIX$4,
6633
+ path: ROUTE_PREFIX$5,
6567
6634
  handler
6568
6635
  }), ctx.webServer.register({
6569
6636
  kind: "exact",
6570
- path: `${ROUTE_PREFIX$4}/login/events`,
6637
+ path: `${ROUTE_PREFIX$5}/login/events`,
6571
6638
  handler: events
6572
6639
  })];
6573
6640
  return () => {
@@ -6675,6 +6742,27 @@ function statusFor(error) {
6675
6742
  }
6676
6743
  //#endregion
6677
6744
  //#region src/host/search-provider-switcher.ts
6745
+ /**
6746
+ * Cordis fiber states, mirrored from its `FiberState` const enum.
6747
+ *
6748
+ * It is a const enum, so importing the values would inline them here anyway and
6749
+ * the numbers would be duplicated in the emitted JavaScript either way. Naming
6750
+ * them locally keeps this module free of a type-only import that a consumer's
6751
+ * build would still have to resolve, and the values are asserted against the
6752
+ * installed cordis in the test below.
6753
+ */
6754
+ const FIBER_LOADING = 1;
6755
+ const FIBER_ACTIVE = 2;
6756
+ const FIBER_UNLOADING = 5;
6757
+ /** How long to wait for a restarted entry to become active again. */
6758
+ const RESTART_TIMEOUT_MS = 1e4;
6759
+ /** Gap between restart-state samples. */
6760
+ const RESTART_POLL_MS = 10;
6761
+ function delay(ms) {
6762
+ return new Promise((resolve) => {
6763
+ setTimeout(resolve, ms);
6764
+ });
6765
+ }
6678
6766
  var SearchProviderSwitcher = class {
6679
6767
  loader;
6680
6768
  originalSearchProvider;
@@ -6733,15 +6821,40 @@ var SearchProviderSwitcher = class {
6733
6821
  else nextConfig.fetchProvider = nextFetch;
6734
6822
  this.state = "applying";
6735
6823
  try {
6736
- if (configured && entry.fiber) await entry.fiber.update(nextConfig, true);
6824
+ if (configured && entry.fiber) entry.fiber.update(nextConfig, true);
6737
6825
  else await entry.update({ config: nextConfig });
6738
- await entry.fiber?.await();
6826
+ await this.awaitSettled(entry);
6739
6827
  this.state = "applied";
6740
6828
  } catch (error) {
6741
6829
  this.state = "failed";
6742
6830
  throw error;
6743
6831
  }
6744
6832
  }
6833
+ /**
6834
+ * Wait until the entry's fiber has finished restarting, not just until it
6835
+ * happens not to be mid-restart when asked.
6836
+ *
6837
+ * `Fiber.await()` alone is a single sample: it returns at once when `inertia`
6838
+ * is unset, which is exactly the state the fiber is in on the first tick after
6839
+ * `update()` queues its restart. Sampling that reports success while the entry
6840
+ * is still UNLOADING. This waits for a restart to actually be observed, then
6841
+ * lets `Fiber.await()` drain whatever queue is left, so a slow or multi-pass
6842
+ * restart cannot be reported as applied either.
6843
+ */
6844
+ async awaitSettled(entry) {
6845
+ const fiber = entry.fiber;
6846
+ if (fiber === void 0) return;
6847
+ const deadline = Date.now() + RESTART_TIMEOUT_MS;
6848
+ let sawRestart = false;
6849
+ while (Date.now() < deadline) {
6850
+ const state = fiber.state;
6851
+ if (state === FIBER_LOADING || state === FIBER_UNLOADING) sawRestart = true;
6852
+ if (sawRestart && state === FIBER_ACTIVE) return;
6853
+ await fiber.await();
6854
+ if (fiber.state === FIBER_ACTIVE) return;
6855
+ await delay(RESTART_POLL_MS);
6856
+ }
6857
+ }
6745
6858
  findWebEntry() {
6746
6859
  for (const entry of this.loader.entries()) if (entry.options.id === "web" || entry.options.name === "@deepseek-ai/dsh-web") return entry;
6747
6860
  return null;
@@ -7971,7 +8084,7 @@ function buildRequest$2(options, model, projectId, runtimeModel, effort, images
7971
8084
  requestId: `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
7972
8085
  };
7973
8086
  }
7974
- function createStreamState$5() {
8087
+ function createStreamState$6() {
7975
8088
  return {
7976
8089
  blocks: [],
7977
8090
  replayBlocks: [],
@@ -8032,7 +8145,7 @@ function processStreamLine$2(line, state) {
8032
8145
  const json = line.slice(5).trim();
8033
8146
  if (json === "[DONE]") {
8034
8147
  state.done = true;
8035
- return closeStream$4(state);
8148
+ return closeStream$5(state);
8036
8149
  }
8037
8150
  if (!json) return [];
8038
8151
  const chunk = safeJsonParse$5(json);
@@ -8149,7 +8262,7 @@ function processStreamLine$2(line, state) {
8149
8262
  }
8150
8263
  return out;
8151
8264
  }
8152
- function closeStream$4(state) {
8265
+ function closeStream$5(state) {
8153
8266
  if (state.finished) return [];
8154
8267
  if (!state.finishReason && !state.done) throw new LlmError$1("Antigravity stream ended before its terminal response", "PROVIDER_ERROR");
8155
8268
  state.finished = true;
@@ -8163,7 +8276,7 @@ function closeStream$4(state) {
8163
8276
  type: "finish",
8164
8277
  reason,
8165
8278
  replayState: {
8166
- response: { provider: PROVIDER_ID$5 },
8279
+ response: { provider: PROVIDER_ID$6 },
8167
8280
  blocks: state.replayBlocks
8168
8281
  }
8169
8282
  });
@@ -8371,7 +8484,7 @@ var AntigravityAdapter = class extends LlmAdapter {
8371
8484
  providerInfo(provider) {
8372
8485
  return {
8373
8486
  id: provider,
8374
- name: PROVIDER_NAME$5
8487
+ name: PROVIDER_NAME$6
8375
8488
  };
8376
8489
  }
8377
8490
  providerRetryPolicy() {}
@@ -8389,7 +8502,7 @@ var AntigravityAdapter = class extends LlmAdapter {
8389
8502
  name: model.name,
8390
8503
  inputModalities: model.inputModalities,
8391
8504
  context: { contextWindow: overrides[model.id] || model.contextWindow },
8392
- defaultMaxTokens: model.maxTokens,
8505
+ ...outputReservation(overrides[model.id] || model.contextWindow, model.maxTokens),
8393
8506
  ...model.reasoningEfforts ? { reasoningEfforts: model.reasoningEfforts } : {}
8394
8507
  }));
8395
8508
  }
@@ -8416,7 +8529,7 @@ var AntigravityAdapter = class extends LlmAdapter {
8416
8529
  name: model.name,
8417
8530
  inputModalities: model.inputModalities,
8418
8531
  context: { contextWindow: overrides[model.id] || model.contextWindow },
8419
- defaultMaxTokens: model.maxTokens,
8532
+ ...outputReservation(overrides[model.id] || model.contextWindow, model.maxTokens),
8420
8533
  ...model.reasoningEfforts ? { reasoning: {
8421
8534
  efforts: efforts.map((effort) => ({
8422
8535
  id: ReasoningEffortId(effort),
@@ -8446,7 +8559,10 @@ var AntigravityAdapter = class extends LlmAdapter {
8446
8559
  ...options,
8447
8560
  reasoningEffort: effectiveEffort
8448
8561
  } : options;
8449
- yield* wrapStreamWithWatchdog((watchdogSignal) => this.requestStream(effectiveOptions, model, watchdogSignal), options.signal, STREAM_IDLE_TIMEOUT_MS$5, STREAM_IDLE_TIMEOUT_CODE$6, "Antigravity");
8562
+ yield* wrapStreamWithWatchdog((watchdogSignal) => this.requestStream({
8563
+ ...effectiveOptions,
8564
+ maxTokens: options.maxTokens ?? model.maxTokens
8565
+ }, model, watchdogSignal), options.signal, STREAM_IDLE_TIMEOUT_MS$5, STREAM_IDLE_TIMEOUT_CODE$6, "Antigravity");
8450
8566
  }
8451
8567
  async *requestStream(options, model, signal) {
8452
8568
  const fetchFn = this.options.fetchFn ?? fetch;
@@ -8519,7 +8635,7 @@ var AntigravityAdapter = class extends LlmAdapter {
8519
8635
  if (!response.body) throw new LlmError("Antigravity returned empty response body", "PROVIDER_ERROR");
8520
8636
  const reader = response.body.getReader();
8521
8637
  const decoder = new TextDecoder();
8522
- const state = createStreamState$5();
8638
+ const state = createStreamState$6();
8523
8639
  let buffer = "";
8524
8640
  try {
8525
8641
  while (true) {
@@ -8541,7 +8657,7 @@ var AntigravityAdapter = class extends LlmAdapter {
8541
8657
  const chunks = processStreamLine$2(buffer.trim(), state);
8542
8658
  for (const chunk of chunks) yield chunk;
8543
8659
  }
8544
- for (const chunk of closeStream$4(state)) yield chunk;
8660
+ for (const chunk of closeStream$5(state)) yield chunk;
8545
8661
  } finally {
8546
8662
  reader.cancel().catch(() => void 0);
8547
8663
  }
@@ -8606,17 +8722,17 @@ var QuotaRefresh = class {
8606
8722
  };
8607
8723
  //#endregion
8608
8724
  //#region src/host/antigravity/routes.ts
8609
- function sendJson$7(response, status, body) {
8725
+ function sendJson$8(response, status, body) {
8610
8726
  response.writeHead(status, { "Content-Type": "application/json" });
8611
8727
  response.end(JSON.stringify(body));
8612
8728
  }
8613
- function sendMethodNotAllowed$5(response) {
8614
- sendJson$7(response, 405, {
8729
+ function sendMethodNotAllowed$6(response) {
8730
+ sendJson$8(response, 405, {
8615
8731
  ok: false,
8616
8732
  error: "Method Not Allowed"
8617
8733
  });
8618
8734
  }
8619
- async function readRequestJson$6(request) {
8735
+ async function readRequestJson$7(request) {
8620
8736
  return new Promise((resolve, reject) => {
8621
8737
  let raw = "";
8622
8738
  request.on("data", (chunk) => {
@@ -8676,7 +8792,7 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
8676
8792
  const path = new URL(request.url || "/", "http://dsh.local").pathname.replace(/^\/antigravity\/api\/?/, "");
8677
8793
  try {
8678
8794
  if (path === "status" || path === "") {
8679
- if (request.method !== "GET") return sendMethodNotAllowed$5(response);
8795
+ if (request.method !== "GET") return sendMethodNotAllowed$6(response);
8680
8796
  const credentials = await store.read();
8681
8797
  const authenticated = !!(credentials?.access || credentials?.access_token);
8682
8798
  const cached = getCachedQuota();
@@ -8685,7 +8801,7 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
8685
8801
  if (cached === void 0) await quotaRefresh.run(refresh);
8686
8802
  else quotaRefresh.start(refresh);
8687
8803
  }
8688
- return sendJson$7(response, 200, {
8804
+ return sendJson$8(response, 200, {
8689
8805
  ok: true,
8690
8806
  value: {
8691
8807
  ...await getAntigravityWebStatus(store, modelSettings, preferences, accountPool),
@@ -8694,8 +8810,8 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
8694
8810
  });
8695
8811
  }
8696
8812
  if (path === "login" || path === "accounts/login") {
8697
- if (request.method !== "POST") return sendMethodNotAllowed$5(response);
8698
- return sendJson$7(response, 200, {
8813
+ if (request.method !== "POST") return sendMethodNotAllowed$6(response);
8814
+ return sendJson$8(response, 200, {
8699
8815
  ok: true,
8700
8816
  value: await beginWebLogin$2(store, fetchFn, void 0, async (creds) => {
8701
8817
  await accountPool.addAccount(creds);
@@ -8703,19 +8819,19 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
8703
8819
  });
8704
8820
  }
8705
8821
  if (path === "login/status") {
8706
- if (request.method !== "GET") return sendMethodNotAllowed$5(response);
8707
- return sendJson$7(response, 200, {
8822
+ if (request.method !== "GET") return sendMethodNotAllowed$6(response);
8823
+ return sendJson$8(response, 200, {
8708
8824
  ok: true,
8709
8825
  value: getWebLoginStatus$3()
8710
8826
  });
8711
8827
  }
8712
8828
  if (path === "accounts") {
8713
- if (request.method === "GET") return sendJson$7(response, 200, {
8829
+ if (request.method === "GET") return sendJson$8(response, 200, {
8714
8830
  ok: true,
8715
8831
  value: await accountPool.listAccounts()
8716
8832
  });
8717
8833
  if (request.method === "POST") {
8718
- const body = await readRequestJson$6(request);
8834
+ const body = await readRequestJson$7(request);
8719
8835
  const action = String(body.action || "");
8720
8836
  if (action === "set-primary" && typeof body.accountId === "string") await accountPool.setPrimary(body.accountId);
8721
8837
  else if (action === "set-alias" && typeof body.accountId === "string" && typeof body.alias === "string") await accountPool.setAlias(body.accountId, body.alias);
@@ -8723,17 +8839,17 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
8723
8839
  else if (action === "strategy" && (body.strategy === "sequential" || body.strategy === "round-robin" || body.strategy === "sticky")) await accountPool.setStrategy(body.strategy);
8724
8840
  else if (action === "clear-cooldown" && typeof body.accountId === "string") await accountPool.clearCooldown(body.accountId);
8725
8841
  clearCachedQuota();
8726
- return sendJson$7(response, 200, {
8842
+ return sendJson$8(response, 200, {
8727
8843
  ok: true,
8728
8844
  value: await getAntigravityWebStatus(store, modelSettings, preferences, accountPool)
8729
8845
  });
8730
8846
  }
8731
- return sendMethodNotAllowed$5(response);
8847
+ return sendMethodNotAllowed$6(response);
8732
8848
  }
8733
8849
  if (path === "quota") {
8734
- if (request.method !== "GET" && request.method !== "POST") return sendMethodNotAllowed$5(response);
8850
+ if (request.method !== "GET" && request.method !== "POST") return sendMethodNotAllowed$6(response);
8735
8851
  const quota = await fetchAccountQuota(store, modelSettings, fetchFn, true);
8736
- return sendJson$7(response, 200, {
8852
+ return sendJson$8(response, 200, {
8737
8853
  ok: true,
8738
8854
  value: {
8739
8855
  ...await getAntigravityWebStatus(store, modelSettings, preferences, accountPool),
@@ -8742,38 +8858,38 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
8742
8858
  });
8743
8859
  }
8744
8860
  if (path === "settings") {
8745
- if (request.method !== "POST") return sendMethodNotAllowed$5(response);
8746
- const body = await readRequestJson$6(request);
8861
+ if (request.method !== "POST") return sendMethodNotAllowed$6(response);
8862
+ const body = await readRequestJson$7(request);
8747
8863
  if (preferences) await preferences.update(body);
8748
8864
  else await modelSettings.updateSettings(body);
8749
8865
  if (body.enabledModelIds !== void 0 || body.enabled !== void 0) ctx.emit?.("llm/adapters-updated");
8750
- return sendJson$7(response, 200, {
8866
+ return sendJson$8(response, 200, {
8751
8867
  ok: true,
8752
8868
  value: await getAntigravityWebStatus(store, modelSettings, preferences, accountPool)
8753
8869
  });
8754
8870
  }
8755
8871
  if (path === "models") {
8756
- if (request.method === "GET") return sendJson$7(response, 200, {
8872
+ if (request.method === "GET") return sendJson$8(response, 200, {
8757
8873
  ok: true,
8758
8874
  value: (await getAntigravityWebStatus(store, modelSettings, preferences, accountPool)).models
8759
8875
  });
8760
8876
  if (request.method === "POST") {
8761
- const body = await readRequestJson$6(request);
8877
+ const body = await readRequestJson$7(request);
8762
8878
  if (Array.isArray(body.enabledModelIds) || body.enabled !== void 0 || body.contextWindowOverrides || body.defaultReasoningEffort !== void 0) {
8763
8879
  if (preferences) await preferences.update(body);
8764
8880
  else await modelSettings.updateSettings(body);
8765
8881
  if (body.enabledModelIds !== void 0 || body.enabled !== void 0) ctx.emit?.("llm/adapters-updated");
8766
8882
  }
8767
- return sendJson$7(response, 200, {
8883
+ return sendJson$8(response, 200, {
8768
8884
  ok: true,
8769
8885
  value: await getAntigravityWebStatus(store, modelSettings, preferences, accountPool)
8770
8886
  });
8771
8887
  }
8772
- return sendMethodNotAllowed$5(response);
8888
+ return sendMethodNotAllowed$6(response);
8773
8889
  }
8774
8890
  if (path === "logout") {
8775
- if (request.method !== "POST") return sendMethodNotAllowed$5(response);
8776
- const body = await readRequestJson$6(request).catch(() => ({}));
8891
+ if (request.method !== "POST") return sendMethodNotAllowed$6(response);
8892
+ const body = await readRequestJson$7(request).catch(() => ({}));
8777
8893
  const targetId = typeof body.accountId === "string" ? body.accountId : void 0;
8778
8894
  if (targetId) await accountPool.deleteAccount(targetId);
8779
8895
  else {
@@ -8782,17 +8898,17 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
8782
8898
  else await store.delete();
8783
8899
  }
8784
8900
  clearCachedQuota();
8785
- return sendJson$7(response, 200, {
8901
+ return sendJson$8(response, 200, {
8786
8902
  ok: true,
8787
8903
  value: await getAntigravityWebStatus(store, modelSettings, preferences, accountPool)
8788
8904
  });
8789
8905
  }
8790
- return sendJson$7(response, 404, {
8906
+ return sendJson$8(response, 404, {
8791
8907
  ok: false,
8792
8908
  error: "not-found"
8793
8909
  });
8794
8910
  } catch (err) {
8795
- return sendJson$7(response, 500, {
8911
+ return sendJson$8(response, 500, {
8796
8912
  ok: false,
8797
8913
  error: err instanceof Error ? err.message : String(err)
8798
8914
  });
@@ -8804,6 +8920,20 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
8804
8920
  //#region src/host/command-code/model-catalog.ts
8805
8921
  /** Every model the Command Code registry describes, in registry order. */
8806
8922
  const COMMAND_CODE_MODELS = [
8923
+ {
8924
+ id: "claude-sonnet-5-5",
8925
+ name: "Claude Sonnet 5.5",
8926
+ inputModalities: ["text", "image"],
8927
+ reasoningEfforts: [
8928
+ "low",
8929
+ "medium",
8930
+ "high",
8931
+ "xhigh",
8932
+ "max"
8933
+ ],
8934
+ contextWindow: 1e6,
8935
+ maxTokens: null
8936
+ },
8807
8937
  {
8808
8938
  id: "claude-sonnet-5",
8809
8939
  name: "Claude Sonnet 5",
@@ -8860,6 +8990,20 @@ const COMMAND_CODE_MODELS = [
8860
8990
  contextWindow: 1e6,
8861
8991
  maxTokens: null
8862
8992
  },
8993
+ {
8994
+ id: "claude-opus-5-5",
8995
+ name: "Claude Opus 5.5",
8996
+ inputModalities: ["text", "image"],
8997
+ reasoningEfforts: [
8998
+ "low",
8999
+ "medium",
9000
+ "high",
9001
+ "xhigh",
9002
+ "max"
9003
+ ],
9004
+ contextWindow: 1e6,
9005
+ maxTokens: null
9006
+ },
8863
9007
  {
8864
9008
  id: "claude-opus-5",
8865
9009
  name: "Claude Opus 5",
@@ -8924,6 +9068,48 @@ const COMMAND_CODE_MODELS = [
8924
9068
  contextWindow: 105e4,
8925
9069
  maxTokens: null
8926
9070
  },
9071
+ {
9072
+ id: "gpt-6.1-sol",
9073
+ name: "GPT-6.1 Sol",
9074
+ inputModalities: ["text", "image"],
9075
+ reasoningEfforts: [
9076
+ "low",
9077
+ "medium",
9078
+ "high",
9079
+ "xhigh",
9080
+ "max"
9081
+ ],
9082
+ contextWindow: 105e4,
9083
+ maxTokens: null
9084
+ },
9085
+ {
9086
+ id: "gpt-6-sol",
9087
+ name: "GPT-6 Sol",
9088
+ inputModalities: ["text", "image"],
9089
+ reasoningEfforts: [
9090
+ "low",
9091
+ "medium",
9092
+ "high",
9093
+ "xhigh",
9094
+ "max"
9095
+ ],
9096
+ contextWindow: 105e4,
9097
+ maxTokens: null
9098
+ },
9099
+ {
9100
+ id: "gpt-6-luna",
9101
+ name: "GPT-6 Luna",
9102
+ inputModalities: ["text", "image"],
9103
+ reasoningEfforts: [
9104
+ "low",
9105
+ "medium",
9106
+ "high",
9107
+ "xhigh",
9108
+ "max"
9109
+ ],
9110
+ contextWindow: 105e4,
9111
+ maxTokens: null
9112
+ },
8927
9113
  {
8928
9114
  id: "gpt-5.6-sol",
8929
9115
  name: "GPT-5.6 Sol",
@@ -9021,7 +9207,11 @@ const COMMAND_CODE_MODELS = [
9021
9207
  id: "deepseek/deepseek-v4-pro",
9022
9208
  name: "DeepSeek V4 Pro (latest)",
9023
9209
  inputModalities: ["text"],
9024
- reasoningEfforts: ["high", "max"],
9210
+ reasoningEfforts: [
9211
+ "off",
9212
+ "high",
9213
+ "max"
9214
+ ],
9025
9215
  contextWindow: 1e6,
9026
9216
  maxTokens: null
9027
9217
  },
@@ -9029,7 +9219,11 @@ const COMMAND_CODE_MODELS = [
9029
9219
  id: "deepseek/deepseek-v4-flash",
9030
9220
  name: "DeepSeek V4 Flash (latest)",
9031
9221
  inputModalities: ["text"],
9032
- reasoningEfforts: ["high", "max"],
9222
+ reasoningEfforts: [
9223
+ "off",
9224
+ "high",
9225
+ "max"
9226
+ ],
9033
9227
  contextWindow: 1e6,
9034
9228
  maxTokens: null
9035
9229
  },
@@ -9037,7 +9231,11 @@ const COMMAND_CODE_MODELS = [
9037
9231
  id: "deepseek/deepseek-v4-flash-vision-exp",
9038
9232
  name: "DeepSeek V4 Flash Vision (exp)",
9039
9233
  inputModalities: ["text", "image"],
9040
- reasoningEfforts: ["high", "max"],
9234
+ reasoningEfforts: [
9235
+ "off",
9236
+ "high",
9237
+ "max"
9238
+ ],
9041
9239
  contextWindow: 1e6,
9042
9240
  maxTokens: null
9043
9241
  },
@@ -9058,6 +9256,20 @@ const COMMAND_CODE_MODELS = [
9058
9256
  name: "DeepSeek V4.1 Flash",
9059
9257
  inputModalities: ["text", "image"],
9060
9258
  reasoningEfforts: [
9259
+ "off",
9260
+ "low",
9261
+ "high",
9262
+ "max"
9263
+ ],
9264
+ contextWindow: 1e6,
9265
+ maxTokens: null
9266
+ },
9267
+ {
9268
+ id: "deepseek/deepseek-v4.1-flash-fast",
9269
+ name: "DeepSeek V4.1 Flash Fast",
9270
+ inputModalities: ["text", "image"],
9271
+ reasoningEfforts: [
9272
+ "off",
9061
9273
  "low",
9062
9274
  "high",
9063
9275
  "max"
@@ -9121,6 +9333,18 @@ const COMMAND_CODE_MODELS = [
9121
9333
  contextWindow: 1048576,
9122
9334
  maxTokens: 131072
9123
9335
  },
9336
+ {
9337
+ id: "z-ai/glm-5.3-flashx",
9338
+ name: "GLM-5.3 FlashX",
9339
+ inputModalities: ["text", "image"],
9340
+ reasoningEfforts: [
9341
+ "low",
9342
+ "high",
9343
+ "max"
9344
+ ],
9345
+ contextWindow: 1e6,
9346
+ maxTokens: 131072
9347
+ },
9124
9348
  {
9125
9349
  id: "zai-org/GLM-5.3",
9126
9350
  name: "GLM-5.3",
@@ -9154,7 +9378,7 @@ const COMMAND_CODE_MODELS = [
9154
9378
  name: "GLM-5.1",
9155
9379
  inputModalities: ["text"],
9156
9380
  reasoningEfforts: [],
9157
- contextWindow: null,
9381
+ contextWindow: 2e5,
9158
9382
  maxTokens: null
9159
9383
  },
9160
9384
  {
@@ -9182,35 +9406,39 @@ const COMMAND_CODE_MODELS = [
9182
9406
  name: "MiniMax M2.7",
9183
9407
  inputModalities: ["text"],
9184
9408
  reasoningEfforts: [],
9185
- contextWindow: null,
9409
+ contextWindow: 2e5,
9410
+ maxTokens: null
9411
+ },
9412
+ {
9413
+ id: "MiniMaxAI/MiniMax-M2.5",
9414
+ name: "MiniMax M2.5",
9415
+ inputModalities: ["text"],
9416
+ reasoningEfforts: [],
9417
+ contextWindow: 2e5,
9186
9418
  maxTokens: null
9187
9419
  },
9188
9420
  {
9189
- id: "minimax/minimax-m3-free",
9190
- name: "MiniMax M3 (Free)",
9421
+ id: "xiaomi/mimo-v2.6-pro",
9422
+ name: "MiMo V2.6 Pro",
9191
9423
  inputModalities: ["text", "image"],
9192
- reasoningEfforts: [
9193
- "low",
9194
- "medium",
9195
- "high"
9196
- ],
9197
- contextWindow: 1e6,
9424
+ reasoningEfforts: [],
9425
+ contextWindow: 1048576,
9198
9426
  maxTokens: null
9199
9427
  },
9200
9428
  {
9201
- id: "minimax/minimax-m2.7-free",
9202
- name: "MiniMax M2.7 (Free)",
9203
- inputModalities: ["text"],
9429
+ id: "xiaomi/mimo-v2.6-pro-ultraspeed",
9430
+ name: "MiMo V2.6 Pro UltraSpeed",
9431
+ inputModalities: ["text", "image"],
9204
9432
  reasoningEfforts: [],
9205
- contextWindow: 197e3,
9433
+ contextWindow: 1048576,
9206
9434
  maxTokens: null
9207
9435
  },
9208
9436
  {
9209
- id: "MiniMaxAI/MiniMax-M2.5",
9210
- name: "MiniMax M2.5",
9211
- inputModalities: ["text"],
9437
+ id: "xiaomi/mimo-v2.6-flash",
9438
+ name: "MiMo V2.6 Flash",
9439
+ inputModalities: ["text", "image"],
9212
9440
  reasoningEfforts: [],
9213
- contextWindow: 2e5,
9441
+ contextWindow: 1048576,
9214
9442
  maxTokens: null
9215
9443
  },
9216
9444
  {
@@ -9229,6 +9457,18 @@ const COMMAND_CODE_MODELS = [
9229
9457
  contextWindow: 1e6,
9230
9458
  maxTokens: null
9231
9459
  },
9460
+ {
9461
+ id: "Qwen/Qwen3.8-Omni-Flash",
9462
+ name: "Qwen 3.8 Omni Flash",
9463
+ inputModalities: ["text", "image"],
9464
+ reasoningEfforts: [
9465
+ "low",
9466
+ "medium",
9467
+ "xhigh"
9468
+ ],
9469
+ contextWindow: 1e6,
9470
+ maxTokens: 131072
9471
+ },
9232
9472
  {
9233
9473
  id: "Qwen/Qwen3.8-Max-0902",
9234
9474
  name: "Qwen 3.8 Max 0902",
@@ -9306,7 +9546,7 @@ const COMMAND_CODE_MODELS = [
9306
9546
  name: "Qwen 3.6 Max Preview",
9307
9547
  inputModalities: ["text"],
9308
9548
  reasoningEfforts: [],
9309
- contextWindow: null,
9549
+ contextWindow: 2e5,
9310
9550
  maxTokens: null
9311
9551
  },
9312
9552
  {
@@ -9314,17 +9554,29 @@ const COMMAND_CODE_MODELS = [
9314
9554
  name: "Qwen 3.6 Plus",
9315
9555
  inputModalities: ["text", "image"],
9316
9556
  reasoningEfforts: [],
9317
- contextWindow: null,
9557
+ contextWindow: 2e5,
9318
9558
  maxTokens: null
9319
9559
  },
9320
9560
  {
9321
- id: "meituan/LongCat-2.0:free",
9561
+ id: "meituan/LongCat-2.0",
9322
9562
  name: "LongCat 2.0",
9323
9563
  inputModalities: ["text"],
9324
9564
  reasoningEfforts: [],
9325
9565
  contextWindow: 1048576,
9326
9566
  maxTokens: null
9327
9567
  },
9568
+ {
9569
+ id: "stepfun/Step-5-Preview",
9570
+ name: "Step 5 Preview",
9571
+ inputModalities: ["text", "image"],
9572
+ reasoningEfforts: [
9573
+ "low",
9574
+ "medium",
9575
+ "high"
9576
+ ],
9577
+ contextWindow: 1e6,
9578
+ maxTokens: null
9579
+ },
9328
9580
  {
9329
9581
  id: "stepfun/Step-3.7-Flash",
9330
9582
  name: "Step 3.7 Flash",
@@ -9338,14 +9590,6 @@ const COMMAND_CODE_MODELS = [
9338
9590
  name: "Step 3.5 Flash",
9339
9591
  inputModalities: ["text"],
9340
9592
  reasoningEfforts: [],
9341
- contextWindow: 1e6,
9342
- maxTokens: null
9343
- },
9344
- {
9345
- id: "tencent/Hy3",
9346
- name: "Tencent Hy3 (Free)",
9347
- inputModalities: ["text"],
9348
- reasoningEfforts: [],
9349
9593
  contextWindow: 262144,
9350
9594
  maxTokens: null
9351
9595
  },
@@ -9473,6 +9717,19 @@ const COMMAND_CODE_MODELS = [
9473
9717
  contextWindow: 1e6,
9474
9718
  maxTokens: null
9475
9719
  },
9720
+ {
9721
+ id: "stealth/space-bunny-alpha",
9722
+ name: "Space Bunny Alpha",
9723
+ inputModalities: ["text", "image"],
9724
+ reasoningEfforts: [
9725
+ "low",
9726
+ "medium",
9727
+ "high",
9728
+ "max"
9729
+ ],
9730
+ contextWindow: 1e6,
9731
+ maxTokens: 524288
9732
+ },
9476
9733
  {
9477
9734
  id: "poolside/laguna-s-2.1-free",
9478
9735
  name: "Laguna S 2.1",
@@ -9482,18 +9739,22 @@ const COMMAND_CODE_MODELS = [
9482
9739
  maxTokens: 32768
9483
9740
  },
9484
9741
  {
9485
- id: "inclusionai/ling-3.0-flash-free",
9486
- name: "Ling 3.0 Flash",
9742
+ id: "inclusionai/ling-3.0-flash-sante:free",
9743
+ name: "Ling 3.0 Flash Sante",
9487
9744
  inputModalities: ["text"],
9488
9745
  reasoningEfforts: [],
9489
- contextWindow: 256e3,
9746
+ contextWindow: 262144,
9490
9747
  maxTokens: 32768
9491
9748
  },
9492
9749
  {
9493
- id: "inclusionai/ling-3.0-flash-sante:free",
9494
- name: "Ling 3.0 Flash Sante",
9750
+ id: "inclusionai/ling-3.1-flash:free",
9751
+ name: "Ling 3.1 Flash",
9495
9752
  inputModalities: ["text"],
9496
- reasoningEfforts: [],
9753
+ reasoningEfforts: [
9754
+ "low",
9755
+ "medium",
9756
+ "high"
9757
+ ],
9497
9758
  contextWindow: 262144,
9498
9759
  maxTokens: 32768
9499
9760
  },
@@ -9587,6 +9848,19 @@ const COMMAND_CODE_MODELS = [
9587
9848
  ],
9588
9849
  contextWindow: 5e5,
9589
9850
  maxTokens: null
9851
+ },
9852
+ {
9853
+ id: "xai/grok-4.7",
9854
+ name: "Grok 4.7",
9855
+ inputModalities: ["text", "image"],
9856
+ reasoningEfforts: [
9857
+ "low",
9858
+ "medium",
9859
+ "high",
9860
+ "xhigh"
9861
+ ],
9862
+ contextWindow: 5e5,
9863
+ maxTokens: null
9590
9864
  }
9591
9865
  ];
9592
9866
  /** Registry entry for one model id, or undefined when it describes no such model. */
@@ -9595,8 +9869,8 @@ function commandCodeModelDef(modelId) {
9595
9869
  }
9596
9870
  //#endregion
9597
9871
  //#region src/host/command-code/types.ts
9598
- const PROVIDER_ID$4 = "command-code";
9599
- const PROVIDER_NAME$4 = "Command Code";
9872
+ const PROVIDER_ID$5 = "command-code";
9873
+ const PROVIDER_NAME$5 = "Command Code";
9600
9874
  /** Browser sign-in page the CLI opens; it redirects back to our loopback server. */
9601
9875
  const STUDIO_PATH = "/studio/auth/cli";
9602
9876
  /** Query parameter that carries the loopback callback URL to the studio page. */
@@ -9674,11 +9948,11 @@ function providerUrl(env = resolveApiEnv()) {
9674
9948
  * @param modelId - exact model id from the live catalog or a caller.
9675
9949
  * @returns the wire dialect to build the request for.
9676
9950
  */
9677
- function wireForModel$1(modelId) {
9951
+ function wireForModel$2(modelId) {
9678
9952
  return /^claude[-/]/i.test(modelId.trim()) ? "anthropic" : "openai";
9679
9953
  }
9680
9954
  /**
9681
- * Reasoning levels one model advertises.
9955
+ * Reasoning levels one model advertises, in DSH's vocabulary.
9682
9956
  *
9683
9957
  * The registry is the authority: a model it describes but gives no
9684
9958
  * `reasoningEfforts` is a non-reasoning model, which is why that answers with an
@@ -9686,9 +9960,42 @@ function wireForModel$1(modelId) {
9686
9960
  * describe declares nothing either — guessing from the family name is exactly
9687
9961
  * how `deepseek-v4-flash` got treated as a reasoner with image input when it is
9688
9962
  * text-only with a different effort set.
9963
+ *
9964
+ * One level is translated rather than copied. Command Code names the
9965
+ * "do not think" level `off`; DSH names the same level `none` (as the Kimi and
9966
+ * Anthropic lines here already do). Both spellings are accepted on the way in —
9967
+ * a stored selection or a session started elsewhere may carry either — but the
9968
+ * adapter only ever advertises `none`, so the picker shows the level DSH
9969
+ * understands.
9970
+ *
9971
+ * `off` is also what the CLI's `thinkingHook` keys on to send no reasoning
9972
+ * field at all, and the OpenAI-family wire has no such level, so
9973
+ * {@link wireReasoningEffort} drops it before the request is built rather than
9974
+ * transmitting a value the endpoint would reject.
9689
9975
  */
9690
9976
  function reasoningEffortsFor$1(modelId) {
9691
- return [...commandCodeModelDef(modelId)?.reasoningEfforts ?? []];
9977
+ return (commandCodeModelDef(modelId)?.reasoningEfforts ?? []).map((effort) => effort === "off" ? DISABLED_THINKING_EFFORT : effort);
9978
+ }
9979
+ /**
9980
+ * The level that means "think as little as possible" on this route.
9981
+ *
9982
+ * DSH's spelling, matching the Kimi and Claude lines. Kept as a constant
9983
+ * because three separate places compare against it — the catalog translation,
9984
+ * the wire filter below, and the Anthropic thinking budget.
9985
+ */
9986
+ const DISABLED_THINKING_EFFORT = "none";
9987
+ /**
9988
+ * Whether one effort survives onto the provider wire.
9989
+ *
9990
+ * `none` is a real DSH level but not a Command Code one: it is expressed by
9991
+ * OMITTING the field, which is exactly what the official CLI does when its
9992
+ * effort is `off`. Every other level this route advertises passes through.
9993
+ */
9994
+ function wireReasoningEffort(effort) {
9995
+ if (effort === void 0 || effort === null) return void 0;
9996
+ const value = String(effort).trim();
9997
+ if (value === "" || value === "none" || value === "off") return void 0;
9998
+ return value;
9692
9999
  }
9693
10000
  /**
9694
10001
  * Accepted request modalities for one model.
@@ -9776,7 +10083,7 @@ function maxOutputTokensFor$4(modelId) {
9776
10083
  * from the registry rather than hand-listed, so a model cannot drift between the
9777
10084
  * offline fallback and its real capability entry.
9778
10085
  */
9779
- const FALLBACK_MODELS$3 = COMMAND_CODE_MODELS.filter((model) => model.contextWindow !== null).map((model) => ({
10086
+ const FALLBACK_MODELS$4 = COMMAND_CODE_MODELS.filter((model) => model.contextWindow !== null).map((model) => ({
9780
10087
  id: model.id,
9781
10088
  name: model.name,
9782
10089
  contextWindow: model.contextWindow
@@ -9785,6 +10092,7 @@ const FALLBACK_MODELS$3 = COMMAND_CODE_MODELS.filter((model) => model.contextWin
9785
10092
  //#region src/shared/command-code-contracts.ts
9786
10093
  /** Every level, in escalating order; the settings card renders exactly these. */
9787
10094
  const COMMAND_CODE_REASONING_EFFORTS = [
10095
+ "none",
9788
10096
  "minimal",
9789
10097
  "low",
9790
10098
  "medium",
@@ -9799,7 +10107,7 @@ const COMMAND_CODE_PREFERENCES_NAMESPACE = "dsh-command-code";
9799
10107
  function isReasoningEffort$3(value) {
9800
10108
  return typeof value === "string" && COMMAND_CODE_REASONING_EFFORTS.includes(value);
9801
10109
  }
9802
- const DEFAULT_ENABLED_MODEL_IDS$4 = FALLBACK_MODELS$3.map((model) => model.id);
10110
+ const DEFAULT_ENABLED_MODEL_IDS$4 = FALLBACK_MODELS$4.map((model) => model.id);
9803
10111
  /**
9804
10112
  * Bind the model selection to the DSH settings document when the harness still
9805
10113
  * offers one, and to the JSON file beside it otherwise: a harness without the
@@ -9865,7 +10173,7 @@ function credentialPath$1() {
9865
10173
  function modelSettingsPath$1() {
9866
10174
  return path.join(dshHomeDir$2(), "storages", "command-code-models.json");
9867
10175
  }
9868
- function optionalString$6(record, key) {
10176
+ function optionalString$8(record, key) {
9869
10177
  const value = record[key];
9870
10178
  if (value === void 0 || value === null) return void 0;
9871
10179
  if (typeof value !== "string") throw new Error("Command Code credential payload is invalid");
@@ -9886,7 +10194,7 @@ function parseCommandCodeCredentials(value) {
9886
10194
  "planLabel",
9887
10195
  "planId"
9888
10196
  ]) {
9889
- const parsed = optionalString$6(record, key);
10197
+ const parsed = optionalString$8(record, key);
9890
10198
  if (parsed !== void 0) credentials[key] = parsed;
9891
10199
  }
9892
10200
  const authenticatedAt = record.authenticatedAt;
@@ -9901,13 +10209,13 @@ function parseCommandCodeCredentials(value) {
9901
10209
  }
9902
10210
  return credentials;
9903
10211
  }
9904
- function credentialAccount$2(filePath) {
10212
+ function credentialAccount$3(filePath) {
9905
10213
  return createHash("sha256").update(path.resolve(filePath)).digest("hex");
9906
10214
  }
9907
- function createCredentialBackend$2(filePath) {
10215
+ function createCredentialBackend$3(filePath) {
9908
10216
  if (process.platform === "win32") return new WindowsDpapiCredentialStore(`${filePath}.dpapi`, parseCommandCodeCredentials);
9909
- if (process.platform === "darwin") return new MacKeychainCredentialStore(PROVIDER_ID$4, credentialAccount$2(filePath), parseCommandCodeCredentials);
9910
- if (process.platform === "linux") return new SecretServiceCredentialStore(PROVIDER_ID$4, credentialAccount$2(filePath), parseCommandCodeCredentials);
10217
+ if (process.platform === "darwin") return new MacKeychainCredentialStore(PROVIDER_ID$5, credentialAccount$3(filePath), parseCommandCodeCredentials);
10218
+ if (process.platform === "linux") return new SecretServiceCredentialStore(PROVIDER_ID$5, credentialAccount$3(filePath), parseCommandCodeCredentials);
9911
10219
  throw new Error("Command Code credential storage requires Windows, macOS, or Linux.");
9912
10220
  }
9913
10221
  const credentialOperations$2 = /* @__PURE__ */ new Map();
@@ -9915,13 +10223,13 @@ const credentialOperations$2 = /* @__PURE__ */ new Map();
9915
10223
  var FileCredentialStore$1 = class {
9916
10224
  filePath;
9917
10225
  backend;
9918
- constructor(filePath = credentialPath$1(), backend = createCredentialBackend$2(filePath)) {
10226
+ constructor(filePath = credentialPath$1(), backend = createCredentialBackend$3(filePath)) {
9919
10227
  this.filePath = filePath;
9920
10228
  this.backend = backend;
9921
10229
  }
9922
10230
  path() {
9923
10231
  if (process.platform === "win32") return `${this.filePath}.dpapi`;
9924
- return `${process.platform === "darwin" ? "Keychain" : "Secret Service"}: ${PROVIDER_ID$4}/${credentialAccount$2(this.filePath)}`;
10232
+ return `${process.platform === "darwin" ? "Keychain" : "Secret Service"}: ${PROVIDER_ID$5}/${credentialAccount$3(this.filePath)}`;
9925
10233
  }
9926
10234
  serialize(operation) {
9927
10235
  const key = path.resolve(this.filePath);
@@ -10136,7 +10444,7 @@ function commandCodeHeaders(apiKey, extra = {}) {
10136
10444
  authorization: `Bearer ${apiKey}`,
10137
10445
  "content-type": "application/json",
10138
10446
  accept: "application/json",
10139
- "user-agent": `${PROVIDER_ID$4}/${CLI_VERSION}`,
10447
+ "user-agent": `${PROVIDER_ID$5}/${CLI_VERSION}`,
10140
10448
  [HEADER_CLI_VERSION]: CLI_VERSION,
10141
10449
  [HEADER_CLI_ENVIRONMENT]: process.platform === "win32" ? "windows" : process.platform === "darwin" ? "macos" : "linux",
10142
10450
  [HEADER_PROJECT_SLUG]: "dsh-chatgpt-subscription",
@@ -10172,7 +10480,7 @@ function firstString$1(record, keys) {
10172
10480
  if (value !== void 0) return value;
10173
10481
  }
10174
10482
  }
10175
- function firstNumber$1(record, keys) {
10483
+ function firstNumber$2(record, keys) {
10176
10484
  for (const key of keys) {
10177
10485
  const value = asNumber$3(record[key]);
10178
10486
  if (value !== void 0) return value;
@@ -10344,12 +10652,12 @@ function parseWindowLimits(payload, consumed = []) {
10344
10652
  if (key === "limited" || key === "exceeded") continue;
10345
10653
  const record = asRecord$9(limits[key]);
10346
10654
  if (record === void 0) continue;
10347
- const cap = firstNumber$1(record, [
10655
+ const cap = firstNumber$2(record, [
10348
10656
  "cap",
10349
10657
  "limit",
10350
10658
  "total"
10351
10659
  ]);
10352
- const used = firstNumber$1(record, ["used", "consumed"]);
10660
+ const used = firstNumber$2(record, ["used", "consumed"]);
10353
10661
  if (cap === void 0 && used === void 0) continue;
10354
10662
  consumed.push(record);
10355
10663
  const usedFraction = cap !== void 0 && cap > 0 && used !== void 0 ? clamp01$2(used / cap) : null;
@@ -10379,9 +10687,9 @@ function parseCreditBalances(payload, consumed = []) {
10379
10687
  const root = asRecord$9(payload) ?? {};
10380
10688
  const credits = asRecord$9(deepValue(asRecord$9(root.data) ?? root, ["credits"]));
10381
10689
  if (credits === void 0) return [];
10382
- const monthly = firstNumber$1(credits, ["monthlyCredits", "monthly_credits"]);
10383
- const purchased = firstNumber$1(credits, ["purchasedCredits", "purchased_credits"]);
10384
- const free = firstNumber$1(credits, ["freeCredits", "free_credits"]);
10690
+ const monthly = firstNumber$2(credits, ["monthlyCredits", "monthly_credits"]);
10691
+ const purchased = firstNumber$2(credits, ["purchasedCredits", "purchased_credits"]);
10692
+ const free = firstNumber$2(credits, ["freeCredits", "free_credits"]);
10385
10693
  if (monthly === void 0 && purchased === void 0 && free === void 0) return [];
10386
10694
  consumed.push(credits);
10387
10695
  const meters = [];
@@ -10460,27 +10768,27 @@ function sweepMeters(payload, skipIds, consumed) {
10460
10768
  let anonymized = 0;
10461
10769
  for (const record of records) {
10462
10770
  if (consumed.has(record)) continue;
10463
- const limit = firstNumber$1(record, [
10771
+ const limit = firstNumber$2(record, [
10464
10772
  "limit",
10465
10773
  "quota",
10466
10774
  "allowance",
10467
10775
  "cap",
10468
10776
  "total"
10469
10777
  ]);
10470
- const used = firstNumber$1(record, [
10778
+ const used = firstNumber$2(record, [
10471
10779
  "used",
10472
10780
  "consumed",
10473
10781
  "spent"
10474
10782
  ]);
10475
- const remainingRaw = firstNumber$1(record, ["remaining", "left"]);
10476
- const balance = firstNumber$1(record, ["balance", "credits"]);
10477
- const usedPercentRaw = firstNumber$1(record, [
10783
+ const remainingRaw = firstNumber$2(record, ["remaining", "left"]);
10784
+ const balance = firstNumber$2(record, ["balance", "credits"]);
10785
+ const usedPercentRaw = firstNumber$2(record, [
10478
10786
  "usedPercent",
10479
10787
  "used_percent",
10480
10788
  "usagePercent",
10481
10789
  "percentUsed"
10482
10790
  ]);
10483
- const remainingPercentRaw = firstNumber$1(record, [
10791
+ const remainingPercentRaw = firstNumber$2(record, [
10484
10792
  "remainingPercent",
10485
10793
  "remaining_percent",
10486
10794
  "percentRemaining"
@@ -10604,7 +10912,7 @@ function parseUsageWindows$1(payload) {
10604
10912
  const windows = [];
10605
10913
  const seen = /* @__PURE__ */ new Set();
10606
10914
  for (const record of records) {
10607
- const percent = firstNumber$1(record, [
10915
+ const percent = firstNumber$2(record, [
10608
10916
  "usedPercent",
10609
10917
  "used_percent",
10610
10918
  "usagePercent",
@@ -10631,7 +10939,7 @@ function parseUsageWindows$1(payload) {
10631
10939
  "window"
10632
10940
  ]) ?? id,
10633
10941
  usedPercent: Math.round(clamp01$2(normalizePercent(percent) ?? 0) * 100),
10634
- windowDurationMins: firstNumber$1(record, [
10942
+ windowDurationMins: firstNumber$2(record, [
10635
10943
  "windowDurationMins",
10636
10944
  "window_minutes",
10637
10945
  "durationMins",
@@ -10707,7 +11015,7 @@ function parseProviderModels(payload) {
10707
11015
  models.push({
10708
11016
  id,
10709
11017
  name: firstString$1(record, ["displayName", "name"]) ?? id,
10710
- contextWindow: firstNumber$1(record, [
11018
+ contextWindow: firstNumber$2(record, [
10711
11019
  "context_length",
10712
11020
  "contextLength",
10713
11021
  "context_window",
@@ -10725,7 +11033,7 @@ async function fetchProviderModels(options = {}) {
10725
11033
  const response = await (options.fetchFn ?? fetch)(`${providerUrl(options.apiEnv ?? resolveApiEnv())}/models`, {
10726
11034
  headers: {
10727
11035
  accept: "application/json",
10728
- "user-agent": PROVIDER_NAME$4
11036
+ "user-agent": PROVIDER_NAME$5
10729
11037
  },
10730
11038
  signal: timeoutSignal$3(options.signal, DISCOVERY_TIMEOUT_MS$3)
10731
11039
  });
@@ -10948,7 +11256,7 @@ function buildModelOptions$2(catalog, enabledModelIds, overrides) {
10948
11256
  contextWindow: overrides[model.id] && overrides[model.id] > 0 ? overrides[model.id] : contextWindow,
10949
11257
  defaultMaxTokens: maxOutputTokensFor$4(model.id),
10950
11258
  ...efforts.length > 0 ? { reasoningEfforts: efforts } : {},
10951
- wire: wireForModel$1(model.id)
11259
+ wire: wireForModel$2(model.id)
10952
11260
  };
10953
11261
  });
10954
11262
  }
@@ -11137,14 +11445,14 @@ function imageBlockToInline$3(block, images) {
11137
11445
  data: resolved.data
11138
11446
  } : void 0;
11139
11447
  }
11140
- function textOf$4(content) {
11448
+ function textOf$5(content) {
11141
11449
  if (typeof content === "string") return sanitizeText$4(content);
11142
11450
  if (!Array.isArray(content)) return "";
11143
11451
  const parts = [];
11144
11452
  for (const block of content) {
11145
11453
  if (!isRecord$18(block)) continue;
11146
11454
  if (block.type === "text" && typeof block.text === "string") parts.push(sanitizeText$4(block.text));
11147
- else if (block.type === "tool-result") parts.push(textOf$4(block.content));
11455
+ else if (block.type === "tool-result") parts.push(textOf$5(block.content));
11148
11456
  }
11149
11457
  return parts.join("");
11150
11458
  }
@@ -11252,7 +11560,7 @@ function leadingSystemText$4(options) {
11252
11560
  if (typeof options.system === "string" && options.system.trim() !== "") parts.push(options.system);
11253
11561
  for (const message of options.messages) {
11254
11562
  if (message.role !== "system") continue;
11255
- const text = textOf$4(message.content);
11563
+ const text = textOf$5(message.content);
11256
11564
  if (text !== "") parts.push(text);
11257
11565
  }
11258
11566
  return parts.length === 0 ? void 0 : parts.join("\n\n");
@@ -11372,7 +11680,7 @@ function buildOpenAIRequest$1(options, images = NO_RESOLVED_IMAGES$4) {
11372
11680
  content
11373
11681
  });
11374
11682
  }
11375
- const effort = options.reasoningEffort === void 0 ? void 0 : String(options.reasoningEffort);
11683
+ const effort = wireReasoningEffort(options.reasoningEffort === void 0 ? void 0 : String(options.reasoningEffort));
11376
11684
  const maxTokens = options.maxTokens ?? maxOutputTokensFor$4(options.model);
11377
11685
  return {
11378
11686
  model: options.model,
@@ -11506,7 +11814,7 @@ function buildAnthropicRequest$1(options, images = NO_RESOLVED_IMAGES$4) {
11506
11814
  }
11507
11815
  const system = leadingSystemText$4(options);
11508
11816
  const maxTokens = options.maxTokens ?? maxOutputTokensFor$4(options.model);
11509
- const budget = thinkingBudgetFor$1(options.reasoningEffort === void 0 ? void 0 : String(options.reasoningEffort), maxTokens);
11817
+ const budget = thinkingBudgetFor$1(wireReasoningEffort(options.reasoningEffort === void 0 ? void 0 : String(options.reasoningEffort)), maxTokens);
11510
11818
  return {
11511
11819
  model: options.model,
11512
11820
  max_tokens: maxTokens,
@@ -11529,7 +11837,7 @@ function buildAnthropicRequest$1(options, images = NO_RESOLVED_IMAGES$4) {
11529
11837
  function buildRequest$1(options, wire, images = NO_RESOLVED_IMAGES$4) {
11530
11838
  return wire === "anthropic" ? buildAnthropicRequest$1(options, images) : buildOpenAIRequest$1(options, images);
11531
11839
  }
11532
- function createStreamState$4(wire) {
11840
+ function createStreamState$5(wire) {
11533
11841
  return {
11534
11842
  wire,
11535
11843
  blocks: [],
@@ -11565,7 +11873,7 @@ function closeCurrent$3(state) {
11565
11873
  block
11566
11874
  }];
11567
11875
  }
11568
- function closeToolCalls$3(state) {
11876
+ function closeToolCalls$4(state) {
11569
11877
  const out = [];
11570
11878
  for (const [wireIndex, call] of [...state.toolCalls.entries()].sort((a, b) => a[0] - b[0])) {
11571
11879
  const block = {
@@ -11610,7 +11918,7 @@ function processOpenAIStreamLine$1(line, state) {
11610
11918
  const payload = trimmed.slice(5).trim();
11611
11919
  if (payload === "[DONE]") {
11612
11920
  state.done = true;
11613
- return closeStream$3(state);
11921
+ return closeStream$4(state);
11614
11922
  }
11615
11923
  if (payload === "") return [];
11616
11924
  const chunk = safeJsonParse$4(payload);
@@ -11633,7 +11941,7 @@ function processOpenAIStreamLine$1(line, state) {
11633
11941
  if (delta) {
11634
11942
  const reasoning = asString$11(delta.reasoning_content) ?? asString$11(delta.reasoning);
11635
11943
  if (reasoning !== void 0 && reasoning !== "") {
11636
- out.push(...closeToolCalls$3(state));
11944
+ out.push(...closeToolCalls$4(state));
11637
11945
  if (state.current === null || state.current.type !== "reasoning") out.push(...openTextBlock$3(state, "reasoning"));
11638
11946
  state.current.text += sanitizeText$4(reasoning);
11639
11947
  state.hasContent = true;
@@ -11645,7 +11953,7 @@ function processOpenAIStreamLine$1(line, state) {
11645
11953
  }
11646
11954
  const content = asString$11(delta.content);
11647
11955
  if (content !== void 0 && content !== "") {
11648
- out.push(...closeToolCalls$3(state));
11956
+ out.push(...closeToolCalls$4(state));
11649
11957
  if (state.current === null || state.current.type !== "text") out.push(...openTextBlock$3(state, "text"));
11650
11958
  state.current.text += sanitizeText$4(content);
11651
11959
  state.hasContent = true;
@@ -11665,7 +11973,7 @@ function processOpenAIStreamLine$1(line, state) {
11665
11973
  if (finish !== void 0 && finish !== "") {
11666
11974
  state.finishReason = finish;
11667
11975
  out.push(...closeCurrent$3(state));
11668
- out.push(...closeToolCalls$3(state));
11976
+ out.push(...closeToolCalls$4(state));
11669
11977
  }
11670
11978
  return out;
11671
11979
  }
@@ -11883,7 +12191,7 @@ function processAnthropicStreamLine$1(line, state) {
11883
12191
  }
11884
12192
  if (type === "message_stop") {
11885
12193
  state.done = true;
11886
- return closeStream$3(state);
12194
+ return closeStream$4(state);
11887
12195
  }
11888
12196
  if (type === "error") throw new LlmError$1(`Command Code stream error: ${asString$11((isRecord$18(event.error) ? event.error : {}).message) ?? "unknown error"}`, "PROVIDER_ERROR");
11889
12197
  return out;
@@ -11904,10 +12212,10 @@ function finishReasonFor$4(state) {
11904
12212
  return { kind: "stop" };
11905
12213
  }
11906
12214
  /** Flush every open block, then emit usage and the terminal finish. */
11907
- function closeStream$3(state) {
12215
+ function closeStream$4(state) {
11908
12216
  if (state.finished) return [];
11909
12217
  state.finished = true;
11910
- const out = [...closeCurrent$3(state), ...closeToolCalls$3(state)];
12218
+ const out = [...closeCurrent$3(state), ...closeToolCalls$4(state)];
11911
12219
  if (state.sawUsage) out.push({
11912
12220
  type: "usage",
11913
12221
  usage: tokenUsage$4(state)
@@ -11937,7 +12245,7 @@ function assertStreamComplete$4(state) {
11937
12245
  * the set: `INVALID_CREDENTIAL` (a rejected key fails identically on every
11938
12246
  * attempt) and `ABORTED` (the caller already cancelled).
11939
12247
  */
11940
- const RETRY_POLICY$4 = resolveRetryPolicy({
12248
+ const RETRY_POLICY$5 = resolveRetryPolicy({
11941
12249
  mode: "normal",
11942
12250
  maxRetries: 3,
11943
12251
  retryableCodes: [
@@ -11957,7 +12265,7 @@ function resolveDefaultReasoningEffort$3(efforts, configuredEffort) {
11957
12265
  if (configuredEffort && efforts.includes(configuredEffort)) return ReasoningEffortId(configuredEffort);
11958
12266
  }
11959
12267
  /** Cooldown one rate-limited key takes when the provider states no delay. */
11960
- const POOL_COOLDOWN_MS$3 = 15 * 6e4;
12268
+ const POOL_COOLDOWN_MS$4 = 15 * 6e4;
11961
12269
  var CommandCodeAdapter = class extends LlmAdapter {
11962
12270
  store;
11963
12271
  modelSettings;
@@ -11983,11 +12291,11 @@ var CommandCodeAdapter = class extends LlmAdapter {
11983
12291
  providerInfo(provider) {
11984
12292
  return {
11985
12293
  id: provider,
11986
- name: PROVIDER_NAME$4
12294
+ name: PROVIDER_NAME$5
11987
12295
  };
11988
12296
  }
11989
12297
  providerRetryPolicy() {
11990
- return RETRY_POLICY$4;
12298
+ return RETRY_POLICY$5;
11991
12299
  }
11992
12300
  imageRequestPricing() {}
11993
12301
  settings() {
@@ -12003,7 +12311,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
12003
12311
  apiEnv: resolveApiEnv()
12004
12312
  })))().catch(() => []);
12005
12313
  if (live.length > 0) return live;
12006
- return FALLBACK_MODELS$3.map((model) => ({
12314
+ return FALLBACK_MODELS$4.map((model) => ({
12007
12315
  id: model.id,
12008
12316
  name: model.name,
12009
12317
  contextWindow: model.contextWindow
@@ -12039,7 +12347,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
12039
12347
  name: entry?.name ?? modelId,
12040
12348
  inputModalities: inputModalitiesFor$1(modelId),
12041
12349
  context: { contextWindow: this.contextWindowFor(modelId, entry, settings.contextWindowOverrides) },
12042
- defaultMaxTokens: maxOutputTokensFor$4(modelId),
12350
+ ...outputReservation(this.contextWindowFor(modelId, entry, settings.contextWindowOverrides), maxOutputTokensFor$4(modelId)),
12043
12351
  ...efforts.length === 0 ? {} : { reasoning: {
12044
12352
  efforts: efforts.map((effort) => ({
12045
12353
  id: ReasoningEffortId(effort),
@@ -12062,11 +12370,17 @@ var CommandCodeAdapter = class extends LlmAdapter {
12062
12370
  ...options,
12063
12371
  reasoningEffort: ReasoningEffortId(String(effort))
12064
12372
  };
12065
- yield* wrapStreamWithWatchdog((watchdogSignal) => this.requestStream(effectiveOptions, watchdogSignal), options.signal, STREAM_IDLE_TIMEOUT_MS$4, STREAM_IDLE_TIMEOUT_CODE$4, PROVIDER_NAME$4);
12373
+ const advertised = reasoningEffortsFor$1(options.model);
12374
+ const effectiveEffort = effectiveOptions.reasoningEffort === void 0 ? void 0 : String(effectiveOptions.reasoningEffort);
12375
+ const resolvedOptions = effectiveEffort === void 0 || advertised.length === 0 || advertised.includes(effectiveEffort) ? effectiveOptions : {
12376
+ ...effectiveOptions,
12377
+ reasoningEffort: void 0
12378
+ };
12379
+ yield* wrapStreamWithWatchdog((watchdogSignal) => this.requestStream(resolvedOptions, watchdogSignal), options.signal, STREAM_IDLE_TIMEOUT_MS$4, STREAM_IDLE_TIMEOUT_CODE$4, PROVIDER_NAME$5);
12066
12380
  }
12067
12381
  async *requestStream(options, signal) {
12068
12382
  const fetchFn = this.options.fetchFn ?? fetch;
12069
- const wire = wireForModel$1(options.model);
12383
+ const wire = wireForModel$2(options.model);
12070
12384
  const requestOptions = offloadOldestRequestImages$3(normalizeGenerateOptions(options));
12071
12385
  const images = await resolveRequestImages$3(requestOptions, this.options.attachments, signal);
12072
12386
  const body = JSON.stringify(buildRequest$1(requestOptions, wire, images));
@@ -12080,7 +12394,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
12080
12394
  let apiEnv;
12081
12395
  if (pool === null) {
12082
12396
  const credentials = await this.store.read();
12083
- if (credentials === null) throw new LlmError(`Not signed in to ${PROVIDER_NAME$4}. Sign in from Settings > Command Code, or paste an API key there.`, "MISSING_CREDENTIAL");
12397
+ if (credentials === null) throw new LlmError(`Not signed in to ${PROVIDER_NAME$5}. Sign in from Settings > Command Code, or paste an API key there.`, "MISSING_CREDENTIAL");
12084
12398
  apiKey = credentials.apiKey;
12085
12399
  apiEnv = credentials.apiEnv ?? resolveApiEnv();
12086
12400
  } else {
@@ -12116,13 +12430,13 @@ var CommandCodeAdapter = class extends LlmAdapter {
12116
12430
  detail = (await response.text().catch(() => "")).slice(0, 600);
12117
12431
  if (pool === null || accountId === void 0) break;
12118
12432
  if (response.status === 401 || response.status === 403) {
12119
- await pool.markAuthFailed(accountId, `${PROVIDER_NAME$4} rejected the stored API key (${response.status}).`, "invalid").catch(() => void 0);
12433
+ await pool.markAuthFailed(accountId, `${PROVIDER_NAME$5} rejected the stored API key (${response.status}).`, "invalid").catch(() => void 0);
12120
12434
  if (await pool.hasAnotherAvailableAccount(tried)) continue;
12121
12435
  break;
12122
12436
  }
12123
12437
  if (response.status === 429) {
12124
12438
  const after = retryAfterMs$1(response.headers);
12125
- await pool.markCooldown(accountId, after ?? POOL_COOLDOWN_MS$3, `${PROVIDER_NAME$4} 429`).catch(() => void 0);
12439
+ await pool.markCooldown(accountId, after ?? POOL_COOLDOWN_MS$4, `${PROVIDER_NAME$5} 429`).catch(() => void 0);
12126
12440
  if (await pool.hasAnotherAvailableAccount(tried)) continue;
12127
12441
  break;
12128
12442
  }
@@ -12130,21 +12444,21 @@ var CommandCodeAdapter = class extends LlmAdapter {
12130
12444
  }
12131
12445
  if (response === void 0 || !response.ok) {
12132
12446
  const status = response?.status ?? 500;
12133
- if (status === 401 || status === 403) throw new LlmError(`${PROVIDER_NAME$4} rejected the stored API key (${status}). Sign in again from Settings > Command Code.${detail ? ` ${detail}` : ""}`, "INVALID_CREDENTIAL", { status });
12447
+ if (status === 401 || status === 403) throw new LlmError(`${PROVIDER_NAME$5} rejected the stored API key (${status}). Sign in again from Settings > Command Code.${detail ? ` ${detail}` : ""}`, "INVALID_CREDENTIAL", { status });
12134
12448
  if (status === 429) {
12135
12449
  const after = response === void 0 ? void 0 : retryAfterMs$1(response.headers);
12136
- throw new LlmError(`${PROVIDER_NAME$4} rate limit or plan quota reached (429). Check the quota card in Settings > Command Code.${detail ? ` ${detail}` : ""}`, "RATE_LIMIT", {
12450
+ throw new LlmError(`${PROVIDER_NAME$5} rate limit or plan quota reached (429). Check the quota card in Settings > Command Code.${detail ? ` ${detail}` : ""}`, "RATE_LIMIT", {
12137
12451
  status: 429,
12138
12452
  ...after === void 0 ? {} : { providerRetryAfterMs: after }
12139
12453
  });
12140
12454
  }
12141
- if (status >= 500) throw new LlmError(`${PROVIDER_NAME$4} upstream server error (${status}): ${detail || "No response"}`, "SERVER", { status });
12142
- throw new LlmError(`${PROVIDER_NAME$4} API error (${status}): ${detail || "No response"}`, "PROVIDER_ERROR", { status });
12455
+ if (status >= 500) throw new LlmError(`${PROVIDER_NAME$5} upstream server error (${status}): ${detail || "No response"}`, "SERVER", { status });
12456
+ throw new LlmError(`${PROVIDER_NAME$5} API error (${status}): ${detail || "No response"}`, "PROVIDER_ERROR", { status });
12143
12457
  }
12144
12458
  if (response.body === null) throw new LlmError("Command Code returned an empty response body", "PROVIDER_ERROR");
12145
12459
  const reader = response.body.getReader();
12146
12460
  const decoder = new TextDecoder();
12147
- const state = createStreamState$4(wire);
12461
+ const state = createStreamState$5(wire);
12148
12462
  let buffer = "";
12149
12463
  try {
12150
12464
  while (true) {
@@ -12162,7 +12476,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
12162
12476
  if (buffer.trim() !== "") for (const line of buffer.split("\n")) for (const chunk of processLine$1(line, state, wire)) yield chunk;
12163
12477
  if (state.finished) return;
12164
12478
  assertStreamComplete$4(state);
12165
- for (const chunk of closeStream$3(state)) yield chunk;
12479
+ for (const chunk of closeStream$4(state)) yield chunk;
12166
12480
  } finally {
12167
12481
  reader.cancel().catch(() => void 0);
12168
12482
  }
@@ -12188,7 +12502,7 @@ function commandCodeAccountKey(credentials) {
12188
12502
  if (credentials.userId !== void 0 && credentials.userId !== "") return `${credentials.userId}:${credentials.keyName ?? ""}`;
12189
12503
  return createHash("sha256").update(credentials.apiKey).digest("hex");
12190
12504
  }
12191
- function optionalString$5(record, key) {
12505
+ function optionalString$7(record, key) {
12192
12506
  const value = record[key];
12193
12507
  if (value === void 0 || value === null) return void 0;
12194
12508
  if (typeof value !== "string") throw new Error("Command Code pool account field is invalid");
@@ -12220,7 +12534,7 @@ function parseCommandCodePoolData(value) {
12220
12534
  "planId",
12221
12535
  "userId"
12222
12536
  ]) {
12223
- const parsed = optionalString$5(raw, key);
12537
+ const parsed = optionalString$7(raw, key);
12224
12538
  if (parsed !== void 0) account[key] = parsed;
12225
12539
  }
12226
12540
  if (account.email === void 0 && credentials.email !== void 0) account.email = credentials.email;
@@ -12252,7 +12566,7 @@ var CommandCodeAccountPool = class extends AccountPoolCore {
12252
12566
  constructor(options = {}) {
12253
12567
  const store = options.store ?? new FileCredentialStore$1();
12254
12568
  const hooks = {
12255
- providerId: PROVIDER_ID$4,
12569
+ providerId: PROVIDER_ID$5,
12256
12570
  displayName: "Command Code",
12257
12571
  poolFile: commandCodePoolPath(),
12258
12572
  keychainService: "dsh-command-code-pool",
@@ -12418,7 +12732,7 @@ function applyCors(request, response) {
12418
12732
  response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
12419
12733
  response.setHeader("Access-Control-Allow-Headers", "Content-Type");
12420
12734
  }
12421
- function sendJson$6(response, status, body) {
12735
+ function sendJson$7(response, status, body) {
12422
12736
  response.writeHead(status, { "content-type": "application/json" });
12423
12737
  response.end(JSON.stringify(body));
12424
12738
  }
@@ -12611,7 +12925,7 @@ function createAuthServer(port, expectedState, options = {}) {
12611
12925
  try {
12612
12926
  fields = fieldsFromPayload(raw, contentType);
12613
12927
  } catch {
12614
- sendJson$6(response, 400, {
12928
+ sendJson$7(response, 400, {
12615
12929
  success: false,
12616
12930
  error: "Invalid JSON"
12617
12931
  });
@@ -12619,7 +12933,7 @@ function createAuthServer(port, expectedState, options = {}) {
12619
12933
  }
12620
12934
  if (fields.error) {
12621
12935
  if (fields.state !== expectedState) {
12622
- sendJson$6(response, 403, {
12936
+ sendJson$7(response, 403, {
12623
12937
  success: false,
12624
12938
  error: "Invalid state token"
12625
12939
  });
@@ -12631,14 +12945,14 @@ function createAuthServer(port, expectedState, options = {}) {
12631
12945
  }
12632
12946
  const apiKey = fields.apiKey;
12633
12947
  if (!apiKey) {
12634
- sendJson$6(response, 400, {
12948
+ sendJson$7(response, 400, {
12635
12949
  success: false,
12636
12950
  error: "Missing required fields"
12637
12951
  });
12638
12952
  return;
12639
12953
  }
12640
12954
  if (fields.state !== expectedState) {
12641
- sendJson$6(response, 403, {
12955
+ sendJson$7(response, 403, {
12642
12956
  success: false,
12643
12957
  error: "Invalid state token"
12644
12958
  });
@@ -12788,18 +13102,18 @@ function isCommandCodeReasoningEffort(value) {
12788
13102
  return typeof value === "string" && COMMAND_CODE_REASONING_EFFORTS.includes(value);
12789
13103
  }
12790
13104
  const MAX_BODY_BYTES$5 = 64 * 1024;
12791
- const ROUTE_PREFIX$3 = "/command-code/api";
12792
- function sendJson$5(response, status, body) {
13105
+ const ROUTE_PREFIX$4 = "/command-code/api";
13106
+ function sendJson$6(response, status, body) {
12793
13107
  response.writeHead(status, { "Content-Type": "application/json" });
12794
13108
  response.end(JSON.stringify(body));
12795
13109
  }
12796
- function sendMethodNotAllowed$4(response) {
12797
- sendJson$5(response, 405, {
13110
+ function sendMethodNotAllowed$5(response) {
13111
+ sendJson$6(response, 405, {
12798
13112
  ok: false,
12799
13113
  error: "Method Not Allowed"
12800
13114
  });
12801
13115
  }
12802
- async function readRequestJson$5(request) {
13116
+ async function readRequestJson$6(request) {
12803
13117
  return new Promise((resolve, reject) => {
12804
13118
  const chunks = [];
12805
13119
  let total = 0;
@@ -12824,7 +13138,7 @@ async function readRequestJson$5(request) {
12824
13138
  });
12825
13139
  }
12826
13140
  function fallbackCatalog$1() {
12827
- return FALLBACK_MODELS$3.map((model) => ({
13141
+ return FALLBACK_MODELS$4.map((model) => ({
12828
13142
  id: model.id,
12829
13143
  name: model.name,
12830
13144
  contextWindow: model.contextWindow
@@ -12841,7 +13155,7 @@ function fallbackCatalog$1() {
12841
13155
  function resolveEnabledModelIds$4(stored, catalog, enabled = true) {
12842
13156
  if (!enabled) return [];
12843
13157
  const catalogIds = catalog.map((model) => model.id);
12844
- const shippedDefaults = new Set(FALLBACK_MODELS$3.map((model) => model.id));
13158
+ const shippedDefaults = new Set(FALLBACK_MODELS$4.map((model) => model.id));
12845
13159
  if (stored.length > 0 && stored.length === shippedDefaults.size && stored.every((id) => shippedDefaults.has(id))) return catalogIds;
12846
13160
  const known = new Set(catalogIds);
12847
13161
  return stored.filter((id) => known.has(id));
@@ -12926,13 +13240,13 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
12926
13240
  const quotaRefresh = new QuotaRefresh();
12927
13241
  return ctx.webServer.register({
12928
13242
  kind: "prefix",
12929
- path: ROUTE_PREFIX$3,
13243
+ path: ROUTE_PREFIX$4,
12930
13244
  handler: async (request, response) => {
12931
13245
  const path = new URL(request.url || "/", "http://dsh.local").pathname.replace(/^\/command-code\/api\/?/, "");
12932
13246
  const method = request.method ?? "GET";
12933
13247
  try {
12934
13248
  if (path === "" || path === "status") {
12935
- if (method !== "GET") return sendMethodNotAllowed$4(response);
13249
+ if (method !== "GET") return sendMethodNotAllowed$5(response);
12936
13250
  const credentials = await store.read();
12937
13251
  const targetId = await activeAccountId();
12938
13252
  const cached = getCachedQuotaFor$1(targetId);
@@ -12941,7 +13255,7 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
12941
13255
  if (cached === void 0) await quotaRefresh.run(refresh);
12942
13256
  else quotaRefresh.start(refresh);
12943
13257
  }
12944
- return sendJson$5(response, 200, {
13258
+ return sendJson$6(response, 200, {
12945
13259
  ok: true,
12946
13260
  value: {
12947
13261
  ...await readStatus(),
@@ -12950,12 +13264,12 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
12950
13264
  });
12951
13265
  }
12952
13266
  if (path === "login") {
12953
- if (method !== "POST") return sendMethodNotAllowed$4(response);
12954
- if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
13267
+ if (method !== "POST") return sendMethodNotAllowed$5(response);
13268
+ if (!isSameOriginMutation(request)) return sendJson$6(response, 403, {
12955
13269
  ok: false,
12956
13270
  error: "Cross-origin request rejected."
12957
13271
  });
12958
- return sendJson$5(response, 200, {
13272
+ return sendJson$6(response, 200, {
12959
13273
  ok: true,
12960
13274
  value: await beginWebLogin$3(store, {
12961
13275
  fetchFn,
@@ -12964,25 +13278,25 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
12964
13278
  });
12965
13279
  }
12966
13280
  if (path === "login/status") {
12967
- if (method !== "GET") return sendMethodNotAllowed$4(response);
12968
- return sendJson$5(response, 200, {
13281
+ if (method !== "GET") return sendMethodNotAllowed$5(response);
13282
+ return sendJson$6(response, 200, {
12969
13283
  ok: true,
12970
13284
  value: getWebLoginStatus()
12971
13285
  });
12972
13286
  }
12973
13287
  if (path === "login/apikey") {
12974
- if (method !== "POST") return sendMethodNotAllowed$4(response);
12975
- if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
13288
+ if (method !== "POST") return sendMethodNotAllowed$5(response);
13289
+ if (!isSameOriginMutation(request)) return sendJson$6(response, 403, {
12976
13290
  ok: false,
12977
13291
  error: "Cross-origin request rejected."
12978
13292
  });
12979
- const body = await readRequestJson$5(request);
13293
+ const body = await readRequestJson$6(request);
12980
13294
  const account = await saveApiKey(store, typeof body.apiKey === "string" ? body.apiKey : "", {
12981
13295
  fetchFn,
12982
13296
  ...accountPool === void 0 ? {} : { onSave: (credentials) => accountPool.addAccount(credentials) }
12983
13297
  });
12984
13298
  clearCachedQuota$2();
12985
- return sendJson$5(response, 200, {
13299
+ return sendJson$6(response, 200, {
12986
13300
  ok: true,
12987
13301
  value: {
12988
13302
  ...await readStatus(),
@@ -12991,13 +13305,13 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
12991
13305
  });
12992
13306
  }
12993
13307
  if (path === "connection/test") {
12994
- if (method !== "POST") return sendMethodNotAllowed$4(response);
12995
- if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
13308
+ if (method !== "POST") return sendMethodNotAllowed$5(response);
13309
+ if (!isSameOriginMutation(request)) return sendJson$6(response, 403, {
12996
13310
  ok: false,
12997
13311
  error: "Cross-origin request rejected."
12998
13312
  });
12999
13313
  const credentials = await store.read();
13000
- if (credentials === null) return sendJson$5(response, 400, {
13314
+ if (credentials === null) return sendJson$6(response, 400, {
13001
13315
  ok: false,
13002
13316
  error: "Not signed in."
13003
13317
  });
@@ -13006,7 +13320,7 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
13006
13320
  fetchFn,
13007
13321
  apiEnv: credentials.apiEnv ?? resolveApiEnv()
13008
13322
  });
13009
- return sendJson$5(response, 200, {
13323
+ return sendJson$6(response, 200, {
13010
13324
  ok: true,
13011
13325
  value: {
13012
13326
  connected: true,
@@ -13024,24 +13338,24 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
13024
13338
  });
13025
13339
  }
13026
13340
  if (path === "quota") {
13027
- if (method !== "GET" && method !== "POST") return sendMethodNotAllowed$4(response);
13341
+ if (method !== "GET" && method !== "POST") return sendMethodNotAllowed$5(response);
13028
13342
  await fetchAccountQuota$2(quotaStore, fetchFn, true, await activeAccountId());
13029
- return sendJson$5(response, 200, {
13343
+ return sendJson$6(response, 200, {
13030
13344
  ok: true,
13031
13345
  value: await readStatus()
13032
13346
  });
13033
13347
  }
13034
13348
  if (path === "models" || path === "settings") {
13035
- if (method === "GET") return sendJson$5(response, 200, {
13349
+ if (method === "GET") return sendJson$6(response, 200, {
13036
13350
  ok: true,
13037
13351
  value: await readStatus()
13038
13352
  });
13039
- if (method !== "POST") return sendMethodNotAllowed$4(response);
13040
- if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
13353
+ if (method !== "POST") return sendMethodNotAllowed$5(response);
13354
+ if (!isSameOriginMutation(request)) return sendJson$6(response, 403, {
13041
13355
  ok: false,
13042
13356
  error: "Cross-origin request rejected."
13043
13357
  });
13044
- const body = await readRequestJson$5(request);
13358
+ const body = await readRequestJson$6(request);
13045
13359
  const patch = {};
13046
13360
  if (typeof body.enabled === "boolean") patch.enabled = body.enabled;
13047
13361
  if (Array.isArray(body.enabledModelIds)) patch.enabledModelIds = body.enabledModelIds.filter((id) => typeof id === "string");
@@ -13058,14 +13372,14 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
13058
13372
  if (preferences) await preferences.update(patch);
13059
13373
  else await modelSettings.updateSettings(patch);
13060
13374
  if (patch.enabledModelIds !== void 0 || patch.enabled !== void 0) ctx.emit?.("llm/adapters-updated");
13061
- return sendJson$5(response, 200, {
13375
+ return sendJson$6(response, 200, {
13062
13376
  ok: true,
13063
13377
  value: await readStatus()
13064
13378
  });
13065
13379
  }
13066
13380
  if (path === "catalog/refresh") {
13067
- if (method !== "POST") return sendMethodNotAllowed$4(response);
13068
- if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
13381
+ if (method !== "POST") return sendMethodNotAllowed$5(response);
13382
+ if (!isSameOriginMutation(request)) return sendJson$6(response, 403, {
13069
13383
  ok: false,
13070
13384
  error: "Cross-origin request rejected."
13071
13385
  });
@@ -13075,7 +13389,7 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
13075
13389
  force: true,
13076
13390
  apiEnv: resolveApiEnv()
13077
13391
  });
13078
- return sendJson$5(response, 200, {
13392
+ return sendJson$6(response, 200, {
13079
13393
  ok: true,
13080
13394
  value: await readStatus()
13081
13395
  });
@@ -13083,7 +13397,7 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
13083
13397
  if (path === "accounts") {
13084
13398
  if (method === "GET") {
13085
13399
  const data = accountPool === void 0 ? null : await accountPool.read().catch(() => null);
13086
- return sendJson$5(response, 200, {
13400
+ return sendJson$6(response, 200, {
13087
13401
  ok: true,
13088
13402
  value: {
13089
13403
  accounts: accountPool === void 0 ? [] : await accountPool.listAccounts().catch(() => []),
@@ -13092,16 +13406,16 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
13092
13406
  }
13093
13407
  });
13094
13408
  }
13095
- if (method !== "POST") return sendMethodNotAllowed$4(response);
13096
- if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
13409
+ if (method !== "POST") return sendMethodNotAllowed$5(response);
13410
+ if (!isSameOriginMutation(request)) return sendJson$6(response, 403, {
13097
13411
  ok: false,
13098
13412
  error: "Cross-origin request rejected."
13099
13413
  });
13100
- if (accountPool === void 0) return sendJson$5(response, 400, {
13414
+ if (accountPool === void 0) return sendJson$6(response, 400, {
13101
13415
  ok: false,
13102
13416
  error: "Account pool is not installed."
13103
13417
  });
13104
- const body = await readRequestJson$5(request);
13418
+ const body = await readRequestJson$6(request);
13105
13419
  const action = typeof body.action === "string" ? body.action : "";
13106
13420
  const accountId = typeof body.accountId === "string" ? body.accountId : void 0;
13107
13421
  if (action === "set-primary" && accountId !== void 0) await accountPool.setPrimary(accountId);
@@ -13112,20 +13426,20 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
13112
13426
  else if (action === "relogin" && accountId !== void 0) await accountPool.clearAuthFailed(accountId);
13113
13427
  clearCachedQuota$2();
13114
13428
  clearCachedCatalog$3();
13115
- return sendJson$5(response, 200, {
13429
+ return sendJson$6(response, 200, {
13116
13430
  ok: true,
13117
13431
  value: await readStatus()
13118
13432
  });
13119
13433
  }
13120
13434
  if (path === "logout") {
13121
- if (method !== "POST") return sendMethodNotAllowed$4(response);
13122
- if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
13435
+ if (method !== "POST") return sendMethodNotAllowed$5(response);
13436
+ if (!isSameOriginMutation(request)) return sendJson$6(response, 403, {
13123
13437
  ok: false,
13124
13438
  error: "Cross-origin request rejected."
13125
13439
  });
13126
13440
  if (accountPool === void 0) await store.delete();
13127
13441
  else {
13128
- const body = await readRequestJson$5(request).catch(() => ({}));
13442
+ const body = await readRequestJson$6(request).catch(() => ({}));
13129
13443
  const data = await accountPool.read().catch(() => null);
13130
13444
  const target = typeof body.accountId === "string" ? body.accountId : data?.activeAccountId ?? data?.accounts.find((account) => account.isPrimary)?.id ?? data?.accounts[0]?.id;
13131
13445
  if (target === void 0) await store.delete();
@@ -13133,17 +13447,17 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
13133
13447
  }
13134
13448
  clearCachedQuota$2();
13135
13449
  clearCachedCatalog$3();
13136
- return sendJson$5(response, 200, {
13450
+ return sendJson$6(response, 200, {
13137
13451
  ok: true,
13138
13452
  value: await readStatus()
13139
13453
  });
13140
13454
  }
13141
- return sendJson$5(response, 404, {
13455
+ return sendJson$6(response, 404, {
13142
13456
  ok: false,
13143
13457
  error: "not-found"
13144
13458
  });
13145
13459
  } catch (error) {
13146
- return sendJson$5(response, 500, {
13460
+ return sendJson$6(response, 500, {
13147
13461
  ok: false,
13148
13462
  error: error instanceof Error ? error.message : String(error)
13149
13463
  });
@@ -13152,6 +13466,1377 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
13152
13466
  });
13153
13467
  }
13154
13468
  //#endregion
13469
+ //#region src/host/ollama/types.ts
13470
+ /**
13471
+ * Static facts about the Ollama provider API.
13472
+ *
13473
+ * Ollama publishes three surfaces, and this line speaks two of them:
13474
+ *
13475
+ * - the **native API** (`https://ollama.com/api`) — Ollama's own wire format,
13476
+ * with `/api/chat` and `/api/tags`;
13477
+ * - the **OpenAI-compatible API** (`https://ollama.com/v1`) — a documented
13478
+ * subset of the OpenAI API, which is what a coding agent wants by default
13479
+ * because its tool-calling and streaming shapes are the familiar ones.
13480
+ *
13481
+ * A third surface, the Anthropic-compatible `/v1/messages`, exists and is not
13482
+ * used here: the Claude line already transcribes the Anthropic wire format
13483
+ * against a snapshot it can verify, and routing Ollama's subset of it through
13484
+ * the same mapper would claim capabilities Ollama does not document.
13485
+ *
13486
+ * DOCUMENTED CLOUD LIMITS (docs/api/openai-compatibility, docs/cloud):
13487
+ *
13488
+ * - No stateful Responses. Only the stateless form works.
13489
+ * - No built-in web search through `/v1/responses`.
13490
+ * - No custom/freeform tool-call replay.
13491
+ *
13492
+ * Those are server-side capability limits, not choices made here, so this line
13493
+ * sends ordinary `tools` arrays and streams ordinary `tool_calls` back, and
13494
+ * makes no promise about replaying a custom tool call across turns. Saying so
13495
+ * in the settings card is the honest rendering of a documented boundary.
13496
+ *
13497
+ * Authentication is a static API key in `Authorization: Bearer`, not an OAuth
13498
+ * flow: keys do not expire and are revoked from the account settings page.
13499
+ */
13500
+ const PROVIDER_ID$4 = "ollama";
13501
+ const PROVIDER_NAME$4 = "Ollama";
13502
+ /** Direct cloud access, per docs/api/introduction. */
13503
+ const CLOUD_BASE_URL = "https://ollama.com";
13504
+ /** Native surface. */
13505
+ const NATIVE_CHAT_PATH = "/api/chat";
13506
+ const NATIVE_TAGS_PATH = "/api/tags";
13507
+ const OPENAI_CHAT_PATH = "/chat/completions";
13508
+ /**
13509
+ * Default context window for a model whose catalog entry states none.
13510
+ *
13511
+ * Ollama's `/api/tags` returns names and families, not context windows, so a
13512
+ * fixed conservative value is the honest number here. It is deliberately small
13513
+ * enough to be true of every cloud model rather than flattering to one.
13514
+ */
13515
+ const DEFAULT_CONTEXT_WINDOW$4 = 128e3;
13516
+ /**
13517
+ * Output cap used when neither the caller nor the catalog states one.
13518
+ *
13519
+ * Ollama does not document a per-model maximum output length, and unlike the
13520
+ * Claude line there is no snapshot to read one from, so this is a request-level
13521
+ * ceiling rather than a claim about what the model can do.
13522
+ */
13523
+ const DEFAULT_MAX_OUTPUT_TOKENS = 8192;
13524
+ /** How long a catalog sync may take before the cached list is used instead. */
13525
+ const CATALOG_TIMEOUT_MS = 15e3;
13526
+ /**
13527
+ * Cooldown one account takes when Ollama refuses it without stating a delay.
13528
+ *
13529
+ * A 429 here is a real quota or rate limit, not a blip, so the account is parked
13530
+ * for long enough that the next request goes to a different one.
13531
+ */
13532
+ const POOL_COOLDOWN_MS$3 = 15 * 6e4;
13533
+ /**
13534
+ * NO fallback model names.
13535
+ *
13536
+ * An earlier revision hard-coded `gpt-oss:120b-cloud` and `gpt-oss:20b-cloud`
13537
+ * here, and they reached the model picker. That was wrong twice over: this line
13538
+ * has no model table of its own, so every name in it would be a guess about what
13539
+ * some account is entitled to, and a guess that lands in the picker is a model a
13540
+ * user can select and then fail on. The other lines can fall back to hard-coded
13541
+ * names because theirs are transcribed from a real table - Claude's is its own
13542
+ * frozen catalog. Ollama's truth is `/api/tags`, and there is nothing honest to
13543
+ * stand in for it before the first sync.
13544
+ *
13545
+ * So the catalog starts empty and the card says the list has not been synced yet,
13546
+ * with the sync button right beside it. A user who has a key is one click from a
13547
+ * list that is actually theirs.
13548
+ */
13549
+ const FALLBACK_MODELS$3 = Object.freeze([]);
13550
+ /**
13551
+ * Resolve the wire shape for a model.
13552
+ *
13553
+ * `openai` first, always: the OpenAI-compatible surface is the one whose tool
13554
+ * and streaming semantics this plugin's mapper already implements faithfully,
13555
+ * and it is what an agent wants. `native` is the fallback for the documented
13556
+ * cases where the OpenAI surface refuses a request, so a model the service
13557
+ * accepts natively still works instead of failing outright.
13558
+ */
13559
+ function wireForModel$1(_model) {
13560
+ return "openai";
13561
+ }
13562
+ /** Context window for a model, falling back to the conservative constant. */
13563
+ function contextWindowFor(model) {
13564
+ const stated = model?.contextWindow;
13565
+ return typeof stated === "number" && Number.isFinite(stated) && stated > 0 ? Math.floor(stated) : DEFAULT_CONTEXT_WINDOW$4;
13566
+ }
13567
+ /** Bearer headers for a stored key. */
13568
+ function ollamaHeaders(apiKey) {
13569
+ return {
13570
+ authorization: `Bearer ${apiKey}`,
13571
+ "content-type": "application/json"
13572
+ };
13573
+ }
13574
+ //#endregion
13575
+ //#region src/host/ollama/token-store.ts
13576
+ function credentialPath$4() {
13577
+ return path.join(dshHomeDir$2(), "storages", "ollama-credentials.json");
13578
+ }
13579
+ function modelSettingsPath$6() {
13580
+ return path.join(dshHomeDir$2(), "storages", "ollama-models.json");
13581
+ }
13582
+ /** Stable keyring account name for one credential file. */
13583
+ function credentialAccount$2(filePath) {
13584
+ return createHash("sha256").update(path.resolve(filePath)).digest("hex");
13585
+ }
13586
+ function optionalString$6(record, key) {
13587
+ const value = record[key];
13588
+ if (value === void 0 || value === null) return void 0;
13589
+ if (typeof value !== "string") throw new Error("Ollama credential payload is invalid");
13590
+ return value;
13591
+ }
13592
+ function parseOllamaCredentials(value) {
13593
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("Ollama credential payload is invalid");
13594
+ const record = value;
13595
+ const apiKey = record.apiKey;
13596
+ if (typeof apiKey !== "string" || apiKey.trim() === "") throw new Error("Ollama credential is missing its API key");
13597
+ const credentials = { apiKey };
13598
+ const alias = optionalString$6(record, "alias");
13599
+ if (alias !== void 0) credentials.alias = alias;
13600
+ const addedAt = record.addedAt;
13601
+ if (typeof addedAt === "number" && Number.isFinite(addedAt)) credentials.addedAt = addedAt;
13602
+ return credentials;
13603
+ }
13604
+ /**
13605
+ * The OS credential backend for this platform.
13606
+ *
13607
+ * Same matrix as every other line here: DPAPI on Windows, Keychain on macOS,
13608
+ * Secret Service on Linux. The plaintext JSON file is a migration source only,
13609
+ * never the live store.
13610
+ */
13611
+ function createCredentialBackend$2(filePath) {
13612
+ if (process.platform === "win32") return new WindowsDpapiCredentialStore(`${filePath}.dpapi`, parseOllamaCredentials);
13613
+ if (process.platform === "darwin") return new MacKeychainCredentialStore(PROVIDER_ID$4, credentialAccount$2(filePath), parseOllamaCredentials);
13614
+ if (process.platform === "linux") return new SecretServiceCredentialStore(PROVIDER_ID$4, credentialAccount$2(filePath), parseOllamaCredentials);
13615
+ throw new Error("Ollama credential storage requires Windows, macOS, or Linux.");
13616
+ }
13617
+ /** Encrypted credential store for the single-key (no pool) configuration. */
13618
+ var FileCredentialStore$5 = class {
13619
+ filePath;
13620
+ backend;
13621
+ constructor(filePath = credentialPath$4(), backend = createCredentialBackend$2(filePath)) {
13622
+ this.filePath = filePath;
13623
+ this.backend = backend;
13624
+ }
13625
+ path() {
13626
+ return this.filePath;
13627
+ }
13628
+ async read() {
13629
+ return this.backend.load();
13630
+ }
13631
+ async write(credentials) {
13632
+ await fs.mkdir(path.dirname(this.filePath), { recursive: true });
13633
+ await this.backend.save(credentials);
13634
+ }
13635
+ async clear() {
13636
+ await this.backend.clear();
13637
+ }
13638
+ };
13639
+ /**
13640
+ * Model settings, bound to the DSH settings document when the harness offers one
13641
+ * and to a JSON file beside it otherwise.
13642
+ */
13643
+ var FileModelSettingsStore$5 = class {
13644
+ filePath;
13645
+ cached = null;
13646
+ loadPromise = null;
13647
+ constructor(filePath = modelSettingsPath$6()) {
13648
+ this.filePath = filePath;
13649
+ }
13650
+ path() {
13651
+ return this.filePath;
13652
+ }
13653
+ status() {
13654
+ return this.cached ?? {
13655
+ enabled: true,
13656
+ enabledModelIds: [],
13657
+ catalogModels: [],
13658
+ defaultReasoningEffort: null
13659
+ };
13660
+ }
13661
+ async read() {
13662
+ if (this.cached !== null) return this.cached;
13663
+ if (this.loadPromise !== null) return this.loadPromise;
13664
+ this.loadPromise = this.load().finally(() => {
13665
+ this.loadPromise = null;
13666
+ });
13667
+ return this.loadPromise;
13668
+ }
13669
+ async load() {
13670
+ try {
13671
+ const content = await fs.readFile(this.filePath, "utf8");
13672
+ const parsed = JSON.parse(content);
13673
+ if (typeof parsed === "object" && parsed !== null) {
13674
+ const record = parsed;
13675
+ const enabledModels = Array.isArray(record.enabledModelIds) ? record.enabledModelIds.filter((id) => typeof id === "string") : [];
13676
+ const catalogModels = Array.isArray(record.catalogModels) ? record.catalogModels.filter((entry) => typeof entry === "object" && entry !== null && typeof entry.id === "string") : [];
13677
+ this.cached = {
13678
+ enabled: record.enabled !== false,
13679
+ enabledModelIds: enabledModels,
13680
+ catalogModels,
13681
+ defaultReasoningEffort: typeof record.defaultReasoningEffort === "string" ? record.defaultReasoningEffort : null
13682
+ };
13683
+ return this.cached;
13684
+ }
13685
+ } catch {}
13686
+ this.cached = {
13687
+ enabled: true,
13688
+ enabledModelIds: [],
13689
+ catalogModels: [],
13690
+ defaultReasoningEffort: null
13691
+ };
13692
+ return this.cached;
13693
+ }
13694
+ async write(settings) {
13695
+ await fs.mkdir(path.dirname(this.filePath), { recursive: true });
13696
+ const tmp = `${this.filePath}.tmp.${Date.now()}`;
13697
+ await fs.writeFile(tmp, JSON.stringify(settings, null, 2), "utf8");
13698
+ await fs.rename(tmp, this.filePath);
13699
+ this.cached = settings;
13700
+ }
13701
+ async update(patch) {
13702
+ const current = await this.read();
13703
+ const next = {
13704
+ enabled: patch.enabled ?? current.enabled,
13705
+ enabledModelIds: patch.enabledModelIds ?? current.enabledModelIds,
13706
+ catalogModels: current.catalogModels,
13707
+ defaultReasoningEffort: patch.defaultReasoningEffort === void 0 ? current.defaultReasoningEffort : patch.defaultReasoningEffort
13708
+ };
13709
+ await this.write(next);
13710
+ return next;
13711
+ }
13712
+ /** Record a catalog sync without touching the user's enabled selection. */
13713
+ async storeCatalog(models) {
13714
+ const next = {
13715
+ ...await this.read(),
13716
+ catalogModels: models
13717
+ };
13718
+ await this.write(next);
13719
+ return next;
13720
+ }
13721
+ };
13722
+ //#endregion
13723
+ //#region src/host/ollama/client.ts
13724
+ /** Headers both surfaces require, per docs/api/authentication. */
13725
+ function headersFor(credentials, accept) {
13726
+ return {
13727
+ ...ollamaHeaders(credentials.apiKey),
13728
+ accept
13729
+ };
13730
+ }
13731
+ /**
13732
+ * Build the request body for one wire shape.
13733
+ *
13734
+ * The two surfaces are not translations of each other — the native API nests the
13735
+ * assistant's tool calls under `message.tool_calls` and expects results under a
13736
+ * `tool` role without ids, while the OpenAI surface uses `tool_calls` and
13737
+ * `tool_call_id`. Mapping them through one shared builder would have to guess at
13738
+ * the fields the other surface does not carry, so each is written out where its
13739
+ * own rules stay visible.
13740
+ */
13741
+ function buildBody(wire, request) {
13742
+ if (wire === "openai") return buildOpenAIBody(request);
13743
+ return buildNativeBody(request);
13744
+ }
13745
+ function buildOpenAIBody(request) {
13746
+ const messages = request.messages.map((message) => {
13747
+ if (message.role === "tool") return {
13748
+ role: "tool",
13749
+ tool_call_id: message.toolCallId ?? "",
13750
+ content: message.content
13751
+ };
13752
+ if (message.role === "assistant" && message.toolCalls !== void 0 && message.toolCalls.length > 0) return {
13753
+ role: "assistant",
13754
+ content: message.content,
13755
+ tool_calls: message.toolCalls.map((call) => ({
13756
+ id: call.id,
13757
+ type: "function",
13758
+ function: {
13759
+ name: call.name,
13760
+ arguments: call.arguments
13761
+ }
13762
+ }))
13763
+ };
13764
+ return {
13765
+ role: message.role,
13766
+ content: message.content
13767
+ };
13768
+ });
13769
+ const body = {
13770
+ model: request.model,
13771
+ messages,
13772
+ stream: true,
13773
+ max_tokens: request.maxOutputTokens ?? 8192
13774
+ };
13775
+ if (request.tools !== void 0 && request.tools.length > 0) body.tools = request.tools.map((tool) => ({
13776
+ type: "function",
13777
+ function: {
13778
+ name: tool.name,
13779
+ description: tool.description ?? "",
13780
+ parameters: tool.parameters
13781
+ }
13782
+ }));
13783
+ if (request.temperature !== void 0) body.temperature = request.temperature;
13784
+ return body;
13785
+ }
13786
+ function buildNativeBody(request) {
13787
+ const messages = request.messages.map((message) => {
13788
+ if (message.role === "tool") return {
13789
+ role: "tool",
13790
+ content: message.content
13791
+ };
13792
+ if (message.role === "assistant" && message.toolCalls !== void 0 && message.toolCalls.length > 0) return {
13793
+ role: "assistant",
13794
+ content: message.content,
13795
+ tool_calls: message.toolCalls.map((call) => ({ function: {
13796
+ name: call.name,
13797
+ arguments: call.arguments
13798
+ } }))
13799
+ };
13800
+ return {
13801
+ role: message.role,
13802
+ content: message.content
13803
+ };
13804
+ });
13805
+ const body = {
13806
+ model: request.model,
13807
+ messages,
13808
+ stream: true
13809
+ };
13810
+ const options = { num_predict: request.maxOutputTokens ?? 8192 };
13811
+ if (request.temperature !== void 0) options.temperature = request.temperature;
13812
+ body.options = options;
13813
+ if (request.tools !== void 0 && request.tools.length > 0) body.tools = request.tools.map((tool) => ({
13814
+ type: "function",
13815
+ function: {
13816
+ name: tool.name,
13817
+ description: tool.description ?? "",
13818
+ parameters: tool.parameters
13819
+ }
13820
+ }));
13821
+ return body;
13822
+ }
13823
+ /** The endpoint one request goes to. */
13824
+ function chatUrl(wire) {
13825
+ return wire === "openai" ? `${CLOUD_BASE_URL}${OPENAI_CHAT_PATH}` : `${CLOUD_BASE_URL}${NATIVE_CHAT_PATH}`;
13826
+ }
13827
+ /**
13828
+ * Start one streaming chat call.
13829
+ *
13830
+ * The HTTP status is surfaced through a promise rather than by handing back the
13831
+ * Response, because the caller has to decide between retrying this account and
13832
+ * failing the turn, and both need the status before any body is read.
13833
+ */
13834
+ function startChat(fetchFn, credentials, wire, request) {
13835
+ let resolveStatus = () => void 0;
13836
+ let rejectStatus = () => void 0;
13837
+ const status = new Promise((resolve, reject) => {
13838
+ resolveStatus = resolve;
13839
+ rejectStatus = reject;
13840
+ });
13841
+ const opened = fetchFn(chatUrl(wire), {
13842
+ method: "POST",
13843
+ headers: headersFor(credentials, "text/event-stream"),
13844
+ body: JSON.stringify(buildBody(wire, request)),
13845
+ signal: request.signal
13846
+ }).then((response) => {
13847
+ resolveStatus(response.status);
13848
+ return response;
13849
+ }, (error) => {
13850
+ rejectStatus(error);
13851
+ throw error;
13852
+ });
13853
+ return {
13854
+ events: (async function* run() {
13855
+ const response = await opened;
13856
+ if (!response.ok) {
13857
+ const detail = await safeErrorText(response);
13858
+ yield {
13859
+ type: "error",
13860
+ message: `Ollama request failed (${response.status}): ${detail}`
13861
+ };
13862
+ return;
13863
+ }
13864
+ yield* readStream(response, wire);
13865
+ })(),
13866
+ status
13867
+ };
13868
+ }
13869
+ async function safeErrorText(response) {
13870
+ try {
13871
+ return (await response.text()).slice(0, 500);
13872
+ } catch {
13873
+ return "";
13874
+ }
13875
+ }
13876
+ /** Parse the SSE stream of either surface. */
13877
+ async function* readStream(response, wire) {
13878
+ const body = response.body;
13879
+ if (body === null) return;
13880
+ const decoder = new TextDecoder();
13881
+ const reader = body.getReader();
13882
+ let buffer = "";
13883
+ const pending = /* @__PURE__ */ new Map();
13884
+ try {
13885
+ for (;;) {
13886
+ const { done, value } = await reader.read();
13887
+ if (done) break;
13888
+ buffer += decoder.decode(value, { stream: true });
13889
+ let newline = buffer.indexOf("\n");
13890
+ while (newline !== -1) {
13891
+ const line = buffer.slice(0, newline).trim();
13892
+ buffer = buffer.slice(newline + 1);
13893
+ newline = buffer.indexOf("\n");
13894
+ if (line === "" || !line.startsWith("data:")) continue;
13895
+ const payload = line.slice(5).trim();
13896
+ if (payload === "" || payload === "[DONE]") continue;
13897
+ for (const event of parseEvent(payload, wire, pending)) yield event;
13898
+ }
13899
+ }
13900
+ } finally {
13901
+ reader.releaseLock?.();
13902
+ }
13903
+ for (const call of pending.values()) yield {
13904
+ type: "tool_call",
13905
+ call: call.arguments === "" ? {
13906
+ ...call,
13907
+ arguments: "{}"
13908
+ } : call
13909
+ };
13910
+ }
13911
+ function parseEvent(payload, wire, pending) {
13912
+ let parsed;
13913
+ try {
13914
+ parsed = JSON.parse(payload);
13915
+ } catch {
13916
+ return [];
13917
+ }
13918
+ if (typeof parsed !== "object" || parsed === null) return [];
13919
+ const record = parsed;
13920
+ if (typeof record.error === "string") return [{
13921
+ type: "error",
13922
+ message: record.error
13923
+ }];
13924
+ return wire === "openai" ? parseOpenAIChunk(record, pending) : parseNativeChunk(record, pending);
13925
+ }
13926
+ function parseOpenAIChunk(record, pending) {
13927
+ const events = [];
13928
+ const choices = Array.isArray(record.choices) ? record.choices : [];
13929
+ for (const choice of choices) {
13930
+ if (typeof choice !== "object" || choice === null) continue;
13931
+ const entry = choice;
13932
+ const delta = entry.delta;
13933
+ if (typeof delta === "object" && delta !== null) {
13934
+ const fields = delta;
13935
+ if (typeof fields.content === "string" && fields.content !== "") events.push({
13936
+ type: "text",
13937
+ text: fields.content
13938
+ });
13939
+ if (Array.isArray(fields.tool_calls)) for (const raw of fields.tool_calls) accumulateToolCall(raw, pending, true);
13940
+ }
13941
+ if (typeof entry.finish_reason === "string" && entry.finish_reason !== "") events.push({
13942
+ type: "done",
13943
+ finishReason: entry.finish_reason
13944
+ });
13945
+ }
13946
+ const usage = readUsage$1(record);
13947
+ if (usage !== null) events.push({
13948
+ type: "usage",
13949
+ usage
13950
+ });
13951
+ return events;
13952
+ }
13953
+ function parseNativeChunk(record, pending) {
13954
+ const events = [];
13955
+ const message = record.message;
13956
+ if (typeof message === "object" && message !== null) {
13957
+ const fields = message;
13958
+ if (typeof fields.content === "string" && fields.content !== "") events.push({
13959
+ type: "text",
13960
+ text: fields.content
13961
+ });
13962
+ if (Array.isArray(fields.tool_calls)) for (const raw of fields.tool_calls) accumulateToolCall(raw, pending, false);
13963
+ }
13964
+ if (record.done === true) events.push({
13965
+ type: "done",
13966
+ finishReason: "stop"
13967
+ });
13968
+ const usage = readNativeUsage(record);
13969
+ if (usage !== null) events.push({
13970
+ type: "usage",
13971
+ usage
13972
+ });
13973
+ return events;
13974
+ }
13975
+ /**
13976
+ * Read token counts off an OpenAI-compatible chunk.
13977
+ *
13978
+ * Ollama names them the OpenAI way here (`prompt_tokens` / `completion_tokens`),
13979
+ * while the native surface uses `prompt_eval_count` / `eval_count`. Both are
13980
+ * accepted on both paths because the service documents the native names in its
13981
+ * own OpenAPI and an OpenAI-compatible alias in the compatibility layer, and a
13982
+ * counter that silently read zero on the wrong surface would be worse than none.
13983
+ *
13984
+ * Returns null when the chunk carries neither pair, which is every content chunk.
13985
+ */
13986
+ function readUsage$1(record) {
13987
+ const container = typeof record.usage === "object" && record.usage !== null ? record.usage : record;
13988
+ const input = firstNumber$1(container, [
13989
+ "prompt_tokens",
13990
+ "prompt_eval_count",
13991
+ "input_tokens"
13992
+ ]);
13993
+ const output = firstNumber$1(container, [
13994
+ "completion_tokens",
13995
+ "eval_count",
13996
+ "output_tokens"
13997
+ ]);
13998
+ if (input === void 0 && output === void 0) return null;
13999
+ return {
14000
+ inputTokens: input ?? 0,
14001
+ outputTokens: output ?? 0
14002
+ };
14003
+ }
14004
+ /** The native surface's names, read from the chunk itself. */
14005
+ function readNativeUsage(record) {
14006
+ const input = firstNumber$1(record, [
14007
+ "prompt_eval_count",
14008
+ "prompt_tokens",
14009
+ "input_tokens"
14010
+ ]);
14011
+ const output = firstNumber$1(record, [
14012
+ "eval_count",
14013
+ "completion_tokens",
14014
+ "output_tokens"
14015
+ ]);
14016
+ if (input === void 0 && output === void 0) return null;
14017
+ return {
14018
+ inputTokens: input ?? 0,
14019
+ outputTokens: output ?? 0
14020
+ };
14021
+ }
14022
+ function firstNumber$1(record, keys) {
14023
+ for (const key of keys) {
14024
+ const value = record[key];
14025
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0) return Math.floor(value);
14026
+ }
14027
+ }
14028
+ /**
14029
+ * Fold one streamed tool-call delta into the pending set.
14030
+ *
14031
+ * Nothing is emitted here: a call is only complete once the stream has ended, and
14032
+ * emitting a half-received argument string would have the caller act on a tool
14033
+ * invocation that does not exist yet. The native surface sends no id, so a stable
14034
+ * synthetic one is minted from the delta index — the only handle both surfaces
14035
+ * agree on for pairing a call with its result.
14036
+ */
14037
+ function accumulateToolCall(raw, pending, openaiShape) {
14038
+ if (typeof raw !== "object" || raw === null) return;
14039
+ const record = raw;
14040
+ const index = typeof record.index === "number" ? record.index : 0;
14041
+ const holder = typeof record.function === "object" && record.function !== null ? record.function : record;
14042
+ const name = typeof holder.name === "string" ? holder.name : void 0;
14043
+ const args = typeof holder.arguments === "string" ? holder.arguments : void 0;
14044
+ const id = openaiShape && typeof record.id === "string" ? record.id : void 0;
14045
+ if (name === void 0 && args === void 0 && id === void 0) return;
14046
+ const call = pending.get(index) ?? {
14047
+ id: id ?? `call_${index}`,
14048
+ name: "",
14049
+ arguments: ""
14050
+ };
14051
+ if (id !== void 0 && id !== "") call.id = id;
14052
+ if (name !== void 0 && name !== "") call.name = name;
14053
+ if (args !== void 0) call.arguments += args;
14054
+ pending.set(index, call);
14055
+ }
14056
+ /**
14057
+ * Read the model catalog.
14058
+ *
14059
+ * `/api/tags` is the native surface's own list and needs no translation; the
14060
+ * OpenAI `/models` endpoint would be a subset of it. Returns an empty list rather
14061
+ * than throwing, so a failed sync degrades to the cached selection instead of
14062
+ * making the line unusable.
14063
+ */
14064
+ async function loadCatalog$1(fetchFn, credentials) {
14065
+ const response = await fetchFn(`${CLOUD_BASE_URL}${NATIVE_TAGS_PATH}`, {
14066
+ method: "GET",
14067
+ headers: headersFor(credentials, "application/json"),
14068
+ signal: AbortSignal.timeout(CATALOG_TIMEOUT_MS)
14069
+ });
14070
+ if (!response.ok) return [];
14071
+ const data = await response.json();
14072
+ if (typeof data !== "object" || data === null) return [];
14073
+ const record = data;
14074
+ const models = Array.isArray(record.models) ? record.models : [];
14075
+ const result = [];
14076
+ for (const entry of models) {
14077
+ if (typeof entry !== "object" || entry === null) continue;
14078
+ const fields = entry;
14079
+ const name = typeof fields.name === "string" ? fields.name : void 0;
14080
+ const model = typeof fields.model === "string" ? fields.model : void 0;
14081
+ const id = name ?? model;
14082
+ if (id === void 0 || id === "") continue;
14083
+ result.push({
14084
+ id,
14085
+ name,
14086
+ fetchedAt: Date.now()
14087
+ });
14088
+ }
14089
+ return result;
14090
+ }
14091
+ //#endregion
14092
+ //#region src/host/ollama/mapper.ts
14093
+ function createStreamState$4() {
14094
+ return {
14095
+ nextBlockIndex: 0,
14096
+ textBlock: null,
14097
+ toolCalls: /* @__PURE__ */ new Map(),
14098
+ hasContent: false
14099
+ };
14100
+ }
14101
+ /** Fold one event into chunks, opening and closing blocks as the stream needs. */
14102
+ function applyEvent(state, event) {
14103
+ if (event.type === "text") return applyText(state, event.text);
14104
+ if (event.type === "tool_call") return applyToolCall(state, event.call);
14105
+ return [];
14106
+ }
14107
+ function applyText(state, text) {
14108
+ state.hasContent = true;
14109
+ if (state.textBlock === null) {
14110
+ const index = state.nextBlockIndex;
14111
+ state.nextBlockIndex += 1;
14112
+ state.textBlock = {
14113
+ index,
14114
+ text
14115
+ };
14116
+ return [{
14117
+ type: "block-start",
14118
+ index,
14119
+ blockType: "text"
14120
+ }, {
14121
+ type: "text-delta",
14122
+ index,
14123
+ text
14124
+ }];
14125
+ }
14126
+ state.textBlock.text += text;
14127
+ return [{
14128
+ type: "text-delta",
14129
+ index: state.textBlock.index,
14130
+ text
14131
+ }];
14132
+ }
14133
+ function applyToolCall(state, call) {
14134
+ state.hasContent = true;
14135
+ const out = [];
14136
+ out.push(...closeText(state));
14137
+ const index = state.nextBlockIndex;
14138
+ state.nextBlockIndex += 1;
14139
+ out.push({
14140
+ type: "block-start",
14141
+ index,
14142
+ blockType: "tool-call"
14143
+ });
14144
+ state.toolCalls.set(call.id, {
14145
+ call,
14146
+ blockIndex: index
14147
+ });
14148
+ out.push({
14149
+ type: "tool-call-delta",
14150
+ index,
14151
+ id: toToolCallId(call.id),
14152
+ name: call.name,
14153
+ argumentsDelta: call.arguments === "" ? "{}" : call.arguments
14154
+ });
14155
+ return out;
14156
+ }
14157
+ function closeText(state) {
14158
+ if (state.textBlock === null) return [];
14159
+ const { index, text } = state.textBlock;
14160
+ state.textBlock = null;
14161
+ return [{
14162
+ type: "block-end",
14163
+ index,
14164
+ block: {
14165
+ type: "text",
14166
+ text
14167
+ }
14168
+ }];
14169
+ }
14170
+ function closeToolCalls$3(state) {
14171
+ const out = [];
14172
+ for (const entry of [...state.toolCalls.values()]) {
14173
+ const { call, blockIndex } = entry;
14174
+ const block = {
14175
+ type: "tool-call",
14176
+ id: toToolCallId(call.id),
14177
+ name: call.name,
14178
+ arguments: call.arguments === "" ? "{}" : call.arguments
14179
+ };
14180
+ out.push({
14181
+ type: "block-end",
14182
+ index: blockIndex,
14183
+ block
14184
+ });
14185
+ }
14186
+ state.toolCalls.clear();
14187
+ return out;
14188
+ }
14189
+ /**
14190
+ * Close the turn.
14191
+ *
14192
+ * A stream that ends while a text block is still open is a real possibility - the
14193
+ * model can be cut off mid-sentence - so the block is closed with what arrived
14194
+ * rather than dropped, and the text the user sees is the text that streamed.
14195
+ */
14196
+ function closeStream$3(state) {
14197
+ const sawToolCall = state.toolCalls.size > 0;
14198
+ const out = [...closeText(state), ...closeToolCalls$3(state)];
14199
+ out.push({
14200
+ type: "finish",
14201
+ reason: sawToolCall ? { kind: "tool-calls" } : { kind: "stop" }
14202
+ });
14203
+ return out;
14204
+ }
14205
+ //#endregion
14206
+ //#region src/host/ollama/adapter.ts
14207
+ /**
14208
+ * Retry policy for the `ollama` route.
14209
+ *
14210
+ * A 5xx from a shared inference service says nothing about this key, so it is
14211
+ * classified as `SERVER` and given the same bounded backoff the other lines use.
14212
+ * Deliberately outside the set: `INVALID_CREDENTIAL` (a rejected key fails
14213
+ * identically every time, and the pool rotates past it instead) and `ABORTED`.
14214
+ */
14215
+ const RETRY_POLICY$4 = resolveRetryPolicy({
14216
+ mode: "normal",
14217
+ maxRetries: 3,
14218
+ retryableCodes: [
14219
+ "RATE_LIMIT",
14220
+ "SERVER",
14221
+ "TIMEOUT",
14222
+ "TRANSPORT"
14223
+ ],
14224
+ backoff: {
14225
+ initialDelayMs: 1500,
14226
+ maxDelayMs: 15e3,
14227
+ jitterRatio: .2
14228
+ }
14229
+ }, "dsh-chatgpt-subscription.ollama.retry");
14230
+ var OllamaAdapter = class extends LlmAdapter {
14231
+ store;
14232
+ modelSettings;
14233
+ options;
14234
+ /**
14235
+ * Rotation pool, or null for the single stored key.
14236
+ *
14237
+ * Only the plugin entry installs it, because the entry owns the storage; an
14238
+ * adapter built without one must keep reading the one credential file rather
14239
+ * than minting pool state that would outlive the process.
14240
+ */
14241
+ accountPool;
14242
+ constructor(store = new FileCredentialStore$5(), modelSettings = new FileModelSettingsStore$5(), options = {}, accountPool) {
14243
+ super();
14244
+ this.store = store;
14245
+ this.modelSettings = modelSettings;
14246
+ this.options = options;
14247
+ this.accountPool = accountPool ?? null;
14248
+ }
14249
+ providerInfo(provider) {
14250
+ return {
14251
+ id: provider,
14252
+ name: PROVIDER_NAME$4
14253
+ };
14254
+ }
14255
+ providerRetryPolicy() {
14256
+ return RETRY_POLICY$4;
14257
+ }
14258
+ imageRequestPricing() {}
14259
+ settings() {
14260
+ return this.modelSettings.read();
14261
+ }
14262
+ /**
14263
+ * Catalog for the picker: the live `/api/tags` listing when a key can reach it,
14264
+ * the shipped fallback otherwise, narrowed by the user's enabled selection.
14265
+ *
14266
+ * Ollama's model set moves quickly, so a stale local table would be the wrong
14267
+ * default; the fallback exists only so the line is usable before the first
14268
+ * successful sync.
14269
+ */
14270
+ async catalog() {
14271
+ const load = this.options.loadCatalog ?? loadCatalog$1;
14272
+ const fetchFn = this.options.fetchFn ?? fetch;
14273
+ const credentials = await this.anyCredentials();
14274
+ if (credentials !== null) {
14275
+ const live = await load(fetchFn, credentials).catch(() => []);
14276
+ if (live.length > 0) {
14277
+ await this.modelSettings.storeCatalog(live).catch(() => void 0);
14278
+ return live;
14279
+ }
14280
+ }
14281
+ const cached = this.modelSettings.status().catalogModels;
14282
+ if (cached.length > 0) return cached;
14283
+ return [...FALLBACK_MODELS$3];
14284
+ }
14285
+ /** Any usable key, for the catalog call; the pool decides real routing later. */
14286
+ async anyCredentials() {
14287
+ if (this.accountPool !== null) return (await this.accountPool.getEffectiveCredential(/* @__PURE__ */ new Set(), this.options.fetchFn ?? fetch).catch(() => null))?.credentials ?? null;
14288
+ return this.store.read().catch(() => null);
14289
+ }
14290
+ async listModels(provider) {
14291
+ const prov = provider || "ollama";
14292
+ const settings = await this.settings();
14293
+ if (settings.enabled === false) return [];
14294
+ const catalog = await this.catalog();
14295
+ const enabled = new Set(settings.enabledModelIds);
14296
+ return (settings.enabledModelIds.length === 0 ? catalog : catalog.filter((model) => enabled.has(model.id))).map((model) => ({
14297
+ provider: prov,
14298
+ id: model.id,
14299
+ name: model.name ?? model.id,
14300
+ inputModalities: ["text", "image"]
14301
+ }));
14302
+ }
14303
+ async resolveModel(provider, modelId, signal) {
14304
+ if (signal?.aborted) throw new LlmError("Ollama model resolution aborted", "ABORTED");
14305
+ const entry = (await this.catalog()).find((model) => model.id === modelId);
14306
+ return {
14307
+ provider,
14308
+ id: modelId,
14309
+ name: entry?.name ?? modelId,
14310
+ inputModalities: ["text", "image"],
14311
+ context: { contextWindow: contextWindowFor(entry) },
14312
+ ...outputReservation(contextWindowFor(entry), DEFAULT_MAX_OUTPUT_TOKENS)
14313
+ };
14314
+ }
14315
+ async prepareCall(provider, model, signal) {
14316
+ return {
14317
+ model: await this.resolveModel(provider, model, signal),
14318
+ stream: (options) => this.stream(options)
14319
+ };
14320
+ }
14321
+ async *stream(options) {
14322
+ yield* wrapStreamWithWatchdog((watchdogSignal) => this.requestStream(options, watchdogSignal), options.signal, STREAM_IDLE_TIMEOUT_MS$4, STREAM_IDLE_TIMEOUT_CODE$4, PROVIDER_NAME$4);
14323
+ }
14324
+ async *requestStream(options, signal) {
14325
+ const fetchFn = this.options.fetchFn ?? fetch;
14326
+ const wire = wireForModel$1(options.model);
14327
+ const request = toOllamaRequest(options);
14328
+ const pool = this.accountPool;
14329
+ const tried = /* @__PURE__ */ new Set();
14330
+ let lastStatus;
14331
+ let lastDetail = "";
14332
+ let accountId;
14333
+ for (;;) {
14334
+ let credentials;
14335
+ if (pool === null) {
14336
+ const stored = await this.store.read();
14337
+ if (stored === null) throw new LlmError(`No ${PROVIDER_NAME$4} API key configured. Add one from Settings > ${PROVIDER_NAME$4} (create a key at https://ollama.com/settings/keys).`, "MISSING_CREDENTIAL");
14338
+ credentials = stored;
14339
+ } else {
14340
+ const effective = await pool.getEffectiveCredential(tried, fetchFn);
14341
+ accountId = effective.account.id;
14342
+ tried.add(accountId);
14343
+ credentials = effective.credentials;
14344
+ }
14345
+ const call = startChat(fetchFn, credentials, wire, {
14346
+ ...request,
14347
+ signal
14348
+ });
14349
+ const state = createStreamState$4();
14350
+ let openedStatus;
14351
+ call.status.then((value) => {
14352
+ openedStatus = value;
14353
+ });
14354
+ let spent = null;
14355
+ let failed = false;
14356
+ for await (const event of call.events) {
14357
+ if (event.type === "error") {
14358
+ lastStatus = openedStatus;
14359
+ lastDetail = event.message;
14360
+ failed = true;
14361
+ break;
14362
+ }
14363
+ if (event.type === "usage") {
14364
+ spent = event.usage;
14365
+ continue;
14366
+ }
14367
+ if (event.type === "text" || event.type === "tool_call") {
14368
+ for (const chunk of applyEvent(state, event)) yield chunk;
14369
+ continue;
14370
+ }
14371
+ }
14372
+ if (!failed) {
14373
+ for (const chunk of closeStream$3(state)) yield chunk;
14374
+ if (pool !== null && accountId !== void 0 && spent !== null) await pool.recordUsage(accountId, spent.inputTokens, spent.outputTokens);
14375
+ return;
14376
+ }
14377
+ if (pool === null || accountId === void 0) break;
14378
+ if (lastStatus === 401 || lastStatus === 403) {
14379
+ await pool.markAuthFailed(accountId, `${PROVIDER_NAME$4} rejected the API key (${lastStatus}).`, "invalid").catch(() => void 0);
14380
+ if (await pool.hasAnotherAvailableAccount(tried)) continue;
14381
+ break;
14382
+ }
14383
+ if (lastStatus === 429) {
14384
+ await pool.markCooldown(accountId, POOL_COOLDOWN_MS$3, `${PROVIDER_NAME$4} 429`).catch(() => void 0);
14385
+ if (await pool.hasAnotherAvailableAccount(tried)) continue;
14386
+ break;
14387
+ }
14388
+ break;
14389
+ }
14390
+ const status = lastStatus;
14391
+ if (status === 401 || status === 403) throw new LlmError(`${PROVIDER_NAME$4} rejected the API key (${status}). Replace it from Settings > ${PROVIDER_NAME$4}.${lastDetail ? ` ${lastDetail}` : ""}`, "INVALID_CREDENTIAL", { status });
14392
+ if (status === 429) throw new LlmError(`${PROVIDER_NAME$4} rate limit or plan quota reached (429). Add another key, or wait for the cooldown shown in Settings > ${PROVIDER_NAME$4}.${lastDetail ? ` ${lastDetail}` : ""}`, "RATE_LIMIT", { status: 429 });
14393
+ throw new LlmError(`${PROVIDER_NAME$4} request failed${status === void 0 ? "" : ` (${status})`}.${lastDetail}`, status !== void 0 && status >= 500 ? "SERVER" : "INVALID_REQUEST", status === void 0 ? {} : { status });
14394
+ }
14395
+ };
14396
+ /** Project DSH's request shape onto Ollama's, per surface. */
14397
+ function toOllamaRequest(options) {
14398
+ const messages = [];
14399
+ for (const message of options.messages) {
14400
+ if (message.role === "tool") {
14401
+ messages.push({
14402
+ role: "tool",
14403
+ content: textOf$4(message.content),
14404
+ toolCallId: message.toolCallId
14405
+ });
14406
+ continue;
14407
+ }
14408
+ const toolCalls = assistantToolCalls(message.content);
14409
+ if (toolCalls.length > 0) {
14410
+ messages.push({
14411
+ role: "assistant",
14412
+ content: textOf$4(message.content),
14413
+ toolCalls
14414
+ });
14415
+ continue;
14416
+ }
14417
+ messages.push({
14418
+ role: message.role === "developer" ? "system" : message.role,
14419
+ content: textOf$4(message.content)
14420
+ });
14421
+ }
14422
+ const request = {
14423
+ model: options.model,
14424
+ messages
14425
+ };
14426
+ if (options.maxTokens !== void 0) request.maxOutputTokens = options.maxTokens;
14427
+ if (options.temperature !== void 0) request.temperature = options.temperature;
14428
+ const tools = options.tools;
14429
+ if (Array.isArray(tools) && tools.length > 0) request.tools = tools.map((tool) => ({
14430
+ name: tool.name,
14431
+ ...typeof tool.description === "string" ? { description: tool.description } : {},
14432
+ parameters: tool.parameters ?? {
14433
+ type: "object",
14434
+ properties: {}
14435
+ }
14436
+ }));
14437
+ return request;
14438
+ }
14439
+ /** Tool calls an assistant turn carries, read from its content blocks. */
14440
+ function assistantToolCalls(content) {
14441
+ if (!Array.isArray(content)) return [];
14442
+ const calls = [];
14443
+ for (const block of content) {
14444
+ if (typeof block !== "object" || block === null) continue;
14445
+ const fields = block;
14446
+ if (fields.type !== "tool-call") continue;
14447
+ const id = fields.id;
14448
+ const name = fields.name;
14449
+ if (typeof id !== "string" || typeof name !== "string") continue;
14450
+ calls.push({
14451
+ id,
14452
+ name,
14453
+ arguments: typeof fields.arguments === "string" ? fields.arguments : JSON.stringify(fields.arguments ?? {})
14454
+ });
14455
+ }
14456
+ return calls;
14457
+ }
14458
+ function textOf$4(content) {
14459
+ if (typeof content === "string") return content;
14460
+ if (Array.isArray(content)) return content.map((part) => {
14461
+ if (typeof part === "string") return part;
14462
+ if (typeof part === "object" && part !== null) {
14463
+ const text = part.text;
14464
+ if (typeof text === "string") return text;
14465
+ }
14466
+ return "";
14467
+ }).join("");
14468
+ return "";
14469
+ }
14470
+ //#endregion
14471
+ //#region src/host/ollama/routes.ts
14472
+ const ROUTE_PREFIX$3 = "/ollama/api";
14473
+ function sendJson$5(response, status, body) {
14474
+ const payload = JSON.stringify(body);
14475
+ response.writeHead(status, { "content-type": "application/json; charset=utf-8" });
14476
+ response.end(payload);
14477
+ }
14478
+ function sendMethodNotAllowed$4(response) {
14479
+ sendJson$5(response, 405, {
14480
+ ok: false,
14481
+ error: "Method not allowed."
14482
+ });
14483
+ }
14484
+ async function readRequestJson$5(request) {
14485
+ const chunks = [];
14486
+ let size = 0;
14487
+ for await (const chunk of request) {
14488
+ const buffer = chunk;
14489
+ size += buffer.length;
14490
+ if (size > 8192) throw new Error("Request body is too large.");
14491
+ chunks.push(buffer);
14492
+ }
14493
+ if (chunks.length === 0) return {};
14494
+ const parsed = JSON.parse(Buffer.concat(chunks).toString("utf8"));
14495
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("Request body must be a JSON object.");
14496
+ return parsed;
14497
+ }
14498
+ /** Whether one account can serve a request right now. */
14499
+ function isUsable(account, now) {
14500
+ if (account.authStatus !== void 0 && account.authStatus !== "ok") return false;
14501
+ if (typeof account.cooldownUntil === "number" && account.cooldownUntil > now) return false;
14502
+ return true;
14503
+ }
14504
+ /** Assemble the whole status payload the Ollama tab renders. */
14505
+ async function getOllamaWebStatus(options) {
14506
+ const { accountPool, modelSettings } = options;
14507
+ const data = await accountPool.read().catch(() => null);
14508
+ const accounts = await accountPool.listAccounts().catch(() => []);
14509
+ const now = Date.now();
14510
+ const pool = {
14511
+ accounts,
14512
+ rotationStrategy: data?.rotationStrategy ?? "sequential",
14513
+ ...data?.activeAccountId === void 0 ? {} : { activeAccountId: data.activeAccountId }
14514
+ };
14515
+ const settings = await modelSettings.read().catch(() => ({
14516
+ enabled: true,
14517
+ enabledModelIds: [],
14518
+ catalogModels: [],
14519
+ defaultReasoningEffort: null
14520
+ }));
14521
+ return {
14522
+ pool,
14523
+ models: settings.catalogModels.map((model) => ({
14524
+ id: model.id,
14525
+ ...model.name === void 0 ? {} : { name: model.name }
14526
+ })),
14527
+ enabledModelIds: settings.enabledModelIds,
14528
+ usable: accounts.some((account) => isUsable(account, now)),
14529
+ catalogSynced: settings.catalogModels.length > 0
14530
+ };
14531
+ }
14532
+ /**
14533
+ * Register the Ollama settings API.
14534
+ *
14535
+ * The surface is deliberately the same one every other line exposes - status,
14536
+ * accounts, models - so the shared settings card and the client API helper
14537
+ * work against it without a per-provider special case.
14538
+ */
14539
+ function registerOllamaRoutes(ctx, options) {
14540
+ const { accountPool, modelSettings } = options;
14541
+ const fetchFn = options.fetchFn ?? fetch;
14542
+ return ctx.webServer.register({
14543
+ kind: "prefix",
14544
+ path: ROUTE_PREFIX$3,
14545
+ handler: async (request, response) => {
14546
+ const path = new URL(request.url || "/", "http://dsh.local").pathname.replace(/^\/ollama\/api\/?/, "");
14547
+ const method = request.method ?? "GET";
14548
+ if (path === "" || path === "status") return sendJson$5(response, 200, {
14549
+ ok: true,
14550
+ value: await getOllamaWebStatus(options)
14551
+ });
14552
+ if (path === "accounts") {
14553
+ if (method === "GET") return sendJson$5(response, 200, {
14554
+ ok: true,
14555
+ value: await getOllamaWebStatus(options)
14556
+ });
14557
+ if (method !== "POST") return sendMethodNotAllowed$4(response);
14558
+ if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
14559
+ ok: false,
14560
+ error: "Cross-origin request rejected."
14561
+ });
14562
+ let body;
14563
+ try {
14564
+ body = await readRequestJson$5(request);
14565
+ } catch (error) {
14566
+ return sendJson$5(response, 400, {
14567
+ ok: false,
14568
+ error: error instanceof Error ? error.message : String(error)
14569
+ });
14570
+ }
14571
+ const action = typeof body.action === "string" ? body.action : "";
14572
+ const accountId = typeof body.accountId === "string" ? body.accountId : void 0;
14573
+ try {
14574
+ if (action === "add") {
14575
+ const apiKey = typeof body.apiKey === "string" ? body.apiKey.trim() : "";
14576
+ if (apiKey === "") return sendJson$5(response, 400, {
14577
+ ok: false,
14578
+ error: "API key is required."
14579
+ });
14580
+ const alias = typeof body.alias === "string" && body.alias.trim() !== "" ? body.alias.trim() : void 0;
14581
+ const credentials = {
14582
+ apiKey,
14583
+ addedAt: Date.now()
14584
+ };
14585
+ if (alias !== void 0) credentials.alias = alias;
14586
+ return sendJson$5(response, 200, {
14587
+ ok: true,
14588
+ value: { id: (await accountPool.addAccount(credentials, alias)).id }
14589
+ });
14590
+ }
14591
+ if (accountId === void 0) return sendJson$5(response, 400, {
14592
+ ok: false,
14593
+ error: "accountId is required."
14594
+ });
14595
+ if (action === "set-primary") await accountPool.setPrimary(accountId);
14596
+ else if (action === "set-alias" && typeof body.alias === "string") await accountPool.setAlias(accountId, body.alias);
14597
+ else if (action === "delete") await accountPool.deleteAccount(accountId);
14598
+ else if (action === "clear-cooldown") await accountPool.clearCooldown(accountId);
14599
+ else if (action === "strategy" && (body.strategy === "sequential" || body.strategy === "round-robin" || body.strategy === "sticky")) await accountPool.setStrategy(body.strategy);
14600
+ else return sendJson$5(response, 400, {
14601
+ ok: false,
14602
+ error: "Unsupported account action."
14603
+ });
14604
+ } catch (error) {
14605
+ return sendJson$5(response, 400, {
14606
+ ok: false,
14607
+ error: error instanceof Error ? error.message : String(error)
14608
+ });
14609
+ }
14610
+ return sendJson$5(response, 200, {
14611
+ ok: true,
14612
+ value: await getOllamaWebStatus(options)
14613
+ });
14614
+ }
14615
+ if (path === "models") {
14616
+ if (method === "GET") {
14617
+ const settings = await modelSettings.read();
14618
+ return sendJson$5(response, 200, {
14619
+ ok: true,
14620
+ value: {
14621
+ models: settings.catalogModels,
14622
+ enabled: settings.enabled,
14623
+ enabledModelIds: settings.enabledModelIds
14624
+ }
14625
+ });
14626
+ }
14627
+ if (method !== "POST") return sendMethodNotAllowed$4(response);
14628
+ if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
14629
+ ok: false,
14630
+ error: "Cross-origin request rejected."
14631
+ });
14632
+ const body = await readRequestJson$5(request).catch(() => ({}));
14633
+ if (typeof body.enabled === "boolean") await modelSettings.update({ enabled: body.enabled });
14634
+ if (Array.isArray(body.enabledModelIds)) await modelSettings.update({ enabledModelIds: body.enabledModelIds.filter((id) => typeof id === "string") });
14635
+ return sendJson$5(response, 200, {
14636
+ ok: true,
14637
+ value: await modelSettings.read()
14638
+ });
14639
+ }
14640
+ if (path === "catalog/refresh") {
14641
+ if (method !== "POST") return sendMethodNotAllowed$4(response);
14642
+ if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
14643
+ ok: false,
14644
+ error: "Cross-origin request rejected."
14645
+ });
14646
+ let credentials = null;
14647
+ try {
14648
+ credentials = (await accountPool.getEffectiveCredential(void 0, fetchFn)).credentials;
14649
+ } catch {
14650
+ credentials = null;
14651
+ }
14652
+ if (credentials === null) return sendJson$5(response, 400, {
14653
+ ok: false,
14654
+ error: "Add an API key before syncing models."
14655
+ });
14656
+ const models = await loadCatalog$1(fetchFn, credentials).catch(() => []);
14657
+ if (models.length === 0) return sendJson$5(response, 502, {
14658
+ ok: false,
14659
+ error: "Could not read the model list from Ollama."
14660
+ });
14661
+ await modelSettings.storeCatalog(models);
14662
+ return sendJson$5(response, 200, {
14663
+ ok: true,
14664
+ value: { models }
14665
+ });
14666
+ }
14667
+ }
14668
+ });
14669
+ }
14670
+ //#endregion
14671
+ //#region src/host/ollama/account-pool.ts
14672
+ /** Encrypted pool file this line owns. */
14673
+ function ollamaPoolPath() {
14674
+ return path.join(dshHomeDir(), "storages", "ollama-pool.json");
14675
+ }
14676
+ /**
14677
+ * Stable identity of one key.
14678
+ *
14679
+ * Ollama hands the user a bare key and nothing else — no account id, no email —
14680
+ * so the key's own digest is the only identity available. It is never written
14681
+ * anywhere: it exists to tell two pasted keys apart inside the pool document,
14682
+ * and it is not a credential and cannot be used as one.
14683
+ */
14684
+ function ollamaAccountKey(credentials) {
14685
+ return createHash("sha256").update(credentials.apiKey).digest("hex");
14686
+ }
14687
+ function optionalString$5(record, key) {
14688
+ const value = record[key];
14689
+ if (value === void 0 || value === null) return void 0;
14690
+ if (typeof value !== "string") throw new Error("Ollama pool account field is invalid");
14691
+ return value;
14692
+ }
14693
+ /** Non-negative integer, or undefined for anything else. */
14694
+ function counter(value) {
14695
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
14696
+ return Math.floor(value);
14697
+ }
14698
+ /** Read one account's counters, dropping the whole record if it is unusable. */
14699
+ function readUsage(value) {
14700
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
14701
+ const record = value;
14702
+ const inputTokens = counter(record.inputTokens);
14703
+ const outputTokens = counter(record.outputTokens);
14704
+ const requestCount = counter(record.requestCount);
14705
+ if (inputTokens === void 0 || outputTokens === void 0 || requestCount === void 0) return void 0;
14706
+ const lastCountedAt = counter(record.lastCountedAt);
14707
+ return {
14708
+ inputTokens,
14709
+ outputTokens,
14710
+ requestCount,
14711
+ ...lastCountedAt === void 0 ? {} : { lastCountedAt }
14712
+ };
14713
+ }
14714
+ /** Validate and normalize one whole pool document. */
14715
+ function parseOllamaPoolData(value) {
14716
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("Ollama pool payload is invalid");
14717
+ const record = value;
14718
+ const accounts = [];
14719
+ for (const item of Array.isArray(record.accounts) ? record.accounts : []) {
14720
+ if (typeof item !== "object" || item === null) continue;
14721
+ const raw = item;
14722
+ if (typeof raw.id !== "string") continue;
14723
+ const credentials = parseOllamaCredentials(raw.credentials);
14724
+ const account = {
14725
+ id: raw.id,
14726
+ alias: typeof raw.alias === "string" && raw.alias !== "" ? raw.alias : "Ollama 账号",
14727
+ credentials,
14728
+ addedAt: typeof raw.addedAt === "number" ? raw.addedAt : Date.now(),
14729
+ isPrimary: raw.isPrimary === true
14730
+ };
14731
+ const lastModelId = optionalString$5(raw, "lastModelId");
14732
+ if (lastModelId !== void 0) account.lastModelId = lastModelId;
14733
+ const usage = readUsage(raw.usage);
14734
+ if (usage !== void 0) account.usage = usage;
14735
+ if (typeof raw.lastUsedAt === "number") account.lastUsedAt = raw.lastUsedAt;
14736
+ if (typeof raw.cooldownUntil === "number") account.cooldownUntil = raw.cooldownUntil;
14737
+ if (typeof raw.cooldownReason === "string") account.cooldownReason = raw.cooldownReason;
14738
+ if (raw.authStatus === "expired" || raw.authStatus === "invalid") account.authStatus = raw.authStatus;
14739
+ if (typeof raw.authFailedReason === "string") account.authFailedReason = raw.authFailedReason;
14740
+ accounts.push(account);
14741
+ }
14742
+ const result = {
14743
+ version: 1,
14744
+ rotationStrategy: normalizeRotationStrategy(record.rotationStrategy),
14745
+ accounts
14746
+ };
14747
+ if (typeof record.activeAccountId === "string") result.activeAccountId = record.activeAccountId;
14748
+ return result;
14749
+ }
14750
+ /**
14751
+ * Ollama's account pool.
14752
+ *
14753
+ * An Ollama credential is a static key that never expires, so this wrapper
14754
+ * supplies identity, the display alias a user chose, and the mirror of the
14755
+ * single-credential file — no refresh logic is needed anywhere.
14756
+ */
14757
+ var OllamaAccountPool = class extends AccountPoolCore {
14758
+ store;
14759
+ constructor(options = {}) {
14760
+ const store = options.store ?? new FileCredentialStore$5();
14761
+ const hooks = {
14762
+ providerId: PROVIDER_ID$4,
14763
+ displayName: PROVIDER_NAME$4,
14764
+ poolFile: ollamaPoolPath(),
14765
+ keychainService: "dsh-ollama-pool",
14766
+ parsePoolData: parseOllamaPoolData,
14767
+ dedupeKey: ollamaAccountKey,
14768
+ defaultAlias: (credentials, position) => credentials.alias ?? `Ollama 账号 ${position}`,
14769
+ createAccount: ({ id, alias, credentials, addedAt, isPrimary }) => ({
14770
+ id,
14771
+ alias,
14772
+ credentials,
14773
+ addedAt,
14774
+ isPrimary
14775
+ }),
14776
+ legacyAccount: async () => {
14777
+ const stored = await store.read();
14778
+ if (stored === null) return null;
14779
+ return {
14780
+ id: "acc_primary",
14781
+ alias: stored.alias ?? "Ollama 账号 1",
14782
+ credentials: stored,
14783
+ addedAt: stored.addedAt ?? Date.now(),
14784
+ isPrimary: true
14785
+ };
14786
+ },
14787
+ mirrorPrimary: async (credentials) => {
14788
+ if (credentials === null) {
14789
+ await store.clear();
14790
+ return;
14791
+ }
14792
+ await store.write(credentials);
14793
+ },
14794
+ extendSummary: (account, base) => {
14795
+ const extra = {};
14796
+ if (account.lastModelId !== void 0) extra.planLabel = account.lastModelId;
14797
+ if (account.usage !== void 0) extra.usage = { ...account.usage };
14798
+ return Object.keys(extra).length === 0 ? base : {
14799
+ ...base,
14800
+ ...extra
14801
+ };
14802
+ },
14803
+ ...options.backend === void 0 ? {} : { backend: options.backend },
14804
+ ...options.maxAccounts === void 0 ? {} : { maxAccounts: options.maxAccounts }
14805
+ };
14806
+ super(hooks);
14807
+ this.store = store;
14808
+ }
14809
+ /**
14810
+ * Pick the key for the next request, skipping accounts already tried.
14811
+ *
14812
+ * Ollama's credential carries no deployment dimension - there is one cloud
14813
+ * endpoint and every key is valid for it - so the account and its key are
14814
+ * returned together and nothing has to be attached to the credential.
14815
+ */
14816
+ async getEffectiveCredential(excludeIds, fetchFn = fetch) {
14817
+ return this.getEffectiveAccount(excludeIds, fetchFn);
14818
+ }
14819
+ /** The pre-pool credential file this pool mirrors its primary account into. */
14820
+ mirrorStore() {
14821
+ return this.store;
14822
+ }
14823
+ async recordUsage(accountId, inputTokens, outputTokens) {
14824
+ if (inputTokens <= 0 && outputTokens <= 0) return;
14825
+ await this.updatePool((data) => {
14826
+ const account = data.accounts.find((entry) => entry.id === accountId);
14827
+ if (account === void 0) return false;
14828
+ const previous = account.usage;
14829
+ account.usage = {
14830
+ inputTokens: (previous?.inputTokens ?? 0) + inputTokens,
14831
+ outputTokens: (previous?.outputTokens ?? 0) + outputTokens,
14832
+ requestCount: (previous?.requestCount ?? 0) + 1,
14833
+ lastCountedAt: Date.now()
14834
+ };
14835
+ return true;
14836
+ }).catch(() => void 0);
14837
+ }
14838
+ };
14839
+ //#endregion
13155
14840
  //#region src/host/kimi-code/model-catalog.ts
13156
14841
  /**
13157
14842
  * The four ids the subscription serves today.
@@ -17188,7 +18873,6 @@ var KimiCodeAdapter = class extends LlmAdapter {
17188
18873
  name: entry?.name ?? modelId,
17189
18874
  inputModalities: inputModalitiesForEntry(modelId, catalog),
17190
18875
  context: { contextWindow: this.contextWindowFor(modelId, entry, settings.contextWindowOverrides) },
17191
- defaultMaxTokens: maxOutputTokensFor$3(modelId, this.contextWindowFor(modelId, entry, settings.contextWindowOverrides)),
17192
18876
  ...efforts.length === 0 ? {} : { reasoning: {
17193
18877
  efforts: efforts.map((effort) => ({
17194
18878
  id: ReasoningEffortId(effort),
@@ -21658,9 +23342,8 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
21658
23342
  * One model's resolved metadata, read through the current selection.
21659
23343
  *
21660
23344
  * The effective context window is the override when one is saved, and the
21661
- * output cap is sized against that same number — a window the card shows and a
21662
- * cap the request path computes from a different one is precisely the drift
21663
- * this single resolver exists to prevent.
23345
+ * wire output cap is sized against that same number in requestStream. It is
23346
+ * deliberately not exposed as a fixed defaultMaxTokens reservation here.
21664
23347
  */
21665
23348
  async resolveModel(provider, modelId, signal) {
21666
23349
  if (signal?.aborted === true) throw new LlmError("MiniMax Code model resolution aborted", "ABORTED");
@@ -21675,7 +23358,6 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
21675
23358
  name: entry?.name ?? modelId,
21676
23359
  inputModalities: modalitiesForModel(modelId),
21677
23360
  context: { contextWindow },
21678
- defaultMaxTokens: maxOutputTokensFor$1(modelId, contextWindow),
21679
23361
  ...efforts.length === 0 ? {} : { reasoning: {
21680
23362
  efforts: efforts.map((effort) => ({
21681
23363
  id: ReasoningEffortId(effort),
@@ -26378,7 +28060,7 @@ var WorkBuddyAdapter = class extends LlmAdapter {
26378
28060
  name: entry.id === modelId ? entry.name : modelId,
26379
28061
  inputModalities: entry.supportsImage ? ["text", "image"] : ["text"],
26380
28062
  context: { contextWindow: contextWindow || 128e3 },
26381
- defaultMaxTokens: maxOutputTokensFor$2(modelId, catalog),
28063
+ ...outputReservation(contextWindow || 128e3, maxOutputTokensFor$2(modelId, catalog)),
26382
28064
  ...efforts.length === 0 ? {} : { reasoning: {
26383
28065
  efforts: efforts.map((effort) => ({
26384
28066
  id: ReasoningEffortId(effort),
@@ -26433,7 +28115,13 @@ var WorkBuddyAdapter = class extends LlmAdapter {
26433
28115
  const fetchFn = this.options.fetchFn ?? fetch;
26434
28116
  const requestOptions = offloadOldestRequestImages$1(normalizeGenerateOptions(options));
26435
28117
  const images = await resolveRequestImages$1(requestOptions, this.options.attachments, signal);
26436
- const body = JSON.stringify(buildChatRequest(requestOptions, images));
28118
+ const settings = await this.settings();
28119
+ const credentials = await this.credentials(settings);
28120
+ const catalog = credentials === null ? FALLBACK_MODELS$1 : await this.catalog(credentials);
28121
+ const body = JSON.stringify(buildChatRequest({
28122
+ ...requestOptions,
28123
+ maxTokens: requestOptions.maxTokens ?? maxOutputTokensFor$2(options.model, catalog)
28124
+ }, images));
26437
28125
  const pool = this.accountPool;
26438
28126
  const tried = /* @__PURE__ */ new Set();
26439
28127
  let response;
@@ -27404,17 +29092,29 @@ function loopbackRedirectUri(port = DEFAULT_CALLBACK_PORT) {
27404
29092
  * LOCAL ADDITIONS — the rows the reference snapshot predates
27405
29093
  * ---------------------------------------------------------------------------
27406
29094
  *
27407
- * `claude-opus-5-5` and `claude-sonnet-5-5` are the two such rows today. The
27408
- * mirror list in the test is `LOCALLY_CURATED_MODEL_IDS`; the two must be
27409
- * changed together. Sonnet 5.5's own reasons are recorded at its row; what
27410
- * follows below is Opus 5.5's.
27411
- *
27412
- * It is CURATED rather than transcribed because the snapshot this table copies
27413
- * has no entry for it, and the one thing a transcription table must never do is
27414
- * invent a row and let it read as if it had been read from somewhere. An
27415
- * invented row that mimics the format of a checked one is worse than a missing
27416
- * row: it is unverifiable and it looks verified. So the row below is marked at
27417
- * the row itself, the test asserts the id is on the curated list, and the test
29095
+ * TWO KINDS OF ROW ARE NOT FULLY TRANSCRIBED, and they are different things.
29096
+ * Do not collapse them.
29097
+ *
29098
+ * 1. `claude-sonnet-5-5` — the snapshot this table was copied from has no
29099
+ * entry for it, so the whole row is CURATED: every value below comes from
29100
+ * the vendor's own documentation and no snapshot field can check it. The
29101
+ * mirror list in the test is `LOCALLY_CURATED_MODEL_IDS`.
29102
+ *
29103
+ * 2. `claude-opus-5-5` — a NEWER snapshot (pi-ai >= 0.87.1) does carry it, so
29104
+ * the row sits in the snapshot's own position and EVERY field is checked
29105
+ * against that snapshot. Exactly one field is not: `thinkingMode`, recorded
29106
+ * in the test as `SNAPSHOT_AGREES_BUT_CURATED_WINS`. The snapshot would
29107
+ * classify it 'mid-convo', whose form forces `output_config.effort = 'high'`
29108
+ * when the caller names none, while the vendor documents this model's
29109
+ * default effort as MEDIUM. Transcribing it would silently outrank the user
29110
+ * and think — and bill — harder than asked, so the documented 'adaptive'
29111
+ * stands. The test asserts the row still disagrees with the snapshot, so a
29112
+ * future snapshot that agrees fails loudly and retires the entry.
29113
+ *
29114
+ * Both must be changed together with the mirror list in the test. An invented
29115
+ * row that mimics the format of a checked one is worse than a missing row: it
29116
+ * is unverifiable and it looks verified. So each row is marked at the row
29117
+ * itself, the test asserts the id is on the curated list, and the test
27418
29118
  * asserts the curated list is exactly the ids the snapshot lacks — which keeps
27419
29119
  * the lock narrow instead of merely weaker.
27420
29120
  *
@@ -27439,10 +29139,12 @@ function loopbackRedirectUri(port = DEFAULT_CALLBACK_PORT) {
27439
29139
  * 2. `thinkingMode: 'adaptive'`, NOT `'mid-convo'`. The two are easy to confuse
27440
29140
  * here because every model in this line that refuses a temperature is also,
27441
29141
  * so far, a managed-effort model. That is a coincidence of `compat`, not a
27442
- * rule: this row carries no `supportsMidConvoEffort` flag, and 'mid-convo'
29142
+ * rule. A newer snapshot DOES flag this row `supportsMidConvoEffort` and
29143
+ * would therefore classify it 'mid-convo' — which is exactly why this is the
29144
+ * one declared exception rather than a transcription: 'mid-convo'
27443
29145
  * additionally forces `output_config = { effort: 'high' }` when the caller
27444
- * names no effort. That forced high is correct for the rows that DO carry the
27445
- * flag, because the vendor makes `high` their default effort; this model's
29146
+ * names no effort. That forced high is correct for the rows whose documented
29147
+ * default effort is high, and wrong for this one; this model's
27446
29148
  * documented default effort is `medium`. Labelling it 'mid-convo' would
27447
29149
  * silently override the vendor's own default and make every request think —
27448
29150
  * and cost — harder than the user asked for.
@@ -27459,8 +29161,8 @@ function loopbackRedirectUri(port = DEFAULT_CALLBACK_PORT) {
27459
29161
  * the user has to reach.
27460
29162
  */
27461
29163
  /**
27462
- * Sixteen rows: the snapshot's own 14, in the snapshot's own declaration order,
27463
- * followed by the curated rows the snapshot predates (see LOCAL ADDITIONS above).
29164
+ * Sixteen rows: the snapshot's own 15, in the snapshot's own declaration order,
29165
+ * followed by the one row the snapshot still predates (see LOCAL ADDITIONS above).
27464
29166
  *
27465
29167
  * The order matters and is not cosmetic. The test asserts the snapshot's ids
27466
29168
  * appear in the table as a SUBSEQUENCE, so a curated row may be appended or
@@ -27649,6 +29351,25 @@ const CLAUDE_MODELS = Object.freeze([
27649
29351
  ],
27650
29352
  canDisableThinking: false
27651
29353
  },
29354
+ {
29355
+ id: "claude-opus-5-5",
29356
+ name: "Claude Opus 5.5",
29357
+ contextWindow: 1e6,
29358
+ maxTokens: 128e3,
29359
+ supportsImage: true,
29360
+ supportsTemperature: false,
29361
+ thinkingMode: "adaptive",
29362
+ reasoningEfforts: [
29363
+ "low",
29364
+ "medium",
29365
+ "high",
29366
+ "xhigh",
29367
+ "max"
29368
+ ],
29369
+ canDisableThinking: false,
29370
+ minCliVersion: "2.1.280",
29371
+ bindsThinkingToPrefix: true
29372
+ },
27652
29373
  {
27653
29374
  id: "claude-sonnet-4-5",
27654
29375
  name: "Claude Sonnet 4.5 (latest)",
@@ -27712,25 +29433,6 @@ const CLAUDE_MODELS = Object.freeze([
27712
29433
  ],
27713
29434
  canDisableThinking: true
27714
29435
  },
27715
- {
27716
- id: "claude-opus-5-5",
27717
- name: "Claude Opus 5.5",
27718
- contextWindow: 1e6,
27719
- maxTokens: 128e3,
27720
- supportsImage: true,
27721
- supportsTemperature: false,
27722
- thinkingMode: "adaptive",
27723
- reasoningEfforts: [
27724
- "low",
27725
- "medium",
27726
- "high",
27727
- "xhigh",
27728
- "max"
27729
- ],
27730
- canDisableThinking: false,
27731
- minCliVersion: "2.1.280",
27732
- bindsThinkingToPrefix: true
27733
- },
27734
29436
  {
27735
29437
  id: "claude-sonnet-5-5",
27736
29438
  name: "Claude Sonnet 5.5",
@@ -32254,11 +33956,9 @@ function cancelLogin() {
32254
33956
  * importing it before it exists would make this adapter uncompilable, and
32255
33957
  * importing it afterwards changes nothing, because the only methods used are
32256
33958
  * the five in {@link ClaudeAccountPoolLike}.
32257
- * - A2. The live `GET /v1/models` listing is NOT read on the request path. It
32258
- * is authoritative for the context window alone (see client.ts), and the only
32259
- * model fact the body depends on — the thinking form — comes from the frozen
32260
- * catalog. Loading it per request would add a cached round trip that cannot
32261
- * change a byte of the body.
33959
+ * - A2. When no fixed output default was materialized, the request path reads
33960
+ * the cached catalog to preserve its wire cap independently of compaction
33961
+ * reservation metadata. Thinking forms still come from the frozen catalog.
32262
33962
  * - A3. A 429 with no `retry-after` and no reset instant cools the account for
32263
33963
  * {@link POOL_COOLDOWN_MS}. That is deliberately shorter than the 5-hour
32264
33964
  * window: an account parked for hours after a burst limit recovers would cost
@@ -32501,7 +34201,7 @@ var ClaudeAdapter = class ClaudeAdapter extends LlmAdapter {
32501
34201
  name: entry.name,
32502
34202
  inputModalities: entry.supportsImage ? ["text", "image"] : ["text"],
32503
34203
  context: { contextWindow: contextWindow || 2e5 },
32504
- defaultMaxTokens: maxOutputTokensFor(modelId, catalog),
34204
+ ...outputReservation(contextWindow || 2e5, maxOutputTokensFor(modelId, catalog)),
32505
34205
  ...efforts.length === 0 ? {} : { reasoning: {
32506
34206
  efforts: efforts.map((effort) => ({
32507
34207
  id: ReasoningEffortId(effort),
@@ -32584,7 +34284,11 @@ var ClaudeAdapter = class ClaudeAdapter extends LlmAdapter {
32584
34284
  }
32585
34285
  async attemptRequest(credentials, requestOptions, images, toolNames, thinking, settings, signal, fetchFn) {
32586
34286
  const cacheTtl = ClaudeAdapter.cacheTtlFor(settings);
32587
- const payload = buildClaudeRequestBody(requestOptions, images, {
34287
+ const catalog = requestOptions.maxTokens === void 0 ? await this.catalog(credentials) : void 0;
34288
+ const payload = buildClaudeRequestBody({
34289
+ ...requestOptions,
34290
+ maxTokens: requestOptions.maxTokens ?? maxOutputTokensFor(requestOptions.model, catalog)
34291
+ }, images, {
32588
34292
  toolNames,
32589
34293
  cacheControl: true,
32590
34294
  cacheTtl
@@ -37709,6 +39413,38 @@ function installRelayProbe(ctx, options) {
37709
39413
  }
37710
39414
  //#endregion
37711
39415
  //#region src/host/reasoning-collapse-guard/index.ts
39416
+ /**
39417
+ * Reasoning-collapse guard.
39418
+ *
39419
+ * A long reasoning stream can degenerate into repetition: it cycles through a
39420
+ * handful of short phrases ("Let me call. Go. Calling. Go.") without emitting a
39421
+ * tool call or concluding anything, and runs until the output-token ceiling
39422
+ * truncates it. One archived session burned 128,000 output tokens that way and
39423
+ * returned an empty answer.
39424
+ *
39425
+ * The guard scores n-gram uniqueness over a trailing window of reasoning text.
39426
+ * Healthy deliberation keeps nearly every n-gram unique and scores near zero; a
39427
+ * degenerate stream scores near one. On a hit it stops yielding chunks, so the
39428
+ * in-flight request ends, and lets the turn resume on a fresh step.
39429
+ *
39430
+ * Seam choice: the guard wraps `llm/stream`, not `agent/assistant-stream`.
39431
+ * `agent/assistant-stream` is emit-mode — it can observe but cannot stop a
39432
+ * stream — and it does not exist before harness 0.1.5, which would make the
39433
+ * guard a silent no-op on every generation this plugin supports below that.
39434
+ * `llm/stream` is a waterfall whose `(options, next) => AsyncIterable<StreamChunk>`
39435
+ * signature is byte-identical from 0.1.2-alpha.5 through 0.2.0-rc.1, and
39436
+ * returning early from the listener is what actually ends the stream.
39437
+ *
39438
+ * The guard subscribes to that waterfall and never reassigns `ctx.llm.stream`.
39439
+ * The harness publishes `stream` as a one-argument method that enters the
39440
+ * waterfall itself, so a property rewrite carrying a second `next` parameter
39441
+ * breaks every caller dispatching it as a method, and misses the prepared
39442
+ * call that skips the published method entirely.
39443
+ *
39444
+ * The guard rewrites nothing and appends nothing to the aborted attempt. Its
39445
+ * only model-visible input is the single resume message it queues afterwards,
39446
+ * on a fresh turn.
39447
+ */
37712
39448
  const DEFAULT_GUARD_OPTIONS = {
37713
39449
  windowChars: 4096,
37714
39450
  minWindowChars: 1500,
@@ -37778,17 +39514,35 @@ function createBreakerState() {
37778
39514
  };
37779
39515
  }
37780
39516
  /**
37781
- * The resume message is a literal user message so the guard loads on every
37782
- * generation without importing a generation-bound message factory.
39517
+ * The resume message the guard queues onto the kept inbox.
39518
+ *
39519
+ * It is built with the harness message factory and carries this package's own
39520
+ * source kind, because the harness rejects a message that is not fully
39521
+ * identified: a steer payload with no `id` is refused by
39522
+ * `assertMessageEventShape` (`session event … lacks an identified message`)
39523
+ * and one with no `source` by the same check (`… has invalid source`). Either
39524
+ * refusal is thrown from `session.append` at the very moment the guard's
39525
+ * microtask steers — after the abort has already been taken — so the failure
39526
+ * surfaced as a repairable-splice violation, the attempt was already cancelled,
39527
+ * and the turn ended with the user told nothing.
39528
+ *
39529
+ * `createUserMessage` is available on every supported generation, and the
39530
+ * `dsh-chatgpt-subscription` kind is this package's own entry in the
39531
+ * merge-extensible `MessageSourceMap`, so the resume carries real provenance
39532
+ * instead of impersonating a human turn.
37783
39533
  */
37784
39534
  function createResumeMessage(text) {
37785
- return {
37786
- role: "user",
39535
+ return createUserMessage({
37787
39536
  content: [{
37788
39537
  type: "text",
37789
39538
  text
37790
- }]
37791
- };
39539
+ }],
39540
+ source: {
39541
+ kind: PLUGIN_MESSAGE_SOURCE_KIND,
39542
+ form: "notice",
39543
+ summary: boundContextSummary("Reasoning collapsed; the attempt was stopped and resumed.")
39544
+ }
39545
+ });
37792
39546
  }
37793
39547
  function appendWindow(current, addition, limit) {
37794
39548
  const combined = current + addition;
@@ -38007,12 +39761,12 @@ function apply(ctx, pluginConfig = {}) {
38007
39761
  const claimAntigravityRoute = () => {
38008
39762
  if (antigravityRegistration !== void 0) return;
38009
39763
  try {
38010
- antigravityRegistration = ctx.llm.registerAdapter([PROVIDER_ID$5], antigravityAdapter);
38011
- if (antigravityConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] Antigravity route "${PROVIDER_ID$5}" is now served by this plugin`);
39764
+ antigravityRegistration = ctx.llm.registerAdapter([PROVIDER_ID$6], antigravityAdapter);
39765
+ if (antigravityConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] Antigravity route "${PROVIDER_ID$6}" is now served by this plugin`);
38012
39766
  antigravityConflict = null;
38013
39767
  } catch (error) {
38014
39768
  antigravityConflict = error instanceof Error ? error.message : String(error);
38015
- ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$5}" is already owned by another adapter; Antigravity models keep being served by that one until its configuration is removed (${antigravityConflict})`);
39769
+ ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$6}" is already owned by another adapter; Antigravity models keep being served by that one until its configuration is removed (${antigravityConflict})`);
38016
39770
  }
38017
39771
  };
38018
39772
  claimAntigravityRoute();
@@ -38031,6 +39785,12 @@ function apply(ctx, pluginConfig = {}) {
38031
39785
  }, commandCodeAccountPool);
38032
39786
  let commandCodeRegistration;
38033
39787
  let commandCodeConflict = null;
39788
+ const ollamaStore = new FileCredentialStore$5();
39789
+ const ollamaModelSettings = new FileModelSettingsStore$5();
39790
+ const ollamaAccountPool = new OllamaAccountPool({ store: ollamaStore });
39791
+ const ollamaAdapter = new OllamaAdapter(ollamaStore, ollamaModelSettings, { fetchFn: proxyFetch }, ollamaAccountPool);
39792
+ let ollamaRegistration;
39793
+ let ollamaConflict = null;
38034
39794
  const kimiCodeAdapter = new KimiCodeAdapter(kimiCodeStore, kimiCodeModelSettings, kimiCodePreferences, {
38035
39795
  fetchFn: proxyFetch,
38036
39796
  attachments: ctx.attachments,
@@ -38056,15 +39816,32 @@ function apply(ctx, pluginConfig = {}) {
38056
39816
  const kimiCodeRouteWatch = typeof ctx.on === "function" ? ctx.on("llm/adapters-updated", () => {
38057
39817
  claimKimiCodeRoute();
38058
39818
  }) : void 0;
39819
+ const claimOllamaRoute = () => {
39820
+ if (ollamaRegistration !== void 0) return;
39821
+ try {
39822
+ ollamaRegistration = ctx.llm.registerAdapter([PROVIDER_ID$4], ollamaAdapter);
39823
+ if (ollamaConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${PROVIDER_NAME$4} route "${PROVIDER_ID$4}" is now served by this plugin`);
39824
+ ollamaConflict = null;
39825
+ } catch (error) {
39826
+ ollamaConflict = error instanceof Error ? error.message : String(error);
39827
+ ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$4}" is already owned by another adapter; ${PROVIDER_NAME$4} models keep being served by that one until its configuration is removed (${ollamaConflict})`);
39828
+ }
39829
+ };
39830
+ claimOllamaRoute();
39831
+ const disposeOllamaRoutes = registerOllamaRoutes(ctx, {
39832
+ accountPool: ollamaAccountPool,
39833
+ modelSettings: ollamaModelSettings,
39834
+ fetchFn: proxyFetch
39835
+ });
38059
39836
  const claimCommandCodeRoute = () => {
38060
39837
  if (commandCodeRegistration !== void 0) return;
38061
39838
  try {
38062
- commandCodeRegistration = ctx.llm.registerAdapter([PROVIDER_ID$4], commandCodeAdapter);
38063
- if (commandCodeConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${PROVIDER_NAME$4} route "${PROVIDER_ID$4}" is now served by this plugin`);
39839
+ commandCodeRegistration = ctx.llm.registerAdapter([PROVIDER_ID$5], commandCodeAdapter);
39840
+ if (commandCodeConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${PROVIDER_NAME$5} route "${PROVIDER_ID$5}" is now served by this plugin`);
38064
39841
  commandCodeConflict = null;
38065
39842
  } catch (error) {
38066
39843
  commandCodeConflict = error instanceof Error ? error.message : String(error);
38067
- ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$4}" is already owned by another adapter; ${PROVIDER_NAME$4} models keep being served by that one until its configuration is removed (${commandCodeConflict})`);
39844
+ ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$5}" is already owned by another adapter; ${PROVIDER_NAME$5} models keep being served by that one until its configuration is removed (${commandCodeConflict})`);
38068
39845
  }
38069
39846
  };
38070
39847
  claimCommandCodeRoute();
@@ -38247,7 +40024,7 @@ function apply(ctx, pluginConfig = {}) {
38247
40024
  });
38248
40025
  };
38249
40026
  const disposeRoutes = registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwitcher, readRouteAudit, codexAccountPool);
38250
- const disposeAdapter = ctx.llm.registerAdapter([PROVIDER_ID$6], adapter);
40027
+ const disposeAdapter = ctx.llm.registerAdapter([PROVIDER_ID$7], adapter);
38251
40028
  const disposeImageTool = ctx.tools.register(createCodexImageTool(oauth, ctx.attachments, { fetchFn: proxyFetch }));
38252
40029
  const disposeVideoTool = ctx.tools.register(createKimiVideoTool(ctx, { fetchFn: proxyFetch }));
38253
40030
  ctx.inject(["web"], (ctx) => {
@@ -38273,6 +40050,9 @@ function apply(ctx, pluginConfig = {}) {
38273
40050
  releaseHandle(antigravityRouteWatch);
38274
40051
  antigravityRegistration?.();
38275
40052
  antigravityRegistration = void 0;
40053
+ disposeOllamaRoutes();
40054
+ ollamaRegistration?.();
40055
+ ollamaRegistration = void 0;
38276
40056
  disposeCommandCodeRoutes();
38277
40057
  releaseHandle(commandCodeRouteWatch);
38278
40058
  commandCodeRegistration?.();