@eddyskywalker/dsh-chatgpt-subscription 0.10.14 → 0.10.20

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 (62) hide show
  1. package/CHANGELOG.md +42 -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 +2040 -305
  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/responses-mapper.d.ts +0 -2
  51. package/lib/types/host/responses-mapper.d.ts.map +1 -1
  52. package/lib/types/host/search-provider-switcher.d.ts +12 -0
  53. package/lib/types/host/search-provider-switcher.d.ts.map +1 -1
  54. package/lib/types/host/workbuddy/adapter.d.ts.map +1 -1
  55. package/lib/types/index.d.ts.map +1 -1
  56. package/lib/types/shared/command-code-contracts.d.ts +10 -4
  57. package/lib/types/shared/command-code-contracts.d.ts.map +1 -1
  58. package/lib/types/shared/model-catalog.d.ts +58 -1
  59. package/lib/types/shared/model-catalog.d.ts.map +1 -1
  60. package/lib/types/shared/ollama-contracts.d.ts +36 -0
  61. package/lib/types/shared/ollama-contracts.d.ts.map +1 -0
  62. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -19,6 +19,21 @@ 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,26 +364,31 @@ 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 [];
304
- const source = live !== void 0 && live.length > 0 ? live.map((entry) => ({
372
+ const listed = live !== void 0 && live.length > 0 ? live.map((entry) => ({
305
373
  id: entry.id,
306
374
  name: entry.name,
307
375
  inputModalities: entry.inputModalities
308
- })) : CODEX_MODEL_CATALOG.map((entry) => ({
376
+ })) : void 0;
377
+ const visible = new Set(status?.visibleModelIds ?? (listed ?? shippedModels()).map((entry) => entry.id));
378
+ const selected = (listed ?? shippedModels()).filter((entry) => visible.has(entry.id));
379
+ return (listed === void 0 || selected.length > 0 ? selected : shippedModels().filter((entry) => visible.has(entry.id))).map((entry) => ({
380
+ provider: PROVIDER_ID$7,
309
381
  id: entry.id,
310
382
  name: entry.name,
311
- inputModalities: entry.inputModalities
383
+ inputModalities: [...entry.inputModalities]
312
384
  }));
313
- const visible = new Set(status?.visibleModelIds ?? source.map((entry) => entry.id));
314
- return source.filter((entry) => visible.has(entry.id)).map((entry) => ({
315
- provider: PROVIDER_ID$6,
385
+ }
386
+ /** The shipped table as picker rows. */
387
+ function shippedModels() {
388
+ return CODEX_MODEL_CATALOG.map((entry) => ({
316
389
  id: entry.id,
317
390
  name: entry.name,
318
- inputModalities: [...entry.inputModalities]
391
+ inputModalities: entry.inputModalities
319
392
  }));
320
393
  }
321
394
  function resolveCodexModel(model, preferences, live) {
@@ -327,12 +400,12 @@ function resolveCodexModel(model, preferences, live) {
327
400
  const statedDefault = liveEntry?.defaultReasoningEffort ?? entry.defaultReasoningEffort;
328
401
  const defaultEffort = isCodexReasoningEffort(statedDefault) && efforts.includes(statedDefault) ? statedDefault : efforts[0];
329
402
  return {
330
- provider: PROVIDER_ID$6,
403
+ provider: PROVIDER_ID$7,
331
404
  id: model,
332
405
  name: liveEntry?.name ?? (entry.id === model ? entry.name : model),
333
406
  inputModalities: [...liveEntry?.inputModalities ?? entry.inputModalities],
334
407
  context: { contextWindow: configuredContextWindow ?? liveEntry?.contextWindow ?? entry.contextWindow },
335
- defaultMaxTokens: codexModelMaxTokens(model),
408
+ ...outputReservation(configuredContextWindow ?? liveEntry?.contextWindow ?? entry.contextWindow, codexModelMaxTokens(model)),
336
409
  reasoning: {
337
410
  efforts: efforts.map((effort) => ({
338
411
  id: ReasoningEffortId(effort),
@@ -344,7 +417,7 @@ function resolveCodexModel(model, preferences, live) {
344
417
  }
345
418
  //#endregion
346
419
  //#region src/host/adapter.ts
347
- const RETRY_POLICY$5 = resolveRetryPolicy({
420
+ const RETRY_POLICY$6 = resolveRetryPolicy({
348
421
  mode: "normal",
349
422
  maxRetries: 3,
350
423
  retryableCodes: [
@@ -374,11 +447,11 @@ var CodexChatGptAdapter = class extends LlmAdapter {
374
447
  providerInfo(provider) {
375
448
  return {
376
449
  id: provider,
377
- name: PROVIDER_NAME$6
450
+ name: PROVIDER_NAME$7
378
451
  };
379
452
  }
380
453
  providerRetryPolicy() {
381
- return RETRY_POLICY$5;
454
+ return RETRY_POLICY$6;
382
455
  }
383
456
  imageRequestPricing(_provider, _model) {}
384
457
  async listModels() {
@@ -2625,7 +2698,7 @@ async function oauthErrorIdentifier(response) {
2625
2698
  function codexPoolPath() {
2626
2699
  return path.join(dshHomeDir(), "storages", "codex-pool.json");
2627
2700
  }
2628
- function optionalString$7(record, key) {
2701
+ function optionalString$9(record, key) {
2629
2702
  const value = record[key];
2630
2703
  if (value === void 0 || value === null) return void 0;
2631
2704
  if (typeof value !== "string") throw new Error("ChatGPT pool account field is invalid");
@@ -2648,11 +2721,11 @@ function parseCodexPoolData(value) {
2648
2721
  addedAt: typeof raw.addedAt === "number" ? raw.addedAt : Date.now(),
2649
2722
  isPrimary: raw.isPrimary === true
2650
2723
  };
2651
- const email = optionalString$7(raw, "email") ?? credentials.email;
2724
+ const email = optionalString$9(raw, "email") ?? credentials.email;
2652
2725
  if (email !== void 0) account.email = email;
2653
- const planType = optionalString$7(raw, "planType") ?? credentials.planType;
2726
+ const planType = optionalString$9(raw, "planType") ?? credentials.planType;
2654
2727
  if (planType !== void 0) account.planType = planType;
2655
- const accountId = optionalString$7(raw, "accountId") ?? credentials.accountId;
2728
+ const accountId = optionalString$9(raw, "accountId") ?? credentials.accountId;
2656
2729
  if (accountId !== void 0) account.accountId = accountId;
2657
2730
  if (typeof raw.lastUsedAt === "number") account.lastUsedAt = raw.lastUsedAt;
2658
2731
  if (typeof raw.cooldownUntil === "number") account.cooldownUntil = raw.cooldownUntil;
@@ -3409,8 +3482,7 @@ function sendSearch(fetchFn, credentials, query, model, id, signal) {
3409
3482
  settings: {
3410
3483
  allowed_callers: ["direct"],
3411
3484
  external_web_access: true
3412
- },
3413
- max_output_tokens: 4096
3485
+ }
3414
3486
  }),
3415
3487
  signal
3416
3488
  });
@@ -3673,8 +3745,8 @@ var ProxyManager = class {
3673
3745
  };
3674
3746
  //#endregion
3675
3747
  //#region src/host/antigravity/types.ts
3676
- const PROVIDER_NAME$5 = "Antigravity";
3677
- const PROVIDER_ID$5 = "antigravity";
3748
+ const PROVIDER_NAME$6 = "Antigravity";
3749
+ const PROVIDER_ID$6 = "antigravity";
3678
3750
  const STREAM_IDLE_TIMEOUT_MS$5 = 3e5;
3679
3751
  const STREAM_IDLE_TIMEOUT_CODE$6 = "LLM_STREAM_IDLE_TIMEOUT";
3680
3752
  const DISCOVERY_TIMEOUT_MS$4 = 8e3;
@@ -4138,13 +4210,13 @@ function parseAntigravityCredentials(value) {
4138
4210
  if (!(credentials.access || credentials.access_token || credentials.refresh || credentials.refresh_token)) throw new Error("Antigravity credential tokens are missing");
4139
4211
  return credentials;
4140
4212
  }
4141
- function credentialAccount$3(filePath) {
4213
+ function credentialAccount$4(filePath) {
4142
4214
  return createHash("sha256").update(path.resolve(filePath)).digest("hex");
4143
4215
  }
4144
- function createCredentialBackend$3(filePath) {
4216
+ function createCredentialBackend$4(filePath) {
4145
4217
  if (process.platform === "win32") return new WindowsDpapiCredentialStore(`${filePath}.dpapi`, parseAntigravityCredentials);
4146
- if (process.platform === "darwin") return new MacKeychainCredentialStore("dsh-antigravity", credentialAccount$3(filePath), parseAntigravityCredentials);
4147
- 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);
4148
4220
  throw new Error("Antigravity encrypted credential storage requires Windows, macOS, or Linux.");
4149
4221
  }
4150
4222
  const credentialOperations$3 = /* @__PURE__ */ new Map();
@@ -4152,13 +4224,13 @@ const credentialOperations$3 = /* @__PURE__ */ new Map();
4152
4224
  var FileCredentialStore$2 = class {
4153
4225
  filePath;
4154
4226
  backend;
4155
- constructor(filePath = credentialPath$2(), backend = createCredentialBackend$3(filePath)) {
4227
+ constructor(filePath = credentialPath$2(), backend = createCredentialBackend$4(filePath)) {
4156
4228
  this.filePath = filePath;
4157
4229
  this.backend = backend;
4158
4230
  }
4159
4231
  path() {
4160
4232
  if (process.platform === "win32") return `${this.filePath}.dpapi`;
4161
- 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)}`;
4162
4234
  }
4163
4235
  serialize(operation) {
4164
4236
  const key = path.resolve(this.filePath);
@@ -4872,15 +4944,15 @@ async function buildResponsesPayload(options, attachments, localRawImages = {},
4872
4944
  payload.tool_choice = "auto";
4873
4945
  payload.parallel_tool_calls = true;
4874
4946
  }
4875
- if (outputVerbosity !== null) payload.text = { verbosity: outputVerbosity };
4947
+ const providerVerbosity = codexDefaultOutputVerbosity(options.model);
4948
+ if (providerVerbosity !== void 0) payload.text = { verbosity: outputVerbosity ?? providerVerbosity };
4876
4949
  if (fastMode) payload.service_tier = "priority";
4877
- const modelMaxTokens = codexModelMaxTokens(options.model);
4878
- payload.max_output_tokens = options.maxTokens === void 0 ? modelMaxTokens : Math.min(options.maxTokens, modelMaxTokens);
4879
4950
  if (options.reasoningEffort !== void 0) {
4880
4951
  const effort = codexWireReasoningEffort(options.model, options.reasoningEffort);
4881
- payload.reasoning = codexModelSupportsReasoningSummary(options.model) ? {
4952
+ const summary = reasoningSummary === null || reasoningSummary === "none" ? void 0 : reasoningSummary;
4953
+ payload.reasoning = summary !== void 0 && codexModelSupportsReasoningSummary(options.model) ? {
4882
4954
  effort,
4883
- summary: reasoningSummary ?? "auto"
4955
+ summary
4884
4956
  } : { effort };
4885
4957
  }
4886
4958
  return payload;
@@ -6403,13 +6475,13 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
6403
6475
  }
6404
6476
  try {
6405
6477
  switch (url.pathname) {
6406
- case `${ROUTE_PREFIX$4}/login/start`:
6478
+ case `${ROUTE_PREFIX$5}/login/start`:
6407
6479
  json(response, {
6408
6480
  ok: true,
6409
6481
  value: await oauth.startLogin()
6410
6482
  });
6411
6483
  return;
6412
- case `${ROUTE_PREFIX$4}/login/cancel`: {
6484
+ case `${ROUTE_PREFIX$5}/login/cancel`: {
6413
6485
  const loginId = field(body, "loginId");
6414
6486
  if (loginId === null) throw new Error("missing loginId");
6415
6487
  oauth.cancelLogin(loginId);
@@ -6419,7 +6491,7 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
6419
6491
  });
6420
6492
  return;
6421
6493
  }
6422
- case `${ROUTE_PREFIX$4}/accounts`: {
6494
+ case `${ROUTE_PREFIX$5}/accounts`: {
6423
6495
  if (accountPool === void 0) throw new Error("The ChatGPT account pool is not installed.");
6424
6496
  const action = field(body, "action");
6425
6497
  const accountId = field(body, "accountId");
@@ -6441,7 +6513,7 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
6441
6513
  });
6442
6514
  return;
6443
6515
  }
6444
- case `${ROUTE_PREFIX$4}/logout`:
6516
+ case `${ROUTE_PREFIX$5}/logout`:
6445
6517
  await oauth.logout(field(body, "accountId") ?? void 0);
6446
6518
  usage.clear();
6447
6519
  json(response, {
@@ -6449,7 +6521,7 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
6449
6521
  value: { authenticated: false }
6450
6522
  });
6451
6523
  return;
6452
- case `${ROUTE_PREFIX$4}/token/refresh`: {
6524
+ case `${ROUTE_PREFIX$5}/token/refresh`: {
6453
6525
  const oauthStatus = await oauth.refresh();
6454
6526
  json(response, {
6455
6527
  ok: true,
@@ -6461,27 +6533,27 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
6461
6533
  });
6462
6534
  return;
6463
6535
  }
6464
- case `${ROUTE_PREFIX$4}/quota/refresh`:
6536
+ case `${ROUTE_PREFIX$5}/quota/refresh`:
6465
6537
  if (!(await oauth.status()).authenticated) throw new Error("not authenticated");
6466
6538
  json(response, {
6467
6539
  ok: true,
6468
6540
  value: await usage.status(true, true)
6469
6541
  });
6470
6542
  return;
6471
- case `${ROUTE_PREFIX$4}/quota/reset-credit/use`:
6543
+ case `${ROUTE_PREFIX$5}/quota/reset-credit/use`:
6472
6544
  if (!(await oauth.status()).authenticated) throw new Error("not authenticated");
6473
6545
  json(response, {
6474
6546
  ok: true,
6475
6547
  value: await usage.consumeResetCredit()
6476
6548
  });
6477
6549
  return;
6478
- case `${ROUTE_PREFIX$4}/connection/test`:
6550
+ case `${ROUTE_PREFIX$5}/connection/test`:
6479
6551
  json(response, {
6480
6552
  ok: true,
6481
6553
  value: await usage.testConnection()
6482
6554
  });
6483
6555
  return;
6484
- case `${ROUTE_PREFIX$4}/preferences/update`: {
6556
+ case `${ROUTE_PREFIX$5}/preferences/update`: {
6485
6557
  const patch = readPreferencesUpdate(body, preferences.status());
6486
6558
  const value = await preferences.update(patch);
6487
6559
  if (patch.visibleModelIds !== void 0 || patch.enabled !== void 0 || patch.contextWindowOverrides !== void 0) ctx.emit?.("llm/adapters-updated");
@@ -6558,11 +6630,11 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
6558
6630
  };
6559
6631
  const disposers = [ctx.webServer.register({
6560
6632
  kind: "prefix",
6561
- path: ROUTE_PREFIX$4,
6633
+ path: ROUTE_PREFIX$5,
6562
6634
  handler
6563
6635
  }), ctx.webServer.register({
6564
6636
  kind: "exact",
6565
- path: `${ROUTE_PREFIX$4}/login/events`,
6637
+ path: `${ROUTE_PREFIX$5}/login/events`,
6566
6638
  handler: events
6567
6639
  })];
6568
6640
  return () => {
@@ -6670,6 +6742,27 @@ function statusFor(error) {
6670
6742
  }
6671
6743
  //#endregion
6672
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
+ }
6673
6766
  var SearchProviderSwitcher = class {
6674
6767
  loader;
6675
6768
  originalSearchProvider;
@@ -6728,15 +6821,40 @@ var SearchProviderSwitcher = class {
6728
6821
  else nextConfig.fetchProvider = nextFetch;
6729
6822
  this.state = "applying";
6730
6823
  try {
6731
- if (configured && entry.fiber) await entry.fiber.update(nextConfig, true);
6824
+ if (configured && entry.fiber) entry.fiber.update(nextConfig, true);
6732
6825
  else await entry.update({ config: nextConfig });
6733
- await entry.fiber?.await();
6826
+ await this.awaitSettled(entry);
6734
6827
  this.state = "applied";
6735
6828
  } catch (error) {
6736
6829
  this.state = "failed";
6737
6830
  throw error;
6738
6831
  }
6739
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
+ }
6740
6858
  findWebEntry() {
6741
6859
  for (const entry of this.loader.entries()) if (entry.options.id === "web" || entry.options.name === "@deepseek-ai/dsh-web") return entry;
6742
6860
  return null;
@@ -7966,7 +8084,7 @@ function buildRequest$2(options, model, projectId, runtimeModel, effort, images
7966
8084
  requestId: `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
7967
8085
  };
7968
8086
  }
7969
- function createStreamState$5() {
8087
+ function createStreamState$6() {
7970
8088
  return {
7971
8089
  blocks: [],
7972
8090
  replayBlocks: [],
@@ -8027,7 +8145,7 @@ function processStreamLine$2(line, state) {
8027
8145
  const json = line.slice(5).trim();
8028
8146
  if (json === "[DONE]") {
8029
8147
  state.done = true;
8030
- return closeStream$4(state);
8148
+ return closeStream$5(state);
8031
8149
  }
8032
8150
  if (!json) return [];
8033
8151
  const chunk = safeJsonParse$5(json);
@@ -8144,7 +8262,7 @@ function processStreamLine$2(line, state) {
8144
8262
  }
8145
8263
  return out;
8146
8264
  }
8147
- function closeStream$4(state) {
8265
+ function closeStream$5(state) {
8148
8266
  if (state.finished) return [];
8149
8267
  if (!state.finishReason && !state.done) throw new LlmError$1("Antigravity stream ended before its terminal response", "PROVIDER_ERROR");
8150
8268
  state.finished = true;
@@ -8158,7 +8276,7 @@ function closeStream$4(state) {
8158
8276
  type: "finish",
8159
8277
  reason,
8160
8278
  replayState: {
8161
- response: { provider: PROVIDER_ID$5 },
8279
+ response: { provider: PROVIDER_ID$6 },
8162
8280
  blocks: state.replayBlocks
8163
8281
  }
8164
8282
  });
@@ -8366,7 +8484,7 @@ var AntigravityAdapter = class extends LlmAdapter {
8366
8484
  providerInfo(provider) {
8367
8485
  return {
8368
8486
  id: provider,
8369
- name: PROVIDER_NAME$5
8487
+ name: PROVIDER_NAME$6
8370
8488
  };
8371
8489
  }
8372
8490
  providerRetryPolicy() {}
@@ -8384,7 +8502,7 @@ var AntigravityAdapter = class extends LlmAdapter {
8384
8502
  name: model.name,
8385
8503
  inputModalities: model.inputModalities,
8386
8504
  context: { contextWindow: overrides[model.id] || model.contextWindow },
8387
- defaultMaxTokens: model.maxTokens,
8505
+ ...outputReservation(overrides[model.id] || model.contextWindow, model.maxTokens),
8388
8506
  ...model.reasoningEfforts ? { reasoningEfforts: model.reasoningEfforts } : {}
8389
8507
  }));
8390
8508
  }
@@ -8411,7 +8529,7 @@ var AntigravityAdapter = class extends LlmAdapter {
8411
8529
  name: model.name,
8412
8530
  inputModalities: model.inputModalities,
8413
8531
  context: { contextWindow: overrides[model.id] || model.contextWindow },
8414
- defaultMaxTokens: model.maxTokens,
8532
+ ...outputReservation(overrides[model.id] || model.contextWindow, model.maxTokens),
8415
8533
  ...model.reasoningEfforts ? { reasoning: {
8416
8534
  efforts: efforts.map((effort) => ({
8417
8535
  id: ReasoningEffortId(effort),
@@ -8441,7 +8559,10 @@ var AntigravityAdapter = class extends LlmAdapter {
8441
8559
  ...options,
8442
8560
  reasoningEffort: effectiveEffort
8443
8561
  } : options;
8444
- 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");
8445
8566
  }
8446
8567
  async *requestStream(options, model, signal) {
8447
8568
  const fetchFn = this.options.fetchFn ?? fetch;
@@ -8514,7 +8635,7 @@ var AntigravityAdapter = class extends LlmAdapter {
8514
8635
  if (!response.body) throw new LlmError("Antigravity returned empty response body", "PROVIDER_ERROR");
8515
8636
  const reader = response.body.getReader();
8516
8637
  const decoder = new TextDecoder();
8517
- const state = createStreamState$5();
8638
+ const state = createStreamState$6();
8518
8639
  let buffer = "";
8519
8640
  try {
8520
8641
  while (true) {
@@ -8536,7 +8657,7 @@ var AntigravityAdapter = class extends LlmAdapter {
8536
8657
  const chunks = processStreamLine$2(buffer.trim(), state);
8537
8658
  for (const chunk of chunks) yield chunk;
8538
8659
  }
8539
- for (const chunk of closeStream$4(state)) yield chunk;
8660
+ for (const chunk of closeStream$5(state)) yield chunk;
8540
8661
  } finally {
8541
8662
  reader.cancel().catch(() => void 0);
8542
8663
  }
@@ -8601,17 +8722,17 @@ var QuotaRefresh = class {
8601
8722
  };
8602
8723
  //#endregion
8603
8724
  //#region src/host/antigravity/routes.ts
8604
- function sendJson$7(response, status, body) {
8725
+ function sendJson$8(response, status, body) {
8605
8726
  response.writeHead(status, { "Content-Type": "application/json" });
8606
8727
  response.end(JSON.stringify(body));
8607
8728
  }
8608
- function sendMethodNotAllowed$5(response) {
8609
- sendJson$7(response, 405, {
8729
+ function sendMethodNotAllowed$6(response) {
8730
+ sendJson$8(response, 405, {
8610
8731
  ok: false,
8611
8732
  error: "Method Not Allowed"
8612
8733
  });
8613
8734
  }
8614
- async function readRequestJson$6(request) {
8735
+ async function readRequestJson$7(request) {
8615
8736
  return new Promise((resolve, reject) => {
8616
8737
  let raw = "";
8617
8738
  request.on("data", (chunk) => {
@@ -8671,7 +8792,7 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
8671
8792
  const path = new URL(request.url || "/", "http://dsh.local").pathname.replace(/^\/antigravity\/api\/?/, "");
8672
8793
  try {
8673
8794
  if (path === "status" || path === "") {
8674
- if (request.method !== "GET") return sendMethodNotAllowed$5(response);
8795
+ if (request.method !== "GET") return sendMethodNotAllowed$6(response);
8675
8796
  const credentials = await store.read();
8676
8797
  const authenticated = !!(credentials?.access || credentials?.access_token);
8677
8798
  const cached = getCachedQuota();
@@ -8680,7 +8801,7 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
8680
8801
  if (cached === void 0) await quotaRefresh.run(refresh);
8681
8802
  else quotaRefresh.start(refresh);
8682
8803
  }
8683
- return sendJson$7(response, 200, {
8804
+ return sendJson$8(response, 200, {
8684
8805
  ok: true,
8685
8806
  value: {
8686
8807
  ...await getAntigravityWebStatus(store, modelSettings, preferences, accountPool),
@@ -8689,8 +8810,8 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
8689
8810
  });
8690
8811
  }
8691
8812
  if (path === "login" || path === "accounts/login") {
8692
- if (request.method !== "POST") return sendMethodNotAllowed$5(response);
8693
- return sendJson$7(response, 200, {
8813
+ if (request.method !== "POST") return sendMethodNotAllowed$6(response);
8814
+ return sendJson$8(response, 200, {
8694
8815
  ok: true,
8695
8816
  value: await beginWebLogin$2(store, fetchFn, void 0, async (creds) => {
8696
8817
  await accountPool.addAccount(creds);
@@ -8698,19 +8819,19 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
8698
8819
  });
8699
8820
  }
8700
8821
  if (path === "login/status") {
8701
- if (request.method !== "GET") return sendMethodNotAllowed$5(response);
8702
- return sendJson$7(response, 200, {
8822
+ if (request.method !== "GET") return sendMethodNotAllowed$6(response);
8823
+ return sendJson$8(response, 200, {
8703
8824
  ok: true,
8704
8825
  value: getWebLoginStatus$3()
8705
8826
  });
8706
8827
  }
8707
8828
  if (path === "accounts") {
8708
- if (request.method === "GET") return sendJson$7(response, 200, {
8829
+ if (request.method === "GET") return sendJson$8(response, 200, {
8709
8830
  ok: true,
8710
8831
  value: await accountPool.listAccounts()
8711
8832
  });
8712
8833
  if (request.method === "POST") {
8713
- const body = await readRequestJson$6(request);
8834
+ const body = await readRequestJson$7(request);
8714
8835
  const action = String(body.action || "");
8715
8836
  if (action === "set-primary" && typeof body.accountId === "string") await accountPool.setPrimary(body.accountId);
8716
8837
  else if (action === "set-alias" && typeof body.accountId === "string" && typeof body.alias === "string") await accountPool.setAlias(body.accountId, body.alias);
@@ -8718,17 +8839,17 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
8718
8839
  else if (action === "strategy" && (body.strategy === "sequential" || body.strategy === "round-robin" || body.strategy === "sticky")) await accountPool.setStrategy(body.strategy);
8719
8840
  else if (action === "clear-cooldown" && typeof body.accountId === "string") await accountPool.clearCooldown(body.accountId);
8720
8841
  clearCachedQuota();
8721
- return sendJson$7(response, 200, {
8842
+ return sendJson$8(response, 200, {
8722
8843
  ok: true,
8723
8844
  value: await getAntigravityWebStatus(store, modelSettings, preferences, accountPool)
8724
8845
  });
8725
8846
  }
8726
- return sendMethodNotAllowed$5(response);
8847
+ return sendMethodNotAllowed$6(response);
8727
8848
  }
8728
8849
  if (path === "quota") {
8729
- if (request.method !== "GET" && request.method !== "POST") return sendMethodNotAllowed$5(response);
8850
+ if (request.method !== "GET" && request.method !== "POST") return sendMethodNotAllowed$6(response);
8730
8851
  const quota = await fetchAccountQuota(store, modelSettings, fetchFn, true);
8731
- return sendJson$7(response, 200, {
8852
+ return sendJson$8(response, 200, {
8732
8853
  ok: true,
8733
8854
  value: {
8734
8855
  ...await getAntigravityWebStatus(store, modelSettings, preferences, accountPool),
@@ -8737,38 +8858,38 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
8737
8858
  });
8738
8859
  }
8739
8860
  if (path === "settings") {
8740
- if (request.method !== "POST") return sendMethodNotAllowed$5(response);
8741
- const body = await readRequestJson$6(request);
8861
+ if (request.method !== "POST") return sendMethodNotAllowed$6(response);
8862
+ const body = await readRequestJson$7(request);
8742
8863
  if (preferences) await preferences.update(body);
8743
8864
  else await modelSettings.updateSettings(body);
8744
8865
  if (body.enabledModelIds !== void 0 || body.enabled !== void 0) ctx.emit?.("llm/adapters-updated");
8745
- return sendJson$7(response, 200, {
8866
+ return sendJson$8(response, 200, {
8746
8867
  ok: true,
8747
8868
  value: await getAntigravityWebStatus(store, modelSettings, preferences, accountPool)
8748
8869
  });
8749
8870
  }
8750
8871
  if (path === "models") {
8751
- if (request.method === "GET") return sendJson$7(response, 200, {
8872
+ if (request.method === "GET") return sendJson$8(response, 200, {
8752
8873
  ok: true,
8753
8874
  value: (await getAntigravityWebStatus(store, modelSettings, preferences, accountPool)).models
8754
8875
  });
8755
8876
  if (request.method === "POST") {
8756
- const body = await readRequestJson$6(request);
8877
+ const body = await readRequestJson$7(request);
8757
8878
  if (Array.isArray(body.enabledModelIds) || body.enabled !== void 0 || body.contextWindowOverrides || body.defaultReasoningEffort !== void 0) {
8758
8879
  if (preferences) await preferences.update(body);
8759
8880
  else await modelSettings.updateSettings(body);
8760
8881
  if (body.enabledModelIds !== void 0 || body.enabled !== void 0) ctx.emit?.("llm/adapters-updated");
8761
8882
  }
8762
- return sendJson$7(response, 200, {
8883
+ return sendJson$8(response, 200, {
8763
8884
  ok: true,
8764
8885
  value: await getAntigravityWebStatus(store, modelSettings, preferences, accountPool)
8765
8886
  });
8766
8887
  }
8767
- return sendMethodNotAllowed$5(response);
8888
+ return sendMethodNotAllowed$6(response);
8768
8889
  }
8769
8890
  if (path === "logout") {
8770
- if (request.method !== "POST") return sendMethodNotAllowed$5(response);
8771
- const body = await readRequestJson$6(request).catch(() => ({}));
8891
+ if (request.method !== "POST") return sendMethodNotAllowed$6(response);
8892
+ const body = await readRequestJson$7(request).catch(() => ({}));
8772
8893
  const targetId = typeof body.accountId === "string" ? body.accountId : void 0;
8773
8894
  if (targetId) await accountPool.deleteAccount(targetId);
8774
8895
  else {
@@ -8777,17 +8898,17 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
8777
8898
  else await store.delete();
8778
8899
  }
8779
8900
  clearCachedQuota();
8780
- return sendJson$7(response, 200, {
8901
+ return sendJson$8(response, 200, {
8781
8902
  ok: true,
8782
8903
  value: await getAntigravityWebStatus(store, modelSettings, preferences, accountPool)
8783
8904
  });
8784
8905
  }
8785
- return sendJson$7(response, 404, {
8906
+ return sendJson$8(response, 404, {
8786
8907
  ok: false,
8787
8908
  error: "not-found"
8788
8909
  });
8789
8910
  } catch (err) {
8790
- return sendJson$7(response, 500, {
8911
+ return sendJson$8(response, 500, {
8791
8912
  ok: false,
8792
8913
  error: err instanceof Error ? err.message : String(err)
8793
8914
  });
@@ -8799,6 +8920,20 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
8799
8920
  //#region src/host/command-code/model-catalog.ts
8800
8921
  /** Every model the Command Code registry describes, in registry order. */
8801
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
+ },
8802
8937
  {
8803
8938
  id: "claude-sonnet-5",
8804
8939
  name: "Claude Sonnet 5",
@@ -8855,6 +8990,20 @@ const COMMAND_CODE_MODELS = [
8855
8990
  contextWindow: 1e6,
8856
8991
  maxTokens: null
8857
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
+ },
8858
9007
  {
8859
9008
  id: "claude-opus-5",
8860
9009
  name: "Claude Opus 5",
@@ -8919,6 +9068,48 @@ const COMMAND_CODE_MODELS = [
8919
9068
  contextWindow: 105e4,
8920
9069
  maxTokens: null
8921
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
+ },
8922
9113
  {
8923
9114
  id: "gpt-5.6-sol",
8924
9115
  name: "GPT-5.6 Sol",
@@ -9016,7 +9207,11 @@ const COMMAND_CODE_MODELS = [
9016
9207
  id: "deepseek/deepseek-v4-pro",
9017
9208
  name: "DeepSeek V4 Pro (latest)",
9018
9209
  inputModalities: ["text"],
9019
- reasoningEfforts: ["high", "max"],
9210
+ reasoningEfforts: [
9211
+ "off",
9212
+ "high",
9213
+ "max"
9214
+ ],
9020
9215
  contextWindow: 1e6,
9021
9216
  maxTokens: null
9022
9217
  },
@@ -9024,7 +9219,11 @@ const COMMAND_CODE_MODELS = [
9024
9219
  id: "deepseek/deepseek-v4-flash",
9025
9220
  name: "DeepSeek V4 Flash (latest)",
9026
9221
  inputModalities: ["text"],
9027
- reasoningEfforts: ["high", "max"],
9222
+ reasoningEfforts: [
9223
+ "off",
9224
+ "high",
9225
+ "max"
9226
+ ],
9028
9227
  contextWindow: 1e6,
9029
9228
  maxTokens: null
9030
9229
  },
@@ -9032,7 +9231,11 @@ const COMMAND_CODE_MODELS = [
9032
9231
  id: "deepseek/deepseek-v4-flash-vision-exp",
9033
9232
  name: "DeepSeek V4 Flash Vision (exp)",
9034
9233
  inputModalities: ["text", "image"],
9035
- reasoningEfforts: ["high", "max"],
9234
+ reasoningEfforts: [
9235
+ "off",
9236
+ "high",
9237
+ "max"
9238
+ ],
9036
9239
  contextWindow: 1e6,
9037
9240
  maxTokens: null
9038
9241
  },
@@ -9053,6 +9256,20 @@ const COMMAND_CODE_MODELS = [
9053
9256
  name: "DeepSeek V4.1 Flash",
9054
9257
  inputModalities: ["text", "image"],
9055
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",
9056
9273
  "low",
9057
9274
  "high",
9058
9275
  "max"
@@ -9116,6 +9333,18 @@ const COMMAND_CODE_MODELS = [
9116
9333
  contextWindow: 1048576,
9117
9334
  maxTokens: 131072
9118
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
+ },
9119
9348
  {
9120
9349
  id: "zai-org/GLM-5.3",
9121
9350
  name: "GLM-5.3",
@@ -9149,7 +9378,7 @@ const COMMAND_CODE_MODELS = [
9149
9378
  name: "GLM-5.1",
9150
9379
  inputModalities: ["text"],
9151
9380
  reasoningEfforts: [],
9152
- contextWindow: null,
9381
+ contextWindow: 2e5,
9153
9382
  maxTokens: null
9154
9383
  },
9155
9384
  {
@@ -9177,35 +9406,39 @@ const COMMAND_CODE_MODELS = [
9177
9406
  name: "MiniMax M2.7",
9178
9407
  inputModalities: ["text"],
9179
9408
  reasoningEfforts: [],
9180
- 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,
9181
9418
  maxTokens: null
9182
9419
  },
9183
9420
  {
9184
- id: "minimax/minimax-m3-free",
9185
- name: "MiniMax M3 (Free)",
9421
+ id: "xiaomi/mimo-v2.6-pro",
9422
+ name: "MiMo V2.6 Pro",
9186
9423
  inputModalities: ["text", "image"],
9187
- reasoningEfforts: [
9188
- "low",
9189
- "medium",
9190
- "high"
9191
- ],
9192
- contextWindow: 1e6,
9424
+ reasoningEfforts: [],
9425
+ contextWindow: 1048576,
9193
9426
  maxTokens: null
9194
9427
  },
9195
9428
  {
9196
- id: "minimax/minimax-m2.7-free",
9197
- name: "MiniMax M2.7 (Free)",
9198
- inputModalities: ["text"],
9429
+ id: "xiaomi/mimo-v2.6-pro-ultraspeed",
9430
+ name: "MiMo V2.6 Pro UltraSpeed",
9431
+ inputModalities: ["text", "image"],
9199
9432
  reasoningEfforts: [],
9200
- contextWindow: 197e3,
9433
+ contextWindow: 1048576,
9201
9434
  maxTokens: null
9202
9435
  },
9203
9436
  {
9204
- id: "MiniMaxAI/MiniMax-M2.5",
9205
- name: "MiniMax M2.5",
9206
- inputModalities: ["text"],
9437
+ id: "xiaomi/mimo-v2.6-flash",
9438
+ name: "MiMo V2.6 Flash",
9439
+ inputModalities: ["text", "image"],
9207
9440
  reasoningEfforts: [],
9208
- contextWindow: 2e5,
9441
+ contextWindow: 1048576,
9209
9442
  maxTokens: null
9210
9443
  },
9211
9444
  {
@@ -9224,6 +9457,18 @@ const COMMAND_CODE_MODELS = [
9224
9457
  contextWindow: 1e6,
9225
9458
  maxTokens: null
9226
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
+ },
9227
9472
  {
9228
9473
  id: "Qwen/Qwen3.8-Max-0902",
9229
9474
  name: "Qwen 3.8 Max 0902",
@@ -9301,7 +9546,7 @@ const COMMAND_CODE_MODELS = [
9301
9546
  name: "Qwen 3.6 Max Preview",
9302
9547
  inputModalities: ["text"],
9303
9548
  reasoningEfforts: [],
9304
- contextWindow: null,
9549
+ contextWindow: 2e5,
9305
9550
  maxTokens: null
9306
9551
  },
9307
9552
  {
@@ -9309,17 +9554,29 @@ const COMMAND_CODE_MODELS = [
9309
9554
  name: "Qwen 3.6 Plus",
9310
9555
  inputModalities: ["text", "image"],
9311
9556
  reasoningEfforts: [],
9312
- contextWindow: null,
9557
+ contextWindow: 2e5,
9313
9558
  maxTokens: null
9314
9559
  },
9315
9560
  {
9316
- id: "meituan/LongCat-2.0:free",
9561
+ id: "meituan/LongCat-2.0",
9317
9562
  name: "LongCat 2.0",
9318
9563
  inputModalities: ["text"],
9319
9564
  reasoningEfforts: [],
9320
9565
  contextWindow: 1048576,
9321
9566
  maxTokens: null
9322
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
+ },
9323
9580
  {
9324
9581
  id: "stepfun/Step-3.7-Flash",
9325
9582
  name: "Step 3.7 Flash",
@@ -9333,14 +9590,6 @@ const COMMAND_CODE_MODELS = [
9333
9590
  name: "Step 3.5 Flash",
9334
9591
  inputModalities: ["text"],
9335
9592
  reasoningEfforts: [],
9336
- contextWindow: 1e6,
9337
- maxTokens: null
9338
- },
9339
- {
9340
- id: "tencent/Hy3",
9341
- name: "Tencent Hy3 (Free)",
9342
- inputModalities: ["text"],
9343
- reasoningEfforts: [],
9344
9593
  contextWindow: 262144,
9345
9594
  maxTokens: null
9346
9595
  },
@@ -9468,6 +9717,19 @@ const COMMAND_CODE_MODELS = [
9468
9717
  contextWindow: 1e6,
9469
9718
  maxTokens: null
9470
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
+ },
9471
9733
  {
9472
9734
  id: "poolside/laguna-s-2.1-free",
9473
9735
  name: "Laguna S 2.1",
@@ -9477,18 +9739,22 @@ const COMMAND_CODE_MODELS = [
9477
9739
  maxTokens: 32768
9478
9740
  },
9479
9741
  {
9480
- id: "inclusionai/ling-3.0-flash-free",
9481
- name: "Ling 3.0 Flash",
9742
+ id: "inclusionai/ling-3.0-flash-sante:free",
9743
+ name: "Ling 3.0 Flash Sante",
9482
9744
  inputModalities: ["text"],
9483
9745
  reasoningEfforts: [],
9484
- contextWindow: 256e3,
9746
+ contextWindow: 262144,
9485
9747
  maxTokens: 32768
9486
9748
  },
9487
9749
  {
9488
- id: "inclusionai/ling-3.0-flash-sante:free",
9489
- name: "Ling 3.0 Flash Sante",
9750
+ id: "inclusionai/ling-3.1-flash:free",
9751
+ name: "Ling 3.1 Flash",
9490
9752
  inputModalities: ["text"],
9491
- reasoningEfforts: [],
9753
+ reasoningEfforts: [
9754
+ "low",
9755
+ "medium",
9756
+ "high"
9757
+ ],
9492
9758
  contextWindow: 262144,
9493
9759
  maxTokens: 32768
9494
9760
  },
@@ -9582,6 +9848,19 @@ const COMMAND_CODE_MODELS = [
9582
9848
  ],
9583
9849
  contextWindow: 5e5,
9584
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
9585
9864
  }
9586
9865
  ];
9587
9866
  /** Registry entry for one model id, or undefined when it describes no such model. */
@@ -9590,8 +9869,8 @@ function commandCodeModelDef(modelId) {
9590
9869
  }
9591
9870
  //#endregion
9592
9871
  //#region src/host/command-code/types.ts
9593
- const PROVIDER_ID$4 = "command-code";
9594
- const PROVIDER_NAME$4 = "Command Code";
9872
+ const PROVIDER_ID$5 = "command-code";
9873
+ const PROVIDER_NAME$5 = "Command Code";
9595
9874
  /** Browser sign-in page the CLI opens; it redirects back to our loopback server. */
9596
9875
  const STUDIO_PATH = "/studio/auth/cli";
9597
9876
  /** Query parameter that carries the loopback callback URL to the studio page. */
@@ -9669,11 +9948,11 @@ function providerUrl(env = resolveApiEnv()) {
9669
9948
  * @param modelId - exact model id from the live catalog or a caller.
9670
9949
  * @returns the wire dialect to build the request for.
9671
9950
  */
9672
- function wireForModel$1(modelId) {
9951
+ function wireForModel$2(modelId) {
9673
9952
  return /^claude[-/]/i.test(modelId.trim()) ? "anthropic" : "openai";
9674
9953
  }
9675
9954
  /**
9676
- * Reasoning levels one model advertises.
9955
+ * Reasoning levels one model advertises, in DSH's vocabulary.
9677
9956
  *
9678
9957
  * The registry is the authority: a model it describes but gives no
9679
9958
  * `reasoningEfforts` is a non-reasoning model, which is why that answers with an
@@ -9681,9 +9960,42 @@ function wireForModel$1(modelId) {
9681
9960
  * describe declares nothing either — guessing from the family name is exactly
9682
9961
  * how `deepseek-v4-flash` got treated as a reasoner with image input when it is
9683
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.
9684
9975
  */
9685
9976
  function reasoningEffortsFor$1(modelId) {
9686
- 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;
9687
9999
  }
9688
10000
  /**
9689
10001
  * Accepted request modalities for one model.
@@ -9771,7 +10083,7 @@ function maxOutputTokensFor$4(modelId) {
9771
10083
  * from the registry rather than hand-listed, so a model cannot drift between the
9772
10084
  * offline fallback and its real capability entry.
9773
10085
  */
9774
- 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) => ({
9775
10087
  id: model.id,
9776
10088
  name: model.name,
9777
10089
  contextWindow: model.contextWindow
@@ -9780,6 +10092,7 @@ const FALLBACK_MODELS$3 = COMMAND_CODE_MODELS.filter((model) => model.contextWin
9780
10092
  //#region src/shared/command-code-contracts.ts
9781
10093
  /** Every level, in escalating order; the settings card renders exactly these. */
9782
10094
  const COMMAND_CODE_REASONING_EFFORTS = [
10095
+ "none",
9783
10096
  "minimal",
9784
10097
  "low",
9785
10098
  "medium",
@@ -9794,7 +10107,7 @@ const COMMAND_CODE_PREFERENCES_NAMESPACE = "dsh-command-code";
9794
10107
  function isReasoningEffort$3(value) {
9795
10108
  return typeof value === "string" && COMMAND_CODE_REASONING_EFFORTS.includes(value);
9796
10109
  }
9797
- 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);
9798
10111
  /**
9799
10112
  * Bind the model selection to the DSH settings document when the harness still
9800
10113
  * offers one, and to the JSON file beside it otherwise: a harness without the
@@ -9860,7 +10173,7 @@ function credentialPath$1() {
9860
10173
  function modelSettingsPath$1() {
9861
10174
  return path.join(dshHomeDir$2(), "storages", "command-code-models.json");
9862
10175
  }
9863
- function optionalString$6(record, key) {
10176
+ function optionalString$8(record, key) {
9864
10177
  const value = record[key];
9865
10178
  if (value === void 0 || value === null) return void 0;
9866
10179
  if (typeof value !== "string") throw new Error("Command Code credential payload is invalid");
@@ -9881,7 +10194,7 @@ function parseCommandCodeCredentials(value) {
9881
10194
  "planLabel",
9882
10195
  "planId"
9883
10196
  ]) {
9884
- const parsed = optionalString$6(record, key);
10197
+ const parsed = optionalString$8(record, key);
9885
10198
  if (parsed !== void 0) credentials[key] = parsed;
9886
10199
  }
9887
10200
  const authenticatedAt = record.authenticatedAt;
@@ -9896,13 +10209,13 @@ function parseCommandCodeCredentials(value) {
9896
10209
  }
9897
10210
  return credentials;
9898
10211
  }
9899
- function credentialAccount$2(filePath) {
10212
+ function credentialAccount$3(filePath) {
9900
10213
  return createHash("sha256").update(path.resolve(filePath)).digest("hex");
9901
10214
  }
9902
- function createCredentialBackend$2(filePath) {
10215
+ function createCredentialBackend$3(filePath) {
9903
10216
  if (process.platform === "win32") return new WindowsDpapiCredentialStore(`${filePath}.dpapi`, parseCommandCodeCredentials);
9904
- if (process.platform === "darwin") return new MacKeychainCredentialStore(PROVIDER_ID$4, credentialAccount$2(filePath), parseCommandCodeCredentials);
9905
- 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);
9906
10219
  throw new Error("Command Code credential storage requires Windows, macOS, or Linux.");
9907
10220
  }
9908
10221
  const credentialOperations$2 = /* @__PURE__ */ new Map();
@@ -9910,13 +10223,13 @@ const credentialOperations$2 = /* @__PURE__ */ new Map();
9910
10223
  var FileCredentialStore$1 = class {
9911
10224
  filePath;
9912
10225
  backend;
9913
- constructor(filePath = credentialPath$1(), backend = createCredentialBackend$2(filePath)) {
10226
+ constructor(filePath = credentialPath$1(), backend = createCredentialBackend$3(filePath)) {
9914
10227
  this.filePath = filePath;
9915
10228
  this.backend = backend;
9916
10229
  }
9917
10230
  path() {
9918
10231
  if (process.platform === "win32") return `${this.filePath}.dpapi`;
9919
- 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)}`;
9920
10233
  }
9921
10234
  serialize(operation) {
9922
10235
  const key = path.resolve(this.filePath);
@@ -10131,7 +10444,7 @@ function commandCodeHeaders(apiKey, extra = {}) {
10131
10444
  authorization: `Bearer ${apiKey}`,
10132
10445
  "content-type": "application/json",
10133
10446
  accept: "application/json",
10134
- "user-agent": `${PROVIDER_ID$4}/${CLI_VERSION}`,
10447
+ "user-agent": `${PROVIDER_ID$5}/${CLI_VERSION}`,
10135
10448
  [HEADER_CLI_VERSION]: CLI_VERSION,
10136
10449
  [HEADER_CLI_ENVIRONMENT]: process.platform === "win32" ? "windows" : process.platform === "darwin" ? "macos" : "linux",
10137
10450
  [HEADER_PROJECT_SLUG]: "dsh-chatgpt-subscription",
@@ -10167,7 +10480,7 @@ function firstString$1(record, keys) {
10167
10480
  if (value !== void 0) return value;
10168
10481
  }
10169
10482
  }
10170
- function firstNumber$1(record, keys) {
10483
+ function firstNumber$2(record, keys) {
10171
10484
  for (const key of keys) {
10172
10485
  const value = asNumber$3(record[key]);
10173
10486
  if (value !== void 0) return value;
@@ -10339,12 +10652,12 @@ function parseWindowLimits(payload, consumed = []) {
10339
10652
  if (key === "limited" || key === "exceeded") continue;
10340
10653
  const record = asRecord$9(limits[key]);
10341
10654
  if (record === void 0) continue;
10342
- const cap = firstNumber$1(record, [
10655
+ const cap = firstNumber$2(record, [
10343
10656
  "cap",
10344
10657
  "limit",
10345
10658
  "total"
10346
10659
  ]);
10347
- const used = firstNumber$1(record, ["used", "consumed"]);
10660
+ const used = firstNumber$2(record, ["used", "consumed"]);
10348
10661
  if (cap === void 0 && used === void 0) continue;
10349
10662
  consumed.push(record);
10350
10663
  const usedFraction = cap !== void 0 && cap > 0 && used !== void 0 ? clamp01$2(used / cap) : null;
@@ -10374,9 +10687,9 @@ function parseCreditBalances(payload, consumed = []) {
10374
10687
  const root = asRecord$9(payload) ?? {};
10375
10688
  const credits = asRecord$9(deepValue(asRecord$9(root.data) ?? root, ["credits"]));
10376
10689
  if (credits === void 0) return [];
10377
- const monthly = firstNumber$1(credits, ["monthlyCredits", "monthly_credits"]);
10378
- const purchased = firstNumber$1(credits, ["purchasedCredits", "purchased_credits"]);
10379
- 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"]);
10380
10693
  if (monthly === void 0 && purchased === void 0 && free === void 0) return [];
10381
10694
  consumed.push(credits);
10382
10695
  const meters = [];
@@ -10455,27 +10768,27 @@ function sweepMeters(payload, skipIds, consumed) {
10455
10768
  let anonymized = 0;
10456
10769
  for (const record of records) {
10457
10770
  if (consumed.has(record)) continue;
10458
- const limit = firstNumber$1(record, [
10771
+ const limit = firstNumber$2(record, [
10459
10772
  "limit",
10460
10773
  "quota",
10461
10774
  "allowance",
10462
10775
  "cap",
10463
10776
  "total"
10464
10777
  ]);
10465
- const used = firstNumber$1(record, [
10778
+ const used = firstNumber$2(record, [
10466
10779
  "used",
10467
10780
  "consumed",
10468
10781
  "spent"
10469
10782
  ]);
10470
- const remainingRaw = firstNumber$1(record, ["remaining", "left"]);
10471
- const balance = firstNumber$1(record, ["balance", "credits"]);
10472
- 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, [
10473
10786
  "usedPercent",
10474
10787
  "used_percent",
10475
10788
  "usagePercent",
10476
10789
  "percentUsed"
10477
10790
  ]);
10478
- const remainingPercentRaw = firstNumber$1(record, [
10791
+ const remainingPercentRaw = firstNumber$2(record, [
10479
10792
  "remainingPercent",
10480
10793
  "remaining_percent",
10481
10794
  "percentRemaining"
@@ -10599,7 +10912,7 @@ function parseUsageWindows$1(payload) {
10599
10912
  const windows = [];
10600
10913
  const seen = /* @__PURE__ */ new Set();
10601
10914
  for (const record of records) {
10602
- const percent = firstNumber$1(record, [
10915
+ const percent = firstNumber$2(record, [
10603
10916
  "usedPercent",
10604
10917
  "used_percent",
10605
10918
  "usagePercent",
@@ -10626,7 +10939,7 @@ function parseUsageWindows$1(payload) {
10626
10939
  "window"
10627
10940
  ]) ?? id,
10628
10941
  usedPercent: Math.round(clamp01$2(normalizePercent(percent) ?? 0) * 100),
10629
- windowDurationMins: firstNumber$1(record, [
10942
+ windowDurationMins: firstNumber$2(record, [
10630
10943
  "windowDurationMins",
10631
10944
  "window_minutes",
10632
10945
  "durationMins",
@@ -10702,7 +11015,7 @@ function parseProviderModels(payload) {
10702
11015
  models.push({
10703
11016
  id,
10704
11017
  name: firstString$1(record, ["displayName", "name"]) ?? id,
10705
- contextWindow: firstNumber$1(record, [
11018
+ contextWindow: firstNumber$2(record, [
10706
11019
  "context_length",
10707
11020
  "contextLength",
10708
11021
  "context_window",
@@ -10720,7 +11033,7 @@ async function fetchProviderModels(options = {}) {
10720
11033
  const response = await (options.fetchFn ?? fetch)(`${providerUrl(options.apiEnv ?? resolveApiEnv())}/models`, {
10721
11034
  headers: {
10722
11035
  accept: "application/json",
10723
- "user-agent": PROVIDER_NAME$4
11036
+ "user-agent": PROVIDER_NAME$5
10724
11037
  },
10725
11038
  signal: timeoutSignal$3(options.signal, DISCOVERY_TIMEOUT_MS$3)
10726
11039
  });
@@ -10943,7 +11256,7 @@ function buildModelOptions$2(catalog, enabledModelIds, overrides) {
10943
11256
  contextWindow: overrides[model.id] && overrides[model.id] > 0 ? overrides[model.id] : contextWindow,
10944
11257
  defaultMaxTokens: maxOutputTokensFor$4(model.id),
10945
11258
  ...efforts.length > 0 ? { reasoningEfforts: efforts } : {},
10946
- wire: wireForModel$1(model.id)
11259
+ wire: wireForModel$2(model.id)
10947
11260
  };
10948
11261
  });
10949
11262
  }
@@ -11132,14 +11445,14 @@ function imageBlockToInline$3(block, images) {
11132
11445
  data: resolved.data
11133
11446
  } : void 0;
11134
11447
  }
11135
- function textOf$4(content) {
11448
+ function textOf$5(content) {
11136
11449
  if (typeof content === "string") return sanitizeText$4(content);
11137
11450
  if (!Array.isArray(content)) return "";
11138
11451
  const parts = [];
11139
11452
  for (const block of content) {
11140
11453
  if (!isRecord$18(block)) continue;
11141
11454
  if (block.type === "text" && typeof block.text === "string") parts.push(sanitizeText$4(block.text));
11142
- 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));
11143
11456
  }
11144
11457
  return parts.join("");
11145
11458
  }
@@ -11247,7 +11560,7 @@ function leadingSystemText$4(options) {
11247
11560
  if (typeof options.system === "string" && options.system.trim() !== "") parts.push(options.system);
11248
11561
  for (const message of options.messages) {
11249
11562
  if (message.role !== "system") continue;
11250
- const text = textOf$4(message.content);
11563
+ const text = textOf$5(message.content);
11251
11564
  if (text !== "") parts.push(text);
11252
11565
  }
11253
11566
  return parts.length === 0 ? void 0 : parts.join("\n\n");
@@ -11367,7 +11680,7 @@ function buildOpenAIRequest$1(options, images = NO_RESOLVED_IMAGES$4) {
11367
11680
  content
11368
11681
  });
11369
11682
  }
11370
- const effort = options.reasoningEffort === void 0 ? void 0 : String(options.reasoningEffort);
11683
+ const effort = wireReasoningEffort(options.reasoningEffort === void 0 ? void 0 : String(options.reasoningEffort));
11371
11684
  const maxTokens = options.maxTokens ?? maxOutputTokensFor$4(options.model);
11372
11685
  return {
11373
11686
  model: options.model,
@@ -11501,7 +11814,7 @@ function buildAnthropicRequest$1(options, images = NO_RESOLVED_IMAGES$4) {
11501
11814
  }
11502
11815
  const system = leadingSystemText$4(options);
11503
11816
  const maxTokens = options.maxTokens ?? maxOutputTokensFor$4(options.model);
11504
- 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);
11505
11818
  return {
11506
11819
  model: options.model,
11507
11820
  max_tokens: maxTokens,
@@ -11524,7 +11837,7 @@ function buildAnthropicRequest$1(options, images = NO_RESOLVED_IMAGES$4) {
11524
11837
  function buildRequest$1(options, wire, images = NO_RESOLVED_IMAGES$4) {
11525
11838
  return wire === "anthropic" ? buildAnthropicRequest$1(options, images) : buildOpenAIRequest$1(options, images);
11526
11839
  }
11527
- function createStreamState$4(wire) {
11840
+ function createStreamState$5(wire) {
11528
11841
  return {
11529
11842
  wire,
11530
11843
  blocks: [],
@@ -11560,7 +11873,7 @@ function closeCurrent$3(state) {
11560
11873
  block
11561
11874
  }];
11562
11875
  }
11563
- function closeToolCalls$3(state) {
11876
+ function closeToolCalls$4(state) {
11564
11877
  const out = [];
11565
11878
  for (const [wireIndex, call] of [...state.toolCalls.entries()].sort((a, b) => a[0] - b[0])) {
11566
11879
  const block = {
@@ -11605,7 +11918,7 @@ function processOpenAIStreamLine$1(line, state) {
11605
11918
  const payload = trimmed.slice(5).trim();
11606
11919
  if (payload === "[DONE]") {
11607
11920
  state.done = true;
11608
- return closeStream$3(state);
11921
+ return closeStream$4(state);
11609
11922
  }
11610
11923
  if (payload === "") return [];
11611
11924
  const chunk = safeJsonParse$4(payload);
@@ -11628,7 +11941,7 @@ function processOpenAIStreamLine$1(line, state) {
11628
11941
  if (delta) {
11629
11942
  const reasoning = asString$11(delta.reasoning_content) ?? asString$11(delta.reasoning);
11630
11943
  if (reasoning !== void 0 && reasoning !== "") {
11631
- out.push(...closeToolCalls$3(state));
11944
+ out.push(...closeToolCalls$4(state));
11632
11945
  if (state.current === null || state.current.type !== "reasoning") out.push(...openTextBlock$3(state, "reasoning"));
11633
11946
  state.current.text += sanitizeText$4(reasoning);
11634
11947
  state.hasContent = true;
@@ -11640,7 +11953,7 @@ function processOpenAIStreamLine$1(line, state) {
11640
11953
  }
11641
11954
  const content = asString$11(delta.content);
11642
11955
  if (content !== void 0 && content !== "") {
11643
- out.push(...closeToolCalls$3(state));
11956
+ out.push(...closeToolCalls$4(state));
11644
11957
  if (state.current === null || state.current.type !== "text") out.push(...openTextBlock$3(state, "text"));
11645
11958
  state.current.text += sanitizeText$4(content);
11646
11959
  state.hasContent = true;
@@ -11660,7 +11973,7 @@ function processOpenAIStreamLine$1(line, state) {
11660
11973
  if (finish !== void 0 && finish !== "") {
11661
11974
  state.finishReason = finish;
11662
11975
  out.push(...closeCurrent$3(state));
11663
- out.push(...closeToolCalls$3(state));
11976
+ out.push(...closeToolCalls$4(state));
11664
11977
  }
11665
11978
  return out;
11666
11979
  }
@@ -11878,7 +12191,7 @@ function processAnthropicStreamLine$1(line, state) {
11878
12191
  }
11879
12192
  if (type === "message_stop") {
11880
12193
  state.done = true;
11881
- return closeStream$3(state);
12194
+ return closeStream$4(state);
11882
12195
  }
11883
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");
11884
12197
  return out;
@@ -11899,10 +12212,10 @@ function finishReasonFor$4(state) {
11899
12212
  return { kind: "stop" };
11900
12213
  }
11901
12214
  /** Flush every open block, then emit usage and the terminal finish. */
11902
- function closeStream$3(state) {
12215
+ function closeStream$4(state) {
11903
12216
  if (state.finished) return [];
11904
12217
  state.finished = true;
11905
- const out = [...closeCurrent$3(state), ...closeToolCalls$3(state)];
12218
+ const out = [...closeCurrent$3(state), ...closeToolCalls$4(state)];
11906
12219
  if (state.sawUsage) out.push({
11907
12220
  type: "usage",
11908
12221
  usage: tokenUsage$4(state)
@@ -11932,7 +12245,7 @@ function assertStreamComplete$4(state) {
11932
12245
  * the set: `INVALID_CREDENTIAL` (a rejected key fails identically on every
11933
12246
  * attempt) and `ABORTED` (the caller already cancelled).
11934
12247
  */
11935
- const RETRY_POLICY$4 = resolveRetryPolicy({
12248
+ const RETRY_POLICY$5 = resolveRetryPolicy({
11936
12249
  mode: "normal",
11937
12250
  maxRetries: 3,
11938
12251
  retryableCodes: [
@@ -11952,7 +12265,7 @@ function resolveDefaultReasoningEffort$3(efforts, configuredEffort) {
11952
12265
  if (configuredEffort && efforts.includes(configuredEffort)) return ReasoningEffortId(configuredEffort);
11953
12266
  }
11954
12267
  /** Cooldown one rate-limited key takes when the provider states no delay. */
11955
- const POOL_COOLDOWN_MS$3 = 15 * 6e4;
12268
+ const POOL_COOLDOWN_MS$4 = 15 * 6e4;
11956
12269
  var CommandCodeAdapter = class extends LlmAdapter {
11957
12270
  store;
11958
12271
  modelSettings;
@@ -11978,11 +12291,11 @@ var CommandCodeAdapter = class extends LlmAdapter {
11978
12291
  providerInfo(provider) {
11979
12292
  return {
11980
12293
  id: provider,
11981
- name: PROVIDER_NAME$4
12294
+ name: PROVIDER_NAME$5
11982
12295
  };
11983
12296
  }
11984
12297
  providerRetryPolicy() {
11985
- return RETRY_POLICY$4;
12298
+ return RETRY_POLICY$5;
11986
12299
  }
11987
12300
  imageRequestPricing() {}
11988
12301
  settings() {
@@ -11998,7 +12311,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
11998
12311
  apiEnv: resolveApiEnv()
11999
12312
  })))().catch(() => []);
12000
12313
  if (live.length > 0) return live;
12001
- return FALLBACK_MODELS$3.map((model) => ({
12314
+ return FALLBACK_MODELS$4.map((model) => ({
12002
12315
  id: model.id,
12003
12316
  name: model.name,
12004
12317
  contextWindow: model.contextWindow
@@ -12034,7 +12347,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
12034
12347
  name: entry?.name ?? modelId,
12035
12348
  inputModalities: inputModalitiesFor$1(modelId),
12036
12349
  context: { contextWindow: this.contextWindowFor(modelId, entry, settings.contextWindowOverrides) },
12037
- defaultMaxTokens: maxOutputTokensFor$4(modelId),
12350
+ ...outputReservation(this.contextWindowFor(modelId, entry, settings.contextWindowOverrides), maxOutputTokensFor$4(modelId)),
12038
12351
  ...efforts.length === 0 ? {} : { reasoning: {
12039
12352
  efforts: efforts.map((effort) => ({
12040
12353
  id: ReasoningEffortId(effort),
@@ -12057,11 +12370,17 @@ var CommandCodeAdapter = class extends LlmAdapter {
12057
12370
  ...options,
12058
12371
  reasoningEffort: ReasoningEffortId(String(effort))
12059
12372
  };
12060
- 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);
12061
12380
  }
12062
12381
  async *requestStream(options, signal) {
12063
12382
  const fetchFn = this.options.fetchFn ?? fetch;
12064
- const wire = wireForModel$1(options.model);
12383
+ const wire = wireForModel$2(options.model);
12065
12384
  const requestOptions = offloadOldestRequestImages$3(normalizeGenerateOptions(options));
12066
12385
  const images = await resolveRequestImages$3(requestOptions, this.options.attachments, signal);
12067
12386
  const body = JSON.stringify(buildRequest$1(requestOptions, wire, images));
@@ -12075,7 +12394,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
12075
12394
  let apiEnv;
12076
12395
  if (pool === null) {
12077
12396
  const credentials = await this.store.read();
12078
- 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");
12079
12398
  apiKey = credentials.apiKey;
12080
12399
  apiEnv = credentials.apiEnv ?? resolveApiEnv();
12081
12400
  } else {
@@ -12111,13 +12430,13 @@ var CommandCodeAdapter = class extends LlmAdapter {
12111
12430
  detail = (await response.text().catch(() => "")).slice(0, 600);
12112
12431
  if (pool === null || accountId === void 0) break;
12113
12432
  if (response.status === 401 || response.status === 403) {
12114
- 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);
12115
12434
  if (await pool.hasAnotherAvailableAccount(tried)) continue;
12116
12435
  break;
12117
12436
  }
12118
12437
  if (response.status === 429) {
12119
12438
  const after = retryAfterMs$1(response.headers);
12120
- 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);
12121
12440
  if (await pool.hasAnotherAvailableAccount(tried)) continue;
12122
12441
  break;
12123
12442
  }
@@ -12125,21 +12444,21 @@ var CommandCodeAdapter = class extends LlmAdapter {
12125
12444
  }
12126
12445
  if (response === void 0 || !response.ok) {
12127
12446
  const status = response?.status ?? 500;
12128
- 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 });
12129
12448
  if (status === 429) {
12130
12449
  const after = response === void 0 ? void 0 : retryAfterMs$1(response.headers);
12131
- 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", {
12132
12451
  status: 429,
12133
12452
  ...after === void 0 ? {} : { providerRetryAfterMs: after }
12134
12453
  });
12135
12454
  }
12136
- if (status >= 500) throw new LlmError(`${PROVIDER_NAME$4} upstream server error (${status}): ${detail || "No response"}`, "SERVER", { status });
12137
- 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 });
12138
12457
  }
12139
12458
  if (response.body === null) throw new LlmError("Command Code returned an empty response body", "PROVIDER_ERROR");
12140
12459
  const reader = response.body.getReader();
12141
12460
  const decoder = new TextDecoder();
12142
- const state = createStreamState$4(wire);
12461
+ const state = createStreamState$5(wire);
12143
12462
  let buffer = "";
12144
12463
  try {
12145
12464
  while (true) {
@@ -12157,7 +12476,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
12157
12476
  if (buffer.trim() !== "") for (const line of buffer.split("\n")) for (const chunk of processLine$1(line, state, wire)) yield chunk;
12158
12477
  if (state.finished) return;
12159
12478
  assertStreamComplete$4(state);
12160
- for (const chunk of closeStream$3(state)) yield chunk;
12479
+ for (const chunk of closeStream$4(state)) yield chunk;
12161
12480
  } finally {
12162
12481
  reader.cancel().catch(() => void 0);
12163
12482
  }
@@ -12183,7 +12502,7 @@ function commandCodeAccountKey(credentials) {
12183
12502
  if (credentials.userId !== void 0 && credentials.userId !== "") return `${credentials.userId}:${credentials.keyName ?? ""}`;
12184
12503
  return createHash("sha256").update(credentials.apiKey).digest("hex");
12185
12504
  }
12186
- function optionalString$5(record, key) {
12505
+ function optionalString$7(record, key) {
12187
12506
  const value = record[key];
12188
12507
  if (value === void 0 || value === null) return void 0;
12189
12508
  if (typeof value !== "string") throw new Error("Command Code pool account field is invalid");
@@ -12215,7 +12534,7 @@ function parseCommandCodePoolData(value) {
12215
12534
  "planId",
12216
12535
  "userId"
12217
12536
  ]) {
12218
- const parsed = optionalString$5(raw, key);
12537
+ const parsed = optionalString$7(raw, key);
12219
12538
  if (parsed !== void 0) account[key] = parsed;
12220
12539
  }
12221
12540
  if (account.email === void 0 && credentials.email !== void 0) account.email = credentials.email;
@@ -12247,7 +12566,7 @@ var CommandCodeAccountPool = class extends AccountPoolCore {
12247
12566
  constructor(options = {}) {
12248
12567
  const store = options.store ?? new FileCredentialStore$1();
12249
12568
  const hooks = {
12250
- providerId: PROVIDER_ID$4,
12569
+ providerId: PROVIDER_ID$5,
12251
12570
  displayName: "Command Code",
12252
12571
  poolFile: commandCodePoolPath(),
12253
12572
  keychainService: "dsh-command-code-pool",
@@ -12413,7 +12732,7 @@ function applyCors(request, response) {
12413
12732
  response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
12414
12733
  response.setHeader("Access-Control-Allow-Headers", "Content-Type");
12415
12734
  }
12416
- function sendJson$6(response, status, body) {
12735
+ function sendJson$7(response, status, body) {
12417
12736
  response.writeHead(status, { "content-type": "application/json" });
12418
12737
  response.end(JSON.stringify(body));
12419
12738
  }
@@ -12606,7 +12925,7 @@ function createAuthServer(port, expectedState, options = {}) {
12606
12925
  try {
12607
12926
  fields = fieldsFromPayload(raw, contentType);
12608
12927
  } catch {
12609
- sendJson$6(response, 400, {
12928
+ sendJson$7(response, 400, {
12610
12929
  success: false,
12611
12930
  error: "Invalid JSON"
12612
12931
  });
@@ -12614,7 +12933,7 @@ function createAuthServer(port, expectedState, options = {}) {
12614
12933
  }
12615
12934
  if (fields.error) {
12616
12935
  if (fields.state !== expectedState) {
12617
- sendJson$6(response, 403, {
12936
+ sendJson$7(response, 403, {
12618
12937
  success: false,
12619
12938
  error: "Invalid state token"
12620
12939
  });
@@ -12626,14 +12945,14 @@ function createAuthServer(port, expectedState, options = {}) {
12626
12945
  }
12627
12946
  const apiKey = fields.apiKey;
12628
12947
  if (!apiKey) {
12629
- sendJson$6(response, 400, {
12948
+ sendJson$7(response, 400, {
12630
12949
  success: false,
12631
12950
  error: "Missing required fields"
12632
12951
  });
12633
12952
  return;
12634
12953
  }
12635
12954
  if (fields.state !== expectedState) {
12636
- sendJson$6(response, 403, {
12955
+ sendJson$7(response, 403, {
12637
12956
  success: false,
12638
12957
  error: "Invalid state token"
12639
12958
  });
@@ -12783,18 +13102,18 @@ function isCommandCodeReasoningEffort(value) {
12783
13102
  return typeof value === "string" && COMMAND_CODE_REASONING_EFFORTS.includes(value);
12784
13103
  }
12785
13104
  const MAX_BODY_BYTES$5 = 64 * 1024;
12786
- const ROUTE_PREFIX$3 = "/command-code/api";
12787
- function sendJson$5(response, status, body) {
13105
+ const ROUTE_PREFIX$4 = "/command-code/api";
13106
+ function sendJson$6(response, status, body) {
12788
13107
  response.writeHead(status, { "Content-Type": "application/json" });
12789
13108
  response.end(JSON.stringify(body));
12790
13109
  }
12791
- function sendMethodNotAllowed$4(response) {
12792
- sendJson$5(response, 405, {
13110
+ function sendMethodNotAllowed$5(response) {
13111
+ sendJson$6(response, 405, {
12793
13112
  ok: false,
12794
13113
  error: "Method Not Allowed"
12795
13114
  });
12796
13115
  }
12797
- async function readRequestJson$5(request) {
13116
+ async function readRequestJson$6(request) {
12798
13117
  return new Promise((resolve, reject) => {
12799
13118
  const chunks = [];
12800
13119
  let total = 0;
@@ -12819,7 +13138,7 @@ async function readRequestJson$5(request) {
12819
13138
  });
12820
13139
  }
12821
13140
  function fallbackCatalog$1() {
12822
- return FALLBACK_MODELS$3.map((model) => ({
13141
+ return FALLBACK_MODELS$4.map((model) => ({
12823
13142
  id: model.id,
12824
13143
  name: model.name,
12825
13144
  contextWindow: model.contextWindow
@@ -12836,7 +13155,7 @@ function fallbackCatalog$1() {
12836
13155
  function resolveEnabledModelIds$4(stored, catalog, enabled = true) {
12837
13156
  if (!enabled) return [];
12838
13157
  const catalogIds = catalog.map((model) => model.id);
12839
- const shippedDefaults = new Set(FALLBACK_MODELS$3.map((model) => model.id));
13158
+ const shippedDefaults = new Set(FALLBACK_MODELS$4.map((model) => model.id));
12840
13159
  if (stored.length > 0 && stored.length === shippedDefaults.size && stored.every((id) => shippedDefaults.has(id))) return catalogIds;
12841
13160
  const known = new Set(catalogIds);
12842
13161
  return stored.filter((id) => known.has(id));
@@ -12921,13 +13240,13 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
12921
13240
  const quotaRefresh = new QuotaRefresh();
12922
13241
  return ctx.webServer.register({
12923
13242
  kind: "prefix",
12924
- path: ROUTE_PREFIX$3,
13243
+ path: ROUTE_PREFIX$4,
12925
13244
  handler: async (request, response) => {
12926
13245
  const path = new URL(request.url || "/", "http://dsh.local").pathname.replace(/^\/command-code\/api\/?/, "");
12927
13246
  const method = request.method ?? "GET";
12928
13247
  try {
12929
13248
  if (path === "" || path === "status") {
12930
- if (method !== "GET") return sendMethodNotAllowed$4(response);
13249
+ if (method !== "GET") return sendMethodNotAllowed$5(response);
12931
13250
  const credentials = await store.read();
12932
13251
  const targetId = await activeAccountId();
12933
13252
  const cached = getCachedQuotaFor$1(targetId);
@@ -12936,7 +13255,7 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
12936
13255
  if (cached === void 0) await quotaRefresh.run(refresh);
12937
13256
  else quotaRefresh.start(refresh);
12938
13257
  }
12939
- return sendJson$5(response, 200, {
13258
+ return sendJson$6(response, 200, {
12940
13259
  ok: true,
12941
13260
  value: {
12942
13261
  ...await readStatus(),
@@ -12945,12 +13264,12 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
12945
13264
  });
12946
13265
  }
12947
13266
  if (path === "login") {
12948
- if (method !== "POST") return sendMethodNotAllowed$4(response);
12949
- 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, {
12950
13269
  ok: false,
12951
13270
  error: "Cross-origin request rejected."
12952
13271
  });
12953
- return sendJson$5(response, 200, {
13272
+ return sendJson$6(response, 200, {
12954
13273
  ok: true,
12955
13274
  value: await beginWebLogin$3(store, {
12956
13275
  fetchFn,
@@ -12959,25 +13278,25 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
12959
13278
  });
12960
13279
  }
12961
13280
  if (path === "login/status") {
12962
- if (method !== "GET") return sendMethodNotAllowed$4(response);
12963
- return sendJson$5(response, 200, {
13281
+ if (method !== "GET") return sendMethodNotAllowed$5(response);
13282
+ return sendJson$6(response, 200, {
12964
13283
  ok: true,
12965
13284
  value: getWebLoginStatus()
12966
13285
  });
12967
13286
  }
12968
13287
  if (path === "login/apikey") {
12969
- if (method !== "POST") return sendMethodNotAllowed$4(response);
12970
- 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, {
12971
13290
  ok: false,
12972
13291
  error: "Cross-origin request rejected."
12973
13292
  });
12974
- const body = await readRequestJson$5(request);
13293
+ const body = await readRequestJson$6(request);
12975
13294
  const account = await saveApiKey(store, typeof body.apiKey === "string" ? body.apiKey : "", {
12976
13295
  fetchFn,
12977
13296
  ...accountPool === void 0 ? {} : { onSave: (credentials) => accountPool.addAccount(credentials) }
12978
13297
  });
12979
13298
  clearCachedQuota$2();
12980
- return sendJson$5(response, 200, {
13299
+ return sendJson$6(response, 200, {
12981
13300
  ok: true,
12982
13301
  value: {
12983
13302
  ...await readStatus(),
@@ -12986,13 +13305,13 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
12986
13305
  });
12987
13306
  }
12988
13307
  if (path === "connection/test") {
12989
- if (method !== "POST") return sendMethodNotAllowed$4(response);
12990
- 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, {
12991
13310
  ok: false,
12992
13311
  error: "Cross-origin request rejected."
12993
13312
  });
12994
13313
  const credentials = await store.read();
12995
- if (credentials === null) return sendJson$5(response, 400, {
13314
+ if (credentials === null) return sendJson$6(response, 400, {
12996
13315
  ok: false,
12997
13316
  error: "Not signed in."
12998
13317
  });
@@ -13001,7 +13320,7 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
13001
13320
  fetchFn,
13002
13321
  apiEnv: credentials.apiEnv ?? resolveApiEnv()
13003
13322
  });
13004
- return sendJson$5(response, 200, {
13323
+ return sendJson$6(response, 200, {
13005
13324
  ok: true,
13006
13325
  value: {
13007
13326
  connected: true,
@@ -13019,24 +13338,24 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
13019
13338
  });
13020
13339
  }
13021
13340
  if (path === "quota") {
13022
- if (method !== "GET" && method !== "POST") return sendMethodNotAllowed$4(response);
13341
+ if (method !== "GET" && method !== "POST") return sendMethodNotAllowed$5(response);
13023
13342
  await fetchAccountQuota$2(quotaStore, fetchFn, true, await activeAccountId());
13024
- return sendJson$5(response, 200, {
13343
+ return sendJson$6(response, 200, {
13025
13344
  ok: true,
13026
13345
  value: await readStatus()
13027
13346
  });
13028
13347
  }
13029
13348
  if (path === "models" || path === "settings") {
13030
- if (method === "GET") return sendJson$5(response, 200, {
13349
+ if (method === "GET") return sendJson$6(response, 200, {
13031
13350
  ok: true,
13032
13351
  value: await readStatus()
13033
13352
  });
13034
- if (method !== "POST") return sendMethodNotAllowed$4(response);
13035
- 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, {
13036
13355
  ok: false,
13037
13356
  error: "Cross-origin request rejected."
13038
13357
  });
13039
- const body = await readRequestJson$5(request);
13358
+ const body = await readRequestJson$6(request);
13040
13359
  const patch = {};
13041
13360
  if (typeof body.enabled === "boolean") patch.enabled = body.enabled;
13042
13361
  if (Array.isArray(body.enabledModelIds)) patch.enabledModelIds = body.enabledModelIds.filter((id) => typeof id === "string");
@@ -13053,14 +13372,14 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
13053
13372
  if (preferences) await preferences.update(patch);
13054
13373
  else await modelSettings.updateSettings(patch);
13055
13374
  if (patch.enabledModelIds !== void 0 || patch.enabled !== void 0) ctx.emit?.("llm/adapters-updated");
13056
- return sendJson$5(response, 200, {
13375
+ return sendJson$6(response, 200, {
13057
13376
  ok: true,
13058
13377
  value: await readStatus()
13059
13378
  });
13060
13379
  }
13061
13380
  if (path === "catalog/refresh") {
13062
- if (method !== "POST") return sendMethodNotAllowed$4(response);
13063
- 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, {
13064
13383
  ok: false,
13065
13384
  error: "Cross-origin request rejected."
13066
13385
  });
@@ -13070,7 +13389,7 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
13070
13389
  force: true,
13071
13390
  apiEnv: resolveApiEnv()
13072
13391
  });
13073
- return sendJson$5(response, 200, {
13392
+ return sendJson$6(response, 200, {
13074
13393
  ok: true,
13075
13394
  value: await readStatus()
13076
13395
  });
@@ -13078,7 +13397,7 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
13078
13397
  if (path === "accounts") {
13079
13398
  if (method === "GET") {
13080
13399
  const data = accountPool === void 0 ? null : await accountPool.read().catch(() => null);
13081
- return sendJson$5(response, 200, {
13400
+ return sendJson$6(response, 200, {
13082
13401
  ok: true,
13083
13402
  value: {
13084
13403
  accounts: accountPool === void 0 ? [] : await accountPool.listAccounts().catch(() => []),
@@ -13087,16 +13406,16 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
13087
13406
  }
13088
13407
  });
13089
13408
  }
13090
- if (method !== "POST") return sendMethodNotAllowed$4(response);
13091
- 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, {
13092
13411
  ok: false,
13093
13412
  error: "Cross-origin request rejected."
13094
13413
  });
13095
- if (accountPool === void 0) return sendJson$5(response, 400, {
13414
+ if (accountPool === void 0) return sendJson$6(response, 400, {
13096
13415
  ok: false,
13097
13416
  error: "Account pool is not installed."
13098
13417
  });
13099
- const body = await readRequestJson$5(request);
13418
+ const body = await readRequestJson$6(request);
13100
13419
  const action = typeof body.action === "string" ? body.action : "";
13101
13420
  const accountId = typeof body.accountId === "string" ? body.accountId : void 0;
13102
13421
  if (action === "set-primary" && accountId !== void 0) await accountPool.setPrimary(accountId);
@@ -13107,20 +13426,20 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
13107
13426
  else if (action === "relogin" && accountId !== void 0) await accountPool.clearAuthFailed(accountId);
13108
13427
  clearCachedQuota$2();
13109
13428
  clearCachedCatalog$3();
13110
- return sendJson$5(response, 200, {
13429
+ return sendJson$6(response, 200, {
13111
13430
  ok: true,
13112
13431
  value: await readStatus()
13113
13432
  });
13114
13433
  }
13115
13434
  if (path === "logout") {
13116
- if (method !== "POST") return sendMethodNotAllowed$4(response);
13117
- 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, {
13118
13437
  ok: false,
13119
13438
  error: "Cross-origin request rejected."
13120
13439
  });
13121
13440
  if (accountPool === void 0) await store.delete();
13122
13441
  else {
13123
- const body = await readRequestJson$5(request).catch(() => ({}));
13442
+ const body = await readRequestJson$6(request).catch(() => ({}));
13124
13443
  const data = await accountPool.read().catch(() => null);
13125
13444
  const target = typeof body.accountId === "string" ? body.accountId : data?.activeAccountId ?? data?.accounts.find((account) => account.isPrimary)?.id ?? data?.accounts[0]?.id;
13126
13445
  if (target === void 0) await store.delete();
@@ -13128,17 +13447,17 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
13128
13447
  }
13129
13448
  clearCachedQuota$2();
13130
13449
  clearCachedCatalog$3();
13131
- return sendJson$5(response, 200, {
13450
+ return sendJson$6(response, 200, {
13132
13451
  ok: true,
13133
13452
  value: await readStatus()
13134
13453
  });
13135
13454
  }
13136
- return sendJson$5(response, 404, {
13455
+ return sendJson$6(response, 404, {
13137
13456
  ok: false,
13138
13457
  error: "not-found"
13139
13458
  });
13140
13459
  } catch (error) {
13141
- return sendJson$5(response, 500, {
13460
+ return sendJson$6(response, 500, {
13142
13461
  ok: false,
13143
13462
  error: error instanceof Error ? error.message : String(error)
13144
13463
  });
@@ -13147,6 +13466,1377 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
13147
13466
  });
13148
13467
  }
13149
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
13150
14840
  //#region src/host/kimi-code/model-catalog.ts
13151
14841
  /**
13152
14842
  * The four ids the subscription serves today.
@@ -17183,7 +18873,6 @@ var KimiCodeAdapter = class extends LlmAdapter {
17183
18873
  name: entry?.name ?? modelId,
17184
18874
  inputModalities: inputModalitiesForEntry(modelId, catalog),
17185
18875
  context: { contextWindow: this.contextWindowFor(modelId, entry, settings.contextWindowOverrides) },
17186
- defaultMaxTokens: maxOutputTokensFor$3(modelId, this.contextWindowFor(modelId, entry, settings.contextWindowOverrides)),
17187
18876
  ...efforts.length === 0 ? {} : { reasoning: {
17188
18877
  efforts: efforts.map((effort) => ({
17189
18878
  id: ReasoningEffortId(effort),
@@ -21653,9 +23342,8 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
21653
23342
  * One model's resolved metadata, read through the current selection.
21654
23343
  *
21655
23344
  * The effective context window is the override when one is saved, and the
21656
- * output cap is sized against that same number — a window the card shows and a
21657
- * cap the request path computes from a different one is precisely the drift
21658
- * 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.
21659
23347
  */
21660
23348
  async resolveModel(provider, modelId, signal) {
21661
23349
  if (signal?.aborted === true) throw new LlmError("MiniMax Code model resolution aborted", "ABORTED");
@@ -21670,7 +23358,6 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
21670
23358
  name: entry?.name ?? modelId,
21671
23359
  inputModalities: modalitiesForModel(modelId),
21672
23360
  context: { contextWindow },
21673
- defaultMaxTokens: maxOutputTokensFor$1(modelId, contextWindow),
21674
23361
  ...efforts.length === 0 ? {} : { reasoning: {
21675
23362
  efforts: efforts.map((effort) => ({
21676
23363
  id: ReasoningEffortId(effort),
@@ -26373,7 +28060,7 @@ var WorkBuddyAdapter = class extends LlmAdapter {
26373
28060
  name: entry.id === modelId ? entry.name : modelId,
26374
28061
  inputModalities: entry.supportsImage ? ["text", "image"] : ["text"],
26375
28062
  context: { contextWindow: contextWindow || 128e3 },
26376
- defaultMaxTokens: maxOutputTokensFor$2(modelId, catalog),
28063
+ ...outputReservation(contextWindow || 128e3, maxOutputTokensFor$2(modelId, catalog)),
26377
28064
  ...efforts.length === 0 ? {} : { reasoning: {
26378
28065
  efforts: efforts.map((effort) => ({
26379
28066
  id: ReasoningEffortId(effort),
@@ -26428,7 +28115,13 @@ var WorkBuddyAdapter = class extends LlmAdapter {
26428
28115
  const fetchFn = this.options.fetchFn ?? fetch;
26429
28116
  const requestOptions = offloadOldestRequestImages$1(normalizeGenerateOptions(options));
26430
28117
  const images = await resolveRequestImages$1(requestOptions, this.options.attachments, signal);
26431
- 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));
26432
28125
  const pool = this.accountPool;
26433
28126
  const tried = /* @__PURE__ */ new Set();
26434
28127
  let response;
@@ -27399,17 +29092,29 @@ function loopbackRedirectUri(port = DEFAULT_CALLBACK_PORT) {
27399
29092
  * LOCAL ADDITIONS — the rows the reference snapshot predates
27400
29093
  * ---------------------------------------------------------------------------
27401
29094
  *
27402
- * `claude-opus-5-5` and `claude-sonnet-5-5` are the two such rows today. The
27403
- * mirror list in the test is `LOCALLY_CURATED_MODEL_IDS`; the two must be
27404
- * changed together. Sonnet 5.5's own reasons are recorded at its row; what
27405
- * follows below is Opus 5.5's.
27406
- *
27407
- * It is CURATED rather than transcribed because the snapshot this table copies
27408
- * has no entry for it, and the one thing a transcription table must never do is
27409
- * invent a row and let it read as if it had been read from somewhere. An
27410
- * invented row that mimics the format of a checked one is worse than a missing
27411
- * row: it is unverifiable and it looks verified. So the row below is marked at
27412
- * 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
27413
29118
  * asserts the curated list is exactly the ids the snapshot lacks — which keeps
27414
29119
  * the lock narrow instead of merely weaker.
27415
29120
  *
@@ -27434,10 +29139,12 @@ function loopbackRedirectUri(port = DEFAULT_CALLBACK_PORT) {
27434
29139
  * 2. `thinkingMode: 'adaptive'`, NOT `'mid-convo'`. The two are easy to confuse
27435
29140
  * here because every model in this line that refuses a temperature is also,
27436
29141
  * so far, a managed-effort model. That is a coincidence of `compat`, not a
27437
- * 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'
27438
29145
  * additionally forces `output_config = { effort: 'high' }` when the caller
27439
- * names no effort. That forced high is correct for the rows that DO carry the
27440
- * 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
27441
29148
  * documented default effort is `medium`. Labelling it 'mid-convo' would
27442
29149
  * silently override the vendor's own default and make every request think —
27443
29150
  * and cost — harder than the user asked for.
@@ -27454,8 +29161,8 @@ function loopbackRedirectUri(port = DEFAULT_CALLBACK_PORT) {
27454
29161
  * the user has to reach.
27455
29162
  */
27456
29163
  /**
27457
- * Sixteen rows: the snapshot's own 14, in the snapshot's own declaration order,
27458
- * 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).
27459
29166
  *
27460
29167
  * The order matters and is not cosmetic. The test asserts the snapshot's ids
27461
29168
  * appear in the table as a SUBSEQUENCE, so a curated row may be appended or
@@ -27644,6 +29351,25 @@ const CLAUDE_MODELS = Object.freeze([
27644
29351
  ],
27645
29352
  canDisableThinking: false
27646
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
+ },
27647
29373
  {
27648
29374
  id: "claude-sonnet-4-5",
27649
29375
  name: "Claude Sonnet 4.5 (latest)",
@@ -27707,25 +29433,6 @@ const CLAUDE_MODELS = Object.freeze([
27707
29433
  ],
27708
29434
  canDisableThinking: true
27709
29435
  },
27710
- {
27711
- id: "claude-opus-5-5",
27712
- name: "Claude Opus 5.5",
27713
- contextWindow: 1e6,
27714
- maxTokens: 128e3,
27715
- supportsImage: true,
27716
- supportsTemperature: false,
27717
- thinkingMode: "adaptive",
27718
- reasoningEfforts: [
27719
- "low",
27720
- "medium",
27721
- "high",
27722
- "xhigh",
27723
- "max"
27724
- ],
27725
- canDisableThinking: false,
27726
- minCliVersion: "2.1.280",
27727
- bindsThinkingToPrefix: true
27728
- },
27729
29436
  {
27730
29437
  id: "claude-sonnet-5-5",
27731
29438
  name: "Claude Sonnet 5.5",
@@ -32249,11 +33956,9 @@ function cancelLogin() {
32249
33956
  * importing it before it exists would make this adapter uncompilable, and
32250
33957
  * importing it afterwards changes nothing, because the only methods used are
32251
33958
  * the five in {@link ClaudeAccountPoolLike}.
32252
- * - A2. The live `GET /v1/models` listing is NOT read on the request path. It
32253
- * is authoritative for the context window alone (see client.ts), and the only
32254
- * model fact the body depends on — the thinking form — comes from the frozen
32255
- * catalog. Loading it per request would add a cached round trip that cannot
32256
- * 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.
32257
33962
  * - A3. A 429 with no `retry-after` and no reset instant cools the account for
32258
33963
  * {@link POOL_COOLDOWN_MS}. That is deliberately shorter than the 5-hour
32259
33964
  * window: an account parked for hours after a burst limit recovers would cost
@@ -32496,7 +34201,7 @@ var ClaudeAdapter = class ClaudeAdapter extends LlmAdapter {
32496
34201
  name: entry.name,
32497
34202
  inputModalities: entry.supportsImage ? ["text", "image"] : ["text"],
32498
34203
  context: { contextWindow: contextWindow || 2e5 },
32499
- defaultMaxTokens: maxOutputTokensFor(modelId, catalog),
34204
+ ...outputReservation(contextWindow || 2e5, maxOutputTokensFor(modelId, catalog)),
32500
34205
  ...efforts.length === 0 ? {} : { reasoning: {
32501
34206
  efforts: efforts.map((effort) => ({
32502
34207
  id: ReasoningEffortId(effort),
@@ -32579,7 +34284,11 @@ var ClaudeAdapter = class ClaudeAdapter extends LlmAdapter {
32579
34284
  }
32580
34285
  async attemptRequest(credentials, requestOptions, images, toolNames, thinking, settings, signal, fetchFn) {
32581
34286
  const cacheTtl = ClaudeAdapter.cacheTtlFor(settings);
32582
- 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, {
32583
34292
  toolNames,
32584
34293
  cacheControl: true,
32585
34294
  cacheTtl
@@ -38002,12 +39711,12 @@ function apply(ctx, pluginConfig = {}) {
38002
39711
  const claimAntigravityRoute = () => {
38003
39712
  if (antigravityRegistration !== void 0) return;
38004
39713
  try {
38005
- antigravityRegistration = ctx.llm.registerAdapter([PROVIDER_ID$5], antigravityAdapter);
38006
- if (antigravityConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] Antigravity route "${PROVIDER_ID$5}" is now served by this plugin`);
39714
+ antigravityRegistration = ctx.llm.registerAdapter([PROVIDER_ID$6], antigravityAdapter);
39715
+ if (antigravityConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] Antigravity route "${PROVIDER_ID$6}" is now served by this plugin`);
38007
39716
  antigravityConflict = null;
38008
39717
  } catch (error) {
38009
39718
  antigravityConflict = error instanceof Error ? error.message : String(error);
38010
- 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})`);
39719
+ 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})`);
38011
39720
  }
38012
39721
  };
38013
39722
  claimAntigravityRoute();
@@ -38026,6 +39735,12 @@ function apply(ctx, pluginConfig = {}) {
38026
39735
  }, commandCodeAccountPool);
38027
39736
  let commandCodeRegistration;
38028
39737
  let commandCodeConflict = null;
39738
+ const ollamaStore = new FileCredentialStore$5();
39739
+ const ollamaModelSettings = new FileModelSettingsStore$5();
39740
+ const ollamaAccountPool = new OllamaAccountPool({ store: ollamaStore });
39741
+ const ollamaAdapter = new OllamaAdapter(ollamaStore, ollamaModelSettings, { fetchFn: proxyFetch }, ollamaAccountPool);
39742
+ let ollamaRegistration;
39743
+ let ollamaConflict = null;
38029
39744
  const kimiCodeAdapter = new KimiCodeAdapter(kimiCodeStore, kimiCodeModelSettings, kimiCodePreferences, {
38030
39745
  fetchFn: proxyFetch,
38031
39746
  attachments: ctx.attachments,
@@ -38051,15 +39766,32 @@ function apply(ctx, pluginConfig = {}) {
38051
39766
  const kimiCodeRouteWatch = typeof ctx.on === "function" ? ctx.on("llm/adapters-updated", () => {
38052
39767
  claimKimiCodeRoute();
38053
39768
  }) : void 0;
39769
+ const claimOllamaRoute = () => {
39770
+ if (ollamaRegistration !== void 0) return;
39771
+ try {
39772
+ ollamaRegistration = ctx.llm.registerAdapter([PROVIDER_ID$4], ollamaAdapter);
39773
+ if (ollamaConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${PROVIDER_NAME$4} route "${PROVIDER_ID$4}" is now served by this plugin`);
39774
+ ollamaConflict = null;
39775
+ } catch (error) {
39776
+ ollamaConflict = error instanceof Error ? error.message : String(error);
39777
+ 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})`);
39778
+ }
39779
+ };
39780
+ claimOllamaRoute();
39781
+ const disposeOllamaRoutes = registerOllamaRoutes(ctx, {
39782
+ accountPool: ollamaAccountPool,
39783
+ modelSettings: ollamaModelSettings,
39784
+ fetchFn: proxyFetch
39785
+ });
38054
39786
  const claimCommandCodeRoute = () => {
38055
39787
  if (commandCodeRegistration !== void 0) return;
38056
39788
  try {
38057
- commandCodeRegistration = ctx.llm.registerAdapter([PROVIDER_ID$4], commandCodeAdapter);
38058
- if (commandCodeConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${PROVIDER_NAME$4} route "${PROVIDER_ID$4}" is now served by this plugin`);
39789
+ commandCodeRegistration = ctx.llm.registerAdapter([PROVIDER_ID$5], commandCodeAdapter);
39790
+ if (commandCodeConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${PROVIDER_NAME$5} route "${PROVIDER_ID$5}" is now served by this plugin`);
38059
39791
  commandCodeConflict = null;
38060
39792
  } catch (error) {
38061
39793
  commandCodeConflict = error instanceof Error ? error.message : String(error);
38062
- 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})`);
39794
+ 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})`);
38063
39795
  }
38064
39796
  };
38065
39797
  claimCommandCodeRoute();
@@ -38242,7 +39974,7 @@ function apply(ctx, pluginConfig = {}) {
38242
39974
  });
38243
39975
  };
38244
39976
  const disposeRoutes = registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwitcher, readRouteAudit, codexAccountPool);
38245
- const disposeAdapter = ctx.llm.registerAdapter([PROVIDER_ID$6], adapter);
39977
+ const disposeAdapter = ctx.llm.registerAdapter([PROVIDER_ID$7], adapter);
38246
39978
  const disposeImageTool = ctx.tools.register(createCodexImageTool(oauth, ctx.attachments, { fetchFn: proxyFetch }));
38247
39979
  const disposeVideoTool = ctx.tools.register(createKimiVideoTool(ctx, { fetchFn: proxyFetch }));
38248
39980
  ctx.inject(["web"], (ctx) => {
@@ -38268,6 +40000,9 @@ function apply(ctx, pluginConfig = {}) {
38268
40000
  releaseHandle(antigravityRouteWatch);
38269
40001
  antigravityRegistration?.();
38270
40002
  antigravityRegistration = void 0;
40003
+ disposeOllamaRoutes();
40004
+ ollamaRegistration?.();
40005
+ ollamaRegistration = void 0;
38271
40006
  disposeCommandCodeRoutes();
38272
40007
  releaseHandle(commandCodeRouteWatch);
38273
40008
  commandCodeRegistration?.();