@eddyskywalker/dsh-chatgpt-subscription 0.10.21 → 0.11.2

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 (56) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/lib/client.js +53 -8
  3. package/lib/client.js.map +1 -1
  4. package/lib/index.js +2501 -305
  5. package/lib/types/client/command-code/CommandCodeSection.d.ts.map +1 -1
  6. package/lib/types/client/command-code/locales.d.ts +14 -2
  7. package/lib/types/client/command-code/locales.d.ts.map +1 -1
  8. package/lib/types/host/antigravity/mapper.d.ts.map +1 -1
  9. package/lib/types/host/antigravity/types.d.ts.map +1 -1
  10. package/lib/types/host/codex-account-pool.d.ts +2 -8
  11. package/lib/types/host/codex-account-pool.d.ts.map +1 -1
  12. package/lib/types/host/command-code/adapter.d.ts +19 -0
  13. package/lib/types/host/command-code/adapter.d.ts.map +1 -1
  14. package/lib/types/host/command-code/client.d.ts +4 -2
  15. package/lib/types/host/command-code/client.d.ts.map +1 -1
  16. package/lib/types/host/command-code/mapper.d.ts +11 -0
  17. package/lib/types/host/command-code/mapper.d.ts.map +1 -1
  18. package/lib/types/host/command-code/routes.d.ts +2 -0
  19. package/lib/types/host/command-code/routes.d.ts.map +1 -1
  20. package/lib/types/host/command-code/token-store.d.ts +7 -0
  21. package/lib/types/host/command-code/token-store.d.ts.map +1 -1
  22. package/lib/types/host/command-code/types.d.ts +15 -0
  23. package/lib/types/host/command-code/types.d.ts.map +1 -1
  24. package/lib/types/host/common/capabilities.d.ts +100 -0
  25. package/lib/types/host/common/capabilities.d.ts.map +1 -0
  26. package/lib/types/host/common/concurrency-gate.d.ts +46 -0
  27. package/lib/types/host/common/concurrency-gate.d.ts.map +1 -0
  28. package/lib/types/host/common/model-request-control.d.ts +21 -0
  29. package/lib/types/host/common/model-request-control.d.ts.map +1 -0
  30. package/lib/types/host/common/request-diagnostics.d.ts +79 -0
  31. package/lib/types/host/common/request-diagnostics.d.ts.map +1 -0
  32. package/lib/types/host/minimax-code/adapter.d.ts.map +1 -1
  33. package/lib/types/host/minimax-code/mapper.d.ts +32 -1
  34. package/lib/types/host/minimax-code/mapper.d.ts.map +1 -1
  35. package/lib/types/host/minimax-code/model-catalog.d.ts +7 -2
  36. package/lib/types/host/minimax-code/model-catalog.d.ts.map +1 -1
  37. package/lib/types/host/minimax-code/types.d.ts +12 -0
  38. package/lib/types/host/minimax-code/types.d.ts.map +1 -1
  39. package/lib/types/host/ollama/adapter.d.ts +18 -4
  40. package/lib/types/host/ollama/adapter.d.ts.map +1 -1
  41. package/lib/types/host/ollama/client.d.ts +14 -0
  42. package/lib/types/host/ollama/client.d.ts.map +1 -1
  43. package/lib/types/host/ollama/mapper.d.ts +5 -0
  44. package/lib/types/host/ollama/mapper.d.ts.map +1 -1
  45. package/lib/types/host/ollama/types.d.ts +23 -0
  46. package/lib/types/host/ollama/types.d.ts.map +1 -1
  47. package/lib/types/host/responses-client.d.ts +27 -10
  48. package/lib/types/host/responses-client.d.ts.map +1 -1
  49. package/lib/types/host/wire-auth.d.ts +19 -5
  50. package/lib/types/host/wire-auth.d.ts.map +1 -1
  51. package/lib/types/host/workbuddy/mapper.d.ts +1 -0
  52. package/lib/types/host/workbuddy/mapper.d.ts.map +1 -1
  53. package/lib/types/index.d.ts.map +1 -1
  54. package/lib/types/shared/command-code-contracts.d.ts +15 -2
  55. package/lib/types/shared/command-code-contracts.d.ts.map +1 -1
  56. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -5,7 +5,7 @@ import { HarnessError, LlmAdapter, LlmError, LlmError as LlmError$1, ProviderReq
5
5
  import fs, { chmod, lstat, mkdir, open, readFile, rename, stat, unlink } from "node:fs/promises";
6
6
  import path, { basename, dirname, join, relative, resolve } from "node:path";
7
7
  import os, { homedir } from "node:os";
8
- import { createHash, randomBytes, randomUUID } from "node:crypto";
8
+ import { createHash, createHmac, randomBytes, randomUUID } from "node:crypto";
9
9
  import { isDeepStrictEqual } from "node:util";
10
10
  import { execSync, spawn } from "node:child_process";
11
11
  import fs$1, { appendFileSync, constants, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, utimesSync, writeFileSync } from "node:fs";
@@ -612,10 +612,16 @@ async function rehydrateCatalogCache(name, parseModels, accept, currentFetchedAt
612
612
  /**
613
613
  * Turn-state header the Codex backend hands back on a responses response.
614
614
  *
615
- * The official CLI reads it and echoes it on the next request of the same
616
- * conversation; the backend uses it to resume instead of re-ingesting the
617
- * whole history. It is opaque, short-lived and account-scoped, so it is
618
- * threaded through rather than computed (see `responses-client.ts`).
615
+ * The official CLI reads it and echoes it on the SAME TURN's next request — a
616
+ * retry, an incremental append or a continuation — and the backend uses it to
617
+ * resume instead of re-ingesting the whole history.
618
+ *
619
+ * Its scope is exactly one turn. The official client builds a fresh per-turn
620
+ * session and documents that reusing one across turns violates the
621
+ * client/server contract and can cause routing bugs; the token is also
622
+ * account-scoped and is discarded when auth ownership changes. It is opaque,
623
+ * short-lived, and threaded through rather than computed (see
624
+ * `responses-client.ts`).
619
625
  */
620
626
  const CODEX_TURN_STATE_HEADER = "x-codex-turn-state";
621
627
  function codexHeaders(credentials, sessionId, options = {}) {
@@ -630,6 +636,16 @@ function codexHeaders(credentials, sessionId, options = {}) {
630
636
  ...options.turnState ? { [CODEX_TURN_STATE_HEADER]: options.turnState } : {}
631
637
  };
632
638
  }
639
+ /**
640
+ * Opaque local identity of whoever signs a request.
641
+ *
642
+ * Turn state is account-scoped, so the client has to detect when the signer
643
+ * changed without ever putting that fact on the wire. The digest is computed
644
+ * and compared locally: it is not a header, not a credential, and never logged.
645
+ */
646
+ function authOwnerKey(credentials) {
647
+ return createHash("sha256").update(credentials.accountId ?? "").update("\0").update(credentials.accessToken).digest("hex").slice(0, 32);
648
+ }
633
649
  function stableSessionId(value) {
634
650
  const source = value === void 0 || value === "" ? randomUUID() : value;
635
651
  return `dsh-${createHash("sha256").update(source).digest("hex").slice(0, 32)}`;
@@ -775,7 +791,7 @@ function parseListing(payload) {
775
791
  function parseListingEntry$1(value) {
776
792
  const record = asRecord$10(value);
777
793
  if (record === void 0) return [];
778
- const id = asString$14(record.slug) ?? asString$14(record.id);
794
+ const id = asString$15(record.slug) ?? asString$15(record.id);
779
795
  if (id === void 0) return [];
780
796
  const contextWindow = asNumber$4(record.context_window) ?? asNumber$4(record.contextWindow);
781
797
  const modalities = parseModalities(record.input_modalities);
@@ -783,18 +799,18 @@ function parseListingEntry$1(value) {
783
799
  const fallback = CODEX_MODEL_CATALOG.find((entry) => entry.id === id);
784
800
  return [{
785
801
  id,
786
- name: asString$14(record.display_name) ?? asString$14(record.displayName) ?? fallback?.name ?? id,
802
+ name: asString$15(record.display_name) ?? asString$15(record.displayName) ?? fallback?.name ?? id,
787
803
  contextWindow: contextWindow !== void 0 && contextWindow > 0 ? contextWindow : null,
788
804
  inputModalities: modalities ?? (fallback ? [...fallback.inputModalities] : ["text"]),
789
805
  ...efforts !== null ? { reasoningEfforts: efforts } : {},
790
- defaultReasoningEffort: asString$14(record.default_reasoning_level) ?? fallback?.defaultReasoningEffort ?? null
806
+ defaultReasoningEffort: asString$15(record.default_reasoning_level) ?? fallback?.defaultReasoningEffort ?? null
791
807
  }];
792
808
  }
793
809
  function parseModalities(value) {
794
810
  if (!Array.isArray(value)) return null;
795
811
  const out = [];
796
812
  for (const item of value) {
797
- const name = asString$14(item);
813
+ const name = asString$15(item);
798
814
  if (name === "text" || name === "image") out.push(name);
799
815
  }
800
816
  return out.length > 0 ? out : null;
@@ -809,7 +825,7 @@ function parseEfforts(value) {
809
825
  if (!Array.isArray(value)) return null;
810
826
  const out = [];
811
827
  for (const item of value) {
812
- const effort = asString$14(item) ?? asString$14(asRecord$10(item)?.effort);
828
+ const effort = asString$15(item) ?? asString$15(asRecord$10(item)?.effort);
813
829
  if (effort !== void 0 && !out.includes(effort)) out.push(effort);
814
830
  }
815
831
  return out.length > 0 ? out : null;
@@ -827,7 +843,7 @@ function accountKeyFor$1(credentials) {
827
843
  function asRecord$10(value) {
828
844
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
829
845
  }
830
- function asString$14(value) {
846
+ function asString$15(value) {
831
847
  return typeof value === "string" && value !== "" ? value : void 0;
832
848
  }
833
849
  function asNumber$4(value) {
@@ -2072,6 +2088,104 @@ function createPlatformTokenStore(platform = process.platform) {
2072
2088
  throw new Error(`Unsupported platform ${platform}; dsh-chatgpt-subscription supports Windows, macOS, and Linux.`);
2073
2089
  }
2074
2090
  //#endregion
2091
+ //#region src/host/common/concurrency-gate.ts
2092
+ var ConcurrencyGate = class {
2093
+ held = /* @__PURE__ */ new Map();
2094
+ queues = /* @__PURE__ */ new Map();
2095
+ limits = /* @__PURE__ */ new Map();
2096
+ /**
2097
+ * Record the concurrency limit for one account.
2098
+ *
2099
+ * A non-positive or non-finite limit clears the cap, which is the same as
2100
+ * never having set one: requests then pass through unbounded rather than
2101
+ * being blocked by a value nobody verified.
2102
+ */
2103
+ setLimit(accountId, limit) {
2104
+ if (!Number.isFinite(limit) || limit <= 0) {
2105
+ this.limits.delete(accountId);
2106
+ this.drain(accountId);
2107
+ return;
2108
+ }
2109
+ this.limits.set(accountId, Math.max(1, Math.floor(limit)));
2110
+ this.drain(accountId);
2111
+ }
2112
+ /** Limit currently in force for one account, or undefined when uncapped. */
2113
+ limitFor(accountId) {
2114
+ return this.limits.get(accountId);
2115
+ }
2116
+ /** Requests currently in flight for one account. */
2117
+ inFlight(accountId) {
2118
+ return this.held.get(accountId) ?? 0;
2119
+ }
2120
+ /**
2121
+ * Take one slot, waiting for it when the account is at its limit.
2122
+ *
2123
+ * The returned function releases the slot and is safe to call more than once:
2124
+ * a double release on an aborted request must not hand a stranger a slot that
2125
+ * this holder never had.
2126
+ */
2127
+ async acquire(accountId, signal) {
2128
+ if (signal?.aborted === true) throw signal.reason ?? new DOMException("Aborted", "AbortError");
2129
+ const limit = this.limits.get(accountId);
2130
+ const current = this.held.get(accountId) ?? 0;
2131
+ if (limit === void 0 || current < limit) return this.take(accountId);
2132
+ await new Promise((resolve, reject) => {
2133
+ const queue = this.queues.get(accountId) ?? [];
2134
+ const waiter = { resolve: () => {
2135
+ remove();
2136
+ resolve();
2137
+ } };
2138
+ const onAbort = () => {
2139
+ remove();
2140
+ reject(signal?.reason ?? new DOMException("Aborted", "AbortError"));
2141
+ };
2142
+ const remove = () => {
2143
+ const index = queue.indexOf(waiter);
2144
+ if (index !== -1) queue.splice(index, 1);
2145
+ if (queue.length === 0) this.queues.delete(accountId);
2146
+ signal?.removeEventListener("abort", onAbort);
2147
+ };
2148
+ queue.push(waiter);
2149
+ this.queues.set(accountId, queue);
2150
+ signal?.addEventListener("abort", onAbort, { once: true });
2151
+ });
2152
+ const release = this.releaseFn(accountId);
2153
+ if (signal?.aborted) {
2154
+ release();
2155
+ throw signal.reason ?? new DOMException("Aborted", "AbortError");
2156
+ }
2157
+ return release;
2158
+ }
2159
+ take(accountId) {
2160
+ this.held.set(accountId, (this.held.get(accountId) ?? 0) + 1);
2161
+ return this.releaseFn(accountId);
2162
+ }
2163
+ releaseFn(accountId) {
2164
+ let released = false;
2165
+ return () => {
2166
+ if (released) return;
2167
+ released = true;
2168
+ const next = (this.held.get(accountId) ?? 1) - 1;
2169
+ if (next <= 0) this.held.delete(accountId);
2170
+ else this.held.set(accountId, next);
2171
+ this.drain(accountId);
2172
+ };
2173
+ }
2174
+ /** Hand slots to as many waiters as the current limit allows. */
2175
+ drain(accountId) {
2176
+ const queue = this.queues.get(accountId);
2177
+ if (queue === void 0 || queue.length === 0) return;
2178
+ const limit = this.limits.get(accountId);
2179
+ const room = limit === void 0 ? queue.length : limit - (this.held.get(accountId) ?? 0);
2180
+ for (let index = 0; index < room && queue.length > 0; index++) {
2181
+ const waiter = queue.shift();
2182
+ this.held.set(accountId, (this.held.get(accountId) ?? 0) + 1);
2183
+ waiter.resolve();
2184
+ }
2185
+ if (queue.length === 0) this.queues.delete(accountId);
2186
+ }
2187
+ };
2188
+ //#endregion
2075
2189
  //#region src/host/callback-server.ts
2076
2190
  /** One-shot localhost OAuth callback listener. */
2077
2191
  var OAuthCallbackServer = class {
@@ -2750,6 +2864,17 @@ function parseCodexPoolData(value) {
2750
2864
  * hooks the core cannot know — how to refresh one account's tokens and which
2751
2865
  * accounts a cached quota window already rules out.
2752
2866
  */
2867
+ /**
2868
+ * Per-account in-flight requests, shared by every ChatGPT request this host
2869
+ * makes. A fan-out of subagents is what reaches a plan's concurrency bound, so
2870
+ * the bound is enforced here rather than discovered by cooling every account at
2871
+ * once. Limits are learned from upstream evidence (see
2872
+ * {@link CodexAccountPool.noteConcurrencySignal}), never assumed.
2873
+ */
2874
+ const concurrency = new ConcurrencyGate();
2875
+ function chatGPTConcurrency() {
2876
+ return concurrency;
2877
+ }
2753
2878
  var CodexAccountPool = class extends AccountPoolCore {
2754
2879
  refresherRef;
2755
2880
  store;
@@ -3156,13 +3281,13 @@ function createCodexFetchProvider(options = {}) {
3156
3281
  //#region src/host/common/llm-compat.ts
3157
3282
  /** This package's kind in the harness message-source map. */
3158
3283
  const PLUGIN_MESSAGE_SOURCE_KIND = "dsh-chatgpt-subscription";
3159
- function isRecord$23(value) {
3284
+ function isRecord$24(value) {
3160
3285
  return typeof value === "object" && value !== null && !Array.isArray(value);
3161
3286
  }
3162
3287
  /** Read a message body's blocks, keeping every entry that carries a block tag. */
3163
3288
  function readBlocks(raw) {
3164
3289
  if (!Array.isArray(raw)) return [];
3165
- return raw.filter((block) => isRecord$23(block) && typeof block.type === "string");
3290
+ return raw.filter((block) => isRecord$24(block) && typeof block.type === "string");
3166
3291
  }
3167
3292
  /**
3168
3293
  * Normalize one harness message list into {@link Message}.
@@ -3177,10 +3302,10 @@ function readBlocks(raw) {
3177
3302
  function normalizeMessages(messages) {
3178
3303
  const normalized = [];
3179
3304
  for (const raw of messages ?? []) {
3180
- if (!isRecord$23(raw)) continue;
3305
+ if (!isRecord$24(raw)) continue;
3181
3306
  const content = readBlocks(raw.content);
3182
3307
  if (raw.role === "tool") {
3183
- const source = isRecord$23(raw.source) ? raw.source : void 0;
3308
+ const source = isRecord$24(raw.source) ? raw.source : void 0;
3184
3309
  const callId = String(raw.toolCallId ?? source?.callId ?? "");
3185
3310
  normalized.push({
3186
3311
  id: raw.id,
@@ -3202,7 +3327,7 @@ function normalizeMessages(messages) {
3202
3327
  id: raw.id,
3203
3328
  role: typeof raw.role === "string" ? raw.role : "user",
3204
3329
  content,
3205
- ...isRecord$23(raw.source) ? { source: raw.source } : {}
3330
+ ...isRecord$24(raw.source) ? { source: raw.source } : {}
3206
3331
  });
3207
3332
  }
3208
3333
  return normalized;
@@ -3744,6 +3869,666 @@ var ProxyManager = class {
3744
3869
  }
3745
3870
  };
3746
3871
  //#endregion
3872
+ //#region src/host/common/model-request-control.ts
3873
+ const CONTROLLED_PROVIDERS = [
3874
+ "codex-chatgpt",
3875
+ "claude",
3876
+ "antigravity",
3877
+ "kimi-code",
3878
+ "minimax-code",
3879
+ "command-code",
3880
+ "workbuddy",
3881
+ "ollama"
3882
+ ];
3883
+ /** Explicit deployment configuration. Invalid values fail closed at startup. */
3884
+ function readModelRequestLimits(env = process.env) {
3885
+ const raw = env.DSH_PROVIDER_CONCURRENCY;
3886
+ if (!raw) return {};
3887
+ let value;
3888
+ try {
3889
+ value = JSON.parse(raw);
3890
+ } catch {
3891
+ throw new Error("DSH_PROVIDER_CONCURRENCY must be a JSON object");
3892
+ }
3893
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("DSH_PROVIDER_CONCURRENCY must be a JSON object");
3894
+ const input = value;
3895
+ const allowed = /* @__PURE__ */ new Set([
3896
+ "default",
3897
+ "queueTimeoutMs",
3898
+ ...CONTROLLED_PROVIDERS
3899
+ ]);
3900
+ for (const key of Object.keys(input)) if (!allowed.has(key)) throw new Error("Unknown DSH_PROVIDER_CONCURRENCY field");
3901
+ const number = (key, max) => {
3902
+ const n = input[key];
3903
+ if (n === void 0) return void 0;
3904
+ if (typeof n !== "number" || !Number.isSafeInteger(n) || n < 0 || n > max) throw new Error("Invalid DSH_PROVIDER_CONCURRENCY numeric value");
3905
+ return n;
3906
+ };
3907
+ const providers = {};
3908
+ for (const provider of CONTROLLED_PROVIDERS) {
3909
+ const limit = number(provider, 1e4);
3910
+ if (limit !== void 0) providers[provider] = limit;
3911
+ }
3912
+ return {
3913
+ default: number("default", 1e4),
3914
+ queueTimeoutMs: number("queueTimeoutMs", 36e5),
3915
+ providers
3916
+ };
3917
+ }
3918
+ /** Only generation POSTs are limited; token refresh/catalog/quota must never wait behind them. */
3919
+ function isModelRequest(input, init) {
3920
+ if ((init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase() !== "POST") return false;
3921
+ let path;
3922
+ try {
3923
+ path = new URL(input instanceof Request ? input.url : String(input)).pathname;
3924
+ } catch {
3925
+ return false;
3926
+ }
3927
+ return [
3928
+ "/responses",
3929
+ "/messages",
3930
+ "/chat/completions",
3931
+ "/api/chat"
3932
+ ].some((suffix) => path.endsWith(suffix)) || /:(?:streamGenerateContent|generateContent)$/.test(path);
3933
+ }
3934
+ /** Hold a slot until EOF, cancel, abort or failure, never merely until response headers. */
3935
+ function createControlledModelFetch(baseFetch, options) {
3936
+ const gate = options.gate ?? new ConcurrencyGate();
3937
+ const limit = options.limits.providers?.[options.provider] ?? options.limits.default ?? 0;
3938
+ if (!Number.isSafeInteger(limit) || limit < 0) throw new Error("Invalid provider concurrency limit");
3939
+ if (limit === 0) return baseFetch;
3940
+ return async (input, init) => {
3941
+ if (!isModelRequest(input, init)) return baseFetch(input, init);
3942
+ const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : void 0));
3943
+ const credential = headers.get("authorization") ?? headers.get("x-api-key") ?? "";
3944
+ const key = options.provider + ":" + createHash("sha256").update(credential).digest("hex");
3945
+ gate.setLimit(key, limit);
3946
+ const signal = init?.signal ?? (input instanceof Request ? input.signal : void 0);
3947
+ const queued = new AbortController();
3948
+ const abortQueue = () => queued.abort(signal?.reason ?? new DOMException("Aborted", "AbortError"));
3949
+ signal?.addEventListener("abort", abortQueue, { once: true });
3950
+ if (signal?.aborted) abortQueue();
3951
+ const timeout = options.limits.queueTimeoutMs ?? 12e4;
3952
+ const timer = timeout > 0 ? setTimeout(() => queued.abort(new DOMException("Provider concurrency queue timed out", "TimeoutError")), timeout) : void 0;
3953
+ let release;
3954
+ try {
3955
+ release = await gate.acquire(key, queued.signal);
3956
+ } finally {
3957
+ if (timer) clearTimeout(timer);
3958
+ signal?.removeEventListener("abort", abortQueue);
3959
+ if (gate.inFlight(key) === 0) gate.setLimit(key, 0);
3960
+ }
3961
+ let done = false;
3962
+ let reader;
3963
+ let controller;
3964
+ const finish = () => {
3965
+ if (done) return;
3966
+ done = true;
3967
+ signal?.removeEventListener("abort", abort);
3968
+ release();
3969
+ if (gate.inFlight(key) === 0) gate.setLimit(key, 0);
3970
+ };
3971
+ const abort = () => {
3972
+ const reason = signal?.reason ?? new DOMException("Aborted", "AbortError");
3973
+ finish();
3974
+ if (reader) reader.cancel(reason).catch(() => void 0).finally(() => {
3975
+ try {
3976
+ reader?.releaseLock();
3977
+ } catch {}
3978
+ });
3979
+ try {
3980
+ controller?.error(reason);
3981
+ } catch {}
3982
+ };
3983
+ signal?.addEventListener("abort", abort, { once: true });
3984
+ try {
3985
+ if (signal?.aborted) {
3986
+ abort();
3987
+ throw signal.reason ?? new DOMException("Aborted", "AbortError");
3988
+ }
3989
+ const response = await baseFetch(input, init);
3990
+ if (done) {
3991
+ response.body?.cancel().catch(() => void 0);
3992
+ throw signal?.reason ?? new DOMException("Aborted", "AbortError");
3993
+ }
3994
+ if (!response.body) {
3995
+ finish();
3996
+ return response;
3997
+ }
3998
+ reader = response.body.getReader();
3999
+ const body = new ReadableStream({
4000
+ start(c) {
4001
+ controller = c;
4002
+ },
4003
+ async pull(c) {
4004
+ try {
4005
+ const chunk = await reader.read();
4006
+ if (done) return;
4007
+ if (chunk.done) {
4008
+ finish();
4009
+ reader.releaseLock();
4010
+ c.close();
4011
+ } else c.enqueue(chunk.value);
4012
+ } catch (error) {
4013
+ finish();
4014
+ try {
4015
+ reader.releaseLock();
4016
+ } catch {}
4017
+ c.error(error);
4018
+ }
4019
+ },
4020
+ async cancel(reason) {
4021
+ finish();
4022
+ try {
4023
+ await reader.cancel(reason);
4024
+ } finally {
4025
+ reader.releaseLock();
4026
+ }
4027
+ }
4028
+ }, { highWaterMark: 0 });
4029
+ const wrapped = new Response(body, {
4030
+ status: response.status,
4031
+ statusText: response.statusText,
4032
+ headers: response.headers
4033
+ });
4034
+ for (const name of [
4035
+ "url",
4036
+ "redirected",
4037
+ "type"
4038
+ ]) Object.defineProperty(wrapped, name, { value: response[name] });
4039
+ return wrapped;
4040
+ } catch (error) {
4041
+ finish();
4042
+ throw error;
4043
+ }
4044
+ };
4045
+ }
4046
+ //#endregion
4047
+ //#region src/host/common/request-diagnostics.ts
4048
+ /**
4049
+ * Reusable request diagnostics wrapper for model provider calls.
4050
+ *
4051
+ * Implements W5 usage, latency, and transport diagnostics:
4052
+ * - First-byte chunk latency (firstByteMs) and total duration
4053
+ * - Request payload bytes (null when unknown stream, not 0)
4054
+ * - Safe runtime-validated enum-only endpoint and provider classification
4055
+ * - Ephemeral keyed-HMAC fingerprints scoped per provider+endpoint
4056
+ * - Disjoint token usage mapping with missing vs zero differentiation and endpoint-awareness
4057
+ * - Complete SSE line and NDJSON streaming parser merging start/delta usage
4058
+ * - Stream proxy with backpressure preservation (highWaterMark: 0) and early cancel/abort
4059
+ * - Isolated failure boundary (diagnostics sink failures never break generation)
4060
+ *
4061
+ * @module dsh-chatgpt-subscription/request-diagnostics
4062
+ */
4063
+ const DIAGNOSTIC_PROVIDERS = [
4064
+ "codex-chatgpt",
4065
+ "claude-subscription",
4066
+ "antigravity",
4067
+ "kimi-code",
4068
+ "minimax-code",
4069
+ "command-code",
4070
+ "workbuddy-subscription",
4071
+ "ollama",
4072
+ "claude",
4073
+ "workbuddy",
4074
+ "codex"
4075
+ ];
4076
+ const KNOWN_WIRE_ENDPOINTS = [
4077
+ "messages",
4078
+ "responses",
4079
+ "chat_completions",
4080
+ "generate_content",
4081
+ "stream_generate_content",
4082
+ "ollama_chat",
4083
+ "ollama_generate",
4084
+ "workbuddy_chat",
4085
+ "unknown"
4086
+ ];
4087
+ function isValidProvider(provider) {
4088
+ return typeof provider === "string" && DIAGNOSTIC_PROVIDERS.includes(provider);
4089
+ }
4090
+ function isValidEndpoint(endpoint) {
4091
+ return typeof endpoint === "string" && KNOWN_WIRE_ENDPOINTS.includes(endpoint);
4092
+ }
4093
+ const EPHEMERAL_HMAC_KEY = randomBytes(32);
4094
+ function computeHmacPrefix(data) {
4095
+ return createHmac("sha256", EPHEMERAL_HMAC_KEY).update(data).digest("hex").slice(0, 16);
4096
+ }
4097
+ const MAX_SESSION_CACHE_SIZE = 500;
4098
+ const sessionCache = /* @__PURE__ */ new Map();
4099
+ function updateSessionFingerprints(scopedSessionKey, current) {
4100
+ const prev = sessionCache.get(scopedSessionKey);
4101
+ const comparison = {
4102
+ ...current,
4103
+ authChanged: current.authFingerprint !== void 0 && prev?.authFingerprint !== void 0 ? current.authFingerprint !== prev.authFingerprint : void 0,
4104
+ modelChanged: current.modelFingerprint !== void 0 && prev?.modelFingerprint !== void 0 ? current.modelFingerprint !== prev.modelFingerprint : void 0,
4105
+ toolChanged: current.toolFingerprint !== void 0 && prev?.toolFingerprint !== void 0 ? current.toolFingerprint !== prev.toolFingerprint : void 0,
4106
+ prefixChanged: current.prefixFingerprint !== void 0 && prev?.prefixFingerprint !== void 0 ? current.prefixFingerprint !== prev.prefixFingerprint : void 0
4107
+ };
4108
+ if (sessionCache.has(scopedSessionKey)) sessionCache.delete(scopedSessionKey);
4109
+ else if (sessionCache.size >= MAX_SESSION_CACHE_SIZE) {
4110
+ const oldestKey = sessionCache.keys().next().value;
4111
+ if (oldestKey) sessionCache.delete(oldestKey);
4112
+ }
4113
+ sessionCache.set(scopedSessionKey, {
4114
+ authFingerprint: current.authFingerprint ?? prev?.authFingerprint,
4115
+ modelFingerprint: current.modelFingerprint ?? prev?.modelFingerprint,
4116
+ toolFingerprint: current.toolFingerprint ?? prev?.toolFingerprint,
4117
+ prefixFingerprint: current.prefixFingerprint ?? prev?.prefixFingerprint
4118
+ });
4119
+ return comparison;
4120
+ }
4121
+ function isDiagnosticsEnabled(options) {
4122
+ if (options?.enabled !== void 0) return Boolean(options.enabled);
4123
+ return process.env.DSH_PROVIDER_DIAGNOSTICS === "1";
4124
+ }
4125
+ function classifyHttpStatus(statusCode) {
4126
+ if (statusCode === null) return "error";
4127
+ if (statusCode >= 200 && statusCode < 300) return "2xx";
4128
+ if (statusCode >= 300 && statusCode < 400) return "3xx";
4129
+ if (statusCode >= 400 && statusCode < 500) return "4xx";
4130
+ if (statusCode >= 500 && statusCode < 600) return "5xx";
4131
+ return "error";
4132
+ }
4133
+ function isDefaultModelPath(pathname) {
4134
+ const path = pathname.toLowerCase();
4135
+ if (path.endsWith("/messages")) return "messages";
4136
+ if (path.endsWith("/responses")) return "responses";
4137
+ if (path.endsWith("/chat/completions") || path.endsWith("/chatcompletion_v2") || path.endsWith("/conversation") || path.endsWith("/latents/chat")) return "chat_completions";
4138
+ if (path.includes(":streamgeneratecontent")) return "stream_generate_content";
4139
+ if (path.includes(":generatecontent")) return "generate_content";
4140
+ if (path.endsWith("/api/chat")) return "ollama_chat";
4141
+ if (path.endsWith("/api/generate")) return "ollama_generate";
4142
+ return null;
4143
+ }
4144
+ function getUrlAndPathname(input) {
4145
+ let fullUrl = "";
4146
+ if (typeof input === "string") fullUrl = input;
4147
+ else if (input instanceof URL) fullUrl = input.toString();
4148
+ else if (typeof input === "object" && input !== null && "url" in input) fullUrl = String(input.url);
4149
+ try {
4150
+ const parsed = new URL(fullUrl, "http://127.0.0.1");
4151
+ return {
4152
+ fullUrl,
4153
+ pathname: parsed.pathname
4154
+ };
4155
+ } catch {
4156
+ return {
4157
+ fullUrl,
4158
+ pathname: ""
4159
+ };
4160
+ }
4161
+ }
4162
+ function getHeaderValue(headers, name) {
4163
+ if (!headers) return void 0;
4164
+ const target = name.toLowerCase();
4165
+ if (headers instanceof Headers) return headers.get(target) ?? void 0;
4166
+ if (Array.isArray(headers)) {
4167
+ for (const [key, val] of headers) if (key.toLowerCase() === target) return val;
4168
+ return;
4169
+ }
4170
+ if (typeof headers === "object") {
4171
+ for (const [key, val] of Object.entries(headers)) if (key.toLowerCase() === target) return typeof val === "string" ? val : void 0;
4172
+ }
4173
+ }
4174
+ function measureRequestBytes(body, input, init) {
4175
+ if (body === null || body === void 0) {
4176
+ if (input instanceof Request && input.body) {
4177
+ const cl = input.headers.get("content-length");
4178
+ if (cl) {
4179
+ const parsed = parseInt(cl, 10);
4180
+ if (!Number.isNaN(parsed) && parsed >= 0) return parsed;
4181
+ }
4182
+ return null;
4183
+ }
4184
+ const cl = getHeaderValue(init?.headers, "content-length");
4185
+ if (cl) {
4186
+ const parsed = parseInt(cl, 10);
4187
+ if (!Number.isNaN(parsed) && parsed >= 0) return parsed;
4188
+ }
4189
+ return 0;
4190
+ }
4191
+ if (typeof body === "string") return Buffer.byteLength(body, "utf-8");
4192
+ if (body instanceof ArrayBuffer) return body.byteLength;
4193
+ if (ArrayBuffer.isView(body)) return body.byteLength;
4194
+ if (typeof Blob !== "undefined" && body instanceof Blob) return body.size;
4195
+ return null;
4196
+ }
4197
+ const MAX_JSON_PARSE_BYTES = 512 * 1024;
4198
+ function extractBodyFingerprints(body) {
4199
+ if (typeof body !== "string" || body.length > MAX_JSON_PARSE_BYTES) return {};
4200
+ const trimmed = body.trim();
4201
+ if (!trimmed.startsWith("{")) return {};
4202
+ try {
4203
+ const parsed = JSON.parse(trimmed);
4204
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
4205
+ let modelFingerprint;
4206
+ if (typeof parsed.model === "string" && parsed.model) modelFingerprint = computeHmacPrefix(parsed.model);
4207
+ let toolFingerprint;
4208
+ if (Array.isArray(parsed.tools)) toolFingerprint = computeHmacPrefix(JSON.stringify(parsed.tools));
4209
+ let prefixFingerprint;
4210
+ let prefixCandidate = "";
4211
+ if (typeof parsed.instructions === "string" && parsed.instructions) prefixCandidate = parsed.instructions;
4212
+ else if (typeof parsed.system === "string" && parsed.system) prefixCandidate = parsed.system;
4213
+ else if (Array.isArray(parsed.system) && parsed.system.length > 0) prefixCandidate = JSON.stringify(parsed.system);
4214
+ else if (Array.isArray(parsed.messages) && parsed.messages.length > 0) {
4215
+ const first = parsed.messages[0];
4216
+ if (first && typeof first === "object") {
4217
+ const f = first;
4218
+ if (f.role === "system" || f.role === "developer") prefixCandidate = typeof f.content === "string" ? f.content : JSON.stringify(f.content);
4219
+ else prefixCandidate = JSON.stringify(f);
4220
+ }
4221
+ }
4222
+ if (prefixCandidate) prefixFingerprint = computeHmacPrefix(prefixCandidate);
4223
+ return {
4224
+ modelFingerprint,
4225
+ toolFingerprint,
4226
+ prefixFingerprint
4227
+ };
4228
+ } catch {
4229
+ return {};
4230
+ }
4231
+ }
4232
+ function parseFiniteNonNegativeNumber(val) {
4233
+ if (typeof val === "number") {
4234
+ if (Number.isFinite(val) && val >= 0) return Math.floor(val);
4235
+ return;
4236
+ }
4237
+ if (typeof val === "string" && val.trim() !== "") {
4238
+ const num = Number(val);
4239
+ if (Number.isFinite(num) && num >= 0) return Math.floor(num);
4240
+ }
4241
+ }
4242
+ function parseUsageObject(raw, endpoint) {
4243
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
4244
+ const r = raw;
4245
+ const totalInput = parseFiniteNonNegativeNumber(r.prompt_tokens) ?? parseFiniteNonNegativeNumber(r.input_tokens);
4246
+ const outputTokens = parseFiniteNonNegativeNumber(r.completion_tokens) ?? parseFiniteNonNegativeNumber(r.output_tokens);
4247
+ const totalTokens = parseFiniteNonNegativeNumber(r.total_tokens);
4248
+ const inputDetails = typeof r.prompt_tokens_details === "object" && r.prompt_tokens_details !== null ? r.prompt_tokens_details : typeof r.input_tokens_details === "object" && r.input_tokens_details !== null ? r.input_tokens_details : null;
4249
+ const outputDetails = typeof r.completion_tokens_details === "object" && r.completion_tokens_details !== null ? r.completion_tokens_details : typeof r.output_tokens_details === "object" && r.output_tokens_details !== null ? r.output_tokens_details : null;
4250
+ const cacheRead = parseFiniteNonNegativeNumber(inputDetails?.cached_tokens) ?? parseFiniteNonNegativeNumber(r.cache_read_input_tokens) ?? parseFiniteNonNegativeNumber(r.cache_read_tokens);
4251
+ const cacheWrite = parseFiniteNonNegativeNumber(inputDetails?.cache_write_tokens) ?? parseFiniteNonNegativeNumber(r.cache_creation_input_tokens) ?? parseFiniteNonNegativeNumber(r.cache_write_tokens);
4252
+ const reasoning = parseFiniteNonNegativeNumber(outputDetails?.reasoning_tokens) ?? parseFiniteNonNegativeNumber(r.reasoning_tokens);
4253
+ if (totalInput === void 0 && outputTokens === void 0 && totalTokens === void 0 && cacheRead === void 0 && cacheWrite === void 0) return;
4254
+ let disjointInput;
4255
+ if (totalInput !== void 0) if (endpoint === "messages") disjointInput = totalInput;
4256
+ else disjointInput = Math.max(0, totalInput - (cacheRead ?? 0) - (cacheWrite ?? 0));
4257
+ return {
4258
+ ...disjointInput !== void 0 ? { inputTokens: disjointInput } : {},
4259
+ ...outputTokens !== void 0 ? { outputTokens } : {},
4260
+ ...cacheRead !== void 0 ? { cacheReadTokens: cacheRead } : {},
4261
+ ...cacheWrite !== void 0 ? { cacheWriteTokens: cacheWrite } : {},
4262
+ ...reasoning !== void 0 ? { reasoningTokens: reasoning } : {},
4263
+ ...totalTokens !== void 0 ? { totalTokens } : {}
4264
+ };
4265
+ }
4266
+ function mergeUsage(target, source) {
4267
+ if (!target) return source ? { ...source } : void 0;
4268
+ if (!source) return target ? { ...target } : void 0;
4269
+ return {
4270
+ inputTokens: source.inputTokens ?? target.inputTokens,
4271
+ outputTokens: source.outputTokens ?? target.outputTokens,
4272
+ cacheReadTokens: source.cacheReadTokens ?? target.cacheReadTokens,
4273
+ cacheWriteTokens: source.cacheWriteTokens ?? target.cacheWriteTokens,
4274
+ reasoningTokens: source.reasoningTokens ?? target.reasoningTokens,
4275
+ totalTokens: source.totalTokens ?? target.totalTokens
4276
+ };
4277
+ }
4278
+ const MAX_LINE_BUFFER_BYTES = 64 * 1024;
4279
+ var StreamingUsageParser = class {
4280
+ endpoint;
4281
+ lineBuffer = "";
4282
+ accumulatedUsage;
4283
+ constructor(endpoint) {
4284
+ this.endpoint = endpoint;
4285
+ }
4286
+ droppingLine = false;
4287
+ feed(chunkText) {
4288
+ let start = 0;
4289
+ while (start < chunkText.length) {
4290
+ const newline = chunkText.indexOf("\n", start);
4291
+ const end = newline === -1 ? chunkText.length : newline;
4292
+ if (!this.droppingLine) if (this.lineBuffer.length + end - start > MAX_LINE_BUFFER_BYTES) {
4293
+ this.lineBuffer = "";
4294
+ this.droppingLine = true;
4295
+ } else this.lineBuffer += chunkText.slice(start, end);
4296
+ if (newline === -1) break;
4297
+ if (!this.droppingLine) this.processLine(this.lineBuffer.trim());
4298
+ this.lineBuffer = "";
4299
+ this.droppingLine = false;
4300
+ start = newline + 1;
4301
+ }
4302
+ }
4303
+ flush() {
4304
+ const trailing = this.lineBuffer.trim();
4305
+ if (trailing) {
4306
+ this.processLine(trailing);
4307
+ this.lineBuffer = "";
4308
+ }
4309
+ return this.accumulatedUsage;
4310
+ }
4311
+ processLine(line) {
4312
+ let payload = line;
4313
+ if (payload.startsWith("data:")) payload = payload.slice(5).trim();
4314
+ if (!payload || payload === "[DONE]") return;
4315
+ if (!payload.startsWith("{")) return;
4316
+ try {
4317
+ const parsed = JSON.parse(payload);
4318
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return;
4319
+ if (parsed.type === "message_start" && parsed.message && typeof parsed.message === "object") {
4320
+ const msg = parsed.message;
4321
+ if (msg.usage) {
4322
+ const parsedUsage = parseUsageObject(msg.usage, this.endpoint);
4323
+ this.accumulatedUsage = mergeUsage(this.accumulatedUsage, parsedUsage);
4324
+ return;
4325
+ }
4326
+ }
4327
+ if (parsed.type === "message_delta" && parsed.usage) {
4328
+ const parsedUsage = parseUsageObject(parsed.usage, this.endpoint);
4329
+ this.accumulatedUsage = mergeUsage(this.accumulatedUsage, parsedUsage);
4330
+ return;
4331
+ }
4332
+ if (parsed.done === true) {
4333
+ const promptEval = parseFiniteNonNegativeNumber(parsed.prompt_eval_count);
4334
+ const evalCount = parseFiniteNonNegativeNumber(parsed.eval_count);
4335
+ if (promptEval !== void 0 || evalCount !== void 0) {
4336
+ const ollamaUsage = {
4337
+ ...promptEval !== void 0 ? { inputTokens: promptEval } : {},
4338
+ ...evalCount !== void 0 ? { outputTokens: evalCount } : {},
4339
+ ...promptEval !== void 0 && evalCount !== void 0 ? { totalTokens: promptEval + evalCount } : {}
4340
+ };
4341
+ this.accumulatedUsage = mergeUsage(this.accumulatedUsage, ollamaUsage);
4342
+ return;
4343
+ }
4344
+ }
4345
+ const rawUsage = parsed.usage ?? (parsed.response && typeof parsed.response === "object" ? parsed.response.usage : void 0) ?? (parsed.message && typeof parsed.message === "object" ? parsed.message.usage : void 0);
4346
+ if (rawUsage && typeof rawUsage === "object") {
4347
+ const parsedUsage = parseUsageObject(rawUsage, this.endpoint);
4348
+ this.accumulatedUsage = mergeUsage(this.accumulatedUsage, parsedUsage);
4349
+ }
4350
+ } catch {}
4351
+ }
4352
+ };
4353
+ /**
4354
+ * Creates a diagnostic wrapper around fetch for model requests.
4355
+ */
4356
+ function createDiagnosticFetch(baseFetch, options) {
4357
+ return async function diagnosticFetch(input, init) {
4358
+ if (!isDiagnosticsEnabled(options)) return baseFetch(input, init);
4359
+ if ((init?.method ?? (input instanceof Request ? input.method : typeof input === "object" && input !== null && "method" in input ? String(input.method) : "GET")).toUpperCase() !== "POST") return baseFetch(input, init);
4360
+ const { fullUrl, pathname } = getUrlAndPathname(input);
4361
+ const rawEndpoint = options?.endpoint ?? (options?.classifyEndpoint ? options.classifyEndpoint(fullUrl, pathname) : isDefaultModelPath(pathname));
4362
+ if (!rawEndpoint || !isValidEndpoint(rawEndpoint)) return baseFetch(input, init);
4363
+ const endpoint = rawEndpoint;
4364
+ const candidateProvider = options?.provider ?? "codex-chatgpt";
4365
+ if (!isValidProvider(candidateProvider)) return baseFetch(input, init);
4366
+ const provider = candidateProvider;
4367
+ const requestBytes = measureRequestBytes(init?.body, input, init);
4368
+ const startTime = performance.now();
4369
+ const authHeader = getHeaderValue(init?.headers, "authorization") ?? getHeaderValue(init?.headers, "x-api-key") ?? (input instanceof Request ? input.headers.get("authorization") ?? input.headers.get("x-api-key") ?? void 0 : void 0);
4370
+ const authFingerprint = authHeader ? computeHmacPrefix(authHeader) : void 0;
4371
+ const sessionRaw = options?.correlation?.sessionId ?? getHeaderValue(init?.headers, "x-session-id") ?? getHeaderValue(init?.headers, "session_id") ?? (input instanceof Request ? input.headers.get("x-session-id") ?? input.headers.get("session_id") ?? void 0 : void 0);
4372
+ const scopedSessionKey = sessionRaw ? computeHmacPrefix(provider + ":" + endpoint + ":" + sessionRaw) : void 0;
4373
+ let retries;
4374
+ if (typeof options?.correlation?.retryCount === "number") retries = options.correlation.retryCount;
4375
+ else {
4376
+ const retryHeader = getHeaderValue(init?.headers, "x-retry-count") ?? getHeaderValue(init?.headers, "retry-count") ?? (input instanceof Request ? input.headers.get("x-retry-count") ?? input.headers.get("retry-count") ?? void 0 : void 0);
4377
+ if (retryHeader) {
4378
+ const parsed = parseInt(retryHeader, 10);
4379
+ if (!Number.isNaN(parsed)) retries = parsed;
4380
+ }
4381
+ }
4382
+ const currentFingerprints = {
4383
+ authFingerprint,
4384
+ ...extractBodyFingerprints(init?.body ?? (input instanceof Request ? void 0 : void 0))
4385
+ };
4386
+ let fingerprints;
4387
+ if (scopedSessionKey) fingerprints = updateSessionFingerprints(scopedSessionKey, currentFingerprints);
4388
+ else if (currentFingerprints.authFingerprint || currentFingerprints.modelFingerprint || currentFingerprints.toolFingerprint || currentFingerprints.prefixFingerprint) fingerprints = { ...currentFingerprints };
4389
+ const signal = init?.signal ?? (input instanceof Request ? input.signal : void 0);
4390
+ let firstByteMs = null;
4391
+ let emitted = false;
4392
+ const usageParser = new StreamingUsageParser(endpoint);
4393
+ const decoder = new TextDecoder("utf-8", { fatal: false });
4394
+ function emitRecord(outcome, statusCode) {
4395
+ if (emitted) return;
4396
+ emitted = true;
4397
+ const durationMs = Math.max(0, Math.round(performance.now() - startTime));
4398
+ const usage = outcome === "completed" ? usageParser.flush() : void 0;
4399
+ const record = {
4400
+ provider,
4401
+ endpoint,
4402
+ requestBytes,
4403
+ firstByteMs,
4404
+ ttftMs: null,
4405
+ durationMs,
4406
+ statusCode,
4407
+ statusClass: classifyHttpStatus(statusCode),
4408
+ outcome,
4409
+ ...usage !== void 0 ? { usage } : {},
4410
+ ...retries !== void 0 ? { retries } : {},
4411
+ ...fingerprints !== void 0 ? { fingerprints } : {}
4412
+ };
4413
+ try {
4414
+ const res = options?.onRecord?.(record);
4415
+ if (res && typeof res.catch === "function") res.catch(() => {});
4416
+ } catch {}
4417
+ }
4418
+ let response;
4419
+ try {
4420
+ response = await baseFetch(input, init);
4421
+ } catch (error) {
4422
+ emitRecord(Boolean(signal?.aborted) || error instanceof Error && error.name === "AbortError" ? "canceled" : "error", null);
4423
+ throw error;
4424
+ }
4425
+ if (signal?.aborted) {
4426
+ const reason = signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
4427
+ emitRecord("canceled", response.status);
4428
+ if (response.body) try {
4429
+ const r = response.body.getReader();
4430
+ await r.cancel(reason);
4431
+ r.releaseLock();
4432
+ } catch {}
4433
+ throw reason;
4434
+ }
4435
+ if (response.body === null || response.status === 204 || response.status === 304) {
4436
+ emitRecord(response.ok ? "completed" : "error", response.status);
4437
+ return response;
4438
+ }
4439
+ const reader = response.body.getReader();
4440
+ let streamClosed = false;
4441
+ let controllerRef;
4442
+ const cleanup = () => {
4443
+ streamClosed = true;
4444
+ if (signal) try {
4445
+ signal.removeEventListener("abort", abortListener);
4446
+ } catch {}
4447
+ };
4448
+ const abortListener = () => {
4449
+ if (streamClosed) return;
4450
+ cleanup();
4451
+ const reason = signal?.reason ?? new DOMException("The operation was aborted.", "AbortError");
4452
+ emitRecord("canceled", response.status);
4453
+ try {
4454
+ controllerRef?.error(reason);
4455
+ } catch {}
4456
+ reader.cancel(reason).catch(() => {}).finally(() => {
4457
+ try {
4458
+ reader.releaseLock();
4459
+ } catch {}
4460
+ });
4461
+ };
4462
+ if (signal) signal.addEventListener("abort", abortListener, { once: true });
4463
+ const wrappedBody = new ReadableStream({
4464
+ start(controller) {
4465
+ controllerRef = controller;
4466
+ },
4467
+ async pull(controller) {
4468
+ try {
4469
+ const { done, value } = await reader.read();
4470
+ if (done) {
4471
+ cleanup();
4472
+ try {
4473
+ reader.releaseLock();
4474
+ } catch {}
4475
+ emitRecord("completed", response.status);
4476
+ controller.close();
4477
+ return;
4478
+ }
4479
+ if (value && value.byteLength > 0) {
4480
+ if (firstByteMs === null) firstByteMs = Math.max(0, Math.round(performance.now() - startTime));
4481
+ const text = decoder.decode(value, { stream: true });
4482
+ if (text) usageParser.feed(text);
4483
+ }
4484
+ controller.enqueue(value);
4485
+ } catch (err) {
4486
+ cleanup();
4487
+ try {
4488
+ reader.releaseLock();
4489
+ } catch {}
4490
+ emitRecord(Boolean(signal?.aborted) || err instanceof Error && err.name === "AbortError" ? "canceled" : "error", response.status);
4491
+ controller.error(err);
4492
+ }
4493
+ },
4494
+ async cancel(reason) {
4495
+ cleanup();
4496
+ emitRecord("canceled", response.status);
4497
+ try {
4498
+ await reader.cancel(reason);
4499
+ } finally {
4500
+ try {
4501
+ reader.releaseLock();
4502
+ } catch {}
4503
+ }
4504
+ }
4505
+ }, { highWaterMark: 0 });
4506
+ const wrappedResponse = new Response(wrappedBody, {
4507
+ status: response.status,
4508
+ statusText: response.statusText,
4509
+ headers: response.headers
4510
+ });
4511
+ try {
4512
+ Object.defineProperty(wrappedResponse, "url", {
4513
+ value: response.url,
4514
+ writable: false,
4515
+ configurable: true
4516
+ });
4517
+ Object.defineProperty(wrappedResponse, "redirected", {
4518
+ value: response.redirected,
4519
+ writable: false,
4520
+ configurable: true
4521
+ });
4522
+ Object.defineProperty(wrappedResponse, "type", {
4523
+ value: response.type,
4524
+ writable: false,
4525
+ configurable: true
4526
+ });
4527
+ } catch {}
4528
+ return wrappedResponse;
4529
+ };
4530
+ }
4531
+ //#endregion
3747
4532
  //#region src/host/antigravity/types.ts
3748
4533
  const PROVIDER_NAME$6 = "Antigravity";
3749
4534
  const PROVIDER_ID$6 = "antigravity";
@@ -3794,6 +4579,32 @@ const ROUTING = {
3794
4579
  defaultRequestId: "gemini-3.8-flash-tiered",
3795
4580
  fallbackCandidates: ["gemini-3.7-flash-tiered", "gemini-3.6-flash-low"]
3796
4581
  },
4582
+ "claude-opus-5-5": {
4583
+ off: "claude-opus-5-5-low",
4584
+ routing: {
4585
+ off: "claude-opus-5-5-low",
4586
+ none: "claude-opus-5-5-low",
4587
+ minimal: "claude-opus-5-5-low",
4588
+ low: "claude-opus-5-5-low",
4589
+ medium: "claude-opus-5-5-medium",
4590
+ high: "claude-opus-5-5-high",
4591
+ xhigh: "claude-opus-5-5-high"
4592
+ },
4593
+ defaultRequestId: "claude-opus-5-5-medium"
4594
+ },
4595
+ "claude-sonnet-5-5": {
4596
+ off: "claude-sonnet-5-5-low",
4597
+ routing: {
4598
+ off: "claude-sonnet-5-5-low",
4599
+ none: "claude-sonnet-5-5-low",
4600
+ minimal: "claude-sonnet-5-5-low",
4601
+ low: "claude-sonnet-5-5-low",
4602
+ medium: "claude-sonnet-5-5-medium",
4603
+ high: "claude-sonnet-5-5-high",
4604
+ xhigh: "claude-sonnet-5-5-high"
4605
+ },
4606
+ defaultRequestId: "claude-sonnet-5-5-medium"
4607
+ },
3797
4608
  "claude-opus-4-6": {
3798
4609
  off: "claude-opus-4-6-thinking",
3799
4610
  routing: {
@@ -3938,6 +4749,14 @@ const RUNTIME_MAX_OUTPUT_TOKENS = {
3938
4749
  "gemini-3.1-pro-low": 65535,
3939
4750
  "gemini-3.1-pro-high": 65535,
3940
4751
  "gemini-pro-agent": 65535,
4752
+ "claude-opus-5-5": 64e3,
4753
+ "claude-opus-5-5-low": 64e3,
4754
+ "claude-opus-5-5-medium": 64e3,
4755
+ "claude-opus-5-5-high": 64e3,
4756
+ "claude-sonnet-5-5": 64e3,
4757
+ "claude-sonnet-5-5-low": 64e3,
4758
+ "claude-sonnet-5-5-medium": 64e3,
4759
+ "claude-sonnet-5-5-high": 64e3,
3941
4760
  "claude-opus-4-6": 64e3,
3942
4761
  "claude-opus-4-6-thinking": 64e3,
3943
4762
  "claude-sonnet-4-6": 64e3,
@@ -4029,6 +4848,30 @@ const MODELS = [
4029
4848
  contextWindow: 1048576,
4030
4849
  maxTokens: 65536
4031
4850
  },
4851
+ {
4852
+ id: "claude-opus-5-5",
4853
+ name: "Claude Opus 5.5",
4854
+ inputModalities: ["text", "image"],
4855
+ contextWindow: 1048576,
4856
+ maxTokens: 64e3,
4857
+ reasoningEfforts: [
4858
+ "low",
4859
+ "medium",
4860
+ "high"
4861
+ ]
4862
+ },
4863
+ {
4864
+ id: "claude-sonnet-5-5",
4865
+ name: "Claude Sonnet 5.5",
4866
+ inputModalities: ["text", "image"],
4867
+ contextWindow: 1048576,
4868
+ maxTokens: 64e3,
4869
+ reasoningEfforts: [
4870
+ "low",
4871
+ "medium",
4872
+ "high"
4873
+ ]
4874
+ },
4032
4875
  {
4033
4876
  id: "claude-opus-4-6",
4034
4877
  name: "Claude Opus 4.6",
@@ -5265,7 +6108,7 @@ const MAX_VISIBLE_REASONING_CHARS = 12e3;
5265
6108
  const DEFAULT_POOL_COOLDOWN_MS = 15 * 6e4;
5266
6109
  const REASONING_DELTA_FLUSH_CHARS = 768;
5267
6110
  const REASONING_TRUNCATED_NOTICE = "\n\n[Reasoning summary truncated to keep the DSH web UI responsive.]";
5268
- /** Cap on conversations whose turn state is remembered at once. */
6111
+ /** Cap on live turns whose routing state is remembered at once. */
5269
6112
  const MAX_TRACKED_TURN_STATES = 200;
5270
6113
  var ResponsesClient = class {
5271
6114
  oauth;
@@ -5277,13 +6120,20 @@ var ResponsesClient = class {
5277
6120
  reasoningSummary;
5278
6121
  accountPool;
5279
6122
  /**
5280
- * Opaque backend turn state, keyed by conversation session id.
6123
+ * Opaque backend routing state, keyed by a unique model-request nonce.
6124
+ * The host does not expose a reliable human-turn identity, so this is the
6125
+ * conservative no-cross-request-reuse fallback, not full turn continuity.
5281
6126
  *
5282
6127
  * The Codex backend hands `x-codex-turn-state` back on each response and
5283
- * expects it on the next request of the same conversation, which lets it
5284
- * resume the turn instead of re-ingesting the whole history. The value is
5285
- * short-lived and account-scoped, so it is stored per session and simply
5286
- * dropped when the backend stops sending it — never a correctness input.
6128
+ * expects it on the next request of the SAME turn, which lets it resume the
6129
+ * turn instead of re-ingesting the whole history.
6130
+ *
6131
+ * It is a client/server contract with a one-turn scope: the official client
6132
+ * creates a fresh per-turn session and warns that replaying the token across
6133
+ * turns breaks routing. The value is also account-scoped, so each entry
6134
+ * records the auth owner that minted it and is discarded when the signer
6135
+ * changes. It is only ever an optimization, so a miss costs a full resend and
6136
+ * never correctness.
5287
6137
  */
5288
6138
  turnStates = /* @__PURE__ */ new Map();
5289
6139
  constructor(oauth, attachments, options = {}) {
@@ -5302,13 +6152,17 @@ var ResponsesClient = class {
5302
6152
  const options = normalizeGenerateOptions(rawOptions);
5303
6153
  const hiddenSandboxControls = hiddenSandboxControlToolNames(options);
5304
6154
  const sessionId = stableSessionId(options.sessionId);
6155
+ const turnKey = turnKeyFor(sessionId);
5305
6156
  let currentModel = options.model;
5306
6157
  let attemptOptions = options;
5307
6158
  let response;
6159
+ let release;
5308
6160
  while (true) {
5309
6161
  const payload = await buildResponsesPayload(attemptOptions, this.attachments, this.localRawImages, this.outputVerbosity(), this.fastMode(), this.reasoningSummary(), promptCacheKeyFor(sessionId));
5310
6162
  try {
5311
- response = await this.send(payload, sessionId, options.signal);
6163
+ const opened = await this.send(payload, sessionId, turnKey, options.signal);
6164
+ response = opened.response;
6165
+ release = opened.release;
5312
6166
  break;
5313
6167
  } catch (error) {
5314
6168
  if (error instanceof LlmError && (error.code === "NOT_FOUND" || error.status === 404)) {
@@ -5328,42 +6182,47 @@ var ResponsesClient = class {
5328
6182
  try {
5329
6183
  yield* wrapStreamWithWatchdog((watchdogSignal) => parseResponsesStream(response, watchdogSignal, hiddenSandboxControls), options.signal, 3e5, "LLM_STREAM_IDLE_TIMEOUT", "Codex");
5330
6184
  } finally {
6185
+ this.turnStates.delete(turnKey);
6186
+ release?.();
5331
6187
  this.onGenerationFinished();
5332
6188
  }
5333
6189
  }
5334
- async send(payload, sessionId, signal) {
5335
- if (this.accountPool !== null) return this.sendWithPool(payload, sessionId, signal);
6190
+ async send(payload, sessionId, turnKey, signal) {
6191
+ if (this.accountPool !== null) return this.sendWithPool(payload, sessionId, turnKey, signal);
5336
6192
  let credentials = await this.oauth.credentials();
5337
- let response = await this.request(payload, credentials, sessionId, signal);
6193
+ let response = await this.request(payload, credentials, sessionId, turnKey, signal);
5338
6194
  if (response.status === 401) {
5339
6195
  await response.body?.cancel().catch(() => void 0);
5340
6196
  credentials = await this.oauth.credentials(true);
5341
- response = await this.request(payload, credentials, sessionId, signal);
6197
+ response = await this.request(payload, credentials, sessionId, turnKey, signal);
5342
6198
  }
5343
6199
  if (!response.ok) throw await responseError(response);
5344
- this.rememberTurnState(response, sessionId);
5345
- return response;
6200
+ this.rememberTurnState(response, turnKey, credentials);
6201
+ return { response };
5346
6202
  }
5347
6203
  /**
5348
- * Record the turn state a response carried, for the next turn of this session.
6204
+ * Record the routing state a response carried, for this turn's next request.
5349
6205
  *
5350
6206
  * A response that carries none clears any stored value: a backend that stops
5351
6207
  * sending the header has stopped honouring it, and replaying a stale value
5352
6208
  * would be guessing. An empty header is treated the same as absent.
5353
6209
  */
5354
- rememberTurnState(response, sessionId) {
6210
+ rememberTurnState(response, turnKey, credentials) {
5355
6211
  const next = response.headers.get(CODEX_TURN_STATE_HEADER)?.trim();
5356
- if (next === void 0 || next === "") this.turnStates.delete(sessionId);
5357
- else this.storeTurnState(sessionId, next);
6212
+ if (next === void 0 || next === "") this.turnStates.delete(turnKey);
6213
+ else this.storeTurnState(turnKey, next, credentials);
5358
6214
  }
5359
6215
  /**
5360
- * Bounded store: a long-lived host serves many sessions, and nothing here ever
5361
- * signals that a session ended, so the map is capped and evicts the oldest
5362
- * entry. The value is an optimization, so a miss only costs a full resend.
6216
+ * Bounded store: a long-lived host serves many sessions and turns, and nothing
6217
+ * here ever signals that either ended, so the map is capped and evicts the
6218
+ * oldest entry. The value is an optimization, so a miss only costs a resend.
5363
6219
  */
5364
- storeTurnState(sessionId, value) {
5365
- this.turnStates.delete(sessionId);
5366
- this.turnStates.set(sessionId, value);
6220
+ storeTurnState(turnKey, value, credentials) {
6221
+ this.turnStates.delete(turnKey);
6222
+ this.turnStates.set(turnKey, {
6223
+ value,
6224
+ owner: authOwnerKey(credentials)
6225
+ });
5367
6226
  while (this.turnStates.size > MAX_TRACKED_TURN_STATES) {
5368
6227
  const oldest = this.turnStates.keys().next();
5369
6228
  if (oldest.done === true) break;
@@ -5371,6 +6230,23 @@ var ResponsesClient = class {
5371
6230
  }
5372
6231
  }
5373
6232
  /**
6233
+ * Routing state this turn may replay, or undefined.
6234
+ *
6235
+ * A token minted by a different auth owner is dropped rather than sent: the
6236
+ * value is account-scoped, and sending it to another account is at best a
6237
+ * routing hint for the wrong machine and at worst a replay of state that
6238
+ * account never issued. Dropping it only costs a full resend.
6239
+ */
6240
+ turnStateFor(turnKey, credentials) {
6241
+ const entry = this.turnStates.get(turnKey);
6242
+ if (entry === void 0) return void 0;
6243
+ if (entry.owner !== authOwnerKey(credentials)) {
6244
+ this.turnStates.delete(turnKey);
6245
+ return;
6246
+ }
6247
+ return entry.value;
6248
+ }
6249
+ /**
5374
6250
  * Send one payload with account rotation.
5375
6251
  *
5376
6252
  * The pool picks the account — skipping cooling, quota-exhausted and failed
@@ -5380,48 +6256,72 @@ var ResponsesClient = class {
5380
6256
  * refresh is rejected leaves the rotation instead of invalidating the others.
5381
6257
  * The payload is account-independent, so it is never rebuilt between attempts.
5382
6258
  */
5383
- async sendWithPool(payload, sessionId, signal) {
6259
+ /** What one pool attempt produced, including the account that served it. */
6260
+ async sendWithPool(payload, sessionId, turnKey, signal) {
5384
6261
  const pool = this.accountPool;
5385
6262
  const tried = /* @__PURE__ */ new Set();
5386
6263
  while (true) {
5387
- const { account, credentials } = await pool.getEffectiveAccount(tried, this.fetchFn);
6264
+ const selected = await pool.getEffectiveAccount(tried, this.fetchFn);
6265
+ const account = selected.account;
6266
+ let credentials = selected.credentials;
5388
6267
  tried.add(account.id);
5389
- let response = await this.request(payload, credentials, sessionId, signal);
5390
- if (response.status === 401) {
5391
- await response.body?.cancel().catch(() => void 0);
5392
- try {
5393
- const refreshed = await pool.refreshAccountNow(account.id);
5394
- response = await this.request(payload, refreshed, sessionId, signal);
5395
- } catch (error) {
5396
- await pool.markAuthFailed(account.id, error instanceof Error ? error.message : "ChatGPT sign-in expired.").catch(() => void 0);
6268
+ const release = await chatGPTConcurrency().acquire(account.id, signal);
6269
+ let held = true;
6270
+ const free = () => {
6271
+ if (!held) return;
6272
+ held = false;
6273
+ release();
6274
+ };
6275
+ try {
6276
+ let response = await this.request(payload, credentials, sessionId, turnKey, signal);
6277
+ if (response.status === 401) {
6278
+ await response.body?.cancel().catch(() => void 0);
6279
+ try {
6280
+ credentials = await pool.refreshAccountNow(account.id);
6281
+ response = await this.request(payload, credentials, sessionId, turnKey, signal);
6282
+ } catch (error) {
6283
+ await pool.markAuthFailed(account.id, error instanceof Error ? error.message : "ChatGPT sign-in expired.").catch(() => void 0);
6284
+ free();
6285
+ if (await pool.hasAnotherAvailableAccount(tried)) continue;
6286
+ throw new LlmError("ChatGPT sign-in has expired. Sign in again.", "AUTH", {
6287
+ status: 401,
6288
+ cause: error
6289
+ });
6290
+ }
6291
+ }
6292
+ if (response.status === 429) {
6293
+ const after = retryAfterMs$1(response.headers);
6294
+ await response.body?.cancel().catch(() => void 0);
6295
+ await pool.markCooldown(account.id, after ?? DEFAULT_POOL_COOLDOWN_MS, "Codex 429").catch(() => void 0);
6296
+ free();
5397
6297
  if (await pool.hasAnotherAvailableAccount(tried)) continue;
5398
- throw new LlmError("ChatGPT sign-in has expired. Sign in again.", "AUTH", {
5399
- status: 401,
5400
- cause: error
6298
+ throw new LlmError("Codex rate limit reached.", "RATE_LIMIT", {
6299
+ status: 429,
6300
+ ...after === void 0 ? {} : { providerRetryAfterMs: after }
5401
6301
  });
5402
6302
  }
6303
+ if (!response.ok) {
6304
+ const error = await responseError(response);
6305
+ free();
6306
+ throw error;
6307
+ }
6308
+ this.rememberTurnState(response, turnKey, credentials);
6309
+ return {
6310
+ response,
6311
+ release: free
6312
+ };
6313
+ } catch (error) {
6314
+ free();
6315
+ throw error;
5403
6316
  }
5404
- if (response.status === 429) {
5405
- const after = retryAfterMs$1(response.headers);
5406
- await response.body?.cancel().catch(() => void 0);
5407
- await pool.markCooldown(account.id, after ?? DEFAULT_POOL_COOLDOWN_MS, "Codex 429").catch(() => void 0);
5408
- if (await pool.hasAnotherAvailableAccount(tried)) continue;
5409
- throw new LlmError("Codex rate limit reached.", "RATE_LIMIT", {
5410
- status: 429,
5411
- ...after === void 0 ? {} : { providerRetryAfterMs: after }
5412
- });
5413
- }
5414
- if (!response.ok) throw await responseError(response);
5415
- this.rememberTurnState(response, sessionId);
5416
- return response;
5417
6317
  }
5418
6318
  }
5419
- async request(payload, credentials, sessionId, signal) {
6319
+ async request(payload, credentials, sessionId, turnKey, signal) {
5420
6320
  try {
5421
6321
  return await this.fetchFn(CODEX_RESPONSES_URL, {
5422
6322
  method: "POST",
5423
6323
  headers: {
5424
- ...codexHeaders(credentials, sessionId, { turnState: this.turnStates.get(sessionId) }),
6324
+ ...codexHeaders(credentials, sessionId, { turnState: this.turnStateFor(turnKey, credentials) }),
5425
6325
  "content-type": "application/json",
5426
6326
  accept: "text/event-stream"
5427
6327
  },
@@ -5445,6 +6345,16 @@ var ResponsesClient = class {
5445
6345
  function promptCacheKeyFor(sessionId) {
5446
6346
  return sessionId;
5447
6347
  }
6348
+ /** Request-local fallback; never infer a human turn identity from message history. */
6349
+ function turnKeyFor(sessionId) {
6350
+ return `${sessionId}#${createHash("sha256").update(sessionId).update("\0").update(String(perRequestNonce())).digest("hex").slice(0, 24)}`;
6351
+ }
6352
+ /** A value no two requests in this process share. */
6353
+ let requestCounter = 0;
6354
+ function perRequestNonce() {
6355
+ requestCounter += 1;
6356
+ return requestCounter;
6357
+ }
5448
6358
  async function* parseResponsesStream(response, signal, hiddenSandboxControls = /* @__PURE__ */ new Set()) {
5449
6359
  if (response.body === null) throw new LlmError("Codex returned no response stream.", "PROVIDER_ERROR");
5450
6360
  const reader = response.body.getReader();
@@ -5750,16 +6660,28 @@ function visibleReasoningDelta(delta, currentVisibleChars, alreadyTruncated) {
5750
6660
  truncated: true
5751
6661
  };
5752
6662
  }
6663
+ /**
6664
+ * Map one response's usage onto DSH's disjoint accounting.
6665
+ *
6666
+ * `input_tokens` is the whole input, so cached input has to be subtracted out of
6667
+ * it and reported on its own field. Cache WRITES are counted separately from
6668
+ * reads and billed separately: leaving a write inside `inputTokens` would make a
6669
+ * cold prefix look cheaper than it was and hide the cost of a write. A field the
6670
+ * service omits is unknown, not zero, so it is simply not reported.
6671
+ */
5753
6672
  function mapUsage(value) {
5754
6673
  if (value === null) return null;
5755
6674
  const totalInput = number(value.input_tokens) ?? 0;
5756
6675
  const outputTokens = number(value.output_tokens) ?? 0;
5757
- const cached = number(record$2(value.input_tokens_details)?.cached_tokens) ?? 0;
6676
+ const details = record$2(value.input_tokens_details);
6677
+ const cached = number(details?.cached_tokens);
6678
+ const written = number(details?.cache_write_tokens);
5758
6679
  const reasoning = number(record$2(value.output_tokens_details)?.reasoning_tokens);
5759
6680
  return {
5760
- inputTokens: Math.max(0, totalInput - cached),
6681
+ inputTokens: Math.max(0, totalInput - (cached ?? 0) - (written ?? 0)),
5761
6682
  outputTokens,
5762
- ...cached > 0 ? { cacheReadTokens: cached } : {},
6683
+ ...cached === void 0 ? {} : { cacheReadTokens: cached },
6684
+ ...written === void 0 ? {} : { cacheWriteTokens: written },
5763
6685
  ...reasoning === void 0 ? {} : { reasoningTokens: reasoning }
5764
6686
  };
5765
6687
  }
@@ -6661,7 +7583,7 @@ function field(value, name) {
6661
7583
  const candidate = value[name];
6662
7584
  return typeof candidate === "string" && candidate !== "" ? candidate : null;
6663
7585
  }
6664
- function isRecord$22(value) {
7586
+ function isRecord$23(value) {
6665
7587
  return typeof value === "object" && value !== null && !Array.isArray(value);
6666
7588
  }
6667
7589
  function readPreferencesUpdate(value, current) {
@@ -6695,7 +7617,7 @@ function readPreferencesUpdate(value, current) {
6695
7617
  patch.searchProvider = value.searchProvider;
6696
7618
  }
6697
7619
  if ("contextWindowOverrides" in value) {
6698
- if (!isRecord$22(value.contextWindowOverrides)) throw new PreferenceError("contextWindowOverrides must be an object.");
7620
+ if (!isRecord$23(value.contextWindowOverrides)) throw new PreferenceError("contextWindowOverrides must be an object.");
6699
7621
  const overrides = {};
6700
7622
  for (const [model, contextWindow] of Object.entries(value.contextWindowOverrides)) {
6701
7623
  if (!isCodexModelId(model)) throw new PreferenceError("Unknown Codex model for a context window override.");
@@ -7567,7 +8489,7 @@ const schemaFields = /* @__PURE__ */ new Set([
7567
8489
  "required",
7568
8490
  "propertyOrdering"
7569
8491
  ]);
7570
- function isRecord$21(value) {
8492
+ function isRecord$22(value) {
7571
8493
  return typeof value === "object" && value !== null && !Array.isArray(value);
7572
8494
  }
7573
8495
  function mergeSchemas(left, right) {
@@ -7575,7 +8497,7 @@ function mergeSchemas(left, right) {
7575
8497
  ...left,
7576
8498
  ...right
7577
8499
  };
7578
- if (isRecord$21(left.properties) && isRecord$21(right.properties)) merged.properties = {
8500
+ if (isRecord$22(left.properties) && isRecord$22(right.properties)) merged.properties = {
7579
8501
  ...left.properties,
7580
8502
  ...right.properties
7581
8503
  };
@@ -7583,7 +8505,7 @@ function mergeSchemas(left, right) {
7583
8505
  return merged;
7584
8506
  }
7585
8507
  function toAntigravityToolSchema(schema) {
7586
- if (!isRecord$21(schema)) return schema;
8508
+ if (!isRecord$22(schema)) return schema;
7587
8509
  const root = schema;
7588
8510
  function resolveReference(reference) {
7589
8511
  let target = root;
@@ -7591,13 +8513,13 @@ function toAntigravityToolSchema(schema) {
7591
8513
  const path = reference === "#" ? [] : reference.slice(2).split("/");
7592
8514
  for (const segment of path) {
7593
8515
  const key = segment.replace(/~1/g, "/").replace(/~0/g, "~");
7594
- target = isRecord$21(target) && Object.hasOwn(target, key) ? target[key] : void 0;
8516
+ target = isRecord$22(target) && Object.hasOwn(target, key) ? target[key] : void 0;
7595
8517
  }
7596
- if (!isRecord$21(target)) throw new LlmError(`Antigravity tool schema reference was not found: ${reference}`, "PROVIDER_ERROR");
8518
+ if (!isRecord$22(target)) throw new LlmError(`Antigravity tool schema reference was not found: ${reference}`, "PROVIDER_ERROR");
7597
8519
  return target;
7598
8520
  }
7599
8521
  function convert(node, references) {
7600
- if (!isRecord$21(node)) return {};
8522
+ if (!isRecord$22(node)) return {};
7601
8523
  let inherited = {};
7602
8524
  if (typeof node.$ref === "string") {
7603
8525
  if (references.has(node.$ref)) throw new LlmError(`Antigravity tool schema contains a recursive reference: ${node.$ref}`, "PROVIDER_ERROR");
@@ -7606,8 +8528,8 @@ function toAntigravityToolSchema(schema) {
7606
8528
  if (Array.isArray(node.allOf)) for (const branch of node.allOf) inherited = mergeSchemas(inherited, convert(branch, references));
7607
8529
  const out = {};
7608
8530
  for (const [key, value] of Object.entries(node)) if (schemaFields.has(key)) out[key] = value;
7609
- if (isRecord$21(node.properties)) out.properties = Object.fromEntries(Object.entries(node.properties).map(([name, child]) => [name, convert(child, references)]));
7610
- if (isRecord$21(node.items)) out.items = convert(node.items, references);
8531
+ if (isRecord$22(node.properties)) out.properties = Object.fromEntries(Object.entries(node.properties).map(([name, child]) => [name, convert(child, references)]));
8532
+ if (isRecord$22(node.items)) out.items = convert(node.items, references);
7611
8533
  const alternatives = Array.isArray(node.anyOf) ? node.anyOf : node.oneOf;
7612
8534
  if (Array.isArray(alternatives)) out.anyOf = alternatives.map((child) => convert(child, references));
7613
8535
  if (Array.isArray(node.type)) {
@@ -7635,10 +8557,10 @@ let toolCallCounter = 0;
7635
8557
  function sanitizeText$5(text) {
7636
8558
  return text.replace(/\0/g, "");
7637
8559
  }
7638
- function isRecord$20(value) {
8560
+ function isRecord$21(value) {
7639
8561
  return typeof value === "object" && value !== null && !Array.isArray(value);
7640
8562
  }
7641
- function asString$13(value) {
8563
+ function asString$14(value) {
7642
8564
  return typeof value === "string" ? value : void 0;
7643
8565
  }
7644
8566
  function safeJsonParse$5(text) {
@@ -7654,26 +8576,26 @@ function sanitizeToolCallId(id, fallbackName) {
7654
8576
  function toolCallIdNeeded(modelId, runtimeModel) {
7655
8577
  return modelId.startsWith("claude-") || modelId.startsWith("gpt-oss-") || runtimeModel.startsWith("claude-") || runtimeModel.startsWith("gpt-oss-");
7656
8578
  }
7657
- function parseArguments(raw) {
7658
- if (isRecord$20(raw)) return raw;
8579
+ function parseArguments$1(raw) {
8580
+ if (isRecord$21(raw)) return raw;
7659
8581
  if (raw === void 0 || raw === null || raw === "") return {};
7660
8582
  const parsed = typeof raw === "string" ? safeJsonParse$5(raw) : raw;
7661
- return isRecord$20(parsed) ? parsed : {};
8583
+ return isRecord$21(parsed) ? parsed : {};
7662
8584
  }
7663
8585
  const NO_RESOLVED_IMAGES$5 = /* @__PURE__ */ new Map();
7664
8586
  function attachmentOf$5(block) {
7665
8587
  const attachment = block.attachment;
7666
- if (!isRecord$20(attachment)) return void 0;
8588
+ if (!isRecord$21(attachment)) return void 0;
7667
8589
  return typeof attachment.attachmentId === "string" ? attachment : void 0;
7668
8590
  }
7669
- function attachmentLabel$5(block) {
7670
- const attachment = isRecord$20(block.attachment) ? block.attachment : void 0;
7671
- return asString$13(attachment?.name) || asString$13(attachment?.attachmentId);
8591
+ function attachmentLabel$6(block) {
8592
+ const attachment = isRecord$21(block.attachment) ? block.attachment : void 0;
8593
+ return asString$14(attachment?.name) || asString$14(attachment?.attachmentId);
7672
8594
  }
7673
8595
  function collectImageRefs$4(content, refs) {
7674
8596
  if (!Array.isArray(content)) return;
7675
8597
  for (const block of content) {
7676
- if (!isRecord$20(block)) continue;
8598
+ if (!isRecord$21(block)) continue;
7677
8599
  if (block.type === "image") {
7678
8600
  const attachment = attachmentOf$5(block);
7679
8601
  if (attachment) refs.set(attachment.attachmentId, attachment);
@@ -7682,7 +8604,7 @@ function collectImageRefs$4(content, refs) {
7682
8604
  if (block.type === "tool-result") collectImageRefs$4(block.content, refs);
7683
8605
  }
7684
8606
  }
7685
- function isAbort$3(error, signal) {
8607
+ function isAbort$4(error, signal) {
7686
8608
  return signal?.aborted === true || error instanceof Error && error.name === "AbortError";
7687
8609
  }
7688
8610
  /**
@@ -7711,13 +8633,13 @@ function base64Length$4(bytes) {
7711
8633
  function requestImageBytes$4(block) {
7712
8634
  const attachment = attachmentOf$5(block);
7713
8635
  if (attachment) return base64Length$4(attachment.bytes);
7714
- const inline = asString$13(block.data) || asString$13(block.base64);
8636
+ const inline = asString$14(block.data) || asString$14(block.base64);
7715
8637
  return inline ? inline.length : void 0;
7716
8638
  }
7717
8639
  function collectRequestImageBytes$4(content, lengths) {
7718
8640
  if (!Array.isArray(content)) return;
7719
8641
  for (const block of content) {
7720
- if (!isRecord$20(block) || block.type !== "image") continue;
8642
+ if (!isRecord$21(block) || block.type !== "image") continue;
7721
8643
  const bytes = requestImageBytes$4(block);
7722
8644
  if (bytes !== void 0) lengths.push(bytes);
7723
8645
  }
@@ -7752,7 +8674,7 @@ function offloadOldestRequestImages$4(options) {
7752
8674
  if (remaining.count === 0 || !Array.isArray(message.content)) return message;
7753
8675
  let replaced = false;
7754
8676
  const content = message.content.map((block) => {
7755
- if (remaining.count === 0 || !isRecord$20(block) || block.type !== "image") return block;
8677
+ if (remaining.count === 0 || !isRecord$21(block) || block.type !== "image") return block;
7756
8678
  if (requestImageBytes$4(block) === void 0) return block;
7757
8679
  remaining.count -= 1;
7758
8680
  replaced = true;
@@ -7804,21 +8726,21 @@ async function resolveRequestImages$4(options, attachments, signal) {
7804
8726
  data: Buffer.from(stored.data).toString("base64")
7805
8727
  });
7806
8728
  } catch (error) {
7807
- if (isAbort$3(error, signal)) throw error;
8729
+ if (isAbort$4(error, signal)) throw error;
7808
8730
  resolved.set(attachmentId, { kind: "unavailable" });
7809
8731
  }
7810
8732
  }));
7811
8733
  return resolved;
7812
8734
  }
7813
- function unavailableImageText$5(block) {
7814
- const label = attachmentLabel$5(block);
8735
+ function unavailableImageText$6(block) {
8736
+ const label = attachmentLabel$6(block);
7815
8737
  return `[image unavailable: ${label ? `${label} could not be read` : "the image could not be read"}; ask the user to attach it again if the image is needed]`;
7816
8738
  }
7817
8739
  function imageBlockToPart(block, images) {
7818
- let data = asString$13(block.data) || asString$13(block.base64);
7819
- const source = isRecord$20(block.source) ? block.source : void 0;
7820
- if (!data && source) data = asString$13(source.data) || asString$13(source.base64);
7821
- let mimeType = asString$13(block.mimeType) || asString$13(block.mediaType) || (source ? asString$13(source.mimeType) || asString$13(source.mediaType) : void 0) || "image/png";
8740
+ let data = asString$14(block.data) || asString$14(block.base64);
8741
+ const source = isRecord$21(block.source) ? block.source : void 0;
8742
+ if (!data && source) data = asString$14(source.data) || asString$14(source.base64);
8743
+ let mimeType = asString$14(block.mimeType) || asString$14(block.mediaType) || (source ? asString$14(source.mimeType) || asString$14(source.mediaType) : void 0) || "image/png";
7822
8744
  if (data?.startsWith("data:")) {
7823
8745
  const match = data.match(/^data:([^;,]+);base64,(.*)$/s);
7824
8746
  if (match) {
@@ -7841,10 +8763,10 @@ function contentToUserParts(content, images) {
7841
8763
  if (typeof content === "string") return [{ text: sanitizeText$5(content) }];
7842
8764
  if (!Array.isArray(content)) return [];
7843
8765
  const parts = [];
7844
- for (const block of content) if (isRecord$20(block) && block.type === "text" && typeof block.text === "string") parts.push({ text: sanitizeText$5(block.text) });
7845
- else if (isRecord$20(block) && block.type === "image") {
8766
+ for (const block of content) if (isRecord$21(block) && block.type === "text" && typeof block.text === "string") parts.push({ text: sanitizeText$5(block.text) });
8767
+ else if (isRecord$21(block) && block.type === "image") {
7846
8768
  const img = imageBlockToPart(block, images);
7847
- parts.push(img ?? { text: unavailableImageText$5(block) });
8769
+ parts.push(img ?? { text: unavailableImageText$6(block) });
7848
8770
  }
7849
8771
  return parts;
7850
8772
  }
@@ -7857,9 +8779,9 @@ function toolResultImageParts(blocks, images) {
7857
8779
  if (!Array.isArray(blocks)) return [];
7858
8780
  const parts = [];
7859
8781
  for (const block of blocks) {
7860
- if (!isRecord$20(block)) continue;
8782
+ if (!isRecord$21(block)) continue;
7861
8783
  if (block.type === "image") {
7862
- parts.push(imageBlockToPart(block, images) ?? { text: unavailableImageText$5(block) });
8784
+ parts.push(imageBlockToPart(block, images) ?? { text: unavailableImageText$6(block) });
7863
8785
  continue;
7864
8786
  }
7865
8787
  if (block.type === "tool-result") parts.push(...toolResultImageParts(block.content, images));
@@ -7869,11 +8791,11 @@ function toolResultImageParts(blocks, images) {
7869
8791
  function toolResultText$5(blocks) {
7870
8792
  if (!Array.isArray(blocks)) return "";
7871
8793
  return blocks.map((block) => {
7872
- if (!isRecord$20(block)) return "";
8794
+ if (!isRecord$21(block)) return "";
7873
8795
  if (block.type === "text" && typeof block.text === "string") return sanitizeText$5(block.text);
7874
8796
  if (block.type === "tool-result") return toolResultText$5(block.content);
7875
8797
  if (block.type === "image") {
7876
- const label = attachmentLabel$5(block);
8798
+ const label = attachmentLabel$6(block);
7877
8799
  return label ? `[image: ${label}]` : "[image]";
7878
8800
  }
7879
8801
  return "";
@@ -7883,16 +8805,16 @@ function replayBlockFor$1(message, index) {
7883
8805
  const source = message.source;
7884
8806
  if (!source || source.kind !== "model" || source.provider !== "antigravity") return void 0;
7885
8807
  const state = source.replayState;
7886
- if (!isRecord$20(state)) return void 0;
8808
+ if (!isRecord$21(state)) return void 0;
7887
8809
  if (Array.isArray(state.blocks)) return state.blocks[index];
7888
- const resp = isRecord$20(state.response) ? state.response : void 0;
8810
+ const resp = isRecord$21(state.response) ? state.response : void 0;
7889
8811
  if (resp) {
7890
8812
  if (Array.isArray(resp.outputItems)) return resp.outputItems[index];
7891
8813
  if (Array.isArray(resp.blocks)) return resp.blocks[index];
7892
8814
  }
7893
8815
  }
7894
8816
  function thoughtSignature(part) {
7895
- return asString$13(part?.thoughtSignature) || asString$13(part?.thought_signature) || asString$13(part?.thinkingSignature) || asString$13(part?.textSignature);
8817
+ return asString$14(part?.thoughtSignature) || asString$14(part?.thought_signature) || asString$14(part?.thinkingSignature) || asString$14(part?.textSignature);
7896
8818
  }
7897
8819
  function replayPart(part) {
7898
8820
  const copy = { ...part };
@@ -7903,15 +8825,48 @@ function replayPart(part) {
7903
8825
  if (typeof copy.text === "string") copy.text = sanitizeText$5(copy.text);
7904
8826
  return copy;
7905
8827
  }
8828
+ /** Claude requires a signature on each thinking block, not a separate SSE part. */
8829
+ function claudeReplayParts(parts) {
8830
+ const result = [];
8831
+ let pending = "";
8832
+ for (const part of parts) {
8833
+ const signature = thoughtSignature(part);
8834
+ if (part.thought === true && typeof part.text === "string") {
8835
+ pending += part.text;
8836
+ if (signature) {
8837
+ result.push({
8838
+ thought: true,
8839
+ text: pending,
8840
+ thoughtSignature: signature
8841
+ });
8842
+ pending = "";
8843
+ }
8844
+ continue;
8845
+ }
8846
+ if (signature && !part.functionCall && !part.functionResponse && !part.text && pending) {
8847
+ result.push({
8848
+ thought: true,
8849
+ text: pending,
8850
+ thoughtSignature: signature
8851
+ });
8852
+ pending = "";
8853
+ continue;
8854
+ }
8855
+ pending = "";
8856
+ if (part.thought !== true) result.push(part);
8857
+ }
8858
+ return result;
8859
+ }
7906
8860
  function assistantParts(message, model, runtimeModel, toolCalls) {
7907
8861
  const parts = [];
7908
8862
  if (!Array.isArray(message.content)) return parts;
7909
8863
  for (let index = 0; index < message.content.length; index++) {
7910
8864
  const block = message.content[index];
7911
- if (!isRecord$20(block)) continue;
8865
+ if (!isRecord$21(block)) continue;
8866
+ if (block.type === "reasoning" && runtimeModel.startsWith("claude-") && (message.source?.kind !== "model" || message.source.provider !== "antigravity" || !("model" in message.source) || message.source.model !== model.id)) continue;
7912
8867
  const replay = replayBlockFor$1(message, index);
7913
- const originalParts = Array.isArray(replay?.parts) ? replay.parts.filter(isRecord$20) : [];
7914
- if ((block.type === "text" || block.type === "reasoning") && originalParts.length > 0 && originalParts.every((part) => !part.functionCall) && originalParts.map((part) => asString$13(part.text) || "").join("") === sanitizeText$5(String(block.text || ""))) {
8868
+ const originalParts = Array.isArray(replay?.parts) ? replay.parts.filter(isRecord$21) : [];
8869
+ if ((block.type === "text" || block.type === "reasoning") && originalParts.length > 0 && originalParts.every((part) => !part.functionCall) && originalParts.map((part) => asString$14(part.text) || "").join("") === sanitizeText$5(String(block.text || ""))) {
7915
8870
  parts.push(...originalParts.map(replayPart));
7916
8871
  continue;
7917
8872
  }
@@ -7931,8 +8886,8 @@ function assistantParts(message, model, runtimeModel, toolCalls) {
7931
8886
  } else if (block.type === "tool-call") {
7932
8887
  const toolId = String(block.id || "");
7933
8888
  const toolName = String(block.name || "");
7934
- const originalCall = originalParts.find((part) => isRecord$20(part.functionCall));
7935
- const wireId = asString$13((isRecord$20(originalCall?.functionCall) ? originalCall.functionCall : void 0)?.id) || (toolCallIdNeeded(model.id, runtimeModel) ? sanitizeToolCallId(toolId, toolName) : originalCall ? void 0 : toolId || void 0);
8889
+ const originalCall = originalParts.find((part) => isRecord$21(part.functionCall));
8890
+ const wireId = asString$14((isRecord$21(originalCall?.functionCall) ? originalCall.functionCall : void 0)?.id) || (toolCallIdNeeded(model.id, runtimeModel) ? sanitizeToolCallId(toolId, toolName) : originalCall ? void 0 : toolId || void 0);
7936
8891
  toolCalls.set(toolId, {
7937
8892
  name: toolName,
7938
8893
  id: wireId
@@ -7941,7 +8896,7 @@ function assistantParts(message, model, runtimeModel, toolCalls) {
7941
8896
  parts.push({
7942
8897
  functionCall: {
7943
8898
  name: toolName,
7944
- args: parseArguments(block.arguments),
8899
+ args: parseArguments$1(block.arguments),
7945
8900
  ...wireId ? { id: wireId } : {}
7946
8901
  },
7947
8902
  ...effectiveSignature ? { thoughtSignature: effectiveSignature } : {}
@@ -7949,7 +8904,7 @@ function assistantParts(message, model, runtimeModel, toolCalls) {
7949
8904
  parts.push(...originalParts.filter((part) => !part.functionCall).map(replayPart));
7950
8905
  }
7951
8906
  }
7952
- return parts;
8907
+ return runtimeModel.startsWith("claude-") ? claudeReplayParts(parts) : parts;
7953
8908
  }
7954
8909
  function pushToolResult(contents, result, toolCalls, model, runtimeModel, images = NO_RESOLVED_IMAGES$5) {
7955
8910
  const toolCallId = String(result.toolCallId || "");
@@ -7984,7 +8939,7 @@ function convertMessages(options, model, runtimeModel, images = NO_RESOLVED_IMAG
7984
8939
  continue;
7985
8940
  }
7986
8941
  const content = Array.isArray(message.content) ? message.content : [];
7987
- const userParts = contentToUserParts(content.filter((b) => !isRecord$20(b) || b.type !== "tool-result"), images);
8942
+ const userParts = contentToUserParts(content.filter((b) => !isRecord$21(b) || b.type !== "tool-result"), images);
7988
8943
  if (role === "system") {
7989
8944
  if (userParts.length) contents.push({
7990
8945
  role: GEMINI_ROLE.user,
@@ -7996,7 +8951,7 @@ function convertMessages(options, model, runtimeModel, images = NO_RESOLVED_IMAG
7996
8951
  role: GEMINI_ROLE.user,
7997
8952
  parts: userParts
7998
8953
  });
7999
- for (const b of content) if (isRecord$20(b) && b.type === "tool-result") pushToolResult(contents, b, toolCalls, model, runtimeModel, images);
8954
+ for (const b of content) if (isRecord$21(b) && b.type === "tool-result") pushToolResult(contents, b, toolCalls, model, runtimeModel, images);
8000
8955
  }
8001
8956
  return contents;
8002
8957
  }
@@ -8119,7 +9074,7 @@ const USAGE_FIELDS = [
8119
9074
  "totalTokenCount"
8120
9075
  ];
8121
9076
  function collectUsage(value, state) {
8122
- if (!isRecord$20(value)) return;
9077
+ if (!isRecord$21(value)) return;
8123
9078
  for (const key of USAGE_FIELDS) {
8124
9079
  const count = value[key];
8125
9080
  if (typeof count !== "number" || !Number.isSafeInteger(count) || count < 0) continue;
@@ -8149,15 +9104,15 @@ function processStreamLine$2(line, state) {
8149
9104
  }
8150
9105
  if (!json) return [];
8151
9106
  const chunk = safeJsonParse$5(json);
8152
- if (!isRecord$20(chunk)) return [];
8153
- const responseData = isRecord$20(chunk.response) ? chunk.response : chunk;
9107
+ if (!isRecord$21(chunk)) return [];
9108
+ const responseData = isRecord$21(chunk.response) ? chunk.response : chunk;
8154
9109
  const candidates = Array.isArray(responseData.candidates) ? responseData.candidates : [];
8155
- const candidate = isRecord$20(candidates[0]) ? candidates[0] : void 0;
8156
- const content = isRecord$20(candidate?.content) ? candidate.content : void 0;
9110
+ const candidate = isRecord$21(candidates[0]) ? candidates[0] : void 0;
9111
+ const content = isRecord$21(candidate?.content) ? candidate.content : void 0;
8157
9112
  const parts = Array.isArray(content?.parts) ? content.parts : [];
8158
9113
  const out = [];
8159
9114
  for (const part of parts) {
8160
- if (!isRecord$20(part)) continue;
9115
+ if (!isRecord$21(part)) continue;
8161
9116
  if (typeof part.text === "string" && part.text !== "") {
8162
9117
  const isThinking = Boolean(part.thought);
8163
9118
  const blockType = isThinking ? "reasoning" : "text";
@@ -8189,7 +9144,7 @@ function processStreamLine$2(line, state) {
8189
9144
  index: state.currentBlock.index,
8190
9145
  text: delta
8191
9146
  });
8192
- } else if (!isRecord$20(part.functionCall) && thoughtSignature(part)) {
9147
+ } else if (!isRecord$21(part.functionCall) && thoughtSignature(part)) {
8193
9148
  if (state.replayBlocks.length === 0) {
8194
9149
  const type = part.thought ? "reasoning" : "text";
8195
9150
  state.blocks.push({
@@ -8213,12 +9168,12 @@ function processStreamLine$2(line, state) {
8213
9168
  }
8214
9169
  state.replayBlocks[state.replayBlocks.length - 1].parts.push(replayPart(part));
8215
9170
  }
8216
- if (isRecord$20(part.functionCall)) {
9171
+ if (isRecord$21(part.functionCall)) {
8217
9172
  out.push(...closeCurrentBlock(state));
8218
9173
  const fc = part.functionCall;
8219
- const toolName = asString$13(fc.name) || "";
8220
- const toolId = asString$13(fc.id) || sanitizeToolCallId("", toolName);
8221
- const argsText = JSON.stringify(isRecord$20(fc.args) ? fc.args : {});
9174
+ const toolName = asString$14(fc.name) || "";
9175
+ const toolId = asString$14(fc.id) || sanitizeToolCallId("", toolName);
9176
+ const argsText = JSON.stringify(isRecord$21(fc.args) ? fc.args : {});
8222
9177
  const index = state.blocks.length;
8223
9178
  const block = {
8224
9179
  type: "tool-call",
@@ -8255,7 +9210,7 @@ function processStreamLine$2(line, state) {
8255
9210
  }
8256
9211
  collectUsage(chunk.usageMetadata, state);
8257
9212
  if (responseData !== chunk) collectUsage(responseData.usageMetadata, state);
8258
- const finishReason = asString$13(candidate?.finishReason) || asString$13(responseData.finishReason);
9213
+ const finishReason = asString$14(candidate?.finishReason) || asString$14(responseData.finishReason);
8259
9214
  if (finishReason) {
8260
9215
  state.finishReason = finishReason;
8261
9216
  out.push(...closeCurrentBlock(state));
@@ -9909,6 +10864,7 @@ const CALLBACK_ALLOWED_ORIGINS = [
9909
10864
  "http://localhost:3000"
9910
10865
  ];
9911
10866
  const DISCOVERY_TIMEOUT_MS$3 = 15e3;
10867
+ const CATALOG_CACHE_TTL_MS$3 = 1800 * 1e3;
9912
10868
  const STREAM_IDLE_TIMEOUT_MS$4 = 3e5;
9913
10869
  const STREAM_IDLE_TIMEOUT_CODE$4 = "LLM_STREAM_IDLE_TIMEOUT";
9914
10870
  /**
@@ -9925,6 +10881,8 @@ const HEADER_PROJECT_SLUG = "x-command-code-project-slug";
9925
10881
  const HEADER_TASTE_LEARNING = "x-command-code-taste-learning";
9926
10882
  const HEADER_SESSION_ID = "x-command-code-session-id";
9927
10883
  const HEADER_OSS_PRIMARY_PROVIDER = "x-command-code-oss-primary-provider";
10884
+ /** Official header that enforces Zero Data Retention (ZDR) routing. */
10885
+ const HEADER_ZDR = "x-cmd-zdr";
9928
10886
  function resolveApiEnv(raw = process.env.DSH_COMMAND_CODE_ENV) {
9929
10887
  const value = (raw || "").trim().toLowerCase();
9930
10888
  return value === "staging" || value === "local" ? value : "prod";
@@ -9952,6 +10910,21 @@ function wireForModel$2(modelId) {
9952
10910
  return /^claude[-/]/i.test(modelId.trim()) ? "anthropic" : "openai";
9953
10911
  }
9954
10912
  /**
10913
+ * Wire one catalog entry says it is served on, when the catalog said so.
10914
+ *
10915
+ * The provider publishes `supported_endpoints` per model, and that is the
10916
+ * authority on which route answers for it. A model the catalog does not describe
10917
+ * returns undefined so the caller falls back to {@link wireForModel} instead of
10918
+ * being sent to a route nobody vouched for.
10919
+ */
10920
+ function wireForCatalogEntry$1(entry) {
10921
+ for (const endpoint of entry.supportedEndpoints ?? []) {
10922
+ if (/\/v1\/messages\b|\bmessages\b/i.test(endpoint)) return "anthropic";
10923
+ if (/\/v1\/responses\b|\bresponses\b/i.test(endpoint)) return "responses";
10924
+ if (/chat\/completions|\bcompletions\b/i.test(endpoint)) return "openai";
10925
+ }
10926
+ }
10927
+ /**
9955
10928
  * Reasoning levels one model advertises, in DSH's vocabulary.
9956
10929
  *
9957
10930
  * The registry is the authority: a model it describes but gives no
@@ -10457,13 +11430,13 @@ function commandCodeHeaders(apiKey, extra = {}) {
10457
11430
  function timeoutSignal$3(signal, ms) {
10458
11431
  return signal ? AbortSignal.any([signal, AbortSignal.timeout(ms)]) : AbortSignal.timeout(ms);
10459
11432
  }
10460
- function isRecord$19(value) {
11433
+ function isRecord$20(value) {
10461
11434
  return typeof value === "object" && value !== null && !Array.isArray(value);
10462
11435
  }
10463
11436
  function asRecord$9(value) {
10464
- return isRecord$19(value) ? value : void 0;
11437
+ return isRecord$20(value) ? value : void 0;
10465
11438
  }
10466
- function asString$12(value) {
11439
+ function asString$13(value) {
10467
11440
  if (typeof value === "string" && value.trim() !== "") return value;
10468
11441
  if (typeof value === "number" && Number.isFinite(value)) return String(value);
10469
11442
  }
@@ -10476,10 +11449,19 @@ function asNumber$3(value) {
10476
11449
  }
10477
11450
  function firstString$1(record, keys) {
10478
11451
  for (const key of keys) {
10479
- const value = asString$12(record[key]);
11452
+ const value = asString$13(record[key]);
10480
11453
  if (value !== void 0) return value;
10481
11454
  }
10482
11455
  }
11456
+ /** First key that holds a non-empty array of strings. */
11457
+ function firstStringArray(record, keys) {
11458
+ for (const key of keys) {
11459
+ const value = record[key];
11460
+ if (!Array.isArray(value)) continue;
11461
+ const entries = value.filter((entry) => typeof entry === "string" && entry !== "");
11462
+ if (entries.length > 0) return entries;
11463
+ }
11464
+ }
10483
11465
  function firstNumber$2(record, keys) {
10484
11466
  for (const key of keys) {
10485
11467
  const value = asNumber$3(record[key]);
@@ -10496,7 +11478,7 @@ function deepValue(value, keys, depth = 0) {
10496
11478
  }
10497
11479
  return;
10498
11480
  }
10499
- if (!isRecord$19(value)) return void 0;
11481
+ if (!isRecord$20(value)) return void 0;
10500
11482
  for (const key of keys) if (value[key] !== void 0 && value[key] !== null) return value[key];
10501
11483
  for (const nested of Object.values(value)) {
10502
11484
  const hit = deepValue(nested, keys, depth + 1);
@@ -10510,7 +11492,7 @@ function collectRecords(value, keys, depth = 0, out = []) {
10510
11492
  for (const entry of value) collectRecords(entry, keys, depth + 1, out);
10511
11493
  return out;
10512
11494
  }
10513
- if (!isRecord$19(value)) return out;
11495
+ if (!isRecord$20(value)) return out;
10514
11496
  if (keys.some((key) => value[key] !== void 0)) out.push(value);
10515
11497
  for (const nested of Object.values(value)) collectRecords(nested, keys, depth + 1, out);
10516
11498
  return out;
@@ -10714,7 +11696,7 @@ function parseCreditBalances(payload, consumed = []) {
10714
11696
  /** Resolve the plan id the subscription or credits payload reports, when either does. */
10715
11697
  function parsePlanId(payloads) {
10716
11698
  for (const payload of payloads) {
10717
- const id = asString$12(deepValue(payload, [
11699
+ const id = asString$13(deepValue(payload, [
10718
11700
  "planId",
10719
11701
  "plan_id",
10720
11702
  "priceId",
@@ -10733,7 +11715,7 @@ function parsePlanId(payloads) {
10733
11715
  */
10734
11716
  function parseSubscriptionStatus(payload) {
10735
11717
  const root = asRecord$9(payload) ?? {};
10736
- return asString$12(deepValue(asRecord$9(root.data) ?? root, ["status"])) ?? null;
11718
+ return asString$13(deepValue(asRecord$9(root.data) ?? root, ["status"])) ?? null;
10737
11719
  }
10738
11720
  /** End of the current billing period, in Unix milliseconds. */
10739
11721
  function parseSubscriptionPeriodEnd(payload) {
@@ -10986,6 +11968,7 @@ function extractUnlimited(payload) {
10986
11968
  return deepValue(payload, ["unlimited", "isUnlimited"]) === true;
10987
11969
  }
10988
11970
  let cachedCatalog$1;
11971
+ let catalogIsRehydrated = false;
10989
11972
  /** Bumped by every cache clear so a fetch already in flight cannot publish. */
10990
11973
  let catalogCacheEpoch$2 = 0;
10991
11974
  let catalogInFlight$2 = null;
@@ -11020,7 +12003,8 @@ function parseProviderModels(payload) {
11020
12003
  "contextLength",
11021
12004
  "context_window",
11022
12005
  "contextWindow"
11023
- ])
12006
+ ]),
12007
+ ...firstStringArray(record, ["supported_endpoints", "supportedEndpoints"]) === void 0 ? {} : { supportedEndpoints: firstStringArray(record, ["supported_endpoints", "supportedEndpoints"]) }
11024
12008
  });
11025
12009
  }
11026
12010
  return models;
@@ -11067,6 +12051,7 @@ async function loadProviderModels(options = {}) {
11067
12051
  models,
11068
12052
  fetchedAt
11069
12053
  };
12054
+ catalogIsRehydrated = true;
11070
12055
  }, 0);
11071
12056
  const warm = cachedCatalog$1;
11072
12057
  if (rehydratedAt > 0 && warm !== void 0) {
@@ -11088,6 +12073,7 @@ function refreshProviderModels$1(options = {}) {
11088
12073
  models,
11089
12074
  fetchedAt
11090
12075
  };
12076
+ catalogIsRehydrated = false;
11091
12077
  writeCatalogSnapshot(catalogSnapshotName("command-code", options.apiEnv ?? resolveApiEnv()), models, fetchedAt);
11092
12078
  }
11093
12079
  return models.length > 0 ? models : cachedCatalog$1?.models ?? [];
@@ -11097,10 +12083,19 @@ function refreshProviderModels$1(options = {}) {
11097
12083
  if (catalogInFlight$2 === request) catalogInFlight$2 = null;
11098
12084
  });
11099
12085
  }
12086
+ function isCatalogStale() {
12087
+ if (cachedCatalog$1 === void 0) return true;
12088
+ if (catalogIsRehydrated) return true;
12089
+ return Date.now() - cachedCatalog$1.fetchedAt >= CATALOG_CACHE_TTL_MS$3;
12090
+ }
12091
+ function getCachedCatalogFetchedAt() {
12092
+ return cachedCatalog$1?.fetchedAt ?? null;
12093
+ }
11100
12094
  function clearCachedCatalog$3() {
11101
12095
  catalogCacheEpoch$2 += 1;
11102
12096
  cachedCatalog$1 = void 0;
11103
12097
  catalogInFlight$2 = null;
12098
+ catalogIsRehydrated = false;
11104
12099
  catalogSnapshotScopesLoaded$2.clear();
11105
12100
  }
11106
12101
  async function getJson(path, apiKey, options = {}) {
@@ -11256,7 +12251,7 @@ function buildModelOptions$2(catalog, enabledModelIds, overrides) {
11256
12251
  contextWindow: overrides[model.id] && overrides[model.id] > 0 ? overrides[model.id] : contextWindow,
11257
12252
  defaultMaxTokens: maxOutputTokensFor$4(model.id),
11258
12253
  ...efforts.length > 0 ? { reasoningEfforts: efforts } : {},
11259
- wire: wireForModel$2(model.id)
12254
+ wire: wireForCatalogEntry$1(model) ?? wireForModel$2(model.id)
11260
12255
  };
11261
12256
  });
11262
12257
  }
@@ -11272,10 +12267,10 @@ function buildModelOptions$2(catalog, enabledModelIds, overrides) {
11272
12267
  *
11273
12268
  * https://commandcode.ai/blog/command-code-provider-api
11274
12269
  */
11275
- function isRecord$18(value) {
12270
+ function isRecord$19(value) {
11276
12271
  return typeof value === "object" && value !== null && !Array.isArray(value);
11277
12272
  }
11278
- function asString$11(value) {
12273
+ function asString$12(value) {
11279
12274
  return typeof value === "string" ? value : void 0;
11280
12275
  }
11281
12276
  function safeJsonParse$4(text) {
@@ -11288,7 +12283,7 @@ function safeJsonParse$4(text) {
11288
12283
  function sanitizeText$4(text) {
11289
12284
  return text.replace(/\0/g, "");
11290
12285
  }
11291
- function isAbort$2(error, signal) {
12286
+ function isAbort$3(error, signal) {
11292
12287
  return signal?.aborted === true || error instanceof Error && error.name === "AbortError";
11293
12288
  }
11294
12289
  const NO_RESOLVED_IMAGES$4 = /* @__PURE__ */ new Map();
@@ -11312,17 +12307,17 @@ const SUPPORTED_IMAGE_MEDIA_TYPES$4 = /* @__PURE__ */ new Set([
11312
12307
  ]);
11313
12308
  function attachmentOf$4(block) {
11314
12309
  const attachment = block.attachment;
11315
- if (!isRecord$18(attachment)) return void 0;
12310
+ if (!isRecord$19(attachment)) return void 0;
11316
12311
  return typeof attachment.attachmentId === "string" ? attachment : void 0;
11317
12312
  }
11318
- function attachmentLabel$4(block) {
11319
- const attachment = isRecord$18(block.attachment) ? block.attachment : void 0;
11320
- return asString$11(attachment?.name) || asString$11(attachment?.attachmentId);
12313
+ function attachmentLabel$5(block) {
12314
+ const attachment = isRecord$19(block.attachment) ? block.attachment : void 0;
12315
+ return asString$12(attachment?.name) || asString$12(attachment?.attachmentId);
11321
12316
  }
11322
12317
  function collectImageRefs$3(content, refs) {
11323
12318
  if (!Array.isArray(content)) return;
11324
12319
  for (const block of content) {
11325
- if (!isRecord$18(block)) continue;
12320
+ if (!isRecord$19(block)) continue;
11326
12321
  if (block.type === "image") {
11327
12322
  const attachment = attachmentOf$4(block);
11328
12323
  if (attachment) refs.set(attachment.attachmentId, attachment);
@@ -11337,13 +12332,13 @@ function base64Length$3(bytes) {
11337
12332
  function requestImageBytes$3(block) {
11338
12333
  const attachment = attachmentOf$4(block);
11339
12334
  if (attachment) return base64Length$3(attachment.bytes);
11340
- const inline = asString$11(block.data) || asString$11(block.base64);
12335
+ const inline = asString$12(block.data) || asString$12(block.base64);
11341
12336
  return inline ? inline.length : void 0;
11342
12337
  }
11343
12338
  function collectRequestImageBytes$3(content, lengths) {
11344
12339
  if (!Array.isArray(content)) return;
11345
12340
  for (const block of content) {
11346
- if (!isRecord$18(block) || block.type !== "image") continue;
12341
+ if (!isRecord$19(block) || block.type !== "image") continue;
11347
12342
  const bytes = requestImageBytes$3(block);
11348
12343
  if (bytes !== void 0) lengths.push(bytes);
11349
12344
  }
@@ -11370,7 +12365,7 @@ function offloadOldestRequestImages$3(options) {
11370
12365
  if (remaining.count === 0 || !Array.isArray(message.content)) return message;
11371
12366
  let replaced = false;
11372
12367
  const content = message.content.map((block) => {
11373
- if (remaining.count === 0 || !isRecord$18(block) || block.type !== "image") return block;
12368
+ if (remaining.count === 0 || !isRecord$19(block) || block.type !== "image") return block;
11374
12369
  if (requestImageBytes$3(block) === void 0) return block;
11375
12370
  remaining.count -= 1;
11376
12371
  replaced = true;
@@ -11412,21 +12407,21 @@ async function resolveRequestImages$3(options, attachments, signal) {
11412
12407
  data: Buffer.from(stored.data).toString("base64")
11413
12408
  });
11414
12409
  } catch (error) {
11415
- if (isAbort$2(error, signal)) throw error;
12410
+ if (isAbort$3(error, signal)) throw error;
11416
12411
  resolved.set(attachmentId, { kind: "unavailable" });
11417
12412
  }
11418
12413
  }));
11419
12414
  return resolved;
11420
12415
  }
11421
- function unavailableImageText$4(block) {
11422
- const label = attachmentLabel$4(block);
12416
+ function unavailableImageText$5(block) {
12417
+ const label = attachmentLabel$5(block);
11423
12418
  return `[image unavailable: ${label ? `${label} could not be read` : "the image could not be read"}; ask the user to attach it again if the image is needed]`;
11424
12419
  }
11425
12420
  function imageBlockToInline$3(block, images) {
11426
- let data = asString$11(block.data) || asString$11(block.base64);
11427
- const source = isRecord$18(block.source) ? block.source : void 0;
11428
- if (!data && source) data = asString$11(source.data) || asString$11(source.base64);
11429
- let mediaType = asString$11(block.mimeType) || asString$11(block.mediaType) || (source ? asString$11(source.mimeType) || asString$11(source.mediaType) : void 0) || "image/png";
12421
+ let data = asString$12(block.data) || asString$12(block.base64);
12422
+ const source = isRecord$19(block.source) ? block.source : void 0;
12423
+ if (!data && source) data = asString$12(source.data) || asString$12(source.base64);
12424
+ let mediaType = asString$12(block.mimeType) || asString$12(block.mediaType) || (source ? asString$12(source.mimeType) || asString$12(source.mediaType) : void 0) || "image/png";
11430
12425
  if (data?.startsWith("data:")) {
11431
12426
  const matched = data.match(/^data:([^;,]+);base64,(.*)$/s);
11432
12427
  if (matched) {
@@ -11450,7 +12445,7 @@ function textOf$5(content) {
11450
12445
  if (!Array.isArray(content)) return "";
11451
12446
  const parts = [];
11452
12447
  for (const block of content) {
11453
- if (!isRecord$18(block)) continue;
12448
+ if (!isRecord$19(block)) continue;
11454
12449
  if (block.type === "text" && typeof block.text === "string") parts.push(sanitizeText$4(block.text));
11455
12450
  else if (block.type === "tool-result") parts.push(textOf$5(block.content));
11456
12451
  }
@@ -11459,10 +12454,10 @@ function textOf$5(content) {
11459
12454
  function toolResultText$4(blocks) {
11460
12455
  if (!Array.isArray(blocks)) return "";
11461
12456
  return blocks.map((block) => {
11462
- if (!isRecord$18(block)) return "";
12457
+ if (!isRecord$19(block)) return "";
11463
12458
  if (block.type === "text" && typeof block.text === "string") return sanitizeText$4(block.text);
11464
12459
  if (block.type === "tool-result") return toolResultText$4(block.content);
11465
- if (block.type === "image") return `[image: ${attachmentLabel$4(block) ?? "attached image"}]`;
12460
+ if (block.type === "image") return `[image: ${attachmentLabel$5(block) ?? "attached image"}]`;
11466
12461
  return "";
11467
12462
  }).join("");
11468
12463
  }
@@ -11477,7 +12472,7 @@ function toolResultBlocks$2(blocks, images) {
11477
12472
  const out = [];
11478
12473
  let hasImage = false;
11479
12474
  for (const block of blocks) {
11480
- if (!isRecord$18(block)) continue;
12475
+ if (!isRecord$19(block)) continue;
11481
12476
  if (block.type === "text" && typeof block.text === "string") {
11482
12477
  out.push({
11483
12478
  type: "text",
@@ -11498,7 +12493,7 @@ function toolResultBlocks$2(blocks, images) {
11498
12493
  });
11499
12494
  else out.push({
11500
12495
  type: "text",
11501
- text: unavailableImageText$4(block)
12496
+ text: unavailableImageText$5(block)
11502
12497
  });
11503
12498
  continue;
11504
12499
  }
@@ -11526,7 +12521,7 @@ function toolResultImageBlocks$2(blocks, images) {
11526
12521
  if (!Array.isArray(blocks)) return [];
11527
12522
  const out = [];
11528
12523
  for (const block of blocks) {
11529
- if (!isRecord$18(block)) continue;
12524
+ if (!isRecord$19(block)) continue;
11530
12525
  if (block.type === "image") {
11531
12526
  const inline = imageBlockToInline$3(block, images);
11532
12527
  if (inline && SUPPORTED_IMAGE_MEDIA_TYPES$4.has(inline.mediaType)) out.push({
@@ -11535,7 +12530,7 @@ function toolResultImageBlocks$2(blocks, images) {
11535
12530
  });
11536
12531
  else out.push({
11537
12532
  type: "text",
11538
- text: unavailableImageText$4(block)
12533
+ text: unavailableImageText$5(block)
11539
12534
  });
11540
12535
  continue;
11541
12536
  }
@@ -11570,7 +12565,7 @@ function nonSystemMessages$4(options) {
11570
12565
  }
11571
12566
  /** Drop the JSON-Schema keywords provider gateways reject or ignore. */
11572
12567
  function stripMetaSchema$2(schema) {
11573
- if (!isRecord$18(schema)) return {
12568
+ if (!isRecord$19(schema)) return {
11574
12569
  type: "object",
11575
12570
  properties: {}
11576
12571
  };
@@ -11587,7 +12582,7 @@ function openAIUserContent$2(message, images) {
11587
12582
  const parts = [];
11588
12583
  let hasImage = false;
11589
12584
  for (const block of message.content) {
11590
- if (!isRecord$18(block)) continue;
12585
+ if (!isRecord$19(block)) continue;
11591
12586
  if (block.type === "text" && typeof block.text === "string") {
11592
12587
  const text = sanitizeText$4(block.text);
11593
12588
  if (text !== "") parts.push({
@@ -11604,7 +12599,7 @@ function openAIUserContent$2(message, images) {
11604
12599
  });
11605
12600
  } else parts.push({
11606
12601
  type: "text",
11607
- text: unavailableImageText$4(block)
12602
+ text: unavailableImageText$5(block)
11608
12603
  });
11609
12604
  }
11610
12605
  }
@@ -11615,7 +12610,7 @@ function openAIAssistantContent$2(message) {
11615
12610
  const textParts = [];
11616
12611
  const toolCalls = [];
11617
12612
  for (const block of message.content) {
11618
- if (!isRecord$18(block)) continue;
12613
+ if (!isRecord$19(block)) continue;
11619
12614
  if (block.type === "text" && typeof block.text === "string") textParts.push(sanitizeText$4(block.text));
11620
12615
  else if (block.type === "tool-call" && typeof block.name === "string") toolCalls.push({
11621
12616
  id: typeof block.id === "string" && block.id !== "" ? block.id : `call_${toolCalls.length}`,
@@ -11647,7 +12642,7 @@ function buildOpenAIRequest$1(options, images = NO_RESOLVED_IMAGES$4) {
11647
12642
  while (index < conversation.length && isToolResultMessage$4(conversation[index])) {
11648
12643
  const current = conversation[index];
11649
12644
  const block = current.content[0];
11650
- const callId = isRecord$18(block) && typeof block.toolCallId === "string" ? block.toolCallId : "";
12645
+ const callId = isRecord$19(block) && typeof block.toolCallId === "string" ? block.toolCallId : "";
11651
12646
  messages.push({
11652
12647
  role: "tool",
11653
12648
  tool_call_id: callId,
@@ -11708,7 +12703,7 @@ function anthropicUserContent$2(message, images) {
11708
12703
  if (!Array.isArray(message.content)) return [];
11709
12704
  const blocks = [];
11710
12705
  for (const block of message.content) {
11711
- if (!isRecord$18(block)) continue;
12706
+ if (!isRecord$19(block)) continue;
11712
12707
  if (block.type === "text" && typeof block.text === "string") {
11713
12708
  const text = sanitizeText$4(block.text);
11714
12709
  if (text !== "") blocks.push({
@@ -11727,7 +12722,7 @@ function anthropicUserContent$2(message, images) {
11727
12722
  });
11728
12723
  else blocks.push({
11729
12724
  type: "text",
11730
- text: unavailableImageText$4(block)
12725
+ text: unavailableImageText$5(block)
11731
12726
  });
11732
12727
  } else if (block.type === "tool-result") {
11733
12728
  const callId = typeof block.toolCallId === "string" ? block.toolCallId : "";
@@ -11745,7 +12740,7 @@ function anthropicUserContent$2(message, images) {
11745
12740
  function anthropicAssistantContent$2(message) {
11746
12741
  const blocks = [];
11747
12742
  for (const block of message.content) {
11748
- if (!isRecord$18(block)) continue;
12743
+ if (!isRecord$19(block)) continue;
11749
12744
  if (block.type === "text" && typeof block.text === "string") {
11750
12745
  const text = sanitizeText$4(block.text);
11751
12746
  if (text !== "") blocks.push({
@@ -11758,7 +12753,7 @@ function anthropicAssistantContent$2(message) {
11758
12753
  type: "tool_use",
11759
12754
  id: typeof block.id === "string" && block.id !== "" ? block.id : `toolu_${blocks.length}`,
11760
12755
  name: block.name,
11761
- input: isRecord$18(parsed) ? parsed : {}
12756
+ input: isRecord$19(parsed) ? parsed : {}
11762
12757
  });
11763
12758
  }
11764
12759
  }
@@ -11835,7 +12830,136 @@ function buildAnthropicRequest$1(options, images = NO_RESOLVED_IMAGES$4) {
11835
12830
  }
11836
12831
  /** Build the body for whichever endpoint serves `modelId`. */
11837
12832
  function buildRequest$1(options, wire, images = NO_RESOLVED_IMAGES$4) {
11838
- return wire === "anthropic" ? buildAnthropicRequest$1(options, images) : buildOpenAIRequest$1(options, images);
12833
+ if (wire === "anthropic") return buildAnthropicRequest$1(options, images);
12834
+ if (wire === "responses") return buildResponsesRequest(options, images);
12835
+ return buildOpenAIRequest$1(options, images);
12836
+ }
12837
+ /**
12838
+ * Build one `/responses` body.
12839
+ *
12840
+ * The Responses API takes a flat `input` list instead of `messages`, and its
12841
+ * reasoning and tool calls are item types rather than message fields. This is a
12842
+ * separate builder rather than a reshaping of the Chat Completions one: the two
12843
+ * APIs differ in more than field names, and a translated body is a request the
12844
+ * route rejects.
12845
+ */
12846
+ function responsesUserContent(message, images) {
12847
+ if (!Array.isArray(message.content)) return [];
12848
+ const parts = [];
12849
+ for (const block of message.content) {
12850
+ if (!isRecord$19(block)) continue;
12851
+ if (block.type === "text" && typeof block.text === "string") {
12852
+ const text = sanitizeText$4(block.text);
12853
+ if (text !== "") parts.push({
12854
+ type: "input_text",
12855
+ text
12856
+ });
12857
+ } else if (block.type === "image") {
12858
+ const inline = imageBlockToInline$3(block, images);
12859
+ if (inline && SUPPORTED_IMAGE_MEDIA_TYPES$4.has(inline.mediaType)) parts.push({
12860
+ type: "input_image",
12861
+ image_url: "data:" + inline.mediaType + ";base64," + inline.data
12862
+ });
12863
+ else parts.push({
12864
+ type: "input_text",
12865
+ text: unavailableImageText$5(block)
12866
+ });
12867
+ }
12868
+ }
12869
+ return parts;
12870
+ }
12871
+ function responsesToolResultImageBlocks(blocks, images) {
12872
+ if (!Array.isArray(blocks)) return [];
12873
+ const out = [];
12874
+ for (const block of blocks) {
12875
+ if (!isRecord$19(block)) continue;
12876
+ if (block.type === "image") {
12877
+ const inline = imageBlockToInline$3(block, images);
12878
+ if (inline && SUPPORTED_IMAGE_MEDIA_TYPES$4.has(inline.mediaType)) out.push({
12879
+ type: "input_image",
12880
+ image_url: "data:" + inline.mediaType + ";base64," + inline.data
12881
+ });
12882
+ else out.push({
12883
+ type: "input_text",
12884
+ text: unavailableImageText$5(block)
12885
+ });
12886
+ continue;
12887
+ }
12888
+ if (block.type === "tool-result") out.push(...responsesToolResultImageBlocks(block.content, images));
12889
+ }
12890
+ return out;
12891
+ }
12892
+ function buildResponsesRequest(options, images = NO_RESOLVED_IMAGES$4) {
12893
+ const input = [];
12894
+ const system = leadingSystemText$4(options);
12895
+ const conversation = nonSystemMessages$4(options);
12896
+ for (let index = 0; index < conversation.length; index++) {
12897
+ const message = conversation[index];
12898
+ if (isToolResultMessage$4(message)) {
12899
+ const imageBlocks = [];
12900
+ while (index < conversation.length && isToolResultMessage$4(conversation[index])) {
12901
+ const current = conversation[index];
12902
+ const toolResult = Array.isArray(current.content) ? current.content.find((b) => isRecord$19(b) && b.type === "tool-result") : void 0;
12903
+ const callId = (isRecord$19(toolResult) && typeof toolResult.toolCallId === "string" ? toolResult.toolCallId : "") || (typeof current.source?.callId === "string" ? current.source.callId : "");
12904
+ input.push({
12905
+ type: "function_call_output",
12906
+ call_id: callId,
12907
+ output: toolResultText$4(current.content)
12908
+ });
12909
+ imageBlocks.push(...responsesToolResultImageBlocks(current.content, images));
12910
+ index += 1;
12911
+ }
12912
+ index -= 1;
12913
+ if (imageBlocks.length > 0) input.push({
12914
+ role: "user",
12915
+ content: imageBlocks
12916
+ });
12917
+ continue;
12918
+ }
12919
+ if (message.role === "assistant") {
12920
+ const { content, toolCalls } = openAIAssistantContent$2(message);
12921
+ if (content !== "") input.push({
12922
+ role: "assistant",
12923
+ content
12924
+ });
12925
+ for (const call of toolCalls) {
12926
+ const fn = isRecord$19(call.function) ? call.function : void 0;
12927
+ input.push({
12928
+ type: "function_call",
12929
+ call_id: call.id,
12930
+ name: typeof fn?.name === "string" ? fn.name : "",
12931
+ arguments: typeof fn?.arguments === "string" ? fn.arguments : "{}"
12932
+ });
12933
+ }
12934
+ continue;
12935
+ }
12936
+ const content = responsesUserContent(message, images);
12937
+ if (content.length === 0) continue;
12938
+ input.push({
12939
+ role: "user",
12940
+ content
12941
+ });
12942
+ }
12943
+ const effort = wireReasoningEffort(options.reasoningEffort === void 0 ? void 0 : String(options.reasoningEffort));
12944
+ const maxTokens = options.maxTokens ?? maxOutputTokensFor$4(options.model);
12945
+ return {
12946
+ model: options.model,
12947
+ input,
12948
+ stream: true,
12949
+ max_output_tokens: maxTokens,
12950
+ ...system === void 0 ? {} : { instructions: system },
12951
+ ...options.temperature === void 0 ? {} : { temperature: options.temperature },
12952
+ ...options.tools && options.tools.length > 0 ? {
12953
+ tools: options.tools.map((tool) => ({
12954
+ type: "function",
12955
+ name: tool.name,
12956
+ description: tool.description,
12957
+ parameters: stripMetaSchema$2(tool.parameters)
12958
+ })),
12959
+ tool_choice: "auto"
12960
+ } : {},
12961
+ ...effort === void 0 || effort === "" ? {} : { reasoning: { effort } }
12962
+ };
11839
12963
  }
11840
12964
  function createStreamState$5(wire) {
11841
12965
  return {
@@ -11922,24 +13046,24 @@ function processOpenAIStreamLine$1(line, state) {
11922
13046
  }
11923
13047
  if (payload === "") return [];
11924
13048
  const chunk = safeJsonParse$4(payload);
11925
- if (!isRecord$18(chunk)) return [];
13049
+ if (!isRecord$19(chunk)) return [];
11926
13050
  const out = [];
11927
- const usage = isRecord$18(chunk.usage) ? chunk.usage : void 0;
13051
+ const usage = isRecord$19(chunk.usage) ? chunk.usage : void 0;
11928
13052
  if (usage) {
11929
13053
  state.sawUsage = true;
11930
13054
  const prompt = numberOr$5(usage.prompt_tokens, 0);
11931
- const details = isRecord$18(usage.prompt_tokens_details) ? usage.prompt_tokens_details : void 0;
13055
+ const details = isRecord$19(usage.prompt_tokens_details) ? usage.prompt_tokens_details : void 0;
11932
13056
  const cached = details ? numberOr$5(details.cached_tokens, 0) : 0;
11933
13057
  state.inputTokens = Math.max(0, prompt - cached);
11934
13058
  state.cacheReadTokens = cached;
11935
13059
  state.outputTokens = numberOr$5(usage.completion_tokens, state.outputTokens);
11936
- if (isRecord$18(usage.completion_tokens_details)) state.reasoningTokens = numberOr$5(usage.completion_tokens_details.reasoning_tokens, state.reasoningTokens);
13060
+ if (isRecord$19(usage.completion_tokens_details)) state.reasoningTokens = numberOr$5(usage.completion_tokens_details.reasoning_tokens, state.reasoningTokens);
11937
13061
  }
11938
13062
  const choices = Array.isArray(chunk.choices) ? chunk.choices : [];
11939
- const choice = isRecord$18(choices[0]) ? choices[0] : void 0;
11940
- const delta = isRecord$18(choice?.delta) ? choice.delta : void 0;
13063
+ const choice = isRecord$19(choices[0]) ? choices[0] : void 0;
13064
+ const delta = isRecord$19(choice?.delta) ? choice.delta : void 0;
11941
13065
  if (delta) {
11942
- const reasoning = asString$11(delta.reasoning_content) ?? asString$11(delta.reasoning);
13066
+ const reasoning = asString$12(delta.reasoning_content) ?? asString$12(delta.reasoning);
11943
13067
  if (reasoning !== void 0 && reasoning !== "") {
11944
13068
  out.push(...closeToolCalls$4(state));
11945
13069
  if (state.current === null || state.current.type !== "reasoning") out.push(...openTextBlock$3(state, "reasoning"));
@@ -11951,7 +13075,7 @@ function processOpenAIStreamLine$1(line, state) {
11951
13075
  text: sanitizeText$4(reasoning)
11952
13076
  });
11953
13077
  }
11954
- const content = asString$11(delta.content);
13078
+ const content = asString$12(delta.content);
11955
13079
  if (content !== void 0 && content !== "") {
11956
13080
  out.push(...closeToolCalls$4(state));
11957
13081
  if (state.current === null || state.current.type !== "text") out.push(...openTextBlock$3(state, "text"));
@@ -11965,11 +13089,11 @@ function processOpenAIStreamLine$1(line, state) {
11965
13089
  }
11966
13090
  const toolDeltas = Array.isArray(delta.tool_calls) ? delta.tool_calls : [];
11967
13091
  for (const entry of toolDeltas) {
11968
- if (!isRecord$18(entry)) continue;
13092
+ if (!isRecord$19(entry)) continue;
11969
13093
  out.push(...applyOpenAIToolDelta$1(entry, state));
11970
13094
  }
11971
13095
  }
11972
- const finish = asString$11(choice?.finish_reason);
13096
+ const finish = asString$12(choice?.finish_reason);
11973
13097
  if (finish !== void 0 && finish !== "") {
11974
13098
  state.finishReason = finish;
11975
13099
  out.push(...closeCurrent$3(state));
@@ -11979,15 +13103,15 @@ function processOpenAIStreamLine$1(line, state) {
11979
13103
  }
11980
13104
  function applyOpenAIToolDelta$1(entry, state) {
11981
13105
  const wireIndex = typeof entry.index === "number" ? entry.index : 0;
11982
- const fn = isRecord$18(entry.function) ? entry.function : {};
13106
+ const fn = isRecord$19(entry.function) ? entry.function : {};
11983
13107
  const out = [];
11984
13108
  let call = state.toolCalls.get(wireIndex);
11985
13109
  if (call === void 0) {
11986
13110
  out.push(...closeCurrent$3(state));
11987
13111
  call = {
11988
13112
  blockIndex: state.blocks.length,
11989
- id: asString$11(entry.id) ?? `call_${wireIndex}`,
11990
- name: asString$11(fn.name) ?? "",
13113
+ id: asString$12(entry.id) ?? `call_${wireIndex}`,
13114
+ name: asString$12(fn.name) ?? "",
11991
13115
  arguments: "",
11992
13116
  started: false
11993
13117
  };
@@ -12000,13 +13124,13 @@ function applyOpenAIToolDelta$1(entry, state) {
12000
13124
  state.toolCalls.set(wireIndex, call);
12001
13125
  } else {
12002
13126
  if (call.id === `call_${wireIndex}`) {
12003
- const id = asString$11(entry.id);
13127
+ const id = asString$12(entry.id);
12004
13128
  if (id !== void 0) call.id = id;
12005
13129
  }
12006
- const name = asString$11(fn.name);
13130
+ const name = asString$12(fn.name);
12007
13131
  if (name !== void 0 && name !== "") call.name = name;
12008
13132
  }
12009
- const argsDelta = asString$11(fn.arguments) ?? "";
13133
+ const argsDelta = asString$12(fn.arguments) ?? "";
12010
13134
  if (argsDelta !== "") call.arguments += argsDelta;
12011
13135
  if (!call.started) {
12012
13136
  call.started = true;
@@ -12037,12 +13161,12 @@ function processAnthropicStreamLine$1(line, state) {
12037
13161
  const payload = trimmed.slice(5).trim();
12038
13162
  if (payload === "" || payload === "[DONE]") return [];
12039
13163
  const event = safeJsonParse$4(payload);
12040
- if (!isRecord$18(event)) return [];
12041
- const type = asString$11(event.type);
13164
+ if (!isRecord$19(event)) return [];
13165
+ const type = asString$12(event.type);
12042
13166
  const out = [];
12043
13167
  if (type === "message_start") {
12044
- const message = isRecord$18(event.message) ? event.message : void 0;
12045
- const usage = message && isRecord$18(message.usage) ? message.usage : void 0;
13168
+ const message = isRecord$19(event.message) ? event.message : void 0;
13169
+ const usage = message && isRecord$19(message.usage) ? message.usage : void 0;
12046
13170
  if (usage) {
12047
13171
  state.sawUsage = true;
12048
13172
  state.inputTokens = numberOr$5(usage.input_tokens, 0);
@@ -12050,21 +13174,21 @@ function processAnthropicStreamLine$1(line, state) {
12050
13174
  state.cacheWriteTokens = numberOr$5(usage.cache_creation_input_tokens, 0);
12051
13175
  state.outputTokens = numberOr$5(usage.output_tokens, 0);
12052
13176
  }
12053
- const stop = message ? asString$11(message.stop_reason) : void 0;
13177
+ const stop = message ? asString$12(message.stop_reason) : void 0;
12054
13178
  if (stop !== void 0 && stop !== null) state.finishReason = stop;
12055
13179
  return out;
12056
13180
  }
12057
13181
  if (type === "content_block_start") {
12058
13182
  const contentIndex = numberOr$5(event.index, 0);
12059
- const block = isRecord$18(event.content_block) ? event.content_block : {};
12060
- const blockType = asString$11(block.type);
13183
+ const block = isRecord$19(event.content_block) ? event.content_block : {};
13184
+ const blockType = asString$12(block.type);
12061
13185
  out.push(...closeCurrent$3(state));
12062
13186
  if (blockType === "tool_use") {
12063
13187
  const index = state.blocks.length;
12064
13188
  const pending = {
12065
13189
  blockIndex: index,
12066
- id: asString$11(block.id) ?? `toolu_${contentIndex}`,
12067
- name: asString$11(block.name) ?? "",
13190
+ id: asString$12(block.id) ?? `toolu_${contentIndex}`,
13191
+ name: asString$12(block.name) ?? "",
12068
13192
  arguments: "",
12069
13193
  started: true
12070
13194
  };
@@ -12106,11 +13230,11 @@ function processAnthropicStreamLine$1(line, state) {
12106
13230
  }
12107
13231
  if (type === "content_block_delta") {
12108
13232
  const contentIndex = numberOr$5(event.index, 0);
12109
- const delta = isRecord$18(event.delta) ? event.delta : {};
12110
- const deltaType = asString$11(delta.type);
13233
+ const delta = isRecord$19(event.delta) ? event.delta : {};
13234
+ const deltaType = asString$12(delta.type);
12111
13235
  if (deltaType === "input_json_delta") {
12112
13236
  const pending = state.toolCalls.get(contentIndex);
12113
- const partial = asString$11(delta.partial_json) ?? "";
13237
+ const partial = asString$12(delta.partial_json) ?? "";
12114
13238
  if (pending !== void 0) {
12115
13239
  pending.arguments += partial;
12116
13240
  out.push({
@@ -12123,7 +13247,7 @@ function processAnthropicStreamLine$1(line, state) {
12123
13247
  }
12124
13248
  return out;
12125
13249
  }
12126
- const text = deltaType === "thinking_delta" ? asString$11(delta.thinking) : asString$11(delta.text);
13250
+ const text = deltaType === "thinking_delta" ? asString$12(delta.thinking) : asString$12(delta.text);
12127
13251
  if (text !== void 0 && text !== "") {
12128
13252
  const index = state.contentIndexes.get(contentIndex) ?? state.current?.index;
12129
13253
  const kind = deltaType === "thinking_delta" ? "reasoning" : "text";
@@ -12179,10 +13303,10 @@ function processAnthropicStreamLine$1(line, state) {
12179
13303
  return out;
12180
13304
  }
12181
13305
  if (type === "message_delta") {
12182
- const delta = isRecord$18(event.delta) ? event.delta : void 0;
12183
- const stop = delta ? asString$11(delta.stop_reason) : void 0;
13306
+ const delta = isRecord$19(event.delta) ? event.delta : void 0;
13307
+ const stop = delta ? asString$12(delta.stop_reason) : void 0;
12184
13308
  if (stop !== void 0 && stop !== "") state.finishReason = stop;
12185
- const usage = isRecord$18(event.usage) ? event.usage : void 0;
13309
+ const usage = isRecord$19(event.usage) ? event.usage : void 0;
12186
13310
  if (usage) {
12187
13311
  state.sawUsage = true;
12188
13312
  state.outputTokens = numberOr$5(usage.output_tokens, state.outputTokens);
@@ -12193,7 +13317,7 @@ function processAnthropicStreamLine$1(line, state) {
12193
13317
  state.done = true;
12194
13318
  return closeStream$4(state);
12195
13319
  }
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");
13320
+ if (type === "error") throw new LlmError$1(`Command Code stream error: ${asString$12((isRecord$19(event.error) ? event.error : {}).message) ?? "unknown error"}`, "PROVIDER_ERROR");
12197
13321
  return out;
12198
13322
  }
12199
13323
  function tokenUsage$4(state) {
@@ -12207,10 +13331,200 @@ function tokenUsage$4(state) {
12207
13331
  }
12208
13332
  function finishReasonFor$4(state) {
12209
13333
  const reason = state.finishReason ?? "";
12210
- if (reason === "length" || reason === "max_tokens") return { kind: "max-tokens" };
13334
+ if (reason === "length" || reason === "max_tokens" || reason === "max_output_tokens") return { kind: "max-tokens" };
12211
13335
  if (state.hasToolCall || reason === "tool_calls" || reason === "tool_use") return { kind: "tool-calls" };
12212
13336
  return { kind: "stop" };
12213
13337
  }
13338
+ /**
13339
+ * Fold one SSE line from `/responses`.
13340
+ *
13341
+ * The Responses stream is event-typed rather than delta-shaped: text arrives as
13342
+ * `response.output_text.delta`, tool calls as `function_call_arguments.delta`
13343
+ * keyed by an item id, and the turn ends on `response.completed` or
13344
+ * `response.failed`. Those are read as written; there is no Chat Completions
13345
+ * shape to reinterpret.
13346
+ */
13347
+ function processResponsesStreamLine(line, state) {
13348
+ const trimmed = line.trim();
13349
+ if (trimmed === "" || !trimmed.startsWith("data:")) return [];
13350
+ const payload = trimmed.slice(5).trim();
13351
+ if (payload === "" || payload === "[DONE]") return [];
13352
+ let parsed;
13353
+ try {
13354
+ parsed = JSON.parse(payload);
13355
+ } catch {
13356
+ return [];
13357
+ }
13358
+ if (!isRecord$19(parsed)) return [];
13359
+ const type = typeof parsed.type === "string" ? parsed.type : "";
13360
+ if (type === "response.output_text.delta") {
13361
+ const delta = typeof parsed.delta === "string" ? parsed.delta : "";
13362
+ if (delta === "") return [];
13363
+ state.hasContent = true;
13364
+ const opened = openBlock(state, "text");
13365
+ state.current.text += delta;
13366
+ return [...opened, {
13367
+ type: "text-delta",
13368
+ index: state.current.index,
13369
+ text: delta
13370
+ }];
13371
+ }
13372
+ if (type === "response.reasoning_summary_text.delta") {
13373
+ const delta = typeof parsed.delta === "string" ? parsed.delta : "";
13374
+ if (delta === "") return [];
13375
+ state.hasContent = true;
13376
+ const opened = openBlock(state, "reasoning");
13377
+ state.current.text += delta;
13378
+ return [...opened, {
13379
+ type: "reasoning-delta",
13380
+ index: state.current.index,
13381
+ text: delta
13382
+ }];
13383
+ }
13384
+ if (type === "response.output_item.added") {
13385
+ const item = isRecord$19(parsed.item) ? parsed.item : null;
13386
+ if (item === null || item.type !== "function_call") return [];
13387
+ return startResponsesToolCall(state, parsed, item);
13388
+ }
13389
+ if (type === "response.function_call_arguments.delta") {
13390
+ const delta = typeof parsed.delta === "string" ? parsed.delta : "";
13391
+ const call = responsesToolCall(state, parsed.item_id ?? parsed.call_id);
13392
+ if (call === void 0 || delta === "") return [];
13393
+ call.arguments += delta;
13394
+ return [{
13395
+ type: "tool-call-delta",
13396
+ index: call.blockIndex,
13397
+ id: toToolCallId(call.id),
13398
+ name: call.name,
13399
+ argumentsDelta: delta
13400
+ }];
13401
+ }
13402
+ if (type === "response.function_call_arguments.done") {
13403
+ const call = responsesToolCall(state, parsed.item_id ?? parsed.call_id);
13404
+ if (call === void 0) return [];
13405
+ if (typeof parsed.arguments === "string") call.arguments = parsed.arguments;
13406
+ return [];
13407
+ }
13408
+ if (type === "response.completed" || type === "response.incomplete") {
13409
+ const response = isRecord$19(parsed.response) ? parsed.response : null;
13410
+ if (response !== null) {
13411
+ if (response.status === "failed") {
13412
+ state.done = true;
13413
+ const errorObj = isRecord$19(response.error) ? response.error : null;
13414
+ const detail = errorObj && typeof errorObj.message === "string" ? errorObj.message : typeof response.error === "string" ? response.error : "";
13415
+ throw new LlmError$1(detail === "" ? "Command Code responses stream failed" : `Command Code responses stream failed: ${detail}`, "PROVIDER_ERROR");
13416
+ }
13417
+ readResponsesUsage(response, state);
13418
+ }
13419
+ state.done = true;
13420
+ if (type === "response.incomplete") {
13421
+ const details = response && isRecord$19(response.incomplete_details) ? response.incomplete_details : null;
13422
+ const reason = details && typeof details.reason === "string" ? details.reason : "";
13423
+ state.finishReason = reason === "max_output_tokens" || reason === "length" || reason === "max_tokens" ? "length" : reason || "length";
13424
+ } else state.finishReason = "stop";
13425
+ return [];
13426
+ }
13427
+ if (type === "response.failed" || type === "error") {
13428
+ state.done = true;
13429
+ const errorObj = isRecord$19(parsed.error) ? parsed.error : isRecord$19(parsed.response) && isRecord$19(parsed.response.error) ? parsed.response.error : null;
13430
+ const detail = (typeof parsed.message === "string" ? parsed.message : "") || (errorObj && typeof errorObj.message === "string" ? errorObj.message : "") || (isRecord$19(parsed.response) && typeof parsed.response.error === "string" ? parsed.response.error : typeof parsed.error === "string" ? parsed.error : "");
13431
+ throw new LlmError$1(detail === "" ? "Command Code responses stream failed" : `Command Code responses stream failed: ${detail}`, "PROVIDER_ERROR");
13432
+ }
13433
+ return [];
13434
+ }
13435
+ function openBlock(state, type) {
13436
+ if (state.current !== null && state.current.type === type) return [];
13437
+ const out = closeCurrent$3(state);
13438
+ state.current = {
13439
+ index: state.blocks.length,
13440
+ type,
13441
+ text: ""
13442
+ };
13443
+ state.blocks.push({
13444
+ type,
13445
+ text: ""
13446
+ });
13447
+ out.push({
13448
+ type: "block-start",
13449
+ index: state.current.index,
13450
+ blockType: type
13451
+ });
13452
+ return out;
13453
+ }
13454
+ function startResponsesToolCall(state, event, item) {
13455
+ const out = closeCurrent$3(state);
13456
+ const index = state.blocks.length;
13457
+ const name = typeof item.name === "string" ? item.name : "";
13458
+ const itemId = typeof item.id === "string" ? item.id : typeof event.item_id === "string" ? event.item_id : null;
13459
+ const call = {
13460
+ blockIndex: index,
13461
+ id: typeof item.call_id === "string" ? item.call_id : `call_${String(itemId ?? index)}`,
13462
+ name,
13463
+ arguments: "",
13464
+ started: true
13465
+ };
13466
+ state.blocks.push({
13467
+ type: "tool-call",
13468
+ id: toToolCallId(call.id),
13469
+ name,
13470
+ arguments: ""
13471
+ });
13472
+ state.toolCalls.set(index, call);
13473
+ if (itemId !== null) responsesItemIndexOf(state).set(itemId, call);
13474
+ state.hasToolCall = true;
13475
+ out.push({
13476
+ type: "block-start",
13477
+ index,
13478
+ blockType: "tool-call"
13479
+ });
13480
+ out.push({
13481
+ type: "tool-call-delta",
13482
+ index,
13483
+ id: toToolCallId(call.id),
13484
+ name,
13485
+ argumentsDelta: ""
13486
+ });
13487
+ return out;
13488
+ }
13489
+ /** Item id -> call, held beside the stream state that produced it. */
13490
+ const responsesItemIndex = /* @__PURE__ */ new WeakMap();
13491
+ function responsesItemIndexOf(state) {
13492
+ let index = responsesItemIndex.get(state);
13493
+ if (index === void 0) {
13494
+ index = /* @__PURE__ */ new Map();
13495
+ responsesItemIndex.set(state, index);
13496
+ }
13497
+ return index;
13498
+ }
13499
+ /**
13500
+ * The call a function-arguments event refers to.
13501
+ *
13502
+ * The event carries `item_id` while the recorded call carries `call_id`, so
13503
+ * matching on the call id alone silently dropped every argument delta. The
13504
+ * call-id fallback stays for a provider that only sends one of the two.
13505
+ */
13506
+ function responsesToolCall(state, key) {
13507
+ if (typeof key !== "string" || key === "") return void 0;
13508
+ const match = responsesItemIndex.get(state)?.get(key);
13509
+ if (match !== void 0) return match;
13510
+ for (const call of state.toolCalls.values()) if (call.id === toToolCallId(key)) return call;
13511
+ }
13512
+ function readResponsesUsage(response, state) {
13513
+ const usage = isRecord$19(response.usage) ? response.usage : null;
13514
+ if (usage === null) return;
13515
+ const cached = isRecord$19(usage.input_tokens_details) ? numberOrZero(usage.input_tokens_details.cached_tokens) : 0;
13516
+ const written = isRecord$19(usage.input_tokens_details) ? numberOrZero(usage.input_tokens_details.cache_write_tokens) : 0;
13517
+ state.inputTokens = Math.max(0, numberOrZero(usage.input_tokens) - cached - written);
13518
+ state.cacheReadTokens = cached;
13519
+ state.cacheWriteTokens = written;
13520
+ state.outputTokens = numberOrZero(usage.output_tokens);
13521
+ const outputDetails = isRecord$19(usage.output_tokens_details) ? usage.output_tokens_details : null;
13522
+ if (outputDetails !== null) state.reasoningTokens = numberOrZero(outputDetails.reasoning_tokens);
13523
+ state.sawUsage = true;
13524
+ }
13525
+ function numberOrZero(value) {
13526
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
13527
+ }
12214
13528
  /** Flush every open block, then emit usage and the terminal finish. */
12215
13529
  function closeStream$4(state) {
12216
13530
  if (state.finished) return [];
@@ -12231,6 +13545,213 @@ function assertStreamComplete$4(state) {
12231
13545
  if (!state.done && state.finishReason === null) throw new LlmError$1("Command Code stream ended before its terminal event", "PROVIDER_ERROR");
12232
13546
  }
12233
13547
  //#endregion
13548
+ //#region src/host/common/capabilities.ts
13549
+ /** Error thrown by requireCapability when a capability contract fails. */
13550
+ var CapabilityError = class extends Error {
13551
+ query;
13552
+ evaluation;
13553
+ constructor(message, query, evaluation) {
13554
+ super(message);
13555
+ this.name = "CapabilityError";
13556
+ this.query = query;
13557
+ this.evaluation = evaluation;
13558
+ }
13559
+ };
13560
+ /** Known Ollama vision model prefixes per official model documentation. */
13561
+ const KNOWN_OLLAMA_VISION_PREFIXES = [
13562
+ "llava",
13563
+ "bakllava",
13564
+ "llama3.2-vision",
13565
+ "moondream",
13566
+ "minicpm-v",
13567
+ "qwen2.5-vl",
13568
+ "qwen2-vl"
13569
+ ];
13570
+ /**
13571
+ * Matcher for Ollama vision models, aligned with src/host/ollama/types.ts.
13572
+ * Checks exact base name before ':' or '/' to prevent partial hijacking (e.g. 'llava-evil').
13573
+ */
13574
+ function isKnownOllamaVisionModel(modelId) {
13575
+ const base = modelId.toLowerCase().trim().split(":")[0]?.trim() ?? "";
13576
+ return KNOWN_OLLAMA_VISION_PREFIXES.includes(base);
13577
+ }
13578
+ /** Ollama thinking models with level control ('low'|'medium'|'high'). */
13579
+ const KNOWN_OLLAMA_THINKING_LEVELS_PREFIXES = ["gpt-oss"];
13580
+ /** Ollama thinking models with boolean control (true|false). */
13581
+ const KNOWN_OLLAMA_THINKING_BOOLEAN_PREFIXES = [
13582
+ "deepseek-r1",
13583
+ "qwq",
13584
+ "qwen3"
13585
+ ];
13586
+ /** Matcher for Ollama thinking models, aligned with src/host/ollama/types.ts. */
13587
+ function isKnownOllamaThinkingModel(modelId) {
13588
+ const base = modelId.toLowerCase().trim().split(":")[0]?.trim() ?? "";
13589
+ return KNOWN_OLLAMA_THINKING_LEVELS_PREFIXES.includes(base) || KNOWN_OLLAMA_THINKING_BOOLEAN_PREFIXES.includes(base);
13590
+ }
13591
+ /** Immutable validated capability evidence registry. */
13592
+ const CAPABILITY_EVIDENCE_REGISTRY = Object.freeze([
13593
+ {
13594
+ id: "ollama-native-image",
13595
+ provider: "ollama",
13596
+ wire: ["openai", "native"],
13597
+ modelSelector: isKnownOllamaVisionModel,
13598
+ authMode: "api-key",
13599
+ capability: "native-image",
13600
+ status: "supported",
13601
+ evidenceLevel: "offline-contract",
13602
+ implemented: true,
13603
+ date: "2026-10-03",
13604
+ testPath: "test/ollama-projection.test.ts",
13605
+ versionTag: "0.2.0-rc.2",
13606
+ notes: "Supported strictly for verified vision model prefixes (llava, bakllava, llama3.2-vision, moondream, minicpm-v, qwen2.5-vl, qwen2-vl). Exact boundary prevents llava-evil."
13607
+ },
13608
+ {
13609
+ id: "ollama-thinking-control",
13610
+ provider: "ollama",
13611
+ wire: ["openai", "native"],
13612
+ modelSelector: isKnownOllamaThinkingModel,
13613
+ authMode: "api-key",
13614
+ capability: "thinking-control",
13615
+ status: "supported",
13616
+ evidenceLevel: "offline-contract",
13617
+ implemented: true,
13618
+ date: "2026-10-03",
13619
+ testPath: "test/ollama-projection.test.ts",
13620
+ link: "https://github.com/ollama/ollama/blob/main/docs/capabilities/thinking.mdx",
13621
+ versionTag: "0.2.0-rc.2",
13622
+ notes: "Model-level think control for gpt-oss (levels) and deepseek-r1/qwq/qwen3 (boolean). Replay across turns remains unsupported."
13623
+ },
13624
+ {
13625
+ id: "command-code-zdr",
13626
+ provider: "command-code",
13627
+ wire: [
13628
+ "responses",
13629
+ "anthropic",
13630
+ "openai",
13631
+ "chat-completions",
13632
+ "messages"
13633
+ ],
13634
+ modelSelector: "*",
13635
+ authMode: [
13636
+ "api-key",
13637
+ "token",
13638
+ "oauth"
13639
+ ],
13640
+ capability: "zdr",
13641
+ status: "supported",
13642
+ evidenceLevel: "offline-contract",
13643
+ implemented: true,
13644
+ date: "2026-10-03",
13645
+ testPath: "test/command-code-adapter.test.ts",
13646
+ link: "https://github.com/CommandCodeAI/pi-commandcode-provider/blob/main/index.ts",
13647
+ versionTag: "0.2.0-rc.2",
13648
+ notes: "Official contract: x-cmd-zdr: 1 header active across provider wires. 422 cmd_zdr_no_providers cannot silently degrade."
13649
+ }
13650
+ ].map((r) => Object.freeze({
13651
+ ...r,
13652
+ wire: Array.isArray(r.wire) ? Object.freeze([...r.wire]) : r.wire,
13653
+ authMode: Array.isArray(r.authMode) ? Object.freeze([...r.authMode]) : r.authMode,
13654
+ modelSelector: Array.isArray(r.modelSelector) ? Object.freeze([...r.modelSelector]) : r.modelSelector
13655
+ })));
13656
+ function matchesWire(expected, actual) {
13657
+ const normActual = actual.toLowerCase().trim();
13658
+ if (typeof expected === "string") {
13659
+ if (expected === "*") return true;
13660
+ return expected.toLowerCase().trim() === normActual;
13661
+ }
13662
+ return expected.some((w) => w.toLowerCase().trim() === normActual);
13663
+ }
13664
+ function matchesAuth(expected, actual) {
13665
+ const normActual = actual.toLowerCase().trim();
13666
+ if (typeof expected === "string") {
13667
+ if (expected === "*") return true;
13668
+ return expected.toLowerCase().trim() === normActual;
13669
+ }
13670
+ return expected.some((a) => a.toLowerCase().trim() === normActual);
13671
+ }
13672
+ function matchesModel(selector, model) {
13673
+ const normModel = model.toLowerCase().trim();
13674
+ if (typeof selector === "function") return selector(model);
13675
+ if (typeof selector === "string") {
13676
+ if (selector === "*") return true;
13677
+ const normSelector = selector.toLowerCase().trim();
13678
+ return normModel === normSelector || normModel.startsWith(normSelector + ":") || normModel.startsWith(normSelector + "/");
13679
+ }
13680
+ return selector.some((s) => {
13681
+ const normS = s.toLowerCase().trim();
13682
+ return normModel === normS || normModel.startsWith(normS + ":") || normModel.startsWith(normS + "/");
13683
+ });
13684
+ }
13685
+ /** Query evidence records matching the given criteria. Supports optional pure registry argument for tests. */
13686
+ function queryCapabilityEvidence(query, registry = CAPABILITY_EVIDENCE_REGISTRY) {
13687
+ return registry.filter((rec) => {
13688
+ if (query.provider && rec.provider.toLowerCase() !== query.provider.toLowerCase().trim()) return false;
13689
+ if (query.capability && rec.capability !== query.capability) return false;
13690
+ if (query.wire && !matchesWire(rec.wire, query.wire)) return false;
13691
+ if (query.authMode && !matchesAuth(rec.authMode, query.authMode)) return false;
13692
+ if (query.model && !matchesModel(rec.modelSelector, query.model)) return false;
13693
+ return true;
13694
+ });
13695
+ }
13696
+ /**
13697
+ * Evaluate whether a capability is permitted for a concrete request target.
13698
+ * Fails closed if query is missing explicit fields, or lacks matching verified evidence.
13699
+ */
13700
+ function evaluateCapability(query, registry = CAPABILITY_EVIDENCE_REGISTRY) {
13701
+ if (!query || typeof query.provider !== "string" || !query.provider.trim() || typeof query.wire !== "string" || !query.wire.trim() || typeof query.model !== "string" || !query.model.trim() || typeof query.authMode !== "string" || !query.authMode.trim() || typeof query.capability !== "string" || !query.capability.trim()) return {
13702
+ allowed: false,
13703
+ state: "unknown",
13704
+ implemented: false,
13705
+ reason: "Capability query requires non-empty provider, wire, model, authMode, and capability"
13706
+ };
13707
+ const matching = queryCapabilityEvidence(query, registry);
13708
+ if (matching.length === 0) return {
13709
+ allowed: false,
13710
+ state: "unknown",
13711
+ implemented: false,
13712
+ reason: `No matching verified evidence for ${query.provider}/${query.wire}/${query.model} (auth: ${query.authMode}, capability: ${query.capability}).`
13713
+ };
13714
+ const record = matching[0];
13715
+ const isSupportedLevel = record.evidenceLevel === "live-validated" || record.evidenceLevel === "offline-contract";
13716
+ const isAllowed = record.implemented && record.status === "supported" && isSupportedLevel;
13717
+ return {
13718
+ allowed: isAllowed,
13719
+ state: record.status,
13720
+ implemented: record.implemented,
13721
+ evidenceLevel: record.evidenceLevel,
13722
+ evidence: record,
13723
+ reason: isAllowed ? void 0 : record.notes ?? `Capability '${query.capability}' is not permitted.`
13724
+ };
13725
+ }
13726
+ /**
13727
+ * Require that a capability is supported and allowed. Throws CapabilityError otherwise.
13728
+ */
13729
+ function requireCapability(query, registry = CAPABILITY_EVIDENCE_REGISTRY) {
13730
+ const result = evaluateCapability(query, registry);
13731
+ if (!result.allowed) throw new CapabilityError(`Capability requirement rejected: '${query.capability}' on ${query.provider} (${query.wire}/${query.model}, auth: ${query.authMode}): ${result.reason ?? "not permitted"}`, query, result);
13732
+ return result;
13733
+ }
13734
+ /** Whether Ollama image input is supported for the target model. */
13735
+ function supportsOllamaImage(model, wire = "openai", authMode = "api-key") {
13736
+ return evaluateCapability({
13737
+ provider: "ollama",
13738
+ wire,
13739
+ model,
13740
+ authMode,
13741
+ capability: "native-image"
13742
+ }).allowed;
13743
+ }
13744
+ /** Whether Ollama thinking control is supported for the target model. */
13745
+ function supportsOllamaThinkingControl(model, wire = "openai", authMode = "api-key") {
13746
+ return evaluateCapability({
13747
+ provider: "ollama",
13748
+ wire,
13749
+ model,
13750
+ authMode,
13751
+ capability: "thinking-control"
13752
+ }).allowed;
13753
+ }
13754
+ //#endregion
12234
13755
  //#region src/host/command-code/adapter.ts
12235
13756
  /**
12236
13757
  * Transient-failure retry policy for the `command-code` route.
@@ -12264,6 +13785,41 @@ const RETRY_POLICY$5 = resolveRetryPolicy({
12264
13785
  function resolveDefaultReasoningEffort$3(efforts, configuredEffort) {
12265
13786
  if (configuredEffort && efforts.includes(configuredEffort)) return ReasoningEffortId(configuredEffort);
12266
13787
  }
13788
+ /**
13789
+ * Resolves whether Zero Data Retention (ZDR) routing is active for Command Code.
13790
+ * An explicit boolean on adapter options takes precedence over host environment variables.
13791
+ * When not specified in options, DSH_COMMAND_CODE_ZDR is consulted first, then CMD_ZDR.
13792
+ */
13793
+ function resolveZeroDataRetention(optionsZdr) {
13794
+ if (optionsZdr !== void 0) return optionsZdr;
13795
+ if (process.env.DSH_COMMAND_CODE_ZDR !== void 0) return process.env.DSH_COMMAND_CODE_ZDR === "1";
13796
+ return process.env.CMD_ZDR === "1";
13797
+ }
13798
+ /**
13799
+ * Classifies model-specific access or plan entitlement denials.
13800
+ * These are failures of permissions/entitlement for this model/account,
13801
+ * not invalid API credentials, so the account must not be marked invalid
13802
+ * and whole-account rotation must not occur.
13803
+ */
13804
+ function isCommandCodeModelAccessDenied(status, detail) {
13805
+ if (status === 403 || status === 404 || status === 400) {
13806
+ const lower = detail.toLowerCase();
13807
+ if (lower.includes("upgrade_required") || lower.includes("not entitled") || lower.includes("entitlement") || lower.includes("model_not_found") || lower.includes("model_not_allowed") || lower.includes("model_access_denied") || lower.includes("permission_denied") || lower.includes("access_denied") || lower.includes("insufficient_permissions") || lower.includes("model") && (lower.includes("access") || lower.includes("permission") || lower.includes("entitled") || lower.includes("forbidden") || lower.includes("not allowed") || lower.includes("unauthorized") || lower.includes("not supported") || lower.includes("plan") || lower.includes("tier")) || lower.includes("plan") && (lower.includes("upgrade") || lower.includes("tier") || lower.includes("include") || lower.includes("support"))) return true;
13808
+ if (status === 403 && !isCommandCodeCredentialInvalid(status, detail)) return true;
13809
+ }
13810
+ return false;
13811
+ }
13812
+ /**
13813
+ * Classifies actual credential invalidity (e.g. 401 Unauthorized, or 403 with explicit key errors).
13814
+ */
13815
+ function isCommandCodeCredentialInvalid(status, detail) {
13816
+ if (status === 401) return true;
13817
+ if (status === 403) {
13818
+ const lower = detail.toLowerCase();
13819
+ if (lower.includes("invalid_api_key") || lower.includes("invalid_token") || lower.includes("bad_api_key") || lower.includes("invalid key") || lower.includes("bad api key") || lower.includes("api key invalid") || lower.includes("key revoked") || lower.includes("revoked_key") || lower.includes("expired_key") || lower.includes("key expired") || lower.includes("token expired") || lower.includes("incorrect api key") || lower.includes("authentication failed")) return true;
13820
+ }
13821
+ return false;
13822
+ }
12267
13823
  /** Cooldown one rate-limited key takes when the provider states no delay. */
12268
13824
  const POOL_COOLDOWN_MS$4 = 15 * 6e4;
12269
13825
  var CommandCodeAdapter = class extends LlmAdapter {
@@ -12380,7 +13936,16 @@ var CommandCodeAdapter = class extends LlmAdapter {
12380
13936
  }
12381
13937
  async *requestStream(options, signal) {
12382
13938
  const fetchFn = this.options.fetchFn ?? fetch;
12383
- const wire = wireForModel$2(options.model);
13939
+ const catalogEntry = (await this.catalog()).find((entry) => entry.id === options.model);
13940
+ const wire = (catalogEntry === void 0 ? void 0 : wireForCatalogEntry$1(catalogEntry)) ?? wireForModel$2(options.model);
13941
+ const isZdr = resolveZeroDataRetention(this.options.zeroDataRetention);
13942
+ if (isZdr) requireCapability({
13943
+ provider: PROVIDER_ID$5,
13944
+ wire,
13945
+ model: options.model,
13946
+ authMode: "api-key",
13947
+ capability: "zdr"
13948
+ });
12384
13949
  const requestOptions = offloadOldestRequestImages$3(normalizeGenerateOptions(options));
12385
13950
  const images = await resolveRequestImages$3(requestOptions, this.options.attachments, signal);
12386
13951
  const body = JSON.stringify(buildRequest$1(requestOptions, wire, images));
@@ -12404,17 +13969,17 @@ var CommandCodeAdapter = class extends LlmAdapter {
12404
13969
  apiKey = effective.credentials.apiKey;
12405
13970
  apiEnv = effective.apiEnv;
12406
13971
  }
12407
- const endpoint = `${providerUrl(apiEnv)}${wire === "anthropic" ? "/messages" : "/chat/completions"}`;
12408
- const headers = wire === "anthropic" ? {
13972
+ const endpoint = `${providerUrl(apiEnv)}${endpointPathFor(wire)}`;
13973
+ const baseHeaders = {
12409
13974
  ...commandCodeHeaders(apiKey),
12410
13975
  "user-agent": PLUGIN_USER_AGENT,
12411
13976
  accept: "text/event-stream",
12412
- "anthropic-version": "2023-06-01"
12413
- } : {
12414
- ...commandCodeHeaders(apiKey),
12415
- "user-agent": PLUGIN_USER_AGENT,
12416
- accept: "text/event-stream"
13977
+ ...isZdr ? { [HEADER_ZDR]: "1" } : {}
12417
13978
  };
13979
+ const headers = wire === "anthropic" ? {
13980
+ ...baseHeaders,
13981
+ "anthropic-version": "2023-06-01"
13982
+ } : baseHeaders;
12418
13983
  try {
12419
13984
  response = await fetchFn(endpoint, {
12420
13985
  method: "POST",
@@ -12428,8 +13993,13 @@ var CommandCodeAdapter = class extends LlmAdapter {
12428
13993
  }
12429
13994
  if (response.ok) break;
12430
13995
  detail = (await response.text().catch(() => "")).slice(0, 600);
13996
+ if (response.status === 422) {
13997
+ if (detail.includes("cmd_zdr_no_providers")) throw new LlmError(`${PROVIDER_NAME$5} rejected request under Zero Data Retention: no ZDR-capable upstream is available for this model (${detail || "cmd_zdr_no_providers"}).`, "PROVIDER_ERROR", { status: 422 });
13998
+ break;
13999
+ }
12431
14000
  if (pool === null || accountId === void 0) break;
12432
- if (response.status === 401 || response.status === 403) {
14001
+ if (isCommandCodeModelAccessDenied(response.status, detail)) break;
14002
+ if (isCommandCodeCredentialInvalid(response.status, detail)) {
12433
14003
  await pool.markAuthFailed(accountId, `${PROVIDER_NAME$5} rejected the stored API key (${response.status}).`, "invalid").catch(() => void 0);
12434
14004
  if (await pool.hasAnotherAvailableAccount(tried)) continue;
12435
14005
  break;
@@ -12444,7 +14014,12 @@ var CommandCodeAdapter = class extends LlmAdapter {
12444
14014
  }
12445
14015
  if (response === void 0 || !response.ok) {
12446
14016
  const status = response?.status ?? 500;
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 });
14017
+ if (status === 422) {
14018
+ if (detail.includes("cmd_zdr_no_providers")) throw new LlmError(`${PROVIDER_NAME$5} rejected request under Zero Data Retention: no ZDR-capable upstream is available for this model (${detail || "cmd_zdr_no_providers"}).`, "PROVIDER_ERROR", { status: 422 });
14019
+ throw new LlmError(`${PROVIDER_NAME$5} validation error (422): ${detail || "Unprocessable Entity"}`, "PROVIDER_ERROR", { status: 422 });
14020
+ }
14021
+ if (isCommandCodeModelAccessDenied(status, detail)) throw new LlmError(`${PROVIDER_NAME$5} access denied for model ${options.model}: this model is not included in the plan or requires higher entitlement (${status}).${detail ? ` ${detail}` : ""}`, "PROVIDER_ERROR", { status });
14022
+ if (isCommandCodeCredentialInvalid(status, detail) || status === 401) throw new LlmError(`${PROVIDER_NAME$5} rejected the stored API key (${status}). Sign in again from Settings > Command Code.${detail ? ` ${detail}` : ""}`, "INVALID_CREDENTIAL", { status });
12448
14023
  if (status === 429) {
12449
14024
  const after = response === void 0 ? void 0 : retryAfterMs$1(response.headers);
12450
14025
  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", {
@@ -12482,8 +14057,16 @@ var CommandCodeAdapter = class extends LlmAdapter {
12482
14057
  }
12483
14058
  }
12484
14059
  };
14060
+ /** Path suffix the chosen route answers on. */
14061
+ function endpointPathFor(wire) {
14062
+ if (wire === "anthropic") return "/messages";
14063
+ if (wire === "responses") return "/responses";
14064
+ return "/chat/completions";
14065
+ }
12485
14066
  function processLine$1(line, state, wire) {
12486
- return wire === "anthropic" ? processAnthropicStreamLine$1(line, state) : processOpenAIStreamLine$1(line, state);
14067
+ if (wire === "anthropic") return processAnthropicStreamLine$1(line, state);
14068
+ if (wire === "responses") return processResponsesStreamLine(line, state);
14069
+ return processOpenAIStreamLine$1(line, state);
12487
14070
  }
12488
14071
  //#endregion
12489
14072
  //#region src/host/command-code/account-pool.ts
@@ -13211,6 +14794,9 @@ async function getCommandCodeWebStatus(store, modelSettings, preferences, option
13211
14794
  }),
13212
14795
  quota: quota ?? null,
13213
14796
  lastFetchedAt: quota?.fetchedAt ?? null,
14797
+ catalogFetchedAt: getCachedCatalogFetchedAt(),
14798
+ catalogStale: isCatalogStale(),
14799
+ ...resolveZeroDataRetention(options.zeroDataRetention) ? { zeroDataRetention: true } : options.zeroDataRetention !== void 0 ? { zeroDataRetention: false } : {},
13214
14800
  models,
13215
14801
  contextWindowOverrides: settings.contextWindowOverrides,
13216
14802
  defaultReasoningEffort: settings.defaultReasoningEffort,
@@ -13571,6 +15157,48 @@ function ollamaHeaders(apiKey) {
13571
15157
  "content-type": "application/json"
13572
15158
  };
13573
15159
  }
15160
+ /**
15161
+ * Tightly-scoped check for known vision-capable models in Ollama.
15162
+ * Conservative allowlist of known vision families; unknown models default to false.
15163
+ */
15164
+ function ollamaModelSupportsImage(modelId) {
15165
+ return supportsOllamaImage(modelId, "openai", "api-key");
15166
+ }
15167
+ /**
15168
+ * Resolve model-specific thinking support in Ollama.
15169
+ * Source: official https://github.com/ollama/ollama/blob/main/docs/capabilities/thinking.mdx
15170
+ * - 'levels': gpt-oss family (values low, medium, high; cannot disable thinking)
15171
+ * - 'boolean': deepseek-r1, qwq, qwen3 families (values true, false)
15172
+ * - 'none': all other / unknown models (omit thinking control entirely)
15173
+ */
15174
+ function thinkingModeForModel(modelId) {
15175
+ if (!supportsOllamaThinkingControl(modelId, "native", "api-key")) return "none";
15176
+ const base = modelId.toLowerCase().split(":")[0] ?? "";
15177
+ if (base === "gpt-oss") return "levels";
15178
+ if (base === "deepseek-r1" || base === "qwq" || base === "qwen3") return "boolean";
15179
+ return "none";
15180
+ }
15181
+ /**
15182
+ * Map reasoning effort to wire think parameter for the target model.
15183
+ * Per Ollama docs:
15184
+ * - gpt-oss: only 'low' | 'medium' | 'high' are valid. gpt-oss cannot disable thinking, so 'none'
15185
+ * as well as 'xhigh'/'max'/garbage MUST be omitted (undefined).
15186
+ * - boolean models: 'none' -> false; 'low'|'medium'|'high'|'auto'|'true' -> true; garbage -> undefined.
15187
+ * - all other models: omit (undefined).
15188
+ */
15189
+ function thinkForModel(modelId, reasoningEffort) {
15190
+ if (reasoningEffort === void 0 || reasoningEffort === null) return void 0;
15191
+ const mode = thinkingModeForModel(modelId);
15192
+ if (mode === "none") return void 0;
15193
+ const effort = String(reasoningEffort).toLowerCase();
15194
+ if (mode === "boolean") {
15195
+ if (effort === "none") return false;
15196
+ if (effort === "low" || effort === "medium" || effort === "high" || effort === "auto" || effort === "true") return true;
15197
+ return;
15198
+ }
15199
+ if (effort === "low" || effort === "medium" || effort === "high") return effort;
15200
+ if (effort === "auto" || effort === "true") return "medium";
15201
+ }
13574
15202
  //#endregion
13575
15203
  //#region src/host/ollama/token-store.ts
13576
15204
  function credentialPath$4() {
@@ -13742,36 +15370,79 @@ function buildBody(wire, request) {
13742
15370
  if (wire === "openai") return buildOpenAIBody(request);
13743
15371
  return buildNativeBody(request);
13744
15372
  }
15373
+ function toBareBase64(image) {
15374
+ const match = /^data:[^;]+;base64,(.+)$/.exec(image);
15375
+ return match ? match[1] : image;
15376
+ }
15377
+ function toDataUrl(image, defaultMediaType = "image/png") {
15378
+ if (image.startsWith("data:") || image.startsWith("http://") || image.startsWith("https://")) return image;
15379
+ return `data:${defaultMediaType};base64,${image}`;
15380
+ }
15381
+ function parseArguments(raw) {
15382
+ if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) return raw;
15383
+ if (raw === void 0 || raw === null || raw === "") return {};
15384
+ if (typeof raw === "string") {
15385
+ const trimmed = raw.trim();
15386
+ if (trimmed === "" || trimmed === "{}") return {};
15387
+ const parsed = JSON.parse(trimmed);
15388
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) return parsed;
15389
+ throw new Error(`Tool call arguments must be a JSON object, got ${typeof parsed}`);
15390
+ }
15391
+ throw new Error(`Invalid tool call arguments: ${String(raw)}`);
15392
+ }
13745
15393
  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
- });
15394
+ const messages = [];
15395
+ let pendingToolImages = [];
15396
+ const flushToolImages = () => {
15397
+ if (pendingToolImages.length > 0) {
15398
+ messages.push({
15399
+ role: "user",
15400
+ content: pendingToolImages.map((image) => ({
15401
+ type: "image_url",
15402
+ image_url: { url: toDataUrl(image) }
15403
+ }))
15404
+ });
15405
+ pendingToolImages = [];
15406
+ }
15407
+ };
15408
+ for (const message of request.messages) {
15409
+ if (message.role === "tool") {
15410
+ messages.push({
15411
+ role: "tool",
15412
+ tool_call_id: message.toolCallId ?? "",
15413
+ content: message.content
15414
+ });
15415
+ if (message.images !== void 0 && message.images.length > 0) pendingToolImages.push(...message.images);
15416
+ continue;
15417
+ }
15418
+ flushToolImages();
15419
+ if (message.role === "assistant" && message.toolCalls !== void 0 && message.toolCalls.length > 0) {
15420
+ messages.push({
15421
+ role: "assistant",
15422
+ content: message.content,
15423
+ tool_calls: message.toolCalls.map((call) => ({
15424
+ id: call.id,
15425
+ type: "function",
15426
+ function: {
15427
+ name: call.name,
15428
+ arguments: typeof call.arguments === "string" ? call.arguments : JSON.stringify(call.arguments ?? {})
15429
+ }
15430
+ }))
15431
+ });
15432
+ continue;
15433
+ }
15434
+ messages.push(openAIContent(message));
15435
+ }
15436
+ flushToolImages();
13769
15437
  const body = {
13770
15438
  model: request.model,
13771
15439
  messages,
13772
15440
  stream: true,
13773
15441
  max_tokens: request.maxOutputTokens ?? 8192
13774
15442
  };
15443
+ if (typeof request.think === "string") body.reasoning_effort = request.think;
15444
+ else if (request.think === true) body.reasoning_effort = "auto";
15445
+ else if (request.think === false) body.reasoning_effort = "none";
13775
15446
  if (request.tools !== void 0 && request.tools.length > 0) body.tools = request.tools.map((tool) => ({
13776
15447
  type: "function",
13777
15448
  function: {
@@ -13785,28 +15456,30 @@ function buildOpenAIBody(request) {
13785
15456
  }
13786
15457
  function buildNativeBody(request) {
13787
15458
  const messages = request.messages.map((message) => {
13788
- if (message.role === "tool") return {
13789
- role: "tool",
13790
- content: message.content
13791
- };
15459
+ if (message.role === "tool") {
15460
+ const item = {
15461
+ role: "tool",
15462
+ content: message.content
15463
+ };
15464
+ if (message.images !== void 0 && message.images.length > 0) item.images = message.images.map(toBareBase64);
15465
+ return item;
15466
+ }
13792
15467
  if (message.role === "assistant" && message.toolCalls !== void 0 && message.toolCalls.length > 0) return {
13793
15468
  role: "assistant",
13794
15469
  content: message.content,
13795
15470
  tool_calls: message.toolCalls.map((call) => ({ function: {
13796
15471
  name: call.name,
13797
- arguments: call.arguments
15472
+ arguments: parseArguments(call.arguments)
13798
15473
  } }))
13799
15474
  };
13800
- return {
13801
- role: message.role,
13802
- content: message.content
13803
- };
15475
+ return nativeContent(message);
13804
15476
  });
13805
15477
  const body = {
13806
15478
  model: request.model,
13807
15479
  messages,
13808
15480
  stream: true
13809
15481
  };
15482
+ if (request.think !== void 0) body.think = request.think;
13810
15483
  const options = { num_predict: request.maxOutputTokens ?? 8192 };
13811
15484
  if (request.temperature !== void 0) options.temperature = request.temperature;
13812
15485
  body.options = options;
@@ -13820,6 +15493,42 @@ function buildNativeBody(request) {
13820
15493
  }));
13821
15494
  return body;
13822
15495
  }
15496
+ /**
15497
+ * A user turn on the OpenAI surface: text stays text, images become parts.
15498
+ *
15499
+ * Ollama's OpenAI compatibility takes the standard `image_url` part with a data URL.
15500
+ */
15501
+ function openAIContent(message) {
15502
+ if (message.images === void 0 || message.images.length === 0) return {
15503
+ role: message.role,
15504
+ content: message.content
15505
+ };
15506
+ return {
15507
+ role: message.role,
15508
+ content: [{
15509
+ type: "text",
15510
+ text: message.content
15511
+ }, ...message.images.map((image) => ({
15512
+ type: "image_url",
15513
+ image_url: { url: toDataUrl(image) }
15514
+ }))]
15515
+ };
15516
+ }
15517
+ /**
15518
+ * A user turn on the native surface, where images are a sibling array of raw
15519
+ * base64 strings rather than data URLs or interleaved content parts.
15520
+ */
15521
+ function nativeContent(message) {
15522
+ if (message.images === void 0 || message.images.length === 0) return {
15523
+ role: message.role,
15524
+ content: message.content
15525
+ };
15526
+ return {
15527
+ role: message.role,
15528
+ content: message.content,
15529
+ images: message.images.map(toBareBase64)
15530
+ };
15531
+ }
13823
15532
  /** The endpoint one request goes to. */
13824
15533
  function chatUrl(wire) {
13825
15534
  return wire === "openai" ? `${CLOUD_BASE_URL}${OPENAI_CHAT_PATH}` : `${CLOUD_BASE_URL}${NATIVE_CHAT_PATH}`;
@@ -13936,6 +15645,11 @@ function parseOpenAIChunk(record, pending) {
13936
15645
  type: "text",
13937
15646
  text: fields.content
13938
15647
  });
15648
+ const thinking = typeof fields.thinking === "string" ? fields.thinking : typeof fields.reasoning === "string" ? fields.reasoning : typeof fields.reasoning_content === "string" ? fields.reasoning_content : "";
15649
+ if (thinking !== "") events.push({
15650
+ type: "thinking",
15651
+ text: thinking
15652
+ });
13939
15653
  if (Array.isArray(fields.tool_calls)) for (const raw of fields.tool_calls) accumulateToolCall(raw, pending, true);
13940
15654
  }
13941
15655
  if (typeof entry.finish_reason === "string" && entry.finish_reason !== "") events.push({
@@ -13959,6 +15673,11 @@ function parseNativeChunk(record, pending) {
13959
15673
  type: "text",
13960
15674
  text: fields.content
13961
15675
  });
15676
+ const thinking = typeof fields.thinking === "string" ? fields.thinking : typeof fields.reasoning === "string" ? fields.reasoning : typeof fields.reasoning_content === "string" ? fields.reasoning_content : "";
15677
+ if (thinking !== "") events.push({
15678
+ type: "thinking",
15679
+ text: thinking
15680
+ });
13962
15681
  if (Array.isArray(fields.tool_calls)) for (const raw of fields.tool_calls) accumulateToolCall(raw, pending, false);
13963
15682
  }
13964
15683
  if (record.done === true) events.push({
@@ -14094,6 +15813,7 @@ function createStreamState$4() {
14094
15813
  return {
14095
15814
  nextBlockIndex: 0,
14096
15815
  textBlock: null,
15816
+ thinkingBlock: null,
14097
15817
  toolCalls: /* @__PURE__ */ new Map(),
14098
15818
  hasContent: false
14099
15819
  };
@@ -14101,30 +15821,85 @@ function createStreamState$4() {
14101
15821
  /** Fold one event into chunks, opening and closing blocks as the stream needs. */
14102
15822
  function applyEvent(state, event) {
14103
15823
  if (event.type === "text") return applyText(state, event.text);
15824
+ if (event.type === "thinking") return applyThinking(state, event.text);
14104
15825
  if (event.type === "tool_call") return applyToolCall(state, event.call);
14105
15826
  return [];
14106
15827
  }
15828
+ /**
15829
+ * Fold a thinking delta into its own block.
15830
+ *
15831
+ * The trace and the answer interleave, so a text delta or a tool call closes
15832
+ * the reasoning block: two open blocks at once would hand the caller chunks in
15833
+ * an order the stream never produced.
15834
+ */
15835
+ function applyThinking(state, text) {
15836
+ state.hasContent = true;
15837
+ if (state.thinkingBlock === null) {
15838
+ const out = closeText(state);
15839
+ const index = state.nextBlockIndex;
15840
+ state.nextBlockIndex += 1;
15841
+ state.thinkingBlock = {
15842
+ index,
15843
+ text
15844
+ };
15845
+ return [
15846
+ ...out,
15847
+ {
15848
+ type: "block-start",
15849
+ index,
15850
+ blockType: "reasoning"
15851
+ },
15852
+ {
15853
+ type: "reasoning-delta",
15854
+ index,
15855
+ text
15856
+ }
15857
+ ];
15858
+ }
15859
+ state.thinkingBlock.text += text;
15860
+ return [{
15861
+ type: "reasoning-delta",
15862
+ index: state.thinkingBlock.index,
15863
+ text
15864
+ }];
15865
+ }
15866
+ function closeThinking(state) {
15867
+ const block = state.thinkingBlock;
15868
+ if (block === null) return [];
15869
+ state.thinkingBlock = null;
15870
+ return [{
15871
+ type: "block-end",
15872
+ index: block.index,
15873
+ block: {
15874
+ type: "reasoning",
15875
+ text: block.text
15876
+ }
15877
+ }];
15878
+ }
14107
15879
  function applyText(state, text) {
14108
15880
  state.hasContent = true;
15881
+ const closed = closeThinking(state);
14109
15882
  if (state.textBlock === null) {
15883
+ closed.push({
15884
+ type: "block-start",
15885
+ index: state.nextBlockIndex,
15886
+ blockType: "text"
15887
+ });
14110
15888
  const index = state.nextBlockIndex;
14111
15889
  state.nextBlockIndex += 1;
14112
15890
  state.textBlock = {
14113
15891
  index,
14114
15892
  text
14115
15893
  };
14116
- return [{
14117
- type: "block-start",
14118
- index,
14119
- blockType: "text"
14120
- }, {
15894
+ closed.push({
14121
15895
  type: "text-delta",
14122
15896
  index,
14123
15897
  text
14124
- }];
15898
+ });
15899
+ return closed;
14125
15900
  }
14126
15901
  state.textBlock.text += text;
14127
- return [{
15902
+ return [...closed, {
14128
15903
  type: "text-delta",
14129
15904
  index: state.textBlock.index,
14130
15905
  text
@@ -14133,6 +15908,7 @@ function applyText(state, text) {
14133
15908
  function applyToolCall(state, call) {
14134
15909
  state.hasContent = true;
14135
15910
  const out = [];
15911
+ out.push(...closeThinking(state));
14136
15912
  out.push(...closeText(state));
14137
15913
  const index = state.nextBlockIndex;
14138
15914
  state.nextBlockIndex += 1;
@@ -14195,7 +15971,11 @@ function closeToolCalls$3(state) {
14195
15971
  */
14196
15972
  function closeStream$3(state) {
14197
15973
  const sawToolCall = state.toolCalls.size > 0;
14198
- const out = [...closeText(state), ...closeToolCalls$3(state)];
15974
+ const out = [
15975
+ ...closeThinking(state),
15976
+ ...closeText(state),
15977
+ ...closeToolCalls$3(state)
15978
+ ];
14199
15979
  out.push({
14200
15980
  type: "finish",
14201
15981
  reason: sawToolCall ? { kind: "tool-calls" } : { kind: "stop" }
@@ -14297,7 +16077,7 @@ var OllamaAdapter = class extends LlmAdapter {
14297
16077
  provider: prov,
14298
16078
  id: model.id,
14299
16079
  name: model.name ?? model.id,
14300
- inputModalities: ["text", "image"]
16080
+ inputModalities: ollamaModelSupportsImage(model.id) ? ["text", "image"] : ["text"]
14301
16081
  }));
14302
16082
  }
14303
16083
  async resolveModel(provider, modelId, signal) {
@@ -14307,7 +16087,7 @@ var OllamaAdapter = class extends LlmAdapter {
14307
16087
  provider,
14308
16088
  id: modelId,
14309
16089
  name: entry?.name ?? modelId,
14310
- inputModalities: ["text", "image"],
16090
+ inputModalities: ollamaModelSupportsImage(modelId) ? ["text", "image"] : ["text"],
14311
16091
  context: { contextWindow: contextWindowFor(entry) },
14312
16092
  ...outputReservation(contextWindowFor(entry), DEFAULT_MAX_OUTPUT_TOKENS)
14313
16093
  };
@@ -14321,10 +16101,11 @@ var OllamaAdapter = class extends LlmAdapter {
14321
16101
  async *stream(options) {
14322
16102
  yield* wrapStreamWithWatchdog((watchdogSignal) => this.requestStream(options, watchdogSignal), options.signal, STREAM_IDLE_TIMEOUT_MS$4, STREAM_IDLE_TIMEOUT_CODE$4, PROVIDER_NAME$4);
14323
16103
  }
14324
- async *requestStream(options, signal) {
16104
+ async *requestStream(rawOptions, signal) {
14325
16105
  const fetchFn = this.options.fetchFn ?? fetch;
16106
+ const options = normalizeGenerateOptions(rawOptions);
14326
16107
  const wire = wireForModel$1(options.model);
14327
- const request = toOllamaRequest(options);
16108
+ const request = await toOllamaRequest(options, this.options.attachments, signal);
14328
16109
  const pool = this.accountPool;
14329
16110
  const tried = /* @__PURE__ */ new Set();
14330
16111
  let lastStatus;
@@ -14364,7 +16145,7 @@ var OllamaAdapter = class extends LlmAdapter {
14364
16145
  spent = event.usage;
14365
16146
  continue;
14366
16147
  }
14367
- if (event.type === "text" || event.type === "tool_call") {
16148
+ if (event.type === "text" || event.type === "thinking" || event.type === "tool_call") {
14368
16149
  for (const chunk of applyEvent(state, event)) yield chunk;
14369
16150
  continue;
14370
16151
  }
@@ -14393,18 +16174,134 @@ var OllamaAdapter = class extends LlmAdapter {
14393
16174
  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
16175
  }
14395
16176
  };
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
- });
16177
+ function isAbort$2(error, signal) {
16178
+ return signal?.aborted === true || error instanceof Error && error.name === "AbortError";
16179
+ }
16180
+ function isRecord$18(value) {
16181
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16182
+ }
16183
+ function asString$11(value) {
16184
+ return typeof value === "string" && value !== "" ? value : void 0;
16185
+ }
16186
+ function attachmentLabel$4(block) {
16187
+ const attachment = isRecord$18(block.attachment) ? block.attachment : void 0;
16188
+ return asString$11(attachment?.name) || asString$11(attachment?.attachmentId);
16189
+ }
16190
+ function unavailableImageText$4(block) {
16191
+ const label = attachmentLabel$4(block);
16192
+ return `[image unavailable: ${label ? `${label} could not be read` : "the image could not be read"}; ask the user to attach it again if the image is needed]`;
16193
+ }
16194
+ function abortException(signal) {
16195
+ if (signal?.reason !== void 0) return signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason));
16196
+ return new DOMException("The operation was aborted", "AbortError");
16197
+ }
16198
+ function unsupportedImageText$1(model, block) {
16199
+ const label = attachmentLabel$4(block);
16200
+ return `[image unsupported: ${label ? `${label}: ` : ""}model ${model} is not known to declare image support; remove the attachment or switch to a vision model]`;
16201
+ }
16202
+ function combineTextParts(parts) {
16203
+ let result = "";
16204
+ for (const part of parts) {
16205
+ if (!part) continue;
16206
+ if (!result) result = part;
16207
+ else if (part.startsWith("[image ") || result.endsWith("\n")) result = result.trimEnd() + "\n\n" + part;
16208
+ else result += part;
16209
+ }
16210
+ return result;
16211
+ }
16212
+ async function processMessageContent(content, model, attachments, signal) {
16213
+ if (signal?.aborted) throw abortException(signal);
16214
+ if (typeof content === "string") return { text: content };
16215
+ if (!Array.isArray(content)) return { text: "" };
16216
+ const textParts = [];
16217
+ const images = [];
16218
+ for (const part of content) {
16219
+ if (typeof part === "string") {
16220
+ textParts.push(part);
16221
+ continue;
16222
+ }
16223
+ if (!isRecord$18(part)) continue;
16224
+ if (part.type === "text") {
16225
+ if (typeof part.text === "string" && part.text !== "") textParts.push(part.text);
14406
16226
  continue;
14407
16227
  }
16228
+ if (part.type === "image") {
16229
+ if (!ollamaModelSupportsImage(model)) {
16230
+ textParts.push(unsupportedImageText$1(model, part));
16231
+ continue;
16232
+ }
16233
+ const reference = isImageRef(part.attachment) ? part.attachment : isImageRef(part.source) ? part.source : null;
16234
+ if (reference !== null) {
16235
+ if (!attachments) {
16236
+ textParts.push(unavailableImageText$4(part));
16237
+ continue;
16238
+ }
16239
+ try {
16240
+ if (signal?.aborted) throw abortException(signal);
16241
+ const loaded = await attachments.readImage(reference, signal);
16242
+ if (signal?.aborted) throw abortException(signal);
16243
+ if (loaded === null) textParts.push(unavailableImageText$4(part));
16244
+ else images.push(encodeImage(loaded.data, loaded.ref.mediaType));
16245
+ } catch (error) {
16246
+ if (isAbort$2(error, signal)) throw error;
16247
+ textParts.push(unavailableImageText$4(part));
16248
+ }
16249
+ continue;
16250
+ }
16251
+ const inline = part.source;
16252
+ if (isRecord$18(inline)) {
16253
+ const data = inline.data;
16254
+ if (typeof data === "string" && data !== "") {
16255
+ images.push(encodeImage(data, asMediaType(inline.mediaType)));
16256
+ continue;
16257
+ }
16258
+ }
16259
+ if (typeof part.image === "string" && part.image !== "") {
16260
+ images.push(part.image);
16261
+ continue;
16262
+ }
16263
+ textParts.push(unavailableImageText$4(part));
16264
+ continue;
16265
+ }
16266
+ }
16267
+ return {
16268
+ text: combineTextParts(textParts),
16269
+ ...images.length > 0 ? { images } : {}
16270
+ };
16271
+ }
16272
+ /**
16273
+ * Project DSH's request shape onto Ollama's, per surface.
16274
+ * Consumes the canonical Message vocabulary produced by normalizeGenerateOptions.
16275
+ */
16276
+ async function toOllamaRequest(options, attachments, signal) {
16277
+ if (signal?.aborted) throw abortException(signal);
16278
+ const messages = [];
16279
+ const trimmedSystem = typeof options.system === "string" && options.system.trim() !== "" ? options.system.trim() : void 0;
16280
+ if (trimmedSystem !== void 0) messages.push({
16281
+ role: "system",
16282
+ content: trimmedSystem
16283
+ });
16284
+ for (let index = 0; index < options.messages.length; index++) {
16285
+ const message = options.messages[index];
16286
+ if (index === 0 && trimmedSystem !== void 0 && (message.role === "system" || message.role === "developer")) {
16287
+ if (textOf$4(message.content).trim() === trimmedSystem) continue;
16288
+ }
16289
+ if ((message.source?.kind === "tool" || Array.isArray(message.content) && message.content.some((b) => b.type === "tool-result")) && Array.isArray(message.content)) {
16290
+ const toolResultBlocks = message.content.filter((b) => b.type === "tool-result");
16291
+ if (toolResultBlocks.length > 0) {
16292
+ for (const block of toolResultBlocks) {
16293
+ const toolCallId = String(block.toolCallId ?? (isRecord$18(message.source) ? message.source.callId : "") ?? "");
16294
+ const processed = await processMessageContent(block.content, options.model, attachments, signal);
16295
+ messages.push({
16296
+ role: "tool",
16297
+ content: processed.text,
16298
+ ...toolCallId !== "" ? { toolCallId } : {},
16299
+ ...processed.images ? { images: processed.images } : {}
16300
+ });
16301
+ }
16302
+ continue;
16303
+ }
16304
+ }
14408
16305
  const toolCalls = assistantToolCalls(message.content);
14409
16306
  if (toolCalls.length > 0) {
14410
16307
  messages.push({
@@ -14414,9 +16311,26 @@ function toOllamaRequest(options) {
14414
16311
  });
14415
16312
  continue;
14416
16313
  }
16314
+ if (message.role === "assistant") {
16315
+ messages.push({
16316
+ role: "assistant",
16317
+ content: textOf$4(message.content)
16318
+ });
16319
+ continue;
16320
+ }
16321
+ if (message.role === "system" || message.role === "developer") {
16322
+ const text = textOf$4(message.content).trim();
16323
+ if (text !== "") messages.push({
16324
+ role: "system",
16325
+ content: text
16326
+ });
16327
+ continue;
16328
+ }
16329
+ const processed = await processMessageContent(message.content, options.model, attachments, signal);
14417
16330
  messages.push({
14418
- role: message.role === "developer" ? "system" : message.role,
14419
- content: textOf$4(message.content)
16331
+ role: "user",
16332
+ content: processed.text,
16333
+ ...processed.images ? { images: processed.images } : {}
14420
16334
  });
14421
16335
  }
14422
16336
  const request = {
@@ -14425,6 +16339,8 @@ function toOllamaRequest(options) {
14425
16339
  };
14426
16340
  if (options.maxTokens !== void 0) request.maxOutputTokens = options.maxTokens;
14427
16341
  if (options.temperature !== void 0) request.temperature = options.temperature;
16342
+ const think = thinkForModel(options.model, options.reasoningEffort);
16343
+ if (think !== void 0) request.think = think;
14428
16344
  const tools = options.tools;
14429
16345
  if (Array.isArray(tools) && tools.length > 0) request.tools = tools.map((tool) => ({
14430
16346
  name: tool.name,
@@ -14455,6 +16371,23 @@ function assistantToolCalls(content) {
14455
16371
  }
14456
16372
  return calls;
14457
16373
  }
16374
+ function isImageRef(value) {
16375
+ return typeof value === "object" && value !== null && typeof value.attachmentId === "string";
16376
+ }
16377
+ function asMediaType(value) {
16378
+ return typeof value === "string" && value !== "" ? value : "image/png";
16379
+ }
16380
+ /**
16381
+ * The base64 payload Ollama wants, with or without a data URL prefix.
16382
+ *
16383
+ * The native surface takes the bare payload; a `data:` URL is accepted by both
16384
+ * surfaces once, so one encoding serves either and avoids a route-specific
16385
+ * guess about what a given model accepts.
16386
+ */
16387
+ function encodeImage(data, mediaType) {
16388
+ const base64 = typeof data === "string" ? data : Buffer.from(data).toString("base64");
16389
+ return base64.startsWith("data:") ? base64 : `data:${mediaType};base64,${base64}`;
16390
+ }
14458
16391
  function textOf$4(content) {
14459
16392
  if (typeof content === "string") return content;
14460
16393
  if (Array.isArray(content)) return content.map((part) => {
@@ -20368,6 +22301,22 @@ function redactToken(token) {
20368
22301
  if (token === void 0 || token === null || token === "") return "<none>";
20369
22302
  return "sha256:" + createHash("sha256").update(token).digest("hex").slice(0, 12) + "/len:" + token.length;
20370
22303
  }
22304
+ /**
22305
+ * Adapter-selected auth owner (nonsecret hash).
22306
+ *
22307
+ * Scoped to the specific account and credential token/key selected by the adapter.
22308
+ * Any account change or token/key replacement produces a different hash,
22309
+ * preventing thinking/signature replay across different authentication boundaries.
22310
+ */
22311
+ function computeMinimaxAuthOwner(credentials, accountId) {
22312
+ const token = (credentials?.apiKey || credentials?.accessToken || "").trim();
22313
+ if (!token) return "";
22314
+ const hash = createHash("sha256");
22315
+ if (accountId && accountId.trim() !== "") hash.update(`acc:${accountId}\0`);
22316
+ if (credentials?.region && credentials.region.trim() !== "") hash.update(`reg:${credentials.region}\0`);
22317
+ hash.update(`tok:${token}`);
22318
+ return hash.digest("hex").slice(0, 32);
22319
+ }
20371
22320
  //#endregion
20372
22321
  //#region src/host/minimax-code/mapper.ts
20373
22322
  /**
@@ -20457,6 +22406,31 @@ function safeJsonParse$2(text) {
20457
22406
  function sanitizeText$2(text) {
20458
22407
  return text.replace(/\0/g, "");
20459
22408
  }
22409
+ function jsonSafeValue$1(value, seen = /* @__PURE__ */ new WeakSet()) {
22410
+ if (value === null) return null;
22411
+ switch (typeof value) {
22412
+ case "string":
22413
+ case "boolean": return value;
22414
+ case "number":
22415
+ if (!Number.isFinite(value)) return void 0;
22416
+ return Object.is(value, -0) ? 0 : value;
22417
+ case "object": break;
22418
+ default: return;
22419
+ }
22420
+ if (seen.has(value)) return void 0;
22421
+ seen.add(value);
22422
+ try {
22423
+ if (Array.isArray(value)) return Array.from(value, (item) => jsonSafeValue$1(item, seen) ?? null);
22424
+ const record = {};
22425
+ for (const key of Object.keys(value)) {
22426
+ const child = jsonSafeValue$1(value[key], seen);
22427
+ if (child !== void 0) record[key] = child;
22428
+ }
22429
+ return record;
22430
+ } finally {
22431
+ seen.delete(value);
22432
+ }
22433
+ }
20460
22434
  /** Media types the wire accepts as inline base64. */
20461
22435
  const SUPPORTED_IMAGE_MEDIA_TYPES$2 = /* @__PURE__ */ new Set([
20462
22436
  "image/png",
@@ -20640,9 +22614,92 @@ function anthropicUserContent(message, images) {
20640
22614
  }
20641
22615
  return blocks;
20642
22616
  }
20643
- function anthropicAssistantContent(message) {
22617
+ /**
22618
+ * Native blocks to replay for one assistant turn, as the service issued them.
22619
+ *
22620
+ * Read from the message's stored replay state rather than rebuilt from the
22621
+ * visible reasoning text: a rebuilt block is not the block the model signed, and
22622
+ * the difference is exactly what the provider validates. Falls back to nothing
22623
+ * when a turn carries no stored state, which is the current behaviour for
22624
+ * history this route did not produce.
22625
+ */
22626
+ /** Fingerprint the entire array of emitted visible blocks in an assistant message. */
22627
+ function computeVisibleContentHash(blocks) {
22628
+ if (!Array.isArray(blocks)) return "";
22629
+ const canonical = blocks.map((b) => {
22630
+ if (!isRecord$15(b)) return null;
22631
+ const type = asString$8(b.type) ?? "";
22632
+ if (type === "text" || type === "reasoning") return [type, sanitizeText$2(asString$8(b.text) ?? "")];
22633
+ if (type === "tool-call") return [
22634
+ type,
22635
+ asString$8(b.id) ?? "",
22636
+ asString$8(b.name) ?? "",
22637
+ toolCallArguments$2(b.arguments)
22638
+ ];
22639
+ return [type, jsonSafeValue$1(b)];
22640
+ });
22641
+ return createHash("sha256").update(JSON.stringify(canonical)).digest("hex").slice(0, 32);
22642
+ }
22643
+ /**
22644
+ * Verbatim wire block to replay for one reasoning block.
22645
+ *
22646
+ * Verifies route, model, and auth owner provenance. Fails safely on legacy unscoped state
22647
+ * or any mismatch, dropping the replay block rather than submitting invalid state.
22648
+ * Preserves raw native text and signature binding.
22649
+ */
22650
+ function replayedThinkingBlock$1(message, index, expectedScope, block) {
22651
+ const source = message.source;
22652
+ if (source === void 0 || source === null || !isRecord$15(source)) return void 0;
22653
+ if (source.provider && source.provider !== PROVIDER_ID$2 && source.provider !== "dsh-chatgpt-subscription") return;
22654
+ if (source.model && expectedScope && source.model !== expectedScope.model) return;
22655
+ const state = source.replayState;
22656
+ if (!isRecord$15(state)) return void 0;
22657
+ const resp = isRecord$15(state.response) ? state.response : state;
22658
+ const stateRoute = asString$8(resp.route) ?? asString$8(resp.provider);
22659
+ const stateModel = asString$8(resp.model);
22660
+ const stateAuthOwner = asString$8(resp.authOwner);
22661
+ if (!stateRoute || !stateModel || !stateAuthOwner || stateAuthOwner.trim() === "") return;
22662
+ if (expectedScope === void 0) return void 0;
22663
+ if (expectedScope.route && stateRoute !== expectedScope.route) return void 0;
22664
+ if (expectedScope.model && stateModel !== expectedScope.model) return void 0;
22665
+ if (!expectedScope.authOwner || expectedScope.authOwner.trim() === "" || stateAuthOwner !== expectedScope.authOwner) return;
22666
+ let candidate;
22667
+ if (Array.isArray(state.blocks)) {
22668
+ const entry = state.blocks[index];
22669
+ if (isRecord$15(entry)) candidate = entry;
22670
+ }
22671
+ if (candidate === void 0 && Array.isArray(resp.blocks)) {
22672
+ const entry = resp.blocks[index];
22673
+ if (isRecord$15(entry)) candidate = entry;
22674
+ }
22675
+ if (candidate === void 0 && Array.isArray(resp.nativeBlocks)) {
22676
+ const entry = resp.nativeBlocks[index];
22677
+ if (isRecord$15(entry)) candidate = entry;
22678
+ }
22679
+ if (candidate === void 0) return void 0;
22680
+ if (Array.isArray(state.blocks) && state.blocks.length !== message.content.length) return void 0;
22681
+ if (Array.isArray(resp.blocks) && resp.blocks.length !== message.content.length) return void 0;
22682
+ const stateVisibleHash = asString$8(resp.visibleContentHash);
22683
+ if (!stateVisibleHash || stateVisibleHash.trim() === "") return void 0;
22684
+ if (computeVisibleContentHash(message.content) !== stateVisibleHash) return void 0;
22685
+ if (candidate.type !== "thinking" && candidate.type !== "redacted_thinking") return;
22686
+ const visibleText = asString$8(block?.text) ?? "";
22687
+ if (candidate.type === "redacted_thinking") {
22688
+ if (visibleText !== "") return void 0;
22689
+ const data = asString$8(candidate.data);
22690
+ if (!data || data.trim() === "") return void 0;
22691
+ return structuredClone(candidate);
22692
+ }
22693
+ if (sanitizeText$2(asString$8(candidate.thinking) ?? "") !== sanitizeText$2(visibleText)) return;
22694
+ const signature = asString$8(candidate.signature);
22695
+ if (!signature || signature.trim() === "") return void 0;
22696
+ return structuredClone(candidate);
22697
+ }
22698
+ function anthropicAssistantContent(message, expectedScope) {
20644
22699
  const blocks = [];
20645
- for (const block of message.content) {
22700
+ if (!Array.isArray(message.content)) return blocks;
22701
+ for (let index = 0; index < message.content.length; index++) {
22702
+ const block = message.content[index];
20646
22703
  if (!isRecord$15(block)) continue;
20647
22704
  if (block.type === "text" && typeof block.text === "string") {
20648
22705
  const text = sanitizeText$2(block.text);
@@ -20660,6 +22717,12 @@ function anthropicAssistantContent(message) {
20660
22717
  name: block.name,
20661
22718
  input: isRecord$15(parsed) ? parsed : {}
20662
22719
  });
22720
+ continue;
22721
+ }
22722
+ if (block.type === "reasoning") {
22723
+ const replay = replayedThinkingBlock$1(message, index, expectedScope, block);
22724
+ if (replay !== void 0) blocks.push(replay);
22725
+ continue;
20663
22726
  }
20664
22727
  }
20665
22728
  return blocks;
@@ -20808,6 +22871,12 @@ function markMinimaxCacheBreakpoints(body) {
20808
22871
  }
20809
22872
  /** Build one Anthropic Messages body for this subscription. */
20810
22873
  function buildMinimaxRequest(options, images = NO_RESOLVED_IMAGES$2, request = {}) {
22874
+ const expectedScope = request.authOwner !== void 0 ? {
22875
+ route: request.route ?? PROVIDER_ID$2,
22876
+ model: options.model,
22877
+ authOwner: request.authOwner,
22878
+ provider: PROVIDER_ID$2
22879
+ } : void 0;
20811
22880
  const entries = [];
20812
22881
  for (const message of nonSystemMessages$2(options)) {
20813
22882
  if (isToolResultMessage$2(message)) {
@@ -20819,7 +22888,7 @@ function buildMinimaxRequest(options, images = NO_RESOLVED_IMAGES$2, request = {
20819
22888
  }
20820
22889
  entries.push({
20821
22890
  role: message.role === "assistant" ? "assistant" : "user",
20822
- content: message.role === "assistant" ? anthropicAssistantContent(message) : anthropicUserContent(message, images)
22891
+ content: message.role === "assistant" ? anthropicAssistantContent(message, expectedScope) : anthropicUserContent(message, images)
20823
22892
  });
20824
22893
  }
20825
22894
  const system = leadingSystemText$2(options);
@@ -20867,8 +22936,16 @@ function assertRequestBodyFits(body, limit = maxMessageBodyBytes()) {
20867
22936
  if (bytes <= limit) return serialized;
20868
22937
  throw new LlmError$1("MiniMax Code(编程订阅) request was not sent: the serialized body is " + bytes + " bytes, above the " + limit + "-byte ceiling this route enforces. Compact the conversation or start a new session, and check for large tool results or attached media.", "PROVIDER_ERROR");
20869
22938
  }
20870
- function createStreamState() {
22939
+ function createStreamState(options) {
20871
22940
  return {
22941
+ provenance: options?.model && options?.authOwner ? {
22942
+ route: options.route ?? PROVIDER_ID$2,
22943
+ model: options.model,
22944
+ authOwner: options.authOwner,
22945
+ provider: options.provider ?? PROVIDER_ID$2
22946
+ } : void 0,
22947
+ replayBlocks: [],
22948
+ pendingThinking: /* @__PURE__ */ new Map(),
20872
22949
  blocks: [],
20873
22950
  current: null,
20874
22951
  toolCalls: /* @__PURE__ */ new Map(),
@@ -20912,6 +22989,7 @@ function openTextBlock$1(state, type) {
20912
22989
  type,
20913
22990
  text: ""
20914
22991
  });
22992
+ state.replayBlocks[index] = null;
20915
22993
  out.push({
20916
22994
  type: "block-start",
20917
22995
  index,
@@ -20929,6 +23007,7 @@ function closeToolCalls$1(state) {
20929
23007
  arguments: call.arguments === "" ? "{}" : call.arguments
20930
23008
  };
20931
23009
  state.blocks[call.blockIndex] = block;
23010
+ state.replayBlocks[call.blockIndex] = null;
20932
23011
  out.push({
20933
23012
  type: "block-end",
20934
23013
  index: call.blockIndex,
@@ -20961,9 +23040,34 @@ function closeMinimaxStream(state) {
20961
23040
  type: "usage",
20962
23041
  usage: tokenUsage$2(state)
20963
23042
  });
23043
+ const paddedBlocks = state.blocks.map((_, i) => {
23044
+ const b = state.replayBlocks[i];
23045
+ return b !== void 0 && b !== null ? jsonSafeValue$1(b) : null;
23046
+ });
23047
+ const replayThinking = paddedBlocks.filter((b) => b !== null);
23048
+ const hasReplay = replayThinking.length > 0;
23049
+ const visibleContentHash = computeVisibleContentHash(state.blocks);
23050
+ const replayState = hasReplay ? state.provenance ? {
23051
+ response: {
23052
+ provider: state.provenance.provider ?? PROVIDER_ID$2,
23053
+ route: state.provenance.route ?? PROVIDER_ID$2,
23054
+ model: state.provenance.model,
23055
+ authOwner: state.provenance.authOwner,
23056
+ visibleContentHash,
23057
+ minimaxThinking: replayThinking
23058
+ },
23059
+ blocks: paddedBlocks
23060
+ } : {
23061
+ response: {
23062
+ visibleContentHash,
23063
+ minimaxThinking: replayThinking
23064
+ },
23065
+ blocks: paddedBlocks
23066
+ } : void 0;
20964
23067
  out.push({
20965
23068
  type: "finish",
20966
- reason: finishReasonFor$2(state)
23069
+ reason: finishReasonFor$2(state),
23070
+ ...replayState !== void 0 ? { replayState } : {}
20967
23071
  });
20968
23072
  return out;
20969
23073
  }
@@ -21034,8 +23138,40 @@ function processMinimaxStreamLine(line, state) {
21034
23138
  return out;
21035
23139
  }
21036
23140
  if (blockType === "thinking" || blockType === "redacted_thinking") {
21037
- out.push(...openTextBlock$1(state, "reasoning"));
21038
- state.contentIndexes.set(contentIndex, state.current.index);
23141
+ const index = state.blocks.length;
23142
+ const kind = blockType === "redacted_thinking" ? "redacted_thinking" : "thinking";
23143
+ const data = asString$8(block.data) ?? "";
23144
+ const signature = asString$8(block.signature) ?? "";
23145
+ const thinking = asString$8(block.thinking) ?? "";
23146
+ state.current = {
23147
+ index,
23148
+ type: "reasoning",
23149
+ text: kind === "thinking" ? thinking : ""
23150
+ };
23151
+ state.blocks.push({
23152
+ type: "reasoning",
23153
+ text: state.current.text
23154
+ });
23155
+ const initialReplay = isRecord$15(event.content_block) ? structuredClone(event.content_block) : { type: blockType };
23156
+ state.replayBlocks[index] = initialReplay;
23157
+ state.pendingThinking.set(contentIndex, {
23158
+ dshIndex: index,
23159
+ kind,
23160
+ text: thinking,
23161
+ signature,
23162
+ data
23163
+ });
23164
+ state.contentIndexes.set(contentIndex, index);
23165
+ out.push({
23166
+ type: "block-start",
23167
+ index,
23168
+ blockType: "reasoning"
23169
+ });
23170
+ if (state.current.text !== "") out.push({
23171
+ type: "reasoning-delta",
23172
+ index,
23173
+ text: state.current.text
23174
+ });
21039
23175
  return out;
21040
23176
  }
21041
23177
  out.push(...openTextBlock$1(state, "text"));
@@ -21061,8 +23197,37 @@ function processMinimaxStreamLine(line, state) {
21061
23197
  }
21062
23198
  return out;
21063
23199
  }
23200
+ if (deltaType === "signature_delta") {
23201
+ const signature = asString$8(delta.signature) ?? "";
23202
+ const pending = state.pendingThinking.get(contentIndex);
23203
+ if (pending !== void 0 && signature !== "") {
23204
+ pending.signature += signature;
23205
+ const replay = state.replayBlocks[pending.dshIndex];
23206
+ if (replay !== void 0 && replay !== null) replay.signature = (asString$8(replay.signature) ?? "") + signature;
23207
+ }
23208
+ return out;
23209
+ }
23210
+ if (deltaType === "data_delta") {
23211
+ const data = asString$8(delta.data) ?? "";
23212
+ const pending = state.pendingThinking.get(contentIndex);
23213
+ if (pending !== void 0 && data !== "") {
23214
+ pending.data = (pending.data ?? "") + data;
23215
+ const replay = state.replayBlocks[pending.dshIndex];
23216
+ if (replay !== void 0 && replay !== null) replay.data = (asString$8(replay.data) ?? "") + data;
23217
+ }
23218
+ return out;
23219
+ }
21064
23220
  const text = deltaType === "thinking_delta" ? asString$8(delta.thinking) : asString$8(delta.text);
21065
23221
  if (text !== void 0 && text !== "") {
23222
+ const pending = state.pendingThinking.get(contentIndex);
23223
+ if (pending !== void 0) {
23224
+ pending.text += text;
23225
+ const replay = state.replayBlocks[pending.dshIndex];
23226
+ if (replay !== void 0 && replay !== null) {
23227
+ const field = replay.type === "redacted_thinking" ? "data" : "thinking";
23228
+ replay[field] = (asString$8(replay[field]) ?? "") + text;
23229
+ }
23230
+ }
21066
23231
  const index = state.contentIndexes.get(contentIndex) ?? state.current?.index;
21067
23232
  const kind = deltaType === "thinking_delta" ? "reasoning" : "text";
21068
23233
  if (state.current === null || state.current.index !== index) {
@@ -21077,6 +23242,7 @@ function processMinimaxStreamLine(line, state) {
21077
23242
  type: kind,
21078
23243
  text: ""
21079
23244
  });
23245
+ state.replayBlocks[next] = null;
21080
23246
  state.contentIndexes.set(contentIndex, next);
21081
23247
  out.push({
21082
23248
  type: "block-start",
@@ -21159,7 +23325,7 @@ const MINIMAX_CODE_MODELS = [
21159
23325
  maxAttachments: null,
21160
23326
  maxImageBytes: null,
21161
23327
  maxVideoBytes: null,
21162
- supportsFilesApi: false,
23328
+ filesApiDocumented: false,
21163
23329
  description: "Thinking is always on and has no selectable level. Text input only."
21164
23330
  },
21165
23331
  {
@@ -21175,7 +23341,7 @@ const MINIMAX_CODE_MODELS = [
21175
23341
  maxAttachments: null,
21176
23342
  maxImageBytes: null,
21177
23343
  maxVideoBytes: null,
21178
- supportsFilesApi: false,
23344
+ filesApiDocumented: false,
21179
23345
  description: "The high-speed M2.7. Thinking is always on with no selectable level. Text input only."
21180
23346
  },
21181
23347
  {
@@ -21191,7 +23357,7 @@ const MINIMAX_CODE_MODELS = [
21191
23357
  maxAttachments: 9,
21192
23358
  maxImageBytes: 10 * 1024 * 1024,
21193
23359
  maxVideoBytes: 50 * 1024 * 1024,
21194
- supportsFilesApi: true,
23360
+ filesApiDocumented: true,
21195
23361
  description: "Flagship subscription model. Thinking is on by default and can be disabled. Accepts text, image and video; up to 9 attachments, 10MB per image and 50MB per video."
21196
23362
  },
21197
23363
  {
@@ -21214,7 +23380,7 @@ const MINIMAX_CODE_MODELS = [
21214
23380
  maxAttachments: 4,
21215
23381
  maxImageBytes: 10 * 1024 * 1024,
21216
23382
  maxVideoBytes: 50 * 1024 * 1024,
21217
- supportsFilesApi: true,
23383
+ filesApiDocumented: true,
21218
23384
  description: "Preview Flash model with a 1M context window. Thinking is forced on with a selectable effort level. Accepts up to 4 attachments."
21219
23385
  }
21220
23386
  ];
@@ -23399,11 +25565,6 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
23399
25565
  const contextWindow = contextWindowForModel(options.model, settings.contextWindowOverrides);
23400
25566
  const requestOptions = offloadOldestRequestImages$2(normalizeGenerateOptions(options), maxRequestImageBytes());
23401
25567
  const images = await resolveRequestImages$2(requestOptions, this.options.attachments, signal);
23402
- const requestedMax = requestOptions.maxTokens ?? maxOutputTokensFor$1(options.model, contextWindow);
23403
- const body = assertRequestBodyFits(buildMinimaxRequest({
23404
- ...requestOptions,
23405
- maxTokens: clampOutputToContext(requestedMax, contextWindow, estimatedInputTokens(requestOptions))
23406
- }, images, { cacheControl: true }));
23407
25568
  let credentials;
23408
25569
  let accountId;
23409
25570
  try {
@@ -23414,6 +25575,19 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
23414
25575
  if (error instanceof MinimaxCodeUnauthorizedError) throw new LlmError(error.message, "INVALID_CREDENTIAL", { cause: error });
23415
25576
  throw new LlmError("MiniMax Code(编程订阅) credential could not be prepared: " + (error instanceof Error ? error.message : String(error)), "MISSING_CREDENTIAL", { cause: error });
23416
25577
  }
25578
+ let authOwner = computeMinimaxAuthOwner(credentials, accountId);
25579
+ const effectiveMax = clampOutputToContext(requestOptions.maxTokens ?? maxOutputTokensFor$1(options.model, contextWindow), contextWindow, estimatedInputTokens(requestOptions));
25580
+ const buildBodyForOwner = (owner) => {
25581
+ return assertRequestBodyFits(buildMinimaxRequest({
25582
+ ...requestOptions,
25583
+ maxTokens: effectiveMax
25584
+ }, images, {
25585
+ cacheControl: true,
25586
+ authOwner: owner,
25587
+ route: PROVIDER_ID$2
25588
+ }));
25589
+ };
25590
+ let body = buildBodyForOwner(authOwner);
23417
25591
  let response;
23418
25592
  for (let attempt = 0;; attempt += 1) {
23419
25593
  try {
@@ -23433,6 +25607,8 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
23433
25607
  const refused = credentials.accessToken;
23434
25608
  try {
23435
25609
  credentials = await this.renewCredential(accountId, fetchFn, signal, refused);
25610
+ authOwner = computeMinimaxAuthOwner(credentials, accountId);
25611
+ body = buildBodyForOwner(authOwner);
23436
25612
  } catch (error) {
23437
25613
  if (error instanceof MinimaxCodeUnauthorizedError) throw new LlmError(error.message, "INVALID_CREDENTIAL", { cause: error });
23438
25614
  throw new LlmError("MiniMax Code(编程订阅) credential could not be renewed after the service refused " + redactToken(refused) + ": " + (error instanceof Error ? error.message : String(error)), "INVALID_CREDENTIAL", { cause: error });
@@ -23451,7 +25627,12 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
23451
25627
  if (response.body === null) throw new LlmError("MiniMax Code(编程订阅) returned an empty response body", "PROVIDER_ERROR");
23452
25628
  const reader = response.body.getReader();
23453
25629
  const decoder = new TextDecoder();
23454
- const state = createStreamState();
25630
+ const state = createStreamState({
25631
+ model: options.model,
25632
+ authOwner,
25633
+ route: PROVIDER_ID$2,
25634
+ provider: PROVIDER_ID$2
25635
+ });
23455
25636
  let buffer = "";
23456
25637
  try {
23457
25638
  while (true) {
@@ -27717,6 +29898,7 @@ function createStreamState$2() {
27717
29898
  inputTokens: 0,
27718
29899
  outputTokens: 0,
27719
29900
  cacheReadTokens: 0,
29901
+ cacheWriteTokens: 0,
27720
29902
  reasoningTokens: 0,
27721
29903
  sawUsage: false
27722
29904
  };
@@ -27798,8 +29980,10 @@ function processStreamLine$1(line, state) {
27798
29980
  const prompt = numberOr$1(usage.prompt_tokens, 0);
27799
29981
  const details = isRecord$8(usage.prompt_tokens_details) ? usage.prompt_tokens_details : void 0;
27800
29982
  const cached = details ? numberOr$1(details.cached_tokens, 0) : 0;
27801
- state.inputTokens = Math.max(0, prompt - cached);
29983
+ const written = details ? numberOr$1(details.cache_write_tokens, 0) : 0;
29984
+ state.inputTokens = Math.max(0, prompt - cached - written);
27802
29985
  state.cacheReadTokens = cached;
29986
+ state.cacheWriteTokens = written;
27803
29987
  state.outputTokens = numberOr$1(usage.completion_tokens, state.outputTokens);
27804
29988
  if (isRecord$8(usage.completion_tokens_details)) state.reasoningTokens = numberOr$1(usage.completion_tokens_details.reasoning_tokens, state.reasoningTokens);
27805
29989
  }
@@ -27900,6 +30084,7 @@ function tokenUsage$1(state) {
27900
30084
  inputTokens: state.inputTokens,
27901
30085
  outputTokens: state.outputTokens,
27902
30086
  ...state.cacheReadTokens > 0 ? { cacheReadTokens: state.cacheReadTokens } : {},
30087
+ ...state.cacheWriteTokens > 0 ? { cacheWriteTokens: state.cacheWriteTokens } : {},
27903
30088
  ...state.reasoningTokens > 0 ? { reasoningTokens: state.reasoningTokens } : {}
27904
30089
  };
27905
30090
  }
@@ -39752,8 +41937,16 @@ function apply(ctx, pluginConfig = {}) {
39752
41937
  logger: ctx.logger
39753
41938
  });
39754
41939
  const proxyFetch = proxyManager.createFetch();
41940
+ const providerLimits = readModelRequestLimits();
41941
+ const modelFetch = Object.fromEntries(CONTROLLED_PROVIDERS.map((provider) => [provider, createControlledModelFetch(createDiagnosticFetch(proxyFetch, {
41942
+ provider,
41943
+ onRecord: (record) => ctx.logger.info("[provider-diagnostics] " + JSON.stringify(record))
41944
+ }), {
41945
+ provider,
41946
+ limits: providerLimits
41947
+ })]));
39755
41948
  const antigravityAdapter = new AntigravityAdapter(antigravityStore, antigravityModelSettings, antigravityPreferences, {
39756
- fetchFn: proxyFetch,
41949
+ fetchFn: modelFetch.antigravity,
39757
41950
  attachments: ctx.attachments
39758
41951
  }, antigravityAccountPool);
39759
41952
  let antigravityRegistration;
@@ -39780,7 +41973,7 @@ function apply(ctx, pluginConfig = {}) {
39780
41973
  conflict: () => commandCodeConflict
39781
41974
  }, commandCodeAccountPool);
39782
41975
  const commandCodeAdapter = new CommandCodeAdapter(commandCodeStore, commandCodeModelSettings, commandCodePreferences, {
39783
- fetchFn: proxyFetch,
41976
+ fetchFn: modelFetch["command-code"],
39784
41977
  attachments: ctx.attachments
39785
41978
  }, commandCodeAccountPool);
39786
41979
  let commandCodeRegistration;
@@ -39788,11 +41981,14 @@ function apply(ctx, pluginConfig = {}) {
39788
41981
  const ollamaStore = new FileCredentialStore$5();
39789
41982
  const ollamaModelSettings = new FileModelSettingsStore$5();
39790
41983
  const ollamaAccountPool = new OllamaAccountPool({ store: ollamaStore });
39791
- const ollamaAdapter = new OllamaAdapter(ollamaStore, ollamaModelSettings, { fetchFn: proxyFetch }, ollamaAccountPool);
41984
+ const ollamaAdapter = new OllamaAdapter(ollamaStore, ollamaModelSettings, {
41985
+ fetchFn: modelFetch.ollama,
41986
+ attachments: ctx.attachments
41987
+ }, ollamaAccountPool);
39792
41988
  let ollamaRegistration;
39793
41989
  let ollamaConflict = null;
39794
41990
  const kimiCodeAdapter = new KimiCodeAdapter(kimiCodeStore, kimiCodeModelSettings, kimiCodePreferences, {
39795
- fetchFn: proxyFetch,
41991
+ fetchFn: modelFetch["kimi-code"],
39796
41992
  attachments: ctx.attachments,
39797
41993
  videos: { readVideo: async (ref) => ({
39798
41994
  data: await readVideoBytes(ref),
@@ -39854,7 +42050,7 @@ function apply(ctx, pluginConfig = {}) {
39854
42050
  conflict: () => kimiCodeConflict
39855
42051
  }, kimiCodeAccountPool);
39856
42052
  const minimaxCodeAdapter = new MinimaxCodeAdapter(minimaxCodeStore, {
39857
- fetchFn: proxyFetch,
42053
+ fetchFn: modelFetch["minimax-code"],
39858
42054
  attachments: ctx.attachments,
39859
42055
  accountPool: minimaxCodeAccountPool
39860
42056
  }, minimaxCodeModelSettings, minimaxCodePreferences);
@@ -39904,7 +42100,7 @@ function apply(ctx, pluginConfig = {}) {
39904
42100
  checkin: minimaxCodeCheckin
39905
42101
  }, minimaxCodeModelSettings, minimaxCodePreferences);
39906
42102
  const workBuddyAdapter = new WorkBuddyAdapter(workBuddyStore, workBuddyModelSettings, workBuddyPreferences, {
39907
- fetchFn: proxyFetch,
42103
+ fetchFn: modelFetch.workbuddy,
39908
42104
  attachments: ctx.attachments,
39909
42105
  accountPool: workBuddyAccountPool
39910
42106
  });
@@ -39946,7 +42142,7 @@ function apply(ctx, pluginConfig = {}) {
39946
42142
  checkin: workBuddyCheckin
39947
42143
  });
39948
42144
  const claudeAdapter = new ClaudeAdapter(claudeStore, claudeModelSettings, claudePreferences, {
39949
- fetchFn: proxyFetch,
42145
+ fetchFn: modelFetch.claude,
39950
42146
  attachments: ctx.attachments,
39951
42147
  accountPool: claudeAccountPool
39952
42148
  });
@@ -39983,7 +42179,7 @@ function apply(ctx, pluginConfig = {}) {
39983
42179
  const usage = new UsageService(oauth, { fetchFn: proxyFetch });
39984
42180
  codexAccountPool.setQuotaBlockedUntil((account, now) => usage.blockedUntilFor(account.credentials, now));
39985
42181
  const adapter = new CodexChatGptAdapter(new ResponsesClient(oauth, ctx.attachments, {
39986
- fetchFn: proxyFetch,
42182
+ fetchFn: modelFetch["codex-chatgpt"],
39987
42183
  accountPool: codexAccountPool,
39988
42184
  localRawImages: { baseUrl: localWebServerBaseUrl(ctx.webServer.host, ctx.webServer.port) },
39989
42185
  onGenerationFinished: () => usage.invalidate(),