@eddyskywalker/dsh-chatgpt-subscription 0.10.21 → 0.11.0

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 (54) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/lib/client.js +53 -8
  3. package/lib/client.js.map +1 -1
  4. package/lib/index.js +2409 -304
  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/codex-account-pool.d.ts +2 -8
  9. package/lib/types/host/codex-account-pool.d.ts.map +1 -1
  10. package/lib/types/host/command-code/adapter.d.ts +19 -0
  11. package/lib/types/host/command-code/adapter.d.ts.map +1 -1
  12. package/lib/types/host/command-code/client.d.ts +4 -2
  13. package/lib/types/host/command-code/client.d.ts.map +1 -1
  14. package/lib/types/host/command-code/mapper.d.ts +11 -0
  15. package/lib/types/host/command-code/mapper.d.ts.map +1 -1
  16. package/lib/types/host/command-code/routes.d.ts +2 -0
  17. package/lib/types/host/command-code/routes.d.ts.map +1 -1
  18. package/lib/types/host/command-code/token-store.d.ts +7 -0
  19. package/lib/types/host/command-code/token-store.d.ts.map +1 -1
  20. package/lib/types/host/command-code/types.d.ts +15 -0
  21. package/lib/types/host/command-code/types.d.ts.map +1 -1
  22. package/lib/types/host/common/capabilities.d.ts +100 -0
  23. package/lib/types/host/common/capabilities.d.ts.map +1 -0
  24. package/lib/types/host/common/concurrency-gate.d.ts +46 -0
  25. package/lib/types/host/common/concurrency-gate.d.ts.map +1 -0
  26. package/lib/types/host/common/model-request-control.d.ts +21 -0
  27. package/lib/types/host/common/model-request-control.d.ts.map +1 -0
  28. package/lib/types/host/common/request-diagnostics.d.ts +79 -0
  29. package/lib/types/host/common/request-diagnostics.d.ts.map +1 -0
  30. package/lib/types/host/minimax-code/adapter.d.ts.map +1 -1
  31. package/lib/types/host/minimax-code/mapper.d.ts +32 -1
  32. package/lib/types/host/minimax-code/mapper.d.ts.map +1 -1
  33. package/lib/types/host/minimax-code/model-catalog.d.ts +7 -2
  34. package/lib/types/host/minimax-code/model-catalog.d.ts.map +1 -1
  35. package/lib/types/host/minimax-code/types.d.ts +12 -0
  36. package/lib/types/host/minimax-code/types.d.ts.map +1 -1
  37. package/lib/types/host/ollama/adapter.d.ts +18 -4
  38. package/lib/types/host/ollama/adapter.d.ts.map +1 -1
  39. package/lib/types/host/ollama/client.d.ts +14 -0
  40. package/lib/types/host/ollama/client.d.ts.map +1 -1
  41. package/lib/types/host/ollama/mapper.d.ts +5 -0
  42. package/lib/types/host/ollama/mapper.d.ts.map +1 -1
  43. package/lib/types/host/ollama/types.d.ts +23 -0
  44. package/lib/types/host/ollama/types.d.ts.map +1 -1
  45. package/lib/types/host/responses-client.d.ts +27 -10
  46. package/lib/types/host/responses-client.d.ts.map +1 -1
  47. package/lib/types/host/wire-auth.d.ts +19 -5
  48. package/lib/types/host/wire-auth.d.ts.map +1 -1
  49. package/lib/types/host/workbuddy/mapper.d.ts +1 -0
  50. package/lib/types/host/workbuddy/mapper.d.ts.map +1 -1
  51. package/lib/types/index.d.ts.map +1 -1
  52. package/lib/types/shared/command-code-contracts.d.ts +15 -2
  53. package/lib/types/shared/command-code-contracts.d.ts.map +1 -1
  54. 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";
@@ -5265,7 +6050,7 @@ const MAX_VISIBLE_REASONING_CHARS = 12e3;
5265
6050
  const DEFAULT_POOL_COOLDOWN_MS = 15 * 6e4;
5266
6051
  const REASONING_DELTA_FLUSH_CHARS = 768;
5267
6052
  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. */
6053
+ /** Cap on live turns whose routing state is remembered at once. */
5269
6054
  const MAX_TRACKED_TURN_STATES = 200;
5270
6055
  var ResponsesClient = class {
5271
6056
  oauth;
@@ -5277,13 +6062,20 @@ var ResponsesClient = class {
5277
6062
  reasoningSummary;
5278
6063
  accountPool;
5279
6064
  /**
5280
- * Opaque backend turn state, keyed by conversation session id.
6065
+ * Opaque backend routing state, keyed by a unique model-request nonce.
6066
+ * The host does not expose a reliable human-turn identity, so this is the
6067
+ * conservative no-cross-request-reuse fallback, not full turn continuity.
5281
6068
  *
5282
6069
  * 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.
6070
+ * expects it on the next request of the SAME turn, which lets it resume the
6071
+ * turn instead of re-ingesting the whole history.
6072
+ *
6073
+ * It is a client/server contract with a one-turn scope: the official client
6074
+ * creates a fresh per-turn session and warns that replaying the token across
6075
+ * turns breaks routing. The value is also account-scoped, so each entry
6076
+ * records the auth owner that minted it and is discarded when the signer
6077
+ * changes. It is only ever an optimization, so a miss costs a full resend and
6078
+ * never correctness.
5287
6079
  */
5288
6080
  turnStates = /* @__PURE__ */ new Map();
5289
6081
  constructor(oauth, attachments, options = {}) {
@@ -5302,13 +6094,17 @@ var ResponsesClient = class {
5302
6094
  const options = normalizeGenerateOptions(rawOptions);
5303
6095
  const hiddenSandboxControls = hiddenSandboxControlToolNames(options);
5304
6096
  const sessionId = stableSessionId(options.sessionId);
6097
+ const turnKey = turnKeyFor(sessionId);
5305
6098
  let currentModel = options.model;
5306
6099
  let attemptOptions = options;
5307
6100
  let response;
6101
+ let release;
5308
6102
  while (true) {
5309
6103
  const payload = await buildResponsesPayload(attemptOptions, this.attachments, this.localRawImages, this.outputVerbosity(), this.fastMode(), this.reasoningSummary(), promptCacheKeyFor(sessionId));
5310
6104
  try {
5311
- response = await this.send(payload, sessionId, options.signal);
6105
+ const opened = await this.send(payload, sessionId, turnKey, options.signal);
6106
+ response = opened.response;
6107
+ release = opened.release;
5312
6108
  break;
5313
6109
  } catch (error) {
5314
6110
  if (error instanceof LlmError && (error.code === "NOT_FOUND" || error.status === 404)) {
@@ -5328,42 +6124,47 @@ var ResponsesClient = class {
5328
6124
  try {
5329
6125
  yield* wrapStreamWithWatchdog((watchdogSignal) => parseResponsesStream(response, watchdogSignal, hiddenSandboxControls), options.signal, 3e5, "LLM_STREAM_IDLE_TIMEOUT", "Codex");
5330
6126
  } finally {
6127
+ this.turnStates.delete(turnKey);
6128
+ release?.();
5331
6129
  this.onGenerationFinished();
5332
6130
  }
5333
6131
  }
5334
- async send(payload, sessionId, signal) {
5335
- if (this.accountPool !== null) return this.sendWithPool(payload, sessionId, signal);
6132
+ async send(payload, sessionId, turnKey, signal) {
6133
+ if (this.accountPool !== null) return this.sendWithPool(payload, sessionId, turnKey, signal);
5336
6134
  let credentials = await this.oauth.credentials();
5337
- let response = await this.request(payload, credentials, sessionId, signal);
6135
+ let response = await this.request(payload, credentials, sessionId, turnKey, signal);
5338
6136
  if (response.status === 401) {
5339
6137
  await response.body?.cancel().catch(() => void 0);
5340
6138
  credentials = await this.oauth.credentials(true);
5341
- response = await this.request(payload, credentials, sessionId, signal);
6139
+ response = await this.request(payload, credentials, sessionId, turnKey, signal);
5342
6140
  }
5343
6141
  if (!response.ok) throw await responseError(response);
5344
- this.rememberTurnState(response, sessionId);
5345
- return response;
6142
+ this.rememberTurnState(response, turnKey, credentials);
6143
+ return { response };
5346
6144
  }
5347
6145
  /**
5348
- * Record the turn state a response carried, for the next turn of this session.
6146
+ * Record the routing state a response carried, for this turn's next request.
5349
6147
  *
5350
6148
  * A response that carries none clears any stored value: a backend that stops
5351
6149
  * sending the header has stopped honouring it, and replaying a stale value
5352
6150
  * would be guessing. An empty header is treated the same as absent.
5353
6151
  */
5354
- rememberTurnState(response, sessionId) {
6152
+ rememberTurnState(response, turnKey, credentials) {
5355
6153
  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);
6154
+ if (next === void 0 || next === "") this.turnStates.delete(turnKey);
6155
+ else this.storeTurnState(turnKey, next, credentials);
5358
6156
  }
5359
6157
  /**
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.
6158
+ * Bounded store: a long-lived host serves many sessions and turns, and nothing
6159
+ * here ever signals that either ended, so the map is capped and evicts the
6160
+ * oldest entry. The value is an optimization, so a miss only costs a resend.
5363
6161
  */
5364
- storeTurnState(sessionId, value) {
5365
- this.turnStates.delete(sessionId);
5366
- this.turnStates.set(sessionId, value);
6162
+ storeTurnState(turnKey, value, credentials) {
6163
+ this.turnStates.delete(turnKey);
6164
+ this.turnStates.set(turnKey, {
6165
+ value,
6166
+ owner: authOwnerKey(credentials)
6167
+ });
5367
6168
  while (this.turnStates.size > MAX_TRACKED_TURN_STATES) {
5368
6169
  const oldest = this.turnStates.keys().next();
5369
6170
  if (oldest.done === true) break;
@@ -5371,6 +6172,23 @@ var ResponsesClient = class {
5371
6172
  }
5372
6173
  }
5373
6174
  /**
6175
+ * Routing state this turn may replay, or undefined.
6176
+ *
6177
+ * A token minted by a different auth owner is dropped rather than sent: the
6178
+ * value is account-scoped, and sending it to another account is at best a
6179
+ * routing hint for the wrong machine and at worst a replay of state that
6180
+ * account never issued. Dropping it only costs a full resend.
6181
+ */
6182
+ turnStateFor(turnKey, credentials) {
6183
+ const entry = this.turnStates.get(turnKey);
6184
+ if (entry === void 0) return void 0;
6185
+ if (entry.owner !== authOwnerKey(credentials)) {
6186
+ this.turnStates.delete(turnKey);
6187
+ return;
6188
+ }
6189
+ return entry.value;
6190
+ }
6191
+ /**
5374
6192
  * Send one payload with account rotation.
5375
6193
  *
5376
6194
  * The pool picks the account — skipping cooling, quota-exhausted and failed
@@ -5380,48 +6198,72 @@ var ResponsesClient = class {
5380
6198
  * refresh is rejected leaves the rotation instead of invalidating the others.
5381
6199
  * The payload is account-independent, so it is never rebuilt between attempts.
5382
6200
  */
5383
- async sendWithPool(payload, sessionId, signal) {
6201
+ /** What one pool attempt produced, including the account that served it. */
6202
+ async sendWithPool(payload, sessionId, turnKey, signal) {
5384
6203
  const pool = this.accountPool;
5385
6204
  const tried = /* @__PURE__ */ new Set();
5386
6205
  while (true) {
5387
- const { account, credentials } = await pool.getEffectiveAccount(tried, this.fetchFn);
6206
+ const selected = await pool.getEffectiveAccount(tried, this.fetchFn);
6207
+ const account = selected.account;
6208
+ let credentials = selected.credentials;
5388
6209
  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);
6210
+ const release = await chatGPTConcurrency().acquire(account.id, signal);
6211
+ let held = true;
6212
+ const free = () => {
6213
+ if (!held) return;
6214
+ held = false;
6215
+ release();
6216
+ };
6217
+ try {
6218
+ let response = await this.request(payload, credentials, sessionId, turnKey, signal);
6219
+ if (response.status === 401) {
6220
+ await response.body?.cancel().catch(() => void 0);
6221
+ try {
6222
+ credentials = await pool.refreshAccountNow(account.id);
6223
+ response = await this.request(payload, credentials, sessionId, turnKey, signal);
6224
+ } catch (error) {
6225
+ await pool.markAuthFailed(account.id, error instanceof Error ? error.message : "ChatGPT sign-in expired.").catch(() => void 0);
6226
+ free();
6227
+ if (await pool.hasAnotherAvailableAccount(tried)) continue;
6228
+ throw new LlmError("ChatGPT sign-in has expired. Sign in again.", "AUTH", {
6229
+ status: 401,
6230
+ cause: error
6231
+ });
6232
+ }
6233
+ }
6234
+ if (response.status === 429) {
6235
+ const after = retryAfterMs$1(response.headers);
6236
+ await response.body?.cancel().catch(() => void 0);
6237
+ await pool.markCooldown(account.id, after ?? DEFAULT_POOL_COOLDOWN_MS, "Codex 429").catch(() => void 0);
6238
+ free();
5397
6239
  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
6240
+ throw new LlmError("Codex rate limit reached.", "RATE_LIMIT", {
6241
+ status: 429,
6242
+ ...after === void 0 ? {} : { providerRetryAfterMs: after }
5401
6243
  });
5402
6244
  }
6245
+ if (!response.ok) {
6246
+ const error = await responseError(response);
6247
+ free();
6248
+ throw error;
6249
+ }
6250
+ this.rememberTurnState(response, turnKey, credentials);
6251
+ return {
6252
+ response,
6253
+ release: free
6254
+ };
6255
+ } catch (error) {
6256
+ free();
6257
+ throw error;
5403
6258
  }
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
6259
  }
5418
6260
  }
5419
- async request(payload, credentials, sessionId, signal) {
6261
+ async request(payload, credentials, sessionId, turnKey, signal) {
5420
6262
  try {
5421
6263
  return await this.fetchFn(CODEX_RESPONSES_URL, {
5422
6264
  method: "POST",
5423
6265
  headers: {
5424
- ...codexHeaders(credentials, sessionId, { turnState: this.turnStates.get(sessionId) }),
6266
+ ...codexHeaders(credentials, sessionId, { turnState: this.turnStateFor(turnKey, credentials) }),
5425
6267
  "content-type": "application/json",
5426
6268
  accept: "text/event-stream"
5427
6269
  },
@@ -5445,6 +6287,16 @@ var ResponsesClient = class {
5445
6287
  function promptCacheKeyFor(sessionId) {
5446
6288
  return sessionId;
5447
6289
  }
6290
+ /** Request-local fallback; never infer a human turn identity from message history. */
6291
+ function turnKeyFor(sessionId) {
6292
+ return `${sessionId}#${createHash("sha256").update(sessionId).update("\0").update(String(perRequestNonce())).digest("hex").slice(0, 24)}`;
6293
+ }
6294
+ /** A value no two requests in this process share. */
6295
+ let requestCounter = 0;
6296
+ function perRequestNonce() {
6297
+ requestCounter += 1;
6298
+ return requestCounter;
6299
+ }
5448
6300
  async function* parseResponsesStream(response, signal, hiddenSandboxControls = /* @__PURE__ */ new Set()) {
5449
6301
  if (response.body === null) throw new LlmError("Codex returned no response stream.", "PROVIDER_ERROR");
5450
6302
  const reader = response.body.getReader();
@@ -5750,16 +6602,28 @@ function visibleReasoningDelta(delta, currentVisibleChars, alreadyTruncated) {
5750
6602
  truncated: true
5751
6603
  };
5752
6604
  }
6605
+ /**
6606
+ * Map one response's usage onto DSH's disjoint accounting.
6607
+ *
6608
+ * `input_tokens` is the whole input, so cached input has to be subtracted out of
6609
+ * it and reported on its own field. Cache WRITES are counted separately from
6610
+ * reads and billed separately: leaving a write inside `inputTokens` would make a
6611
+ * cold prefix look cheaper than it was and hide the cost of a write. A field the
6612
+ * service omits is unknown, not zero, so it is simply not reported.
6613
+ */
5753
6614
  function mapUsage(value) {
5754
6615
  if (value === null) return null;
5755
6616
  const totalInput = number(value.input_tokens) ?? 0;
5756
6617
  const outputTokens = number(value.output_tokens) ?? 0;
5757
- const cached = number(record$2(value.input_tokens_details)?.cached_tokens) ?? 0;
6618
+ const details = record$2(value.input_tokens_details);
6619
+ const cached = number(details?.cached_tokens);
6620
+ const written = number(details?.cache_write_tokens);
5758
6621
  const reasoning = number(record$2(value.output_tokens_details)?.reasoning_tokens);
5759
6622
  return {
5760
- inputTokens: Math.max(0, totalInput - cached),
6623
+ inputTokens: Math.max(0, totalInput - (cached ?? 0) - (written ?? 0)),
5761
6624
  outputTokens,
5762
- ...cached > 0 ? { cacheReadTokens: cached } : {},
6625
+ ...cached === void 0 ? {} : { cacheReadTokens: cached },
6626
+ ...written === void 0 ? {} : { cacheWriteTokens: written },
5763
6627
  ...reasoning === void 0 ? {} : { reasoningTokens: reasoning }
5764
6628
  };
5765
6629
  }
@@ -6661,7 +7525,7 @@ function field(value, name) {
6661
7525
  const candidate = value[name];
6662
7526
  return typeof candidate === "string" && candidate !== "" ? candidate : null;
6663
7527
  }
6664
- function isRecord$22(value) {
7528
+ function isRecord$23(value) {
6665
7529
  return typeof value === "object" && value !== null && !Array.isArray(value);
6666
7530
  }
6667
7531
  function readPreferencesUpdate(value, current) {
@@ -6695,7 +7559,7 @@ function readPreferencesUpdate(value, current) {
6695
7559
  patch.searchProvider = value.searchProvider;
6696
7560
  }
6697
7561
  if ("contextWindowOverrides" in value) {
6698
- if (!isRecord$22(value.contextWindowOverrides)) throw new PreferenceError("contextWindowOverrides must be an object.");
7562
+ if (!isRecord$23(value.contextWindowOverrides)) throw new PreferenceError("contextWindowOverrides must be an object.");
6699
7563
  const overrides = {};
6700
7564
  for (const [model, contextWindow] of Object.entries(value.contextWindowOverrides)) {
6701
7565
  if (!isCodexModelId(model)) throw new PreferenceError("Unknown Codex model for a context window override.");
@@ -7567,7 +8431,7 @@ const schemaFields = /* @__PURE__ */ new Set([
7567
8431
  "required",
7568
8432
  "propertyOrdering"
7569
8433
  ]);
7570
- function isRecord$21(value) {
8434
+ function isRecord$22(value) {
7571
8435
  return typeof value === "object" && value !== null && !Array.isArray(value);
7572
8436
  }
7573
8437
  function mergeSchemas(left, right) {
@@ -7575,7 +8439,7 @@ function mergeSchemas(left, right) {
7575
8439
  ...left,
7576
8440
  ...right
7577
8441
  };
7578
- if (isRecord$21(left.properties) && isRecord$21(right.properties)) merged.properties = {
8442
+ if (isRecord$22(left.properties) && isRecord$22(right.properties)) merged.properties = {
7579
8443
  ...left.properties,
7580
8444
  ...right.properties
7581
8445
  };
@@ -7583,7 +8447,7 @@ function mergeSchemas(left, right) {
7583
8447
  return merged;
7584
8448
  }
7585
8449
  function toAntigravityToolSchema(schema) {
7586
- if (!isRecord$21(schema)) return schema;
8450
+ if (!isRecord$22(schema)) return schema;
7587
8451
  const root = schema;
7588
8452
  function resolveReference(reference) {
7589
8453
  let target = root;
@@ -7591,13 +8455,13 @@ function toAntigravityToolSchema(schema) {
7591
8455
  const path = reference === "#" ? [] : reference.slice(2).split("/");
7592
8456
  for (const segment of path) {
7593
8457
  const key = segment.replace(/~1/g, "/").replace(/~0/g, "~");
7594
- target = isRecord$21(target) && Object.hasOwn(target, key) ? target[key] : void 0;
8458
+ target = isRecord$22(target) && Object.hasOwn(target, key) ? target[key] : void 0;
7595
8459
  }
7596
- if (!isRecord$21(target)) throw new LlmError(`Antigravity tool schema reference was not found: ${reference}`, "PROVIDER_ERROR");
8460
+ if (!isRecord$22(target)) throw new LlmError(`Antigravity tool schema reference was not found: ${reference}`, "PROVIDER_ERROR");
7597
8461
  return target;
7598
8462
  }
7599
8463
  function convert(node, references) {
7600
- if (!isRecord$21(node)) return {};
8464
+ if (!isRecord$22(node)) return {};
7601
8465
  let inherited = {};
7602
8466
  if (typeof node.$ref === "string") {
7603
8467
  if (references.has(node.$ref)) throw new LlmError(`Antigravity tool schema contains a recursive reference: ${node.$ref}`, "PROVIDER_ERROR");
@@ -7606,8 +8470,8 @@ function toAntigravityToolSchema(schema) {
7606
8470
  if (Array.isArray(node.allOf)) for (const branch of node.allOf) inherited = mergeSchemas(inherited, convert(branch, references));
7607
8471
  const out = {};
7608
8472
  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);
8473
+ if (isRecord$22(node.properties)) out.properties = Object.fromEntries(Object.entries(node.properties).map(([name, child]) => [name, convert(child, references)]));
8474
+ if (isRecord$22(node.items)) out.items = convert(node.items, references);
7611
8475
  const alternatives = Array.isArray(node.anyOf) ? node.anyOf : node.oneOf;
7612
8476
  if (Array.isArray(alternatives)) out.anyOf = alternatives.map((child) => convert(child, references));
7613
8477
  if (Array.isArray(node.type)) {
@@ -7635,10 +8499,10 @@ let toolCallCounter = 0;
7635
8499
  function sanitizeText$5(text) {
7636
8500
  return text.replace(/\0/g, "");
7637
8501
  }
7638
- function isRecord$20(value) {
8502
+ function isRecord$21(value) {
7639
8503
  return typeof value === "object" && value !== null && !Array.isArray(value);
7640
8504
  }
7641
- function asString$13(value) {
8505
+ function asString$14(value) {
7642
8506
  return typeof value === "string" ? value : void 0;
7643
8507
  }
7644
8508
  function safeJsonParse$5(text) {
@@ -7654,26 +8518,26 @@ function sanitizeToolCallId(id, fallbackName) {
7654
8518
  function toolCallIdNeeded(modelId, runtimeModel) {
7655
8519
  return modelId.startsWith("claude-") || modelId.startsWith("gpt-oss-") || runtimeModel.startsWith("claude-") || runtimeModel.startsWith("gpt-oss-");
7656
8520
  }
7657
- function parseArguments(raw) {
7658
- if (isRecord$20(raw)) return raw;
8521
+ function parseArguments$1(raw) {
8522
+ if (isRecord$21(raw)) return raw;
7659
8523
  if (raw === void 0 || raw === null || raw === "") return {};
7660
8524
  const parsed = typeof raw === "string" ? safeJsonParse$5(raw) : raw;
7661
- return isRecord$20(parsed) ? parsed : {};
8525
+ return isRecord$21(parsed) ? parsed : {};
7662
8526
  }
7663
8527
  const NO_RESOLVED_IMAGES$5 = /* @__PURE__ */ new Map();
7664
8528
  function attachmentOf$5(block) {
7665
8529
  const attachment = block.attachment;
7666
- if (!isRecord$20(attachment)) return void 0;
8530
+ if (!isRecord$21(attachment)) return void 0;
7667
8531
  return typeof attachment.attachmentId === "string" ? attachment : void 0;
7668
8532
  }
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);
8533
+ function attachmentLabel$6(block) {
8534
+ const attachment = isRecord$21(block.attachment) ? block.attachment : void 0;
8535
+ return asString$14(attachment?.name) || asString$14(attachment?.attachmentId);
7672
8536
  }
7673
8537
  function collectImageRefs$4(content, refs) {
7674
8538
  if (!Array.isArray(content)) return;
7675
8539
  for (const block of content) {
7676
- if (!isRecord$20(block)) continue;
8540
+ if (!isRecord$21(block)) continue;
7677
8541
  if (block.type === "image") {
7678
8542
  const attachment = attachmentOf$5(block);
7679
8543
  if (attachment) refs.set(attachment.attachmentId, attachment);
@@ -7682,7 +8546,7 @@ function collectImageRefs$4(content, refs) {
7682
8546
  if (block.type === "tool-result") collectImageRefs$4(block.content, refs);
7683
8547
  }
7684
8548
  }
7685
- function isAbort$3(error, signal) {
8549
+ function isAbort$4(error, signal) {
7686
8550
  return signal?.aborted === true || error instanceof Error && error.name === "AbortError";
7687
8551
  }
7688
8552
  /**
@@ -7711,13 +8575,13 @@ function base64Length$4(bytes) {
7711
8575
  function requestImageBytes$4(block) {
7712
8576
  const attachment = attachmentOf$5(block);
7713
8577
  if (attachment) return base64Length$4(attachment.bytes);
7714
- const inline = asString$13(block.data) || asString$13(block.base64);
8578
+ const inline = asString$14(block.data) || asString$14(block.base64);
7715
8579
  return inline ? inline.length : void 0;
7716
8580
  }
7717
8581
  function collectRequestImageBytes$4(content, lengths) {
7718
8582
  if (!Array.isArray(content)) return;
7719
8583
  for (const block of content) {
7720
- if (!isRecord$20(block) || block.type !== "image") continue;
8584
+ if (!isRecord$21(block) || block.type !== "image") continue;
7721
8585
  const bytes = requestImageBytes$4(block);
7722
8586
  if (bytes !== void 0) lengths.push(bytes);
7723
8587
  }
@@ -7752,7 +8616,7 @@ function offloadOldestRequestImages$4(options) {
7752
8616
  if (remaining.count === 0 || !Array.isArray(message.content)) return message;
7753
8617
  let replaced = false;
7754
8618
  const content = message.content.map((block) => {
7755
- if (remaining.count === 0 || !isRecord$20(block) || block.type !== "image") return block;
8619
+ if (remaining.count === 0 || !isRecord$21(block) || block.type !== "image") return block;
7756
8620
  if (requestImageBytes$4(block) === void 0) return block;
7757
8621
  remaining.count -= 1;
7758
8622
  replaced = true;
@@ -7804,21 +8668,21 @@ async function resolveRequestImages$4(options, attachments, signal) {
7804
8668
  data: Buffer.from(stored.data).toString("base64")
7805
8669
  });
7806
8670
  } catch (error) {
7807
- if (isAbort$3(error, signal)) throw error;
8671
+ if (isAbort$4(error, signal)) throw error;
7808
8672
  resolved.set(attachmentId, { kind: "unavailable" });
7809
8673
  }
7810
8674
  }));
7811
8675
  return resolved;
7812
8676
  }
7813
- function unavailableImageText$5(block) {
7814
- const label = attachmentLabel$5(block);
8677
+ function unavailableImageText$6(block) {
8678
+ const label = attachmentLabel$6(block);
7815
8679
  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
8680
  }
7817
8681
  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";
8682
+ let data = asString$14(block.data) || asString$14(block.base64);
8683
+ const source = isRecord$21(block.source) ? block.source : void 0;
8684
+ if (!data && source) data = asString$14(source.data) || asString$14(source.base64);
8685
+ let mimeType = asString$14(block.mimeType) || asString$14(block.mediaType) || (source ? asString$14(source.mimeType) || asString$14(source.mediaType) : void 0) || "image/png";
7822
8686
  if (data?.startsWith("data:")) {
7823
8687
  const match = data.match(/^data:([^;,]+);base64,(.*)$/s);
7824
8688
  if (match) {
@@ -7841,10 +8705,10 @@ function contentToUserParts(content, images) {
7841
8705
  if (typeof content === "string") return [{ text: sanitizeText$5(content) }];
7842
8706
  if (!Array.isArray(content)) return [];
7843
8707
  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") {
8708
+ for (const block of content) if (isRecord$21(block) && block.type === "text" && typeof block.text === "string") parts.push({ text: sanitizeText$5(block.text) });
8709
+ else if (isRecord$21(block) && block.type === "image") {
7846
8710
  const img = imageBlockToPart(block, images);
7847
- parts.push(img ?? { text: unavailableImageText$5(block) });
8711
+ parts.push(img ?? { text: unavailableImageText$6(block) });
7848
8712
  }
7849
8713
  return parts;
7850
8714
  }
@@ -7857,9 +8721,9 @@ function toolResultImageParts(blocks, images) {
7857
8721
  if (!Array.isArray(blocks)) return [];
7858
8722
  const parts = [];
7859
8723
  for (const block of blocks) {
7860
- if (!isRecord$20(block)) continue;
8724
+ if (!isRecord$21(block)) continue;
7861
8725
  if (block.type === "image") {
7862
- parts.push(imageBlockToPart(block, images) ?? { text: unavailableImageText$5(block) });
8726
+ parts.push(imageBlockToPart(block, images) ?? { text: unavailableImageText$6(block) });
7863
8727
  continue;
7864
8728
  }
7865
8729
  if (block.type === "tool-result") parts.push(...toolResultImageParts(block.content, images));
@@ -7869,11 +8733,11 @@ function toolResultImageParts(blocks, images) {
7869
8733
  function toolResultText$5(blocks) {
7870
8734
  if (!Array.isArray(blocks)) return "";
7871
8735
  return blocks.map((block) => {
7872
- if (!isRecord$20(block)) return "";
8736
+ if (!isRecord$21(block)) return "";
7873
8737
  if (block.type === "text" && typeof block.text === "string") return sanitizeText$5(block.text);
7874
8738
  if (block.type === "tool-result") return toolResultText$5(block.content);
7875
8739
  if (block.type === "image") {
7876
- const label = attachmentLabel$5(block);
8740
+ const label = attachmentLabel$6(block);
7877
8741
  return label ? `[image: ${label}]` : "[image]";
7878
8742
  }
7879
8743
  return "";
@@ -7883,16 +8747,16 @@ function replayBlockFor$1(message, index) {
7883
8747
  const source = message.source;
7884
8748
  if (!source || source.kind !== "model" || source.provider !== "antigravity") return void 0;
7885
8749
  const state = source.replayState;
7886
- if (!isRecord$20(state)) return void 0;
8750
+ if (!isRecord$21(state)) return void 0;
7887
8751
  if (Array.isArray(state.blocks)) return state.blocks[index];
7888
- const resp = isRecord$20(state.response) ? state.response : void 0;
8752
+ const resp = isRecord$21(state.response) ? state.response : void 0;
7889
8753
  if (resp) {
7890
8754
  if (Array.isArray(resp.outputItems)) return resp.outputItems[index];
7891
8755
  if (Array.isArray(resp.blocks)) return resp.blocks[index];
7892
8756
  }
7893
8757
  }
7894
8758
  function thoughtSignature(part) {
7895
- return asString$13(part?.thoughtSignature) || asString$13(part?.thought_signature) || asString$13(part?.thinkingSignature) || asString$13(part?.textSignature);
8759
+ return asString$14(part?.thoughtSignature) || asString$14(part?.thought_signature) || asString$14(part?.thinkingSignature) || asString$14(part?.textSignature);
7896
8760
  }
7897
8761
  function replayPart(part) {
7898
8762
  const copy = { ...part };
@@ -7908,10 +8772,10 @@ function assistantParts(message, model, runtimeModel, toolCalls) {
7908
8772
  if (!Array.isArray(message.content)) return parts;
7909
8773
  for (let index = 0; index < message.content.length; index++) {
7910
8774
  const block = message.content[index];
7911
- if (!isRecord$20(block)) continue;
8775
+ if (!isRecord$21(block)) continue;
7912
8776
  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 || ""))) {
8777
+ const originalParts = Array.isArray(replay?.parts) ? replay.parts.filter(isRecord$21) : [];
8778
+ 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
8779
  parts.push(...originalParts.map(replayPart));
7916
8780
  continue;
7917
8781
  }
@@ -7931,8 +8795,8 @@ function assistantParts(message, model, runtimeModel, toolCalls) {
7931
8795
  } else if (block.type === "tool-call") {
7932
8796
  const toolId = String(block.id || "");
7933
8797
  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);
8798
+ const originalCall = originalParts.find((part) => isRecord$21(part.functionCall));
8799
+ 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
8800
  toolCalls.set(toolId, {
7937
8801
  name: toolName,
7938
8802
  id: wireId
@@ -7941,7 +8805,7 @@ function assistantParts(message, model, runtimeModel, toolCalls) {
7941
8805
  parts.push({
7942
8806
  functionCall: {
7943
8807
  name: toolName,
7944
- args: parseArguments(block.arguments),
8808
+ args: parseArguments$1(block.arguments),
7945
8809
  ...wireId ? { id: wireId } : {}
7946
8810
  },
7947
8811
  ...effectiveSignature ? { thoughtSignature: effectiveSignature } : {}
@@ -7984,7 +8848,7 @@ function convertMessages(options, model, runtimeModel, images = NO_RESOLVED_IMAG
7984
8848
  continue;
7985
8849
  }
7986
8850
  const content = Array.isArray(message.content) ? message.content : [];
7987
- const userParts = contentToUserParts(content.filter((b) => !isRecord$20(b) || b.type !== "tool-result"), images);
8851
+ const userParts = contentToUserParts(content.filter((b) => !isRecord$21(b) || b.type !== "tool-result"), images);
7988
8852
  if (role === "system") {
7989
8853
  if (userParts.length) contents.push({
7990
8854
  role: GEMINI_ROLE.user,
@@ -7996,7 +8860,7 @@ function convertMessages(options, model, runtimeModel, images = NO_RESOLVED_IMAG
7996
8860
  role: GEMINI_ROLE.user,
7997
8861
  parts: userParts
7998
8862
  });
7999
- for (const b of content) if (isRecord$20(b) && b.type === "tool-result") pushToolResult(contents, b, toolCalls, model, runtimeModel, images);
8863
+ for (const b of content) if (isRecord$21(b) && b.type === "tool-result") pushToolResult(contents, b, toolCalls, model, runtimeModel, images);
8000
8864
  }
8001
8865
  return contents;
8002
8866
  }
@@ -8119,7 +8983,7 @@ const USAGE_FIELDS = [
8119
8983
  "totalTokenCount"
8120
8984
  ];
8121
8985
  function collectUsage(value, state) {
8122
- if (!isRecord$20(value)) return;
8986
+ if (!isRecord$21(value)) return;
8123
8987
  for (const key of USAGE_FIELDS) {
8124
8988
  const count = value[key];
8125
8989
  if (typeof count !== "number" || !Number.isSafeInteger(count) || count < 0) continue;
@@ -8149,15 +9013,15 @@ function processStreamLine$2(line, state) {
8149
9013
  }
8150
9014
  if (!json) return [];
8151
9015
  const chunk = safeJsonParse$5(json);
8152
- if (!isRecord$20(chunk)) return [];
8153
- const responseData = isRecord$20(chunk.response) ? chunk.response : chunk;
9016
+ if (!isRecord$21(chunk)) return [];
9017
+ const responseData = isRecord$21(chunk.response) ? chunk.response : chunk;
8154
9018
  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;
9019
+ const candidate = isRecord$21(candidates[0]) ? candidates[0] : void 0;
9020
+ const content = isRecord$21(candidate?.content) ? candidate.content : void 0;
8157
9021
  const parts = Array.isArray(content?.parts) ? content.parts : [];
8158
9022
  const out = [];
8159
9023
  for (const part of parts) {
8160
- if (!isRecord$20(part)) continue;
9024
+ if (!isRecord$21(part)) continue;
8161
9025
  if (typeof part.text === "string" && part.text !== "") {
8162
9026
  const isThinking = Boolean(part.thought);
8163
9027
  const blockType = isThinking ? "reasoning" : "text";
@@ -8189,7 +9053,7 @@ function processStreamLine$2(line, state) {
8189
9053
  index: state.currentBlock.index,
8190
9054
  text: delta
8191
9055
  });
8192
- } else if (!isRecord$20(part.functionCall) && thoughtSignature(part)) {
9056
+ } else if (!isRecord$21(part.functionCall) && thoughtSignature(part)) {
8193
9057
  if (state.replayBlocks.length === 0) {
8194
9058
  const type = part.thought ? "reasoning" : "text";
8195
9059
  state.blocks.push({
@@ -8213,12 +9077,12 @@ function processStreamLine$2(line, state) {
8213
9077
  }
8214
9078
  state.replayBlocks[state.replayBlocks.length - 1].parts.push(replayPart(part));
8215
9079
  }
8216
- if (isRecord$20(part.functionCall)) {
9080
+ if (isRecord$21(part.functionCall)) {
8217
9081
  out.push(...closeCurrentBlock(state));
8218
9082
  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 : {});
9083
+ const toolName = asString$14(fc.name) || "";
9084
+ const toolId = asString$14(fc.id) || sanitizeToolCallId("", toolName);
9085
+ const argsText = JSON.stringify(isRecord$21(fc.args) ? fc.args : {});
8222
9086
  const index = state.blocks.length;
8223
9087
  const block = {
8224
9088
  type: "tool-call",
@@ -8255,7 +9119,7 @@ function processStreamLine$2(line, state) {
8255
9119
  }
8256
9120
  collectUsage(chunk.usageMetadata, state);
8257
9121
  if (responseData !== chunk) collectUsage(responseData.usageMetadata, state);
8258
- const finishReason = asString$13(candidate?.finishReason) || asString$13(responseData.finishReason);
9122
+ const finishReason = asString$14(candidate?.finishReason) || asString$14(responseData.finishReason);
8259
9123
  if (finishReason) {
8260
9124
  state.finishReason = finishReason;
8261
9125
  out.push(...closeCurrentBlock(state));
@@ -9909,6 +10773,7 @@ const CALLBACK_ALLOWED_ORIGINS = [
9909
10773
  "http://localhost:3000"
9910
10774
  ];
9911
10775
  const DISCOVERY_TIMEOUT_MS$3 = 15e3;
10776
+ const CATALOG_CACHE_TTL_MS$3 = 1800 * 1e3;
9912
10777
  const STREAM_IDLE_TIMEOUT_MS$4 = 3e5;
9913
10778
  const STREAM_IDLE_TIMEOUT_CODE$4 = "LLM_STREAM_IDLE_TIMEOUT";
9914
10779
  /**
@@ -9925,6 +10790,8 @@ const HEADER_PROJECT_SLUG = "x-command-code-project-slug";
9925
10790
  const HEADER_TASTE_LEARNING = "x-command-code-taste-learning";
9926
10791
  const HEADER_SESSION_ID = "x-command-code-session-id";
9927
10792
  const HEADER_OSS_PRIMARY_PROVIDER = "x-command-code-oss-primary-provider";
10793
+ /** Official header that enforces Zero Data Retention (ZDR) routing. */
10794
+ const HEADER_ZDR = "x-cmd-zdr";
9928
10795
  function resolveApiEnv(raw = process.env.DSH_COMMAND_CODE_ENV) {
9929
10796
  const value = (raw || "").trim().toLowerCase();
9930
10797
  return value === "staging" || value === "local" ? value : "prod";
@@ -9952,6 +10819,21 @@ function wireForModel$2(modelId) {
9952
10819
  return /^claude[-/]/i.test(modelId.trim()) ? "anthropic" : "openai";
9953
10820
  }
9954
10821
  /**
10822
+ * Wire one catalog entry says it is served on, when the catalog said so.
10823
+ *
10824
+ * The provider publishes `supported_endpoints` per model, and that is the
10825
+ * authority on which route answers for it. A model the catalog does not describe
10826
+ * returns undefined so the caller falls back to {@link wireForModel} instead of
10827
+ * being sent to a route nobody vouched for.
10828
+ */
10829
+ function wireForCatalogEntry$1(entry) {
10830
+ for (const endpoint of entry.supportedEndpoints ?? []) {
10831
+ if (/\/v1\/messages\b|\bmessages\b/i.test(endpoint)) return "anthropic";
10832
+ if (/\/v1\/responses\b|\bresponses\b/i.test(endpoint)) return "responses";
10833
+ if (/chat\/completions|\bcompletions\b/i.test(endpoint)) return "openai";
10834
+ }
10835
+ }
10836
+ /**
9955
10837
  * Reasoning levels one model advertises, in DSH's vocabulary.
9956
10838
  *
9957
10839
  * The registry is the authority: a model it describes but gives no
@@ -10457,13 +11339,13 @@ function commandCodeHeaders(apiKey, extra = {}) {
10457
11339
  function timeoutSignal$3(signal, ms) {
10458
11340
  return signal ? AbortSignal.any([signal, AbortSignal.timeout(ms)]) : AbortSignal.timeout(ms);
10459
11341
  }
10460
- function isRecord$19(value) {
11342
+ function isRecord$20(value) {
10461
11343
  return typeof value === "object" && value !== null && !Array.isArray(value);
10462
11344
  }
10463
11345
  function asRecord$9(value) {
10464
- return isRecord$19(value) ? value : void 0;
11346
+ return isRecord$20(value) ? value : void 0;
10465
11347
  }
10466
- function asString$12(value) {
11348
+ function asString$13(value) {
10467
11349
  if (typeof value === "string" && value.trim() !== "") return value;
10468
11350
  if (typeof value === "number" && Number.isFinite(value)) return String(value);
10469
11351
  }
@@ -10476,10 +11358,19 @@ function asNumber$3(value) {
10476
11358
  }
10477
11359
  function firstString$1(record, keys) {
10478
11360
  for (const key of keys) {
10479
- const value = asString$12(record[key]);
11361
+ const value = asString$13(record[key]);
10480
11362
  if (value !== void 0) return value;
10481
11363
  }
10482
11364
  }
11365
+ /** First key that holds a non-empty array of strings. */
11366
+ function firstStringArray(record, keys) {
11367
+ for (const key of keys) {
11368
+ const value = record[key];
11369
+ if (!Array.isArray(value)) continue;
11370
+ const entries = value.filter((entry) => typeof entry === "string" && entry !== "");
11371
+ if (entries.length > 0) return entries;
11372
+ }
11373
+ }
10483
11374
  function firstNumber$2(record, keys) {
10484
11375
  for (const key of keys) {
10485
11376
  const value = asNumber$3(record[key]);
@@ -10496,7 +11387,7 @@ function deepValue(value, keys, depth = 0) {
10496
11387
  }
10497
11388
  return;
10498
11389
  }
10499
- if (!isRecord$19(value)) return void 0;
11390
+ if (!isRecord$20(value)) return void 0;
10500
11391
  for (const key of keys) if (value[key] !== void 0 && value[key] !== null) return value[key];
10501
11392
  for (const nested of Object.values(value)) {
10502
11393
  const hit = deepValue(nested, keys, depth + 1);
@@ -10510,7 +11401,7 @@ function collectRecords(value, keys, depth = 0, out = []) {
10510
11401
  for (const entry of value) collectRecords(entry, keys, depth + 1, out);
10511
11402
  return out;
10512
11403
  }
10513
- if (!isRecord$19(value)) return out;
11404
+ if (!isRecord$20(value)) return out;
10514
11405
  if (keys.some((key) => value[key] !== void 0)) out.push(value);
10515
11406
  for (const nested of Object.values(value)) collectRecords(nested, keys, depth + 1, out);
10516
11407
  return out;
@@ -10714,7 +11605,7 @@ function parseCreditBalances(payload, consumed = []) {
10714
11605
  /** Resolve the plan id the subscription or credits payload reports, when either does. */
10715
11606
  function parsePlanId(payloads) {
10716
11607
  for (const payload of payloads) {
10717
- const id = asString$12(deepValue(payload, [
11608
+ const id = asString$13(deepValue(payload, [
10718
11609
  "planId",
10719
11610
  "plan_id",
10720
11611
  "priceId",
@@ -10733,7 +11624,7 @@ function parsePlanId(payloads) {
10733
11624
  */
10734
11625
  function parseSubscriptionStatus(payload) {
10735
11626
  const root = asRecord$9(payload) ?? {};
10736
- return asString$12(deepValue(asRecord$9(root.data) ?? root, ["status"])) ?? null;
11627
+ return asString$13(deepValue(asRecord$9(root.data) ?? root, ["status"])) ?? null;
10737
11628
  }
10738
11629
  /** End of the current billing period, in Unix milliseconds. */
10739
11630
  function parseSubscriptionPeriodEnd(payload) {
@@ -10986,6 +11877,7 @@ function extractUnlimited(payload) {
10986
11877
  return deepValue(payload, ["unlimited", "isUnlimited"]) === true;
10987
11878
  }
10988
11879
  let cachedCatalog$1;
11880
+ let catalogIsRehydrated = false;
10989
11881
  /** Bumped by every cache clear so a fetch already in flight cannot publish. */
10990
11882
  let catalogCacheEpoch$2 = 0;
10991
11883
  let catalogInFlight$2 = null;
@@ -11020,7 +11912,8 @@ function parseProviderModels(payload) {
11020
11912
  "contextLength",
11021
11913
  "context_window",
11022
11914
  "contextWindow"
11023
- ])
11915
+ ]),
11916
+ ...firstStringArray(record, ["supported_endpoints", "supportedEndpoints"]) === void 0 ? {} : { supportedEndpoints: firstStringArray(record, ["supported_endpoints", "supportedEndpoints"]) }
11024
11917
  });
11025
11918
  }
11026
11919
  return models;
@@ -11067,6 +11960,7 @@ async function loadProviderModels(options = {}) {
11067
11960
  models,
11068
11961
  fetchedAt
11069
11962
  };
11963
+ catalogIsRehydrated = true;
11070
11964
  }, 0);
11071
11965
  const warm = cachedCatalog$1;
11072
11966
  if (rehydratedAt > 0 && warm !== void 0) {
@@ -11088,6 +11982,7 @@ function refreshProviderModels$1(options = {}) {
11088
11982
  models,
11089
11983
  fetchedAt
11090
11984
  };
11985
+ catalogIsRehydrated = false;
11091
11986
  writeCatalogSnapshot(catalogSnapshotName("command-code", options.apiEnv ?? resolveApiEnv()), models, fetchedAt);
11092
11987
  }
11093
11988
  return models.length > 0 ? models : cachedCatalog$1?.models ?? [];
@@ -11097,10 +11992,19 @@ function refreshProviderModels$1(options = {}) {
11097
11992
  if (catalogInFlight$2 === request) catalogInFlight$2 = null;
11098
11993
  });
11099
11994
  }
11995
+ function isCatalogStale() {
11996
+ if (cachedCatalog$1 === void 0) return true;
11997
+ if (catalogIsRehydrated) return true;
11998
+ return Date.now() - cachedCatalog$1.fetchedAt >= CATALOG_CACHE_TTL_MS$3;
11999
+ }
12000
+ function getCachedCatalogFetchedAt() {
12001
+ return cachedCatalog$1?.fetchedAt ?? null;
12002
+ }
11100
12003
  function clearCachedCatalog$3() {
11101
12004
  catalogCacheEpoch$2 += 1;
11102
12005
  cachedCatalog$1 = void 0;
11103
12006
  catalogInFlight$2 = null;
12007
+ catalogIsRehydrated = false;
11104
12008
  catalogSnapshotScopesLoaded$2.clear();
11105
12009
  }
11106
12010
  async function getJson(path, apiKey, options = {}) {
@@ -11256,7 +12160,7 @@ function buildModelOptions$2(catalog, enabledModelIds, overrides) {
11256
12160
  contextWindow: overrides[model.id] && overrides[model.id] > 0 ? overrides[model.id] : contextWindow,
11257
12161
  defaultMaxTokens: maxOutputTokensFor$4(model.id),
11258
12162
  ...efforts.length > 0 ? { reasoningEfforts: efforts } : {},
11259
- wire: wireForModel$2(model.id)
12163
+ wire: wireForCatalogEntry$1(model) ?? wireForModel$2(model.id)
11260
12164
  };
11261
12165
  });
11262
12166
  }
@@ -11272,10 +12176,10 @@ function buildModelOptions$2(catalog, enabledModelIds, overrides) {
11272
12176
  *
11273
12177
  * https://commandcode.ai/blog/command-code-provider-api
11274
12178
  */
11275
- function isRecord$18(value) {
12179
+ function isRecord$19(value) {
11276
12180
  return typeof value === "object" && value !== null && !Array.isArray(value);
11277
12181
  }
11278
- function asString$11(value) {
12182
+ function asString$12(value) {
11279
12183
  return typeof value === "string" ? value : void 0;
11280
12184
  }
11281
12185
  function safeJsonParse$4(text) {
@@ -11288,7 +12192,7 @@ function safeJsonParse$4(text) {
11288
12192
  function sanitizeText$4(text) {
11289
12193
  return text.replace(/\0/g, "");
11290
12194
  }
11291
- function isAbort$2(error, signal) {
12195
+ function isAbort$3(error, signal) {
11292
12196
  return signal?.aborted === true || error instanceof Error && error.name === "AbortError";
11293
12197
  }
11294
12198
  const NO_RESOLVED_IMAGES$4 = /* @__PURE__ */ new Map();
@@ -11312,17 +12216,17 @@ const SUPPORTED_IMAGE_MEDIA_TYPES$4 = /* @__PURE__ */ new Set([
11312
12216
  ]);
11313
12217
  function attachmentOf$4(block) {
11314
12218
  const attachment = block.attachment;
11315
- if (!isRecord$18(attachment)) return void 0;
12219
+ if (!isRecord$19(attachment)) return void 0;
11316
12220
  return typeof attachment.attachmentId === "string" ? attachment : void 0;
11317
12221
  }
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);
12222
+ function attachmentLabel$5(block) {
12223
+ const attachment = isRecord$19(block.attachment) ? block.attachment : void 0;
12224
+ return asString$12(attachment?.name) || asString$12(attachment?.attachmentId);
11321
12225
  }
11322
12226
  function collectImageRefs$3(content, refs) {
11323
12227
  if (!Array.isArray(content)) return;
11324
12228
  for (const block of content) {
11325
- if (!isRecord$18(block)) continue;
12229
+ if (!isRecord$19(block)) continue;
11326
12230
  if (block.type === "image") {
11327
12231
  const attachment = attachmentOf$4(block);
11328
12232
  if (attachment) refs.set(attachment.attachmentId, attachment);
@@ -11337,13 +12241,13 @@ function base64Length$3(bytes) {
11337
12241
  function requestImageBytes$3(block) {
11338
12242
  const attachment = attachmentOf$4(block);
11339
12243
  if (attachment) return base64Length$3(attachment.bytes);
11340
- const inline = asString$11(block.data) || asString$11(block.base64);
12244
+ const inline = asString$12(block.data) || asString$12(block.base64);
11341
12245
  return inline ? inline.length : void 0;
11342
12246
  }
11343
12247
  function collectRequestImageBytes$3(content, lengths) {
11344
12248
  if (!Array.isArray(content)) return;
11345
12249
  for (const block of content) {
11346
- if (!isRecord$18(block) || block.type !== "image") continue;
12250
+ if (!isRecord$19(block) || block.type !== "image") continue;
11347
12251
  const bytes = requestImageBytes$3(block);
11348
12252
  if (bytes !== void 0) lengths.push(bytes);
11349
12253
  }
@@ -11370,7 +12274,7 @@ function offloadOldestRequestImages$3(options) {
11370
12274
  if (remaining.count === 0 || !Array.isArray(message.content)) return message;
11371
12275
  let replaced = false;
11372
12276
  const content = message.content.map((block) => {
11373
- if (remaining.count === 0 || !isRecord$18(block) || block.type !== "image") return block;
12277
+ if (remaining.count === 0 || !isRecord$19(block) || block.type !== "image") return block;
11374
12278
  if (requestImageBytes$3(block) === void 0) return block;
11375
12279
  remaining.count -= 1;
11376
12280
  replaced = true;
@@ -11412,21 +12316,21 @@ async function resolveRequestImages$3(options, attachments, signal) {
11412
12316
  data: Buffer.from(stored.data).toString("base64")
11413
12317
  });
11414
12318
  } catch (error) {
11415
- if (isAbort$2(error, signal)) throw error;
12319
+ if (isAbort$3(error, signal)) throw error;
11416
12320
  resolved.set(attachmentId, { kind: "unavailable" });
11417
12321
  }
11418
12322
  }));
11419
12323
  return resolved;
11420
12324
  }
11421
- function unavailableImageText$4(block) {
11422
- const label = attachmentLabel$4(block);
12325
+ function unavailableImageText$5(block) {
12326
+ const label = attachmentLabel$5(block);
11423
12327
  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
12328
  }
11425
12329
  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";
12330
+ let data = asString$12(block.data) || asString$12(block.base64);
12331
+ const source = isRecord$19(block.source) ? block.source : void 0;
12332
+ if (!data && source) data = asString$12(source.data) || asString$12(source.base64);
12333
+ let mediaType = asString$12(block.mimeType) || asString$12(block.mediaType) || (source ? asString$12(source.mimeType) || asString$12(source.mediaType) : void 0) || "image/png";
11430
12334
  if (data?.startsWith("data:")) {
11431
12335
  const matched = data.match(/^data:([^;,]+);base64,(.*)$/s);
11432
12336
  if (matched) {
@@ -11450,7 +12354,7 @@ function textOf$5(content) {
11450
12354
  if (!Array.isArray(content)) return "";
11451
12355
  const parts = [];
11452
12356
  for (const block of content) {
11453
- if (!isRecord$18(block)) continue;
12357
+ if (!isRecord$19(block)) continue;
11454
12358
  if (block.type === "text" && typeof block.text === "string") parts.push(sanitizeText$4(block.text));
11455
12359
  else if (block.type === "tool-result") parts.push(textOf$5(block.content));
11456
12360
  }
@@ -11459,10 +12363,10 @@ function textOf$5(content) {
11459
12363
  function toolResultText$4(blocks) {
11460
12364
  if (!Array.isArray(blocks)) return "";
11461
12365
  return blocks.map((block) => {
11462
- if (!isRecord$18(block)) return "";
12366
+ if (!isRecord$19(block)) return "";
11463
12367
  if (block.type === "text" && typeof block.text === "string") return sanitizeText$4(block.text);
11464
12368
  if (block.type === "tool-result") return toolResultText$4(block.content);
11465
- if (block.type === "image") return `[image: ${attachmentLabel$4(block) ?? "attached image"}]`;
12369
+ if (block.type === "image") return `[image: ${attachmentLabel$5(block) ?? "attached image"}]`;
11466
12370
  return "";
11467
12371
  }).join("");
11468
12372
  }
@@ -11477,7 +12381,7 @@ function toolResultBlocks$2(blocks, images) {
11477
12381
  const out = [];
11478
12382
  let hasImage = false;
11479
12383
  for (const block of blocks) {
11480
- if (!isRecord$18(block)) continue;
12384
+ if (!isRecord$19(block)) continue;
11481
12385
  if (block.type === "text" && typeof block.text === "string") {
11482
12386
  out.push({
11483
12387
  type: "text",
@@ -11498,7 +12402,7 @@ function toolResultBlocks$2(blocks, images) {
11498
12402
  });
11499
12403
  else out.push({
11500
12404
  type: "text",
11501
- text: unavailableImageText$4(block)
12405
+ text: unavailableImageText$5(block)
11502
12406
  });
11503
12407
  continue;
11504
12408
  }
@@ -11526,7 +12430,7 @@ function toolResultImageBlocks$2(blocks, images) {
11526
12430
  if (!Array.isArray(blocks)) return [];
11527
12431
  const out = [];
11528
12432
  for (const block of blocks) {
11529
- if (!isRecord$18(block)) continue;
12433
+ if (!isRecord$19(block)) continue;
11530
12434
  if (block.type === "image") {
11531
12435
  const inline = imageBlockToInline$3(block, images);
11532
12436
  if (inline && SUPPORTED_IMAGE_MEDIA_TYPES$4.has(inline.mediaType)) out.push({
@@ -11535,7 +12439,7 @@ function toolResultImageBlocks$2(blocks, images) {
11535
12439
  });
11536
12440
  else out.push({
11537
12441
  type: "text",
11538
- text: unavailableImageText$4(block)
12442
+ text: unavailableImageText$5(block)
11539
12443
  });
11540
12444
  continue;
11541
12445
  }
@@ -11570,7 +12474,7 @@ function nonSystemMessages$4(options) {
11570
12474
  }
11571
12475
  /** Drop the JSON-Schema keywords provider gateways reject or ignore. */
11572
12476
  function stripMetaSchema$2(schema) {
11573
- if (!isRecord$18(schema)) return {
12477
+ if (!isRecord$19(schema)) return {
11574
12478
  type: "object",
11575
12479
  properties: {}
11576
12480
  };
@@ -11587,7 +12491,7 @@ function openAIUserContent$2(message, images) {
11587
12491
  const parts = [];
11588
12492
  let hasImage = false;
11589
12493
  for (const block of message.content) {
11590
- if (!isRecord$18(block)) continue;
12494
+ if (!isRecord$19(block)) continue;
11591
12495
  if (block.type === "text" && typeof block.text === "string") {
11592
12496
  const text = sanitizeText$4(block.text);
11593
12497
  if (text !== "") parts.push({
@@ -11604,7 +12508,7 @@ function openAIUserContent$2(message, images) {
11604
12508
  });
11605
12509
  } else parts.push({
11606
12510
  type: "text",
11607
- text: unavailableImageText$4(block)
12511
+ text: unavailableImageText$5(block)
11608
12512
  });
11609
12513
  }
11610
12514
  }
@@ -11615,7 +12519,7 @@ function openAIAssistantContent$2(message) {
11615
12519
  const textParts = [];
11616
12520
  const toolCalls = [];
11617
12521
  for (const block of message.content) {
11618
- if (!isRecord$18(block)) continue;
12522
+ if (!isRecord$19(block)) continue;
11619
12523
  if (block.type === "text" && typeof block.text === "string") textParts.push(sanitizeText$4(block.text));
11620
12524
  else if (block.type === "tool-call" && typeof block.name === "string") toolCalls.push({
11621
12525
  id: typeof block.id === "string" && block.id !== "" ? block.id : `call_${toolCalls.length}`,
@@ -11647,7 +12551,7 @@ function buildOpenAIRequest$1(options, images = NO_RESOLVED_IMAGES$4) {
11647
12551
  while (index < conversation.length && isToolResultMessage$4(conversation[index])) {
11648
12552
  const current = conversation[index];
11649
12553
  const block = current.content[0];
11650
- const callId = isRecord$18(block) && typeof block.toolCallId === "string" ? block.toolCallId : "";
12554
+ const callId = isRecord$19(block) && typeof block.toolCallId === "string" ? block.toolCallId : "";
11651
12555
  messages.push({
11652
12556
  role: "tool",
11653
12557
  tool_call_id: callId,
@@ -11708,7 +12612,7 @@ function anthropicUserContent$2(message, images) {
11708
12612
  if (!Array.isArray(message.content)) return [];
11709
12613
  const blocks = [];
11710
12614
  for (const block of message.content) {
11711
- if (!isRecord$18(block)) continue;
12615
+ if (!isRecord$19(block)) continue;
11712
12616
  if (block.type === "text" && typeof block.text === "string") {
11713
12617
  const text = sanitizeText$4(block.text);
11714
12618
  if (text !== "") blocks.push({
@@ -11727,7 +12631,7 @@ function anthropicUserContent$2(message, images) {
11727
12631
  });
11728
12632
  else blocks.push({
11729
12633
  type: "text",
11730
- text: unavailableImageText$4(block)
12634
+ text: unavailableImageText$5(block)
11731
12635
  });
11732
12636
  } else if (block.type === "tool-result") {
11733
12637
  const callId = typeof block.toolCallId === "string" ? block.toolCallId : "";
@@ -11745,7 +12649,7 @@ function anthropicUserContent$2(message, images) {
11745
12649
  function anthropicAssistantContent$2(message) {
11746
12650
  const blocks = [];
11747
12651
  for (const block of message.content) {
11748
- if (!isRecord$18(block)) continue;
12652
+ if (!isRecord$19(block)) continue;
11749
12653
  if (block.type === "text" && typeof block.text === "string") {
11750
12654
  const text = sanitizeText$4(block.text);
11751
12655
  if (text !== "") blocks.push({
@@ -11758,7 +12662,7 @@ function anthropicAssistantContent$2(message) {
11758
12662
  type: "tool_use",
11759
12663
  id: typeof block.id === "string" && block.id !== "" ? block.id : `toolu_${blocks.length}`,
11760
12664
  name: block.name,
11761
- input: isRecord$18(parsed) ? parsed : {}
12665
+ input: isRecord$19(parsed) ? parsed : {}
11762
12666
  });
11763
12667
  }
11764
12668
  }
@@ -11835,7 +12739,136 @@ function buildAnthropicRequest$1(options, images = NO_RESOLVED_IMAGES$4) {
11835
12739
  }
11836
12740
  /** Build the body for whichever endpoint serves `modelId`. */
11837
12741
  function buildRequest$1(options, wire, images = NO_RESOLVED_IMAGES$4) {
11838
- return wire === "anthropic" ? buildAnthropicRequest$1(options, images) : buildOpenAIRequest$1(options, images);
12742
+ if (wire === "anthropic") return buildAnthropicRequest$1(options, images);
12743
+ if (wire === "responses") return buildResponsesRequest(options, images);
12744
+ return buildOpenAIRequest$1(options, images);
12745
+ }
12746
+ /**
12747
+ * Build one `/responses` body.
12748
+ *
12749
+ * The Responses API takes a flat `input` list instead of `messages`, and its
12750
+ * reasoning and tool calls are item types rather than message fields. This is a
12751
+ * separate builder rather than a reshaping of the Chat Completions one: the two
12752
+ * APIs differ in more than field names, and a translated body is a request the
12753
+ * route rejects.
12754
+ */
12755
+ function responsesUserContent(message, images) {
12756
+ if (!Array.isArray(message.content)) return [];
12757
+ const parts = [];
12758
+ for (const block of message.content) {
12759
+ if (!isRecord$19(block)) continue;
12760
+ if (block.type === "text" && typeof block.text === "string") {
12761
+ const text = sanitizeText$4(block.text);
12762
+ if (text !== "") parts.push({
12763
+ type: "input_text",
12764
+ text
12765
+ });
12766
+ } else if (block.type === "image") {
12767
+ const inline = imageBlockToInline$3(block, images);
12768
+ if (inline && SUPPORTED_IMAGE_MEDIA_TYPES$4.has(inline.mediaType)) parts.push({
12769
+ type: "input_image",
12770
+ image_url: "data:" + inline.mediaType + ";base64," + inline.data
12771
+ });
12772
+ else parts.push({
12773
+ type: "input_text",
12774
+ text: unavailableImageText$5(block)
12775
+ });
12776
+ }
12777
+ }
12778
+ return parts;
12779
+ }
12780
+ function responsesToolResultImageBlocks(blocks, images) {
12781
+ if (!Array.isArray(blocks)) return [];
12782
+ const out = [];
12783
+ for (const block of blocks) {
12784
+ if (!isRecord$19(block)) continue;
12785
+ if (block.type === "image") {
12786
+ const inline = imageBlockToInline$3(block, images);
12787
+ if (inline && SUPPORTED_IMAGE_MEDIA_TYPES$4.has(inline.mediaType)) out.push({
12788
+ type: "input_image",
12789
+ image_url: "data:" + inline.mediaType + ";base64," + inline.data
12790
+ });
12791
+ else out.push({
12792
+ type: "input_text",
12793
+ text: unavailableImageText$5(block)
12794
+ });
12795
+ continue;
12796
+ }
12797
+ if (block.type === "tool-result") out.push(...responsesToolResultImageBlocks(block.content, images));
12798
+ }
12799
+ return out;
12800
+ }
12801
+ function buildResponsesRequest(options, images = NO_RESOLVED_IMAGES$4) {
12802
+ const input = [];
12803
+ const system = leadingSystemText$4(options);
12804
+ const conversation = nonSystemMessages$4(options);
12805
+ for (let index = 0; index < conversation.length; index++) {
12806
+ const message = conversation[index];
12807
+ if (isToolResultMessage$4(message)) {
12808
+ const imageBlocks = [];
12809
+ while (index < conversation.length && isToolResultMessage$4(conversation[index])) {
12810
+ const current = conversation[index];
12811
+ const toolResult = Array.isArray(current.content) ? current.content.find((b) => isRecord$19(b) && b.type === "tool-result") : void 0;
12812
+ const callId = (isRecord$19(toolResult) && typeof toolResult.toolCallId === "string" ? toolResult.toolCallId : "") || (typeof current.source?.callId === "string" ? current.source.callId : "");
12813
+ input.push({
12814
+ type: "function_call_output",
12815
+ call_id: callId,
12816
+ output: toolResultText$4(current.content)
12817
+ });
12818
+ imageBlocks.push(...responsesToolResultImageBlocks(current.content, images));
12819
+ index += 1;
12820
+ }
12821
+ index -= 1;
12822
+ if (imageBlocks.length > 0) input.push({
12823
+ role: "user",
12824
+ content: imageBlocks
12825
+ });
12826
+ continue;
12827
+ }
12828
+ if (message.role === "assistant") {
12829
+ const { content, toolCalls } = openAIAssistantContent$2(message);
12830
+ if (content !== "") input.push({
12831
+ role: "assistant",
12832
+ content
12833
+ });
12834
+ for (const call of toolCalls) {
12835
+ const fn = isRecord$19(call.function) ? call.function : void 0;
12836
+ input.push({
12837
+ type: "function_call",
12838
+ call_id: call.id,
12839
+ name: typeof fn?.name === "string" ? fn.name : "",
12840
+ arguments: typeof fn?.arguments === "string" ? fn.arguments : "{}"
12841
+ });
12842
+ }
12843
+ continue;
12844
+ }
12845
+ const content = responsesUserContent(message, images);
12846
+ if (content.length === 0) continue;
12847
+ input.push({
12848
+ role: "user",
12849
+ content
12850
+ });
12851
+ }
12852
+ const effort = wireReasoningEffort(options.reasoningEffort === void 0 ? void 0 : String(options.reasoningEffort));
12853
+ const maxTokens = options.maxTokens ?? maxOutputTokensFor$4(options.model);
12854
+ return {
12855
+ model: options.model,
12856
+ input,
12857
+ stream: true,
12858
+ max_output_tokens: maxTokens,
12859
+ ...system === void 0 ? {} : { instructions: system },
12860
+ ...options.temperature === void 0 ? {} : { temperature: options.temperature },
12861
+ ...options.tools && options.tools.length > 0 ? {
12862
+ tools: options.tools.map((tool) => ({
12863
+ type: "function",
12864
+ name: tool.name,
12865
+ description: tool.description,
12866
+ parameters: stripMetaSchema$2(tool.parameters)
12867
+ })),
12868
+ tool_choice: "auto"
12869
+ } : {},
12870
+ ...effort === void 0 || effort === "" ? {} : { reasoning: { effort } }
12871
+ };
11839
12872
  }
11840
12873
  function createStreamState$5(wire) {
11841
12874
  return {
@@ -11922,24 +12955,24 @@ function processOpenAIStreamLine$1(line, state) {
11922
12955
  }
11923
12956
  if (payload === "") return [];
11924
12957
  const chunk = safeJsonParse$4(payload);
11925
- if (!isRecord$18(chunk)) return [];
12958
+ if (!isRecord$19(chunk)) return [];
11926
12959
  const out = [];
11927
- const usage = isRecord$18(chunk.usage) ? chunk.usage : void 0;
12960
+ const usage = isRecord$19(chunk.usage) ? chunk.usage : void 0;
11928
12961
  if (usage) {
11929
12962
  state.sawUsage = true;
11930
12963
  const prompt = numberOr$5(usage.prompt_tokens, 0);
11931
- const details = isRecord$18(usage.prompt_tokens_details) ? usage.prompt_tokens_details : void 0;
12964
+ const details = isRecord$19(usage.prompt_tokens_details) ? usage.prompt_tokens_details : void 0;
11932
12965
  const cached = details ? numberOr$5(details.cached_tokens, 0) : 0;
11933
12966
  state.inputTokens = Math.max(0, prompt - cached);
11934
12967
  state.cacheReadTokens = cached;
11935
12968
  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);
12969
+ if (isRecord$19(usage.completion_tokens_details)) state.reasoningTokens = numberOr$5(usage.completion_tokens_details.reasoning_tokens, state.reasoningTokens);
11937
12970
  }
11938
12971
  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;
12972
+ const choice = isRecord$19(choices[0]) ? choices[0] : void 0;
12973
+ const delta = isRecord$19(choice?.delta) ? choice.delta : void 0;
11941
12974
  if (delta) {
11942
- const reasoning = asString$11(delta.reasoning_content) ?? asString$11(delta.reasoning);
12975
+ const reasoning = asString$12(delta.reasoning_content) ?? asString$12(delta.reasoning);
11943
12976
  if (reasoning !== void 0 && reasoning !== "") {
11944
12977
  out.push(...closeToolCalls$4(state));
11945
12978
  if (state.current === null || state.current.type !== "reasoning") out.push(...openTextBlock$3(state, "reasoning"));
@@ -11951,7 +12984,7 @@ function processOpenAIStreamLine$1(line, state) {
11951
12984
  text: sanitizeText$4(reasoning)
11952
12985
  });
11953
12986
  }
11954
- const content = asString$11(delta.content);
12987
+ const content = asString$12(delta.content);
11955
12988
  if (content !== void 0 && content !== "") {
11956
12989
  out.push(...closeToolCalls$4(state));
11957
12990
  if (state.current === null || state.current.type !== "text") out.push(...openTextBlock$3(state, "text"));
@@ -11965,11 +12998,11 @@ function processOpenAIStreamLine$1(line, state) {
11965
12998
  }
11966
12999
  const toolDeltas = Array.isArray(delta.tool_calls) ? delta.tool_calls : [];
11967
13000
  for (const entry of toolDeltas) {
11968
- if (!isRecord$18(entry)) continue;
13001
+ if (!isRecord$19(entry)) continue;
11969
13002
  out.push(...applyOpenAIToolDelta$1(entry, state));
11970
13003
  }
11971
13004
  }
11972
- const finish = asString$11(choice?.finish_reason);
13005
+ const finish = asString$12(choice?.finish_reason);
11973
13006
  if (finish !== void 0 && finish !== "") {
11974
13007
  state.finishReason = finish;
11975
13008
  out.push(...closeCurrent$3(state));
@@ -11979,15 +13012,15 @@ function processOpenAIStreamLine$1(line, state) {
11979
13012
  }
11980
13013
  function applyOpenAIToolDelta$1(entry, state) {
11981
13014
  const wireIndex = typeof entry.index === "number" ? entry.index : 0;
11982
- const fn = isRecord$18(entry.function) ? entry.function : {};
13015
+ const fn = isRecord$19(entry.function) ? entry.function : {};
11983
13016
  const out = [];
11984
13017
  let call = state.toolCalls.get(wireIndex);
11985
13018
  if (call === void 0) {
11986
13019
  out.push(...closeCurrent$3(state));
11987
13020
  call = {
11988
13021
  blockIndex: state.blocks.length,
11989
- id: asString$11(entry.id) ?? `call_${wireIndex}`,
11990
- name: asString$11(fn.name) ?? "",
13022
+ id: asString$12(entry.id) ?? `call_${wireIndex}`,
13023
+ name: asString$12(fn.name) ?? "",
11991
13024
  arguments: "",
11992
13025
  started: false
11993
13026
  };
@@ -12000,13 +13033,13 @@ function applyOpenAIToolDelta$1(entry, state) {
12000
13033
  state.toolCalls.set(wireIndex, call);
12001
13034
  } else {
12002
13035
  if (call.id === `call_${wireIndex}`) {
12003
- const id = asString$11(entry.id);
13036
+ const id = asString$12(entry.id);
12004
13037
  if (id !== void 0) call.id = id;
12005
13038
  }
12006
- const name = asString$11(fn.name);
13039
+ const name = asString$12(fn.name);
12007
13040
  if (name !== void 0 && name !== "") call.name = name;
12008
13041
  }
12009
- const argsDelta = asString$11(fn.arguments) ?? "";
13042
+ const argsDelta = asString$12(fn.arguments) ?? "";
12010
13043
  if (argsDelta !== "") call.arguments += argsDelta;
12011
13044
  if (!call.started) {
12012
13045
  call.started = true;
@@ -12037,12 +13070,12 @@ function processAnthropicStreamLine$1(line, state) {
12037
13070
  const payload = trimmed.slice(5).trim();
12038
13071
  if (payload === "" || payload === "[DONE]") return [];
12039
13072
  const event = safeJsonParse$4(payload);
12040
- if (!isRecord$18(event)) return [];
12041
- const type = asString$11(event.type);
13073
+ if (!isRecord$19(event)) return [];
13074
+ const type = asString$12(event.type);
12042
13075
  const out = [];
12043
13076
  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;
13077
+ const message = isRecord$19(event.message) ? event.message : void 0;
13078
+ const usage = message && isRecord$19(message.usage) ? message.usage : void 0;
12046
13079
  if (usage) {
12047
13080
  state.sawUsage = true;
12048
13081
  state.inputTokens = numberOr$5(usage.input_tokens, 0);
@@ -12050,21 +13083,21 @@ function processAnthropicStreamLine$1(line, state) {
12050
13083
  state.cacheWriteTokens = numberOr$5(usage.cache_creation_input_tokens, 0);
12051
13084
  state.outputTokens = numberOr$5(usage.output_tokens, 0);
12052
13085
  }
12053
- const stop = message ? asString$11(message.stop_reason) : void 0;
13086
+ const stop = message ? asString$12(message.stop_reason) : void 0;
12054
13087
  if (stop !== void 0 && stop !== null) state.finishReason = stop;
12055
13088
  return out;
12056
13089
  }
12057
13090
  if (type === "content_block_start") {
12058
13091
  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);
13092
+ const block = isRecord$19(event.content_block) ? event.content_block : {};
13093
+ const blockType = asString$12(block.type);
12061
13094
  out.push(...closeCurrent$3(state));
12062
13095
  if (blockType === "tool_use") {
12063
13096
  const index = state.blocks.length;
12064
13097
  const pending = {
12065
13098
  blockIndex: index,
12066
- id: asString$11(block.id) ?? `toolu_${contentIndex}`,
12067
- name: asString$11(block.name) ?? "",
13099
+ id: asString$12(block.id) ?? `toolu_${contentIndex}`,
13100
+ name: asString$12(block.name) ?? "",
12068
13101
  arguments: "",
12069
13102
  started: true
12070
13103
  };
@@ -12106,11 +13139,11 @@ function processAnthropicStreamLine$1(line, state) {
12106
13139
  }
12107
13140
  if (type === "content_block_delta") {
12108
13141
  const contentIndex = numberOr$5(event.index, 0);
12109
- const delta = isRecord$18(event.delta) ? event.delta : {};
12110
- const deltaType = asString$11(delta.type);
13142
+ const delta = isRecord$19(event.delta) ? event.delta : {};
13143
+ const deltaType = asString$12(delta.type);
12111
13144
  if (deltaType === "input_json_delta") {
12112
13145
  const pending = state.toolCalls.get(contentIndex);
12113
- const partial = asString$11(delta.partial_json) ?? "";
13146
+ const partial = asString$12(delta.partial_json) ?? "";
12114
13147
  if (pending !== void 0) {
12115
13148
  pending.arguments += partial;
12116
13149
  out.push({
@@ -12123,7 +13156,7 @@ function processAnthropicStreamLine$1(line, state) {
12123
13156
  }
12124
13157
  return out;
12125
13158
  }
12126
- const text = deltaType === "thinking_delta" ? asString$11(delta.thinking) : asString$11(delta.text);
13159
+ const text = deltaType === "thinking_delta" ? asString$12(delta.thinking) : asString$12(delta.text);
12127
13160
  if (text !== void 0 && text !== "") {
12128
13161
  const index = state.contentIndexes.get(contentIndex) ?? state.current?.index;
12129
13162
  const kind = deltaType === "thinking_delta" ? "reasoning" : "text";
@@ -12179,10 +13212,10 @@ function processAnthropicStreamLine$1(line, state) {
12179
13212
  return out;
12180
13213
  }
12181
13214
  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;
13215
+ const delta = isRecord$19(event.delta) ? event.delta : void 0;
13216
+ const stop = delta ? asString$12(delta.stop_reason) : void 0;
12184
13217
  if (stop !== void 0 && stop !== "") state.finishReason = stop;
12185
- const usage = isRecord$18(event.usage) ? event.usage : void 0;
13218
+ const usage = isRecord$19(event.usage) ? event.usage : void 0;
12186
13219
  if (usage) {
12187
13220
  state.sawUsage = true;
12188
13221
  state.outputTokens = numberOr$5(usage.output_tokens, state.outputTokens);
@@ -12193,7 +13226,7 @@ function processAnthropicStreamLine$1(line, state) {
12193
13226
  state.done = true;
12194
13227
  return closeStream$4(state);
12195
13228
  }
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");
13229
+ 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
13230
  return out;
12198
13231
  }
12199
13232
  function tokenUsage$4(state) {
@@ -12207,10 +13240,200 @@ function tokenUsage$4(state) {
12207
13240
  }
12208
13241
  function finishReasonFor$4(state) {
12209
13242
  const reason = state.finishReason ?? "";
12210
- if (reason === "length" || reason === "max_tokens") return { kind: "max-tokens" };
13243
+ if (reason === "length" || reason === "max_tokens" || reason === "max_output_tokens") return { kind: "max-tokens" };
12211
13244
  if (state.hasToolCall || reason === "tool_calls" || reason === "tool_use") return { kind: "tool-calls" };
12212
13245
  return { kind: "stop" };
12213
13246
  }
13247
+ /**
13248
+ * Fold one SSE line from `/responses`.
13249
+ *
13250
+ * The Responses stream is event-typed rather than delta-shaped: text arrives as
13251
+ * `response.output_text.delta`, tool calls as `function_call_arguments.delta`
13252
+ * keyed by an item id, and the turn ends on `response.completed` or
13253
+ * `response.failed`. Those are read as written; there is no Chat Completions
13254
+ * shape to reinterpret.
13255
+ */
13256
+ function processResponsesStreamLine(line, state) {
13257
+ const trimmed = line.trim();
13258
+ if (trimmed === "" || !trimmed.startsWith("data:")) return [];
13259
+ const payload = trimmed.slice(5).trim();
13260
+ if (payload === "" || payload === "[DONE]") return [];
13261
+ let parsed;
13262
+ try {
13263
+ parsed = JSON.parse(payload);
13264
+ } catch {
13265
+ return [];
13266
+ }
13267
+ if (!isRecord$19(parsed)) return [];
13268
+ const type = typeof parsed.type === "string" ? parsed.type : "";
13269
+ if (type === "response.output_text.delta") {
13270
+ const delta = typeof parsed.delta === "string" ? parsed.delta : "";
13271
+ if (delta === "") return [];
13272
+ state.hasContent = true;
13273
+ const opened = openBlock(state, "text");
13274
+ state.current.text += delta;
13275
+ return [...opened, {
13276
+ type: "text-delta",
13277
+ index: state.current.index,
13278
+ text: delta
13279
+ }];
13280
+ }
13281
+ if (type === "response.reasoning_summary_text.delta") {
13282
+ const delta = typeof parsed.delta === "string" ? parsed.delta : "";
13283
+ if (delta === "") return [];
13284
+ state.hasContent = true;
13285
+ const opened = openBlock(state, "reasoning");
13286
+ state.current.text += delta;
13287
+ return [...opened, {
13288
+ type: "reasoning-delta",
13289
+ index: state.current.index,
13290
+ text: delta
13291
+ }];
13292
+ }
13293
+ if (type === "response.output_item.added") {
13294
+ const item = isRecord$19(parsed.item) ? parsed.item : null;
13295
+ if (item === null || item.type !== "function_call") return [];
13296
+ return startResponsesToolCall(state, parsed, item);
13297
+ }
13298
+ if (type === "response.function_call_arguments.delta") {
13299
+ const delta = typeof parsed.delta === "string" ? parsed.delta : "";
13300
+ const call = responsesToolCall(state, parsed.item_id ?? parsed.call_id);
13301
+ if (call === void 0 || delta === "") return [];
13302
+ call.arguments += delta;
13303
+ return [{
13304
+ type: "tool-call-delta",
13305
+ index: call.blockIndex,
13306
+ id: toToolCallId(call.id),
13307
+ name: call.name,
13308
+ argumentsDelta: delta
13309
+ }];
13310
+ }
13311
+ if (type === "response.function_call_arguments.done") {
13312
+ const call = responsesToolCall(state, parsed.item_id ?? parsed.call_id);
13313
+ if (call === void 0) return [];
13314
+ if (typeof parsed.arguments === "string") call.arguments = parsed.arguments;
13315
+ return [];
13316
+ }
13317
+ if (type === "response.completed" || type === "response.incomplete") {
13318
+ const response = isRecord$19(parsed.response) ? parsed.response : null;
13319
+ if (response !== null) {
13320
+ if (response.status === "failed") {
13321
+ state.done = true;
13322
+ const errorObj = isRecord$19(response.error) ? response.error : null;
13323
+ const detail = errorObj && typeof errorObj.message === "string" ? errorObj.message : typeof response.error === "string" ? response.error : "";
13324
+ throw new LlmError$1(detail === "" ? "Command Code responses stream failed" : `Command Code responses stream failed: ${detail}`, "PROVIDER_ERROR");
13325
+ }
13326
+ readResponsesUsage(response, state);
13327
+ }
13328
+ state.done = true;
13329
+ if (type === "response.incomplete") {
13330
+ const details = response && isRecord$19(response.incomplete_details) ? response.incomplete_details : null;
13331
+ const reason = details && typeof details.reason === "string" ? details.reason : "";
13332
+ state.finishReason = reason === "max_output_tokens" || reason === "length" || reason === "max_tokens" ? "length" : reason || "length";
13333
+ } else state.finishReason = "stop";
13334
+ return [];
13335
+ }
13336
+ if (type === "response.failed" || type === "error") {
13337
+ state.done = true;
13338
+ const errorObj = isRecord$19(parsed.error) ? parsed.error : isRecord$19(parsed.response) && isRecord$19(parsed.response.error) ? parsed.response.error : null;
13339
+ 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 : "");
13340
+ throw new LlmError$1(detail === "" ? "Command Code responses stream failed" : `Command Code responses stream failed: ${detail}`, "PROVIDER_ERROR");
13341
+ }
13342
+ return [];
13343
+ }
13344
+ function openBlock(state, type) {
13345
+ if (state.current !== null && state.current.type === type) return [];
13346
+ const out = closeCurrent$3(state);
13347
+ state.current = {
13348
+ index: state.blocks.length,
13349
+ type,
13350
+ text: ""
13351
+ };
13352
+ state.blocks.push({
13353
+ type,
13354
+ text: ""
13355
+ });
13356
+ out.push({
13357
+ type: "block-start",
13358
+ index: state.current.index,
13359
+ blockType: type
13360
+ });
13361
+ return out;
13362
+ }
13363
+ function startResponsesToolCall(state, event, item) {
13364
+ const out = closeCurrent$3(state);
13365
+ const index = state.blocks.length;
13366
+ const name = typeof item.name === "string" ? item.name : "";
13367
+ const itemId = typeof item.id === "string" ? item.id : typeof event.item_id === "string" ? event.item_id : null;
13368
+ const call = {
13369
+ blockIndex: index,
13370
+ id: typeof item.call_id === "string" ? item.call_id : `call_${String(itemId ?? index)}`,
13371
+ name,
13372
+ arguments: "",
13373
+ started: true
13374
+ };
13375
+ state.blocks.push({
13376
+ type: "tool-call",
13377
+ id: toToolCallId(call.id),
13378
+ name,
13379
+ arguments: ""
13380
+ });
13381
+ state.toolCalls.set(index, call);
13382
+ if (itemId !== null) responsesItemIndexOf(state).set(itemId, call);
13383
+ state.hasToolCall = true;
13384
+ out.push({
13385
+ type: "block-start",
13386
+ index,
13387
+ blockType: "tool-call"
13388
+ });
13389
+ out.push({
13390
+ type: "tool-call-delta",
13391
+ index,
13392
+ id: toToolCallId(call.id),
13393
+ name,
13394
+ argumentsDelta: ""
13395
+ });
13396
+ return out;
13397
+ }
13398
+ /** Item id -> call, held beside the stream state that produced it. */
13399
+ const responsesItemIndex = /* @__PURE__ */ new WeakMap();
13400
+ function responsesItemIndexOf(state) {
13401
+ let index = responsesItemIndex.get(state);
13402
+ if (index === void 0) {
13403
+ index = /* @__PURE__ */ new Map();
13404
+ responsesItemIndex.set(state, index);
13405
+ }
13406
+ return index;
13407
+ }
13408
+ /**
13409
+ * The call a function-arguments event refers to.
13410
+ *
13411
+ * The event carries `item_id` while the recorded call carries `call_id`, so
13412
+ * matching on the call id alone silently dropped every argument delta. The
13413
+ * call-id fallback stays for a provider that only sends one of the two.
13414
+ */
13415
+ function responsesToolCall(state, key) {
13416
+ if (typeof key !== "string" || key === "") return void 0;
13417
+ const match = responsesItemIndex.get(state)?.get(key);
13418
+ if (match !== void 0) return match;
13419
+ for (const call of state.toolCalls.values()) if (call.id === toToolCallId(key)) return call;
13420
+ }
13421
+ function readResponsesUsage(response, state) {
13422
+ const usage = isRecord$19(response.usage) ? response.usage : null;
13423
+ if (usage === null) return;
13424
+ const cached = isRecord$19(usage.input_tokens_details) ? numberOrZero(usage.input_tokens_details.cached_tokens) : 0;
13425
+ const written = isRecord$19(usage.input_tokens_details) ? numberOrZero(usage.input_tokens_details.cache_write_tokens) : 0;
13426
+ state.inputTokens = Math.max(0, numberOrZero(usage.input_tokens) - cached - written);
13427
+ state.cacheReadTokens = cached;
13428
+ state.cacheWriteTokens = written;
13429
+ state.outputTokens = numberOrZero(usage.output_tokens);
13430
+ const outputDetails = isRecord$19(usage.output_tokens_details) ? usage.output_tokens_details : null;
13431
+ if (outputDetails !== null) state.reasoningTokens = numberOrZero(outputDetails.reasoning_tokens);
13432
+ state.sawUsage = true;
13433
+ }
13434
+ function numberOrZero(value) {
13435
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
13436
+ }
12214
13437
  /** Flush every open block, then emit usage and the terminal finish. */
12215
13438
  function closeStream$4(state) {
12216
13439
  if (state.finished) return [];
@@ -12231,6 +13454,213 @@ function assertStreamComplete$4(state) {
12231
13454
  if (!state.done && state.finishReason === null) throw new LlmError$1("Command Code stream ended before its terminal event", "PROVIDER_ERROR");
12232
13455
  }
12233
13456
  //#endregion
13457
+ //#region src/host/common/capabilities.ts
13458
+ /** Error thrown by requireCapability when a capability contract fails. */
13459
+ var CapabilityError = class extends Error {
13460
+ query;
13461
+ evaluation;
13462
+ constructor(message, query, evaluation) {
13463
+ super(message);
13464
+ this.name = "CapabilityError";
13465
+ this.query = query;
13466
+ this.evaluation = evaluation;
13467
+ }
13468
+ };
13469
+ /** Known Ollama vision model prefixes per official model documentation. */
13470
+ const KNOWN_OLLAMA_VISION_PREFIXES = [
13471
+ "llava",
13472
+ "bakllava",
13473
+ "llama3.2-vision",
13474
+ "moondream",
13475
+ "minicpm-v",
13476
+ "qwen2.5-vl",
13477
+ "qwen2-vl"
13478
+ ];
13479
+ /**
13480
+ * Matcher for Ollama vision models, aligned with src/host/ollama/types.ts.
13481
+ * Checks exact base name before ':' or '/' to prevent partial hijacking (e.g. 'llava-evil').
13482
+ */
13483
+ function isKnownOllamaVisionModel(modelId) {
13484
+ const base = modelId.toLowerCase().trim().split(":")[0]?.trim() ?? "";
13485
+ return KNOWN_OLLAMA_VISION_PREFIXES.includes(base);
13486
+ }
13487
+ /** Ollama thinking models with level control ('low'|'medium'|'high'). */
13488
+ const KNOWN_OLLAMA_THINKING_LEVELS_PREFIXES = ["gpt-oss"];
13489
+ /** Ollama thinking models with boolean control (true|false). */
13490
+ const KNOWN_OLLAMA_THINKING_BOOLEAN_PREFIXES = [
13491
+ "deepseek-r1",
13492
+ "qwq",
13493
+ "qwen3"
13494
+ ];
13495
+ /** Matcher for Ollama thinking models, aligned with src/host/ollama/types.ts. */
13496
+ function isKnownOllamaThinkingModel(modelId) {
13497
+ const base = modelId.toLowerCase().trim().split(":")[0]?.trim() ?? "";
13498
+ return KNOWN_OLLAMA_THINKING_LEVELS_PREFIXES.includes(base) || KNOWN_OLLAMA_THINKING_BOOLEAN_PREFIXES.includes(base);
13499
+ }
13500
+ /** Immutable validated capability evidence registry. */
13501
+ const CAPABILITY_EVIDENCE_REGISTRY = Object.freeze([
13502
+ {
13503
+ id: "ollama-native-image",
13504
+ provider: "ollama",
13505
+ wire: ["openai", "native"],
13506
+ modelSelector: isKnownOllamaVisionModel,
13507
+ authMode: "api-key",
13508
+ capability: "native-image",
13509
+ status: "supported",
13510
+ evidenceLevel: "offline-contract",
13511
+ implemented: true,
13512
+ date: "2026-10-03",
13513
+ testPath: "test/ollama-projection.test.ts",
13514
+ versionTag: "0.2.0-rc.2",
13515
+ 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."
13516
+ },
13517
+ {
13518
+ id: "ollama-thinking-control",
13519
+ provider: "ollama",
13520
+ wire: ["openai", "native"],
13521
+ modelSelector: isKnownOllamaThinkingModel,
13522
+ authMode: "api-key",
13523
+ capability: "thinking-control",
13524
+ status: "supported",
13525
+ evidenceLevel: "offline-contract",
13526
+ implemented: true,
13527
+ date: "2026-10-03",
13528
+ testPath: "test/ollama-projection.test.ts",
13529
+ link: "https://github.com/ollama/ollama/blob/main/docs/capabilities/thinking.mdx",
13530
+ versionTag: "0.2.0-rc.2",
13531
+ notes: "Model-level think control for gpt-oss (levels) and deepseek-r1/qwq/qwen3 (boolean). Replay across turns remains unsupported."
13532
+ },
13533
+ {
13534
+ id: "command-code-zdr",
13535
+ provider: "command-code",
13536
+ wire: [
13537
+ "responses",
13538
+ "anthropic",
13539
+ "openai",
13540
+ "chat-completions",
13541
+ "messages"
13542
+ ],
13543
+ modelSelector: "*",
13544
+ authMode: [
13545
+ "api-key",
13546
+ "token",
13547
+ "oauth"
13548
+ ],
13549
+ capability: "zdr",
13550
+ status: "supported",
13551
+ evidenceLevel: "offline-contract",
13552
+ implemented: true,
13553
+ date: "2026-10-03",
13554
+ testPath: "test/command-code-adapter.test.ts",
13555
+ link: "https://github.com/CommandCodeAI/pi-commandcode-provider/blob/main/index.ts",
13556
+ versionTag: "0.2.0-rc.2",
13557
+ notes: "Official contract: x-cmd-zdr: 1 header active across provider wires. 422 cmd_zdr_no_providers cannot silently degrade."
13558
+ }
13559
+ ].map((r) => Object.freeze({
13560
+ ...r,
13561
+ wire: Array.isArray(r.wire) ? Object.freeze([...r.wire]) : r.wire,
13562
+ authMode: Array.isArray(r.authMode) ? Object.freeze([...r.authMode]) : r.authMode,
13563
+ modelSelector: Array.isArray(r.modelSelector) ? Object.freeze([...r.modelSelector]) : r.modelSelector
13564
+ })));
13565
+ function matchesWire(expected, actual) {
13566
+ const normActual = actual.toLowerCase().trim();
13567
+ if (typeof expected === "string") {
13568
+ if (expected === "*") return true;
13569
+ return expected.toLowerCase().trim() === normActual;
13570
+ }
13571
+ return expected.some((w) => w.toLowerCase().trim() === normActual);
13572
+ }
13573
+ function matchesAuth(expected, actual) {
13574
+ const normActual = actual.toLowerCase().trim();
13575
+ if (typeof expected === "string") {
13576
+ if (expected === "*") return true;
13577
+ return expected.toLowerCase().trim() === normActual;
13578
+ }
13579
+ return expected.some((a) => a.toLowerCase().trim() === normActual);
13580
+ }
13581
+ function matchesModel(selector, model) {
13582
+ const normModel = model.toLowerCase().trim();
13583
+ if (typeof selector === "function") return selector(model);
13584
+ if (typeof selector === "string") {
13585
+ if (selector === "*") return true;
13586
+ const normSelector = selector.toLowerCase().trim();
13587
+ return normModel === normSelector || normModel.startsWith(normSelector + ":") || normModel.startsWith(normSelector + "/");
13588
+ }
13589
+ return selector.some((s) => {
13590
+ const normS = s.toLowerCase().trim();
13591
+ return normModel === normS || normModel.startsWith(normS + ":") || normModel.startsWith(normS + "/");
13592
+ });
13593
+ }
13594
+ /** Query evidence records matching the given criteria. Supports optional pure registry argument for tests. */
13595
+ function queryCapabilityEvidence(query, registry = CAPABILITY_EVIDENCE_REGISTRY) {
13596
+ return registry.filter((rec) => {
13597
+ if (query.provider && rec.provider.toLowerCase() !== query.provider.toLowerCase().trim()) return false;
13598
+ if (query.capability && rec.capability !== query.capability) return false;
13599
+ if (query.wire && !matchesWire(rec.wire, query.wire)) return false;
13600
+ if (query.authMode && !matchesAuth(rec.authMode, query.authMode)) return false;
13601
+ if (query.model && !matchesModel(rec.modelSelector, query.model)) return false;
13602
+ return true;
13603
+ });
13604
+ }
13605
+ /**
13606
+ * Evaluate whether a capability is permitted for a concrete request target.
13607
+ * Fails closed if query is missing explicit fields, or lacks matching verified evidence.
13608
+ */
13609
+ function evaluateCapability(query, registry = CAPABILITY_EVIDENCE_REGISTRY) {
13610
+ 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 {
13611
+ allowed: false,
13612
+ state: "unknown",
13613
+ implemented: false,
13614
+ reason: "Capability query requires non-empty provider, wire, model, authMode, and capability"
13615
+ };
13616
+ const matching = queryCapabilityEvidence(query, registry);
13617
+ if (matching.length === 0) return {
13618
+ allowed: false,
13619
+ state: "unknown",
13620
+ implemented: false,
13621
+ reason: `No matching verified evidence for ${query.provider}/${query.wire}/${query.model} (auth: ${query.authMode}, capability: ${query.capability}).`
13622
+ };
13623
+ const record = matching[0];
13624
+ const isSupportedLevel = record.evidenceLevel === "live-validated" || record.evidenceLevel === "offline-contract";
13625
+ const isAllowed = record.implemented && record.status === "supported" && isSupportedLevel;
13626
+ return {
13627
+ allowed: isAllowed,
13628
+ state: record.status,
13629
+ implemented: record.implemented,
13630
+ evidenceLevel: record.evidenceLevel,
13631
+ evidence: record,
13632
+ reason: isAllowed ? void 0 : record.notes ?? `Capability '${query.capability}' is not permitted.`
13633
+ };
13634
+ }
13635
+ /**
13636
+ * Require that a capability is supported and allowed. Throws CapabilityError otherwise.
13637
+ */
13638
+ function requireCapability(query, registry = CAPABILITY_EVIDENCE_REGISTRY) {
13639
+ const result = evaluateCapability(query, registry);
13640
+ 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);
13641
+ return result;
13642
+ }
13643
+ /** Whether Ollama image input is supported for the target model. */
13644
+ function supportsOllamaImage(model, wire = "openai", authMode = "api-key") {
13645
+ return evaluateCapability({
13646
+ provider: "ollama",
13647
+ wire,
13648
+ model,
13649
+ authMode,
13650
+ capability: "native-image"
13651
+ }).allowed;
13652
+ }
13653
+ /** Whether Ollama thinking control is supported for the target model. */
13654
+ function supportsOllamaThinkingControl(model, wire = "openai", authMode = "api-key") {
13655
+ return evaluateCapability({
13656
+ provider: "ollama",
13657
+ wire,
13658
+ model,
13659
+ authMode,
13660
+ capability: "thinking-control"
13661
+ }).allowed;
13662
+ }
13663
+ //#endregion
12234
13664
  //#region src/host/command-code/adapter.ts
12235
13665
  /**
12236
13666
  * Transient-failure retry policy for the `command-code` route.
@@ -12264,6 +13694,41 @@ const RETRY_POLICY$5 = resolveRetryPolicy({
12264
13694
  function resolveDefaultReasoningEffort$3(efforts, configuredEffort) {
12265
13695
  if (configuredEffort && efforts.includes(configuredEffort)) return ReasoningEffortId(configuredEffort);
12266
13696
  }
13697
+ /**
13698
+ * Resolves whether Zero Data Retention (ZDR) routing is active for Command Code.
13699
+ * An explicit boolean on adapter options takes precedence over host environment variables.
13700
+ * When not specified in options, DSH_COMMAND_CODE_ZDR is consulted first, then CMD_ZDR.
13701
+ */
13702
+ function resolveZeroDataRetention(optionsZdr) {
13703
+ if (optionsZdr !== void 0) return optionsZdr;
13704
+ if (process.env.DSH_COMMAND_CODE_ZDR !== void 0) return process.env.DSH_COMMAND_CODE_ZDR === "1";
13705
+ return process.env.CMD_ZDR === "1";
13706
+ }
13707
+ /**
13708
+ * Classifies model-specific access or plan entitlement denials.
13709
+ * These are failures of permissions/entitlement for this model/account,
13710
+ * not invalid API credentials, so the account must not be marked invalid
13711
+ * and whole-account rotation must not occur.
13712
+ */
13713
+ function isCommandCodeModelAccessDenied(status, detail) {
13714
+ if (status === 403 || status === 404 || status === 400) {
13715
+ const lower = detail.toLowerCase();
13716
+ 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;
13717
+ if (status === 403 && !isCommandCodeCredentialInvalid(status, detail)) return true;
13718
+ }
13719
+ return false;
13720
+ }
13721
+ /**
13722
+ * Classifies actual credential invalidity (e.g. 401 Unauthorized, or 403 with explicit key errors).
13723
+ */
13724
+ function isCommandCodeCredentialInvalid(status, detail) {
13725
+ if (status === 401) return true;
13726
+ if (status === 403) {
13727
+ const lower = detail.toLowerCase();
13728
+ 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;
13729
+ }
13730
+ return false;
13731
+ }
12267
13732
  /** Cooldown one rate-limited key takes when the provider states no delay. */
12268
13733
  const POOL_COOLDOWN_MS$4 = 15 * 6e4;
12269
13734
  var CommandCodeAdapter = class extends LlmAdapter {
@@ -12380,7 +13845,16 @@ var CommandCodeAdapter = class extends LlmAdapter {
12380
13845
  }
12381
13846
  async *requestStream(options, signal) {
12382
13847
  const fetchFn = this.options.fetchFn ?? fetch;
12383
- const wire = wireForModel$2(options.model);
13848
+ const catalogEntry = (await this.catalog()).find((entry) => entry.id === options.model);
13849
+ const wire = (catalogEntry === void 0 ? void 0 : wireForCatalogEntry$1(catalogEntry)) ?? wireForModel$2(options.model);
13850
+ const isZdr = resolveZeroDataRetention(this.options.zeroDataRetention);
13851
+ if (isZdr) requireCapability({
13852
+ provider: PROVIDER_ID$5,
13853
+ wire,
13854
+ model: options.model,
13855
+ authMode: "api-key",
13856
+ capability: "zdr"
13857
+ });
12384
13858
  const requestOptions = offloadOldestRequestImages$3(normalizeGenerateOptions(options));
12385
13859
  const images = await resolveRequestImages$3(requestOptions, this.options.attachments, signal);
12386
13860
  const body = JSON.stringify(buildRequest$1(requestOptions, wire, images));
@@ -12404,17 +13878,17 @@ var CommandCodeAdapter = class extends LlmAdapter {
12404
13878
  apiKey = effective.credentials.apiKey;
12405
13879
  apiEnv = effective.apiEnv;
12406
13880
  }
12407
- const endpoint = `${providerUrl(apiEnv)}${wire === "anthropic" ? "/messages" : "/chat/completions"}`;
12408
- const headers = wire === "anthropic" ? {
13881
+ const endpoint = `${providerUrl(apiEnv)}${endpointPathFor(wire)}`;
13882
+ const baseHeaders = {
12409
13883
  ...commandCodeHeaders(apiKey),
12410
13884
  "user-agent": PLUGIN_USER_AGENT,
12411
13885
  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"
13886
+ ...isZdr ? { [HEADER_ZDR]: "1" } : {}
12417
13887
  };
13888
+ const headers = wire === "anthropic" ? {
13889
+ ...baseHeaders,
13890
+ "anthropic-version": "2023-06-01"
13891
+ } : baseHeaders;
12418
13892
  try {
12419
13893
  response = await fetchFn(endpoint, {
12420
13894
  method: "POST",
@@ -12428,8 +13902,13 @@ var CommandCodeAdapter = class extends LlmAdapter {
12428
13902
  }
12429
13903
  if (response.ok) break;
12430
13904
  detail = (await response.text().catch(() => "")).slice(0, 600);
13905
+ if (response.status === 422) {
13906
+ 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 });
13907
+ break;
13908
+ }
12431
13909
  if (pool === null || accountId === void 0) break;
12432
- if (response.status === 401 || response.status === 403) {
13910
+ if (isCommandCodeModelAccessDenied(response.status, detail)) break;
13911
+ if (isCommandCodeCredentialInvalid(response.status, detail)) {
12433
13912
  await pool.markAuthFailed(accountId, `${PROVIDER_NAME$5} rejected the stored API key (${response.status}).`, "invalid").catch(() => void 0);
12434
13913
  if (await pool.hasAnotherAvailableAccount(tried)) continue;
12435
13914
  break;
@@ -12444,7 +13923,12 @@ var CommandCodeAdapter = class extends LlmAdapter {
12444
13923
  }
12445
13924
  if (response === void 0 || !response.ok) {
12446
13925
  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 });
13926
+ if (status === 422) {
13927
+ 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 });
13928
+ throw new LlmError(`${PROVIDER_NAME$5} validation error (422): ${detail || "Unprocessable Entity"}`, "PROVIDER_ERROR", { status: 422 });
13929
+ }
13930
+ 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 });
13931
+ 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
13932
  if (status === 429) {
12449
13933
  const after = response === void 0 ? void 0 : retryAfterMs$1(response.headers);
12450
13934
  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 +13966,16 @@ var CommandCodeAdapter = class extends LlmAdapter {
12482
13966
  }
12483
13967
  }
12484
13968
  };
13969
+ /** Path suffix the chosen route answers on. */
13970
+ function endpointPathFor(wire) {
13971
+ if (wire === "anthropic") return "/messages";
13972
+ if (wire === "responses") return "/responses";
13973
+ return "/chat/completions";
13974
+ }
12485
13975
  function processLine$1(line, state, wire) {
12486
- return wire === "anthropic" ? processAnthropicStreamLine$1(line, state) : processOpenAIStreamLine$1(line, state);
13976
+ if (wire === "anthropic") return processAnthropicStreamLine$1(line, state);
13977
+ if (wire === "responses") return processResponsesStreamLine(line, state);
13978
+ return processOpenAIStreamLine$1(line, state);
12487
13979
  }
12488
13980
  //#endregion
12489
13981
  //#region src/host/command-code/account-pool.ts
@@ -13211,6 +14703,9 @@ async function getCommandCodeWebStatus(store, modelSettings, preferences, option
13211
14703
  }),
13212
14704
  quota: quota ?? null,
13213
14705
  lastFetchedAt: quota?.fetchedAt ?? null,
14706
+ catalogFetchedAt: getCachedCatalogFetchedAt(),
14707
+ catalogStale: isCatalogStale(),
14708
+ ...resolveZeroDataRetention(options.zeroDataRetention) ? { zeroDataRetention: true } : options.zeroDataRetention !== void 0 ? { zeroDataRetention: false } : {},
13214
14709
  models,
13215
14710
  contextWindowOverrides: settings.contextWindowOverrides,
13216
14711
  defaultReasoningEffort: settings.defaultReasoningEffort,
@@ -13571,6 +15066,48 @@ function ollamaHeaders(apiKey) {
13571
15066
  "content-type": "application/json"
13572
15067
  };
13573
15068
  }
15069
+ /**
15070
+ * Tightly-scoped check for known vision-capable models in Ollama.
15071
+ * Conservative allowlist of known vision families; unknown models default to false.
15072
+ */
15073
+ function ollamaModelSupportsImage(modelId) {
15074
+ return supportsOllamaImage(modelId, "openai", "api-key");
15075
+ }
15076
+ /**
15077
+ * Resolve model-specific thinking support in Ollama.
15078
+ * Source: official https://github.com/ollama/ollama/blob/main/docs/capabilities/thinking.mdx
15079
+ * - 'levels': gpt-oss family (values low, medium, high; cannot disable thinking)
15080
+ * - 'boolean': deepseek-r1, qwq, qwen3 families (values true, false)
15081
+ * - 'none': all other / unknown models (omit thinking control entirely)
15082
+ */
15083
+ function thinkingModeForModel(modelId) {
15084
+ if (!supportsOllamaThinkingControl(modelId, "native", "api-key")) return "none";
15085
+ const base = modelId.toLowerCase().split(":")[0] ?? "";
15086
+ if (base === "gpt-oss") return "levels";
15087
+ if (base === "deepseek-r1" || base === "qwq" || base === "qwen3") return "boolean";
15088
+ return "none";
15089
+ }
15090
+ /**
15091
+ * Map reasoning effort to wire think parameter for the target model.
15092
+ * Per Ollama docs:
15093
+ * - gpt-oss: only 'low' | 'medium' | 'high' are valid. gpt-oss cannot disable thinking, so 'none'
15094
+ * as well as 'xhigh'/'max'/garbage MUST be omitted (undefined).
15095
+ * - boolean models: 'none' -> false; 'low'|'medium'|'high'|'auto'|'true' -> true; garbage -> undefined.
15096
+ * - all other models: omit (undefined).
15097
+ */
15098
+ function thinkForModel(modelId, reasoningEffort) {
15099
+ if (reasoningEffort === void 0 || reasoningEffort === null) return void 0;
15100
+ const mode = thinkingModeForModel(modelId);
15101
+ if (mode === "none") return void 0;
15102
+ const effort = String(reasoningEffort).toLowerCase();
15103
+ if (mode === "boolean") {
15104
+ if (effort === "none") return false;
15105
+ if (effort === "low" || effort === "medium" || effort === "high" || effort === "auto" || effort === "true") return true;
15106
+ return;
15107
+ }
15108
+ if (effort === "low" || effort === "medium" || effort === "high") return effort;
15109
+ if (effort === "auto" || effort === "true") return "medium";
15110
+ }
13574
15111
  //#endregion
13575
15112
  //#region src/host/ollama/token-store.ts
13576
15113
  function credentialPath$4() {
@@ -13742,36 +15279,79 @@ function buildBody(wire, request) {
13742
15279
  if (wire === "openai") return buildOpenAIBody(request);
13743
15280
  return buildNativeBody(request);
13744
15281
  }
15282
+ function toBareBase64(image) {
15283
+ const match = /^data:[^;]+;base64,(.+)$/.exec(image);
15284
+ return match ? match[1] : image;
15285
+ }
15286
+ function toDataUrl(image, defaultMediaType = "image/png") {
15287
+ if (image.startsWith("data:") || image.startsWith("http://") || image.startsWith("https://")) return image;
15288
+ return `data:${defaultMediaType};base64,${image}`;
15289
+ }
15290
+ function parseArguments(raw) {
15291
+ if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) return raw;
15292
+ if (raw === void 0 || raw === null || raw === "") return {};
15293
+ if (typeof raw === "string") {
15294
+ const trimmed = raw.trim();
15295
+ if (trimmed === "" || trimmed === "{}") return {};
15296
+ const parsed = JSON.parse(trimmed);
15297
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) return parsed;
15298
+ throw new Error(`Tool call arguments must be a JSON object, got ${typeof parsed}`);
15299
+ }
15300
+ throw new Error(`Invalid tool call arguments: ${String(raw)}`);
15301
+ }
13745
15302
  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
- });
15303
+ const messages = [];
15304
+ let pendingToolImages = [];
15305
+ const flushToolImages = () => {
15306
+ if (pendingToolImages.length > 0) {
15307
+ messages.push({
15308
+ role: "user",
15309
+ content: pendingToolImages.map((image) => ({
15310
+ type: "image_url",
15311
+ image_url: { url: toDataUrl(image) }
15312
+ }))
15313
+ });
15314
+ pendingToolImages = [];
15315
+ }
15316
+ };
15317
+ for (const message of request.messages) {
15318
+ if (message.role === "tool") {
15319
+ messages.push({
15320
+ role: "tool",
15321
+ tool_call_id: message.toolCallId ?? "",
15322
+ content: message.content
15323
+ });
15324
+ if (message.images !== void 0 && message.images.length > 0) pendingToolImages.push(...message.images);
15325
+ continue;
15326
+ }
15327
+ flushToolImages();
15328
+ if (message.role === "assistant" && message.toolCalls !== void 0 && message.toolCalls.length > 0) {
15329
+ messages.push({
15330
+ role: "assistant",
15331
+ content: message.content,
15332
+ tool_calls: message.toolCalls.map((call) => ({
15333
+ id: call.id,
15334
+ type: "function",
15335
+ function: {
15336
+ name: call.name,
15337
+ arguments: typeof call.arguments === "string" ? call.arguments : JSON.stringify(call.arguments ?? {})
15338
+ }
15339
+ }))
15340
+ });
15341
+ continue;
15342
+ }
15343
+ messages.push(openAIContent(message));
15344
+ }
15345
+ flushToolImages();
13769
15346
  const body = {
13770
15347
  model: request.model,
13771
15348
  messages,
13772
15349
  stream: true,
13773
15350
  max_tokens: request.maxOutputTokens ?? 8192
13774
15351
  };
15352
+ if (typeof request.think === "string") body.reasoning_effort = request.think;
15353
+ else if (request.think === true) body.reasoning_effort = "auto";
15354
+ else if (request.think === false) body.reasoning_effort = "none";
13775
15355
  if (request.tools !== void 0 && request.tools.length > 0) body.tools = request.tools.map((tool) => ({
13776
15356
  type: "function",
13777
15357
  function: {
@@ -13785,28 +15365,30 @@ function buildOpenAIBody(request) {
13785
15365
  }
13786
15366
  function buildNativeBody(request) {
13787
15367
  const messages = request.messages.map((message) => {
13788
- if (message.role === "tool") return {
13789
- role: "tool",
13790
- content: message.content
13791
- };
15368
+ if (message.role === "tool") {
15369
+ const item = {
15370
+ role: "tool",
15371
+ content: message.content
15372
+ };
15373
+ if (message.images !== void 0 && message.images.length > 0) item.images = message.images.map(toBareBase64);
15374
+ return item;
15375
+ }
13792
15376
  if (message.role === "assistant" && message.toolCalls !== void 0 && message.toolCalls.length > 0) return {
13793
15377
  role: "assistant",
13794
15378
  content: message.content,
13795
15379
  tool_calls: message.toolCalls.map((call) => ({ function: {
13796
15380
  name: call.name,
13797
- arguments: call.arguments
15381
+ arguments: parseArguments(call.arguments)
13798
15382
  } }))
13799
15383
  };
13800
- return {
13801
- role: message.role,
13802
- content: message.content
13803
- };
15384
+ return nativeContent(message);
13804
15385
  });
13805
15386
  const body = {
13806
15387
  model: request.model,
13807
15388
  messages,
13808
15389
  stream: true
13809
15390
  };
15391
+ if (request.think !== void 0) body.think = request.think;
13810
15392
  const options = { num_predict: request.maxOutputTokens ?? 8192 };
13811
15393
  if (request.temperature !== void 0) options.temperature = request.temperature;
13812
15394
  body.options = options;
@@ -13820,6 +15402,42 @@ function buildNativeBody(request) {
13820
15402
  }));
13821
15403
  return body;
13822
15404
  }
15405
+ /**
15406
+ * A user turn on the OpenAI surface: text stays text, images become parts.
15407
+ *
15408
+ * Ollama's OpenAI compatibility takes the standard `image_url` part with a data URL.
15409
+ */
15410
+ function openAIContent(message) {
15411
+ if (message.images === void 0 || message.images.length === 0) return {
15412
+ role: message.role,
15413
+ content: message.content
15414
+ };
15415
+ return {
15416
+ role: message.role,
15417
+ content: [{
15418
+ type: "text",
15419
+ text: message.content
15420
+ }, ...message.images.map((image) => ({
15421
+ type: "image_url",
15422
+ image_url: { url: toDataUrl(image) }
15423
+ }))]
15424
+ };
15425
+ }
15426
+ /**
15427
+ * A user turn on the native surface, where images are a sibling array of raw
15428
+ * base64 strings rather than data URLs or interleaved content parts.
15429
+ */
15430
+ function nativeContent(message) {
15431
+ if (message.images === void 0 || message.images.length === 0) return {
15432
+ role: message.role,
15433
+ content: message.content
15434
+ };
15435
+ return {
15436
+ role: message.role,
15437
+ content: message.content,
15438
+ images: message.images.map(toBareBase64)
15439
+ };
15440
+ }
13823
15441
  /** The endpoint one request goes to. */
13824
15442
  function chatUrl(wire) {
13825
15443
  return wire === "openai" ? `${CLOUD_BASE_URL}${OPENAI_CHAT_PATH}` : `${CLOUD_BASE_URL}${NATIVE_CHAT_PATH}`;
@@ -13936,6 +15554,11 @@ function parseOpenAIChunk(record, pending) {
13936
15554
  type: "text",
13937
15555
  text: fields.content
13938
15556
  });
15557
+ const thinking = typeof fields.thinking === "string" ? fields.thinking : typeof fields.reasoning === "string" ? fields.reasoning : typeof fields.reasoning_content === "string" ? fields.reasoning_content : "";
15558
+ if (thinking !== "") events.push({
15559
+ type: "thinking",
15560
+ text: thinking
15561
+ });
13939
15562
  if (Array.isArray(fields.tool_calls)) for (const raw of fields.tool_calls) accumulateToolCall(raw, pending, true);
13940
15563
  }
13941
15564
  if (typeof entry.finish_reason === "string" && entry.finish_reason !== "") events.push({
@@ -13959,6 +15582,11 @@ function parseNativeChunk(record, pending) {
13959
15582
  type: "text",
13960
15583
  text: fields.content
13961
15584
  });
15585
+ const thinking = typeof fields.thinking === "string" ? fields.thinking : typeof fields.reasoning === "string" ? fields.reasoning : typeof fields.reasoning_content === "string" ? fields.reasoning_content : "";
15586
+ if (thinking !== "") events.push({
15587
+ type: "thinking",
15588
+ text: thinking
15589
+ });
13962
15590
  if (Array.isArray(fields.tool_calls)) for (const raw of fields.tool_calls) accumulateToolCall(raw, pending, false);
13963
15591
  }
13964
15592
  if (record.done === true) events.push({
@@ -14094,6 +15722,7 @@ function createStreamState$4() {
14094
15722
  return {
14095
15723
  nextBlockIndex: 0,
14096
15724
  textBlock: null,
15725
+ thinkingBlock: null,
14097
15726
  toolCalls: /* @__PURE__ */ new Map(),
14098
15727
  hasContent: false
14099
15728
  };
@@ -14101,30 +15730,85 @@ function createStreamState$4() {
14101
15730
  /** Fold one event into chunks, opening and closing blocks as the stream needs. */
14102
15731
  function applyEvent(state, event) {
14103
15732
  if (event.type === "text") return applyText(state, event.text);
15733
+ if (event.type === "thinking") return applyThinking(state, event.text);
14104
15734
  if (event.type === "tool_call") return applyToolCall(state, event.call);
14105
15735
  return [];
14106
15736
  }
15737
+ /**
15738
+ * Fold a thinking delta into its own block.
15739
+ *
15740
+ * The trace and the answer interleave, so a text delta or a tool call closes
15741
+ * the reasoning block: two open blocks at once would hand the caller chunks in
15742
+ * an order the stream never produced.
15743
+ */
15744
+ function applyThinking(state, text) {
15745
+ state.hasContent = true;
15746
+ if (state.thinkingBlock === null) {
15747
+ const out = closeText(state);
15748
+ const index = state.nextBlockIndex;
15749
+ state.nextBlockIndex += 1;
15750
+ state.thinkingBlock = {
15751
+ index,
15752
+ text
15753
+ };
15754
+ return [
15755
+ ...out,
15756
+ {
15757
+ type: "block-start",
15758
+ index,
15759
+ blockType: "reasoning"
15760
+ },
15761
+ {
15762
+ type: "reasoning-delta",
15763
+ index,
15764
+ text
15765
+ }
15766
+ ];
15767
+ }
15768
+ state.thinkingBlock.text += text;
15769
+ return [{
15770
+ type: "reasoning-delta",
15771
+ index: state.thinkingBlock.index,
15772
+ text
15773
+ }];
15774
+ }
15775
+ function closeThinking(state) {
15776
+ const block = state.thinkingBlock;
15777
+ if (block === null) return [];
15778
+ state.thinkingBlock = null;
15779
+ return [{
15780
+ type: "block-end",
15781
+ index: block.index,
15782
+ block: {
15783
+ type: "reasoning",
15784
+ text: block.text
15785
+ }
15786
+ }];
15787
+ }
14107
15788
  function applyText(state, text) {
14108
15789
  state.hasContent = true;
15790
+ const closed = closeThinking(state);
14109
15791
  if (state.textBlock === null) {
15792
+ closed.push({
15793
+ type: "block-start",
15794
+ index: state.nextBlockIndex,
15795
+ blockType: "text"
15796
+ });
14110
15797
  const index = state.nextBlockIndex;
14111
15798
  state.nextBlockIndex += 1;
14112
15799
  state.textBlock = {
14113
15800
  index,
14114
15801
  text
14115
15802
  };
14116
- return [{
14117
- type: "block-start",
14118
- index,
14119
- blockType: "text"
14120
- }, {
15803
+ closed.push({
14121
15804
  type: "text-delta",
14122
15805
  index,
14123
15806
  text
14124
- }];
15807
+ });
15808
+ return closed;
14125
15809
  }
14126
15810
  state.textBlock.text += text;
14127
- return [{
15811
+ return [...closed, {
14128
15812
  type: "text-delta",
14129
15813
  index: state.textBlock.index,
14130
15814
  text
@@ -14133,6 +15817,7 @@ function applyText(state, text) {
14133
15817
  function applyToolCall(state, call) {
14134
15818
  state.hasContent = true;
14135
15819
  const out = [];
15820
+ out.push(...closeThinking(state));
14136
15821
  out.push(...closeText(state));
14137
15822
  const index = state.nextBlockIndex;
14138
15823
  state.nextBlockIndex += 1;
@@ -14195,7 +15880,11 @@ function closeToolCalls$3(state) {
14195
15880
  */
14196
15881
  function closeStream$3(state) {
14197
15882
  const sawToolCall = state.toolCalls.size > 0;
14198
- const out = [...closeText(state), ...closeToolCalls$3(state)];
15883
+ const out = [
15884
+ ...closeThinking(state),
15885
+ ...closeText(state),
15886
+ ...closeToolCalls$3(state)
15887
+ ];
14199
15888
  out.push({
14200
15889
  type: "finish",
14201
15890
  reason: sawToolCall ? { kind: "tool-calls" } : { kind: "stop" }
@@ -14297,7 +15986,7 @@ var OllamaAdapter = class extends LlmAdapter {
14297
15986
  provider: prov,
14298
15987
  id: model.id,
14299
15988
  name: model.name ?? model.id,
14300
- inputModalities: ["text", "image"]
15989
+ inputModalities: ollamaModelSupportsImage(model.id) ? ["text", "image"] : ["text"]
14301
15990
  }));
14302
15991
  }
14303
15992
  async resolveModel(provider, modelId, signal) {
@@ -14307,7 +15996,7 @@ var OllamaAdapter = class extends LlmAdapter {
14307
15996
  provider,
14308
15997
  id: modelId,
14309
15998
  name: entry?.name ?? modelId,
14310
- inputModalities: ["text", "image"],
15999
+ inputModalities: ollamaModelSupportsImage(modelId) ? ["text", "image"] : ["text"],
14311
16000
  context: { contextWindow: contextWindowFor(entry) },
14312
16001
  ...outputReservation(contextWindowFor(entry), DEFAULT_MAX_OUTPUT_TOKENS)
14313
16002
  };
@@ -14321,10 +16010,11 @@ var OllamaAdapter = class extends LlmAdapter {
14321
16010
  async *stream(options) {
14322
16011
  yield* wrapStreamWithWatchdog((watchdogSignal) => this.requestStream(options, watchdogSignal), options.signal, STREAM_IDLE_TIMEOUT_MS$4, STREAM_IDLE_TIMEOUT_CODE$4, PROVIDER_NAME$4);
14323
16012
  }
14324
- async *requestStream(options, signal) {
16013
+ async *requestStream(rawOptions, signal) {
14325
16014
  const fetchFn = this.options.fetchFn ?? fetch;
16015
+ const options = normalizeGenerateOptions(rawOptions);
14326
16016
  const wire = wireForModel$1(options.model);
14327
- const request = toOllamaRequest(options);
16017
+ const request = await toOllamaRequest(options, this.options.attachments, signal);
14328
16018
  const pool = this.accountPool;
14329
16019
  const tried = /* @__PURE__ */ new Set();
14330
16020
  let lastStatus;
@@ -14364,7 +16054,7 @@ var OllamaAdapter = class extends LlmAdapter {
14364
16054
  spent = event.usage;
14365
16055
  continue;
14366
16056
  }
14367
- if (event.type === "text" || event.type === "tool_call") {
16057
+ if (event.type === "text" || event.type === "thinking" || event.type === "tool_call") {
14368
16058
  for (const chunk of applyEvent(state, event)) yield chunk;
14369
16059
  continue;
14370
16060
  }
@@ -14393,18 +16083,134 @@ var OllamaAdapter = class extends LlmAdapter {
14393
16083
  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
16084
  }
14395
16085
  };
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
- });
16086
+ function isAbort$2(error, signal) {
16087
+ return signal?.aborted === true || error instanceof Error && error.name === "AbortError";
16088
+ }
16089
+ function isRecord$18(value) {
16090
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16091
+ }
16092
+ function asString$11(value) {
16093
+ return typeof value === "string" && value !== "" ? value : void 0;
16094
+ }
16095
+ function attachmentLabel$4(block) {
16096
+ const attachment = isRecord$18(block.attachment) ? block.attachment : void 0;
16097
+ return asString$11(attachment?.name) || asString$11(attachment?.attachmentId);
16098
+ }
16099
+ function unavailableImageText$4(block) {
16100
+ const label = attachmentLabel$4(block);
16101
+ 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]`;
16102
+ }
16103
+ function abortException(signal) {
16104
+ if (signal?.reason !== void 0) return signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason));
16105
+ return new DOMException("The operation was aborted", "AbortError");
16106
+ }
16107
+ function unsupportedImageText$1(model, block) {
16108
+ const label = attachmentLabel$4(block);
16109
+ return `[image unsupported: ${label ? `${label}: ` : ""}model ${model} is not known to declare image support; remove the attachment or switch to a vision model]`;
16110
+ }
16111
+ function combineTextParts(parts) {
16112
+ let result = "";
16113
+ for (const part of parts) {
16114
+ if (!part) continue;
16115
+ if (!result) result = part;
16116
+ else if (part.startsWith("[image ") || result.endsWith("\n")) result = result.trimEnd() + "\n\n" + part;
16117
+ else result += part;
16118
+ }
16119
+ return result;
16120
+ }
16121
+ async function processMessageContent(content, model, attachments, signal) {
16122
+ if (signal?.aborted) throw abortException(signal);
16123
+ if (typeof content === "string") return { text: content };
16124
+ if (!Array.isArray(content)) return { text: "" };
16125
+ const textParts = [];
16126
+ const images = [];
16127
+ for (const part of content) {
16128
+ if (typeof part === "string") {
16129
+ textParts.push(part);
14406
16130
  continue;
14407
16131
  }
16132
+ if (!isRecord$18(part)) continue;
16133
+ if (part.type === "text") {
16134
+ if (typeof part.text === "string" && part.text !== "") textParts.push(part.text);
16135
+ continue;
16136
+ }
16137
+ if (part.type === "image") {
16138
+ if (!ollamaModelSupportsImage(model)) {
16139
+ textParts.push(unsupportedImageText$1(model, part));
16140
+ continue;
16141
+ }
16142
+ const reference = isImageRef(part.attachment) ? part.attachment : isImageRef(part.source) ? part.source : null;
16143
+ if (reference !== null) {
16144
+ if (!attachments) {
16145
+ textParts.push(unavailableImageText$4(part));
16146
+ continue;
16147
+ }
16148
+ try {
16149
+ if (signal?.aborted) throw abortException(signal);
16150
+ const loaded = await attachments.readImage(reference, signal);
16151
+ if (signal?.aborted) throw abortException(signal);
16152
+ if (loaded === null) textParts.push(unavailableImageText$4(part));
16153
+ else images.push(encodeImage(loaded.data, loaded.ref.mediaType));
16154
+ } catch (error) {
16155
+ if (isAbort$2(error, signal)) throw error;
16156
+ textParts.push(unavailableImageText$4(part));
16157
+ }
16158
+ continue;
16159
+ }
16160
+ const inline = part.source;
16161
+ if (isRecord$18(inline)) {
16162
+ const data = inline.data;
16163
+ if (typeof data === "string" && data !== "") {
16164
+ images.push(encodeImage(data, asMediaType(inline.mediaType)));
16165
+ continue;
16166
+ }
16167
+ }
16168
+ if (typeof part.image === "string" && part.image !== "") {
16169
+ images.push(part.image);
16170
+ continue;
16171
+ }
16172
+ textParts.push(unavailableImageText$4(part));
16173
+ continue;
16174
+ }
16175
+ }
16176
+ return {
16177
+ text: combineTextParts(textParts),
16178
+ ...images.length > 0 ? { images } : {}
16179
+ };
16180
+ }
16181
+ /**
16182
+ * Project DSH's request shape onto Ollama's, per surface.
16183
+ * Consumes the canonical Message vocabulary produced by normalizeGenerateOptions.
16184
+ */
16185
+ async function toOllamaRequest(options, attachments, signal) {
16186
+ if (signal?.aborted) throw abortException(signal);
16187
+ const messages = [];
16188
+ const trimmedSystem = typeof options.system === "string" && options.system.trim() !== "" ? options.system.trim() : void 0;
16189
+ if (trimmedSystem !== void 0) messages.push({
16190
+ role: "system",
16191
+ content: trimmedSystem
16192
+ });
16193
+ for (let index = 0; index < options.messages.length; index++) {
16194
+ const message = options.messages[index];
16195
+ if (index === 0 && trimmedSystem !== void 0 && (message.role === "system" || message.role === "developer")) {
16196
+ if (textOf$4(message.content).trim() === trimmedSystem) continue;
16197
+ }
16198
+ if ((message.source?.kind === "tool" || Array.isArray(message.content) && message.content.some((b) => b.type === "tool-result")) && Array.isArray(message.content)) {
16199
+ const toolResultBlocks = message.content.filter((b) => b.type === "tool-result");
16200
+ if (toolResultBlocks.length > 0) {
16201
+ for (const block of toolResultBlocks) {
16202
+ const toolCallId = String(block.toolCallId ?? (isRecord$18(message.source) ? message.source.callId : "") ?? "");
16203
+ const processed = await processMessageContent(block.content, options.model, attachments, signal);
16204
+ messages.push({
16205
+ role: "tool",
16206
+ content: processed.text,
16207
+ ...toolCallId !== "" ? { toolCallId } : {},
16208
+ ...processed.images ? { images: processed.images } : {}
16209
+ });
16210
+ }
16211
+ continue;
16212
+ }
16213
+ }
14408
16214
  const toolCalls = assistantToolCalls(message.content);
14409
16215
  if (toolCalls.length > 0) {
14410
16216
  messages.push({
@@ -14414,9 +16220,26 @@ function toOllamaRequest(options) {
14414
16220
  });
14415
16221
  continue;
14416
16222
  }
16223
+ if (message.role === "assistant") {
16224
+ messages.push({
16225
+ role: "assistant",
16226
+ content: textOf$4(message.content)
16227
+ });
16228
+ continue;
16229
+ }
16230
+ if (message.role === "system" || message.role === "developer") {
16231
+ const text = textOf$4(message.content).trim();
16232
+ if (text !== "") messages.push({
16233
+ role: "system",
16234
+ content: text
16235
+ });
16236
+ continue;
16237
+ }
16238
+ const processed = await processMessageContent(message.content, options.model, attachments, signal);
14417
16239
  messages.push({
14418
- role: message.role === "developer" ? "system" : message.role,
14419
- content: textOf$4(message.content)
16240
+ role: "user",
16241
+ content: processed.text,
16242
+ ...processed.images ? { images: processed.images } : {}
14420
16243
  });
14421
16244
  }
14422
16245
  const request = {
@@ -14425,6 +16248,8 @@ function toOllamaRequest(options) {
14425
16248
  };
14426
16249
  if (options.maxTokens !== void 0) request.maxOutputTokens = options.maxTokens;
14427
16250
  if (options.temperature !== void 0) request.temperature = options.temperature;
16251
+ const think = thinkForModel(options.model, options.reasoningEffort);
16252
+ if (think !== void 0) request.think = think;
14428
16253
  const tools = options.tools;
14429
16254
  if (Array.isArray(tools) && tools.length > 0) request.tools = tools.map((tool) => ({
14430
16255
  name: tool.name,
@@ -14455,6 +16280,23 @@ function assistantToolCalls(content) {
14455
16280
  }
14456
16281
  return calls;
14457
16282
  }
16283
+ function isImageRef(value) {
16284
+ return typeof value === "object" && value !== null && typeof value.attachmentId === "string";
16285
+ }
16286
+ function asMediaType(value) {
16287
+ return typeof value === "string" && value !== "" ? value : "image/png";
16288
+ }
16289
+ /**
16290
+ * The base64 payload Ollama wants, with or without a data URL prefix.
16291
+ *
16292
+ * The native surface takes the bare payload; a `data:` URL is accepted by both
16293
+ * surfaces once, so one encoding serves either and avoids a route-specific
16294
+ * guess about what a given model accepts.
16295
+ */
16296
+ function encodeImage(data, mediaType) {
16297
+ const base64 = typeof data === "string" ? data : Buffer.from(data).toString("base64");
16298
+ return base64.startsWith("data:") ? base64 : `data:${mediaType};base64,${base64}`;
16299
+ }
14458
16300
  function textOf$4(content) {
14459
16301
  if (typeof content === "string") return content;
14460
16302
  if (Array.isArray(content)) return content.map((part) => {
@@ -20368,6 +22210,22 @@ function redactToken(token) {
20368
22210
  if (token === void 0 || token === null || token === "") return "<none>";
20369
22211
  return "sha256:" + createHash("sha256").update(token).digest("hex").slice(0, 12) + "/len:" + token.length;
20370
22212
  }
22213
+ /**
22214
+ * Adapter-selected auth owner (nonsecret hash).
22215
+ *
22216
+ * Scoped to the specific account and credential token/key selected by the adapter.
22217
+ * Any account change or token/key replacement produces a different hash,
22218
+ * preventing thinking/signature replay across different authentication boundaries.
22219
+ */
22220
+ function computeMinimaxAuthOwner(credentials, accountId) {
22221
+ const token = (credentials?.apiKey || credentials?.accessToken || "").trim();
22222
+ if (!token) return "";
22223
+ const hash = createHash("sha256");
22224
+ if (accountId && accountId.trim() !== "") hash.update(`acc:${accountId}\0`);
22225
+ if (credentials?.region && credentials.region.trim() !== "") hash.update(`reg:${credentials.region}\0`);
22226
+ hash.update(`tok:${token}`);
22227
+ return hash.digest("hex").slice(0, 32);
22228
+ }
20371
22229
  //#endregion
20372
22230
  //#region src/host/minimax-code/mapper.ts
20373
22231
  /**
@@ -20457,6 +22315,31 @@ function safeJsonParse$2(text) {
20457
22315
  function sanitizeText$2(text) {
20458
22316
  return text.replace(/\0/g, "");
20459
22317
  }
22318
+ function jsonSafeValue$1(value, seen = /* @__PURE__ */ new WeakSet()) {
22319
+ if (value === null) return null;
22320
+ switch (typeof value) {
22321
+ case "string":
22322
+ case "boolean": return value;
22323
+ case "number":
22324
+ if (!Number.isFinite(value)) return void 0;
22325
+ return Object.is(value, -0) ? 0 : value;
22326
+ case "object": break;
22327
+ default: return;
22328
+ }
22329
+ if (seen.has(value)) return void 0;
22330
+ seen.add(value);
22331
+ try {
22332
+ if (Array.isArray(value)) return Array.from(value, (item) => jsonSafeValue$1(item, seen) ?? null);
22333
+ const record = {};
22334
+ for (const key of Object.keys(value)) {
22335
+ const child = jsonSafeValue$1(value[key], seen);
22336
+ if (child !== void 0) record[key] = child;
22337
+ }
22338
+ return record;
22339
+ } finally {
22340
+ seen.delete(value);
22341
+ }
22342
+ }
20460
22343
  /** Media types the wire accepts as inline base64. */
20461
22344
  const SUPPORTED_IMAGE_MEDIA_TYPES$2 = /* @__PURE__ */ new Set([
20462
22345
  "image/png",
@@ -20640,9 +22523,92 @@ function anthropicUserContent(message, images) {
20640
22523
  }
20641
22524
  return blocks;
20642
22525
  }
20643
- function anthropicAssistantContent(message) {
22526
+ /**
22527
+ * Native blocks to replay for one assistant turn, as the service issued them.
22528
+ *
22529
+ * Read from the message's stored replay state rather than rebuilt from the
22530
+ * visible reasoning text: a rebuilt block is not the block the model signed, and
22531
+ * the difference is exactly what the provider validates. Falls back to nothing
22532
+ * when a turn carries no stored state, which is the current behaviour for
22533
+ * history this route did not produce.
22534
+ */
22535
+ /** Fingerprint the entire array of emitted visible blocks in an assistant message. */
22536
+ function computeVisibleContentHash(blocks) {
22537
+ if (!Array.isArray(blocks)) return "";
22538
+ const canonical = blocks.map((b) => {
22539
+ if (!isRecord$15(b)) return null;
22540
+ const type = asString$8(b.type) ?? "";
22541
+ if (type === "text" || type === "reasoning") return [type, sanitizeText$2(asString$8(b.text) ?? "")];
22542
+ if (type === "tool-call") return [
22543
+ type,
22544
+ asString$8(b.id) ?? "",
22545
+ asString$8(b.name) ?? "",
22546
+ toolCallArguments$2(b.arguments)
22547
+ ];
22548
+ return [type, jsonSafeValue$1(b)];
22549
+ });
22550
+ return createHash("sha256").update(JSON.stringify(canonical)).digest("hex").slice(0, 32);
22551
+ }
22552
+ /**
22553
+ * Verbatim wire block to replay for one reasoning block.
22554
+ *
22555
+ * Verifies route, model, and auth owner provenance. Fails safely on legacy unscoped state
22556
+ * or any mismatch, dropping the replay block rather than submitting invalid state.
22557
+ * Preserves raw native text and signature binding.
22558
+ */
22559
+ function replayedThinkingBlock$1(message, index, expectedScope, block) {
22560
+ const source = message.source;
22561
+ if (source === void 0 || source === null || !isRecord$15(source)) return void 0;
22562
+ if (source.provider && source.provider !== PROVIDER_ID$2 && source.provider !== "dsh-chatgpt-subscription") return;
22563
+ if (source.model && expectedScope && source.model !== expectedScope.model) return;
22564
+ const state = source.replayState;
22565
+ if (!isRecord$15(state)) return void 0;
22566
+ const resp = isRecord$15(state.response) ? state.response : state;
22567
+ const stateRoute = asString$8(resp.route) ?? asString$8(resp.provider);
22568
+ const stateModel = asString$8(resp.model);
22569
+ const stateAuthOwner = asString$8(resp.authOwner);
22570
+ if (!stateRoute || !stateModel || !stateAuthOwner || stateAuthOwner.trim() === "") return;
22571
+ if (expectedScope === void 0) return void 0;
22572
+ if (expectedScope.route && stateRoute !== expectedScope.route) return void 0;
22573
+ if (expectedScope.model && stateModel !== expectedScope.model) return void 0;
22574
+ if (!expectedScope.authOwner || expectedScope.authOwner.trim() === "" || stateAuthOwner !== expectedScope.authOwner) return;
22575
+ let candidate;
22576
+ if (Array.isArray(state.blocks)) {
22577
+ const entry = state.blocks[index];
22578
+ if (isRecord$15(entry)) candidate = entry;
22579
+ }
22580
+ if (candidate === void 0 && Array.isArray(resp.blocks)) {
22581
+ const entry = resp.blocks[index];
22582
+ if (isRecord$15(entry)) candidate = entry;
22583
+ }
22584
+ if (candidate === void 0 && Array.isArray(resp.nativeBlocks)) {
22585
+ const entry = resp.nativeBlocks[index];
22586
+ if (isRecord$15(entry)) candidate = entry;
22587
+ }
22588
+ if (candidate === void 0) return void 0;
22589
+ if (Array.isArray(state.blocks) && state.blocks.length !== message.content.length) return void 0;
22590
+ if (Array.isArray(resp.blocks) && resp.blocks.length !== message.content.length) return void 0;
22591
+ const stateVisibleHash = asString$8(resp.visibleContentHash);
22592
+ if (!stateVisibleHash || stateVisibleHash.trim() === "") return void 0;
22593
+ if (computeVisibleContentHash(message.content) !== stateVisibleHash) return void 0;
22594
+ if (candidate.type !== "thinking" && candidate.type !== "redacted_thinking") return;
22595
+ const visibleText = asString$8(block?.text) ?? "";
22596
+ if (candidate.type === "redacted_thinking") {
22597
+ if (visibleText !== "") return void 0;
22598
+ const data = asString$8(candidate.data);
22599
+ if (!data || data.trim() === "") return void 0;
22600
+ return structuredClone(candidate);
22601
+ }
22602
+ if (sanitizeText$2(asString$8(candidate.thinking) ?? "") !== sanitizeText$2(visibleText)) return;
22603
+ const signature = asString$8(candidate.signature);
22604
+ if (!signature || signature.trim() === "") return void 0;
22605
+ return structuredClone(candidate);
22606
+ }
22607
+ function anthropicAssistantContent(message, expectedScope) {
20644
22608
  const blocks = [];
20645
- for (const block of message.content) {
22609
+ if (!Array.isArray(message.content)) return blocks;
22610
+ for (let index = 0; index < message.content.length; index++) {
22611
+ const block = message.content[index];
20646
22612
  if (!isRecord$15(block)) continue;
20647
22613
  if (block.type === "text" && typeof block.text === "string") {
20648
22614
  const text = sanitizeText$2(block.text);
@@ -20660,6 +22626,12 @@ function anthropicAssistantContent(message) {
20660
22626
  name: block.name,
20661
22627
  input: isRecord$15(parsed) ? parsed : {}
20662
22628
  });
22629
+ continue;
22630
+ }
22631
+ if (block.type === "reasoning") {
22632
+ const replay = replayedThinkingBlock$1(message, index, expectedScope, block);
22633
+ if (replay !== void 0) blocks.push(replay);
22634
+ continue;
20663
22635
  }
20664
22636
  }
20665
22637
  return blocks;
@@ -20808,6 +22780,12 @@ function markMinimaxCacheBreakpoints(body) {
20808
22780
  }
20809
22781
  /** Build one Anthropic Messages body for this subscription. */
20810
22782
  function buildMinimaxRequest(options, images = NO_RESOLVED_IMAGES$2, request = {}) {
22783
+ const expectedScope = request.authOwner !== void 0 ? {
22784
+ route: request.route ?? PROVIDER_ID$2,
22785
+ model: options.model,
22786
+ authOwner: request.authOwner,
22787
+ provider: PROVIDER_ID$2
22788
+ } : void 0;
20811
22789
  const entries = [];
20812
22790
  for (const message of nonSystemMessages$2(options)) {
20813
22791
  if (isToolResultMessage$2(message)) {
@@ -20819,7 +22797,7 @@ function buildMinimaxRequest(options, images = NO_RESOLVED_IMAGES$2, request = {
20819
22797
  }
20820
22798
  entries.push({
20821
22799
  role: message.role === "assistant" ? "assistant" : "user",
20822
- content: message.role === "assistant" ? anthropicAssistantContent(message) : anthropicUserContent(message, images)
22800
+ content: message.role === "assistant" ? anthropicAssistantContent(message, expectedScope) : anthropicUserContent(message, images)
20823
22801
  });
20824
22802
  }
20825
22803
  const system = leadingSystemText$2(options);
@@ -20867,8 +22845,16 @@ function assertRequestBodyFits(body, limit = maxMessageBodyBytes()) {
20867
22845
  if (bytes <= limit) return serialized;
20868
22846
  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
22847
  }
20870
- function createStreamState() {
22848
+ function createStreamState(options) {
20871
22849
  return {
22850
+ provenance: options?.model && options?.authOwner ? {
22851
+ route: options.route ?? PROVIDER_ID$2,
22852
+ model: options.model,
22853
+ authOwner: options.authOwner,
22854
+ provider: options.provider ?? PROVIDER_ID$2
22855
+ } : void 0,
22856
+ replayBlocks: [],
22857
+ pendingThinking: /* @__PURE__ */ new Map(),
20872
22858
  blocks: [],
20873
22859
  current: null,
20874
22860
  toolCalls: /* @__PURE__ */ new Map(),
@@ -20912,6 +22898,7 @@ function openTextBlock$1(state, type) {
20912
22898
  type,
20913
22899
  text: ""
20914
22900
  });
22901
+ state.replayBlocks[index] = null;
20915
22902
  out.push({
20916
22903
  type: "block-start",
20917
22904
  index,
@@ -20929,6 +22916,7 @@ function closeToolCalls$1(state) {
20929
22916
  arguments: call.arguments === "" ? "{}" : call.arguments
20930
22917
  };
20931
22918
  state.blocks[call.blockIndex] = block;
22919
+ state.replayBlocks[call.blockIndex] = null;
20932
22920
  out.push({
20933
22921
  type: "block-end",
20934
22922
  index: call.blockIndex,
@@ -20961,9 +22949,34 @@ function closeMinimaxStream(state) {
20961
22949
  type: "usage",
20962
22950
  usage: tokenUsage$2(state)
20963
22951
  });
22952
+ const paddedBlocks = state.blocks.map((_, i) => {
22953
+ const b = state.replayBlocks[i];
22954
+ return b !== void 0 && b !== null ? jsonSafeValue$1(b) : null;
22955
+ });
22956
+ const replayThinking = paddedBlocks.filter((b) => b !== null);
22957
+ const hasReplay = replayThinking.length > 0;
22958
+ const visibleContentHash = computeVisibleContentHash(state.blocks);
22959
+ const replayState = hasReplay ? state.provenance ? {
22960
+ response: {
22961
+ provider: state.provenance.provider ?? PROVIDER_ID$2,
22962
+ route: state.provenance.route ?? PROVIDER_ID$2,
22963
+ model: state.provenance.model,
22964
+ authOwner: state.provenance.authOwner,
22965
+ visibleContentHash,
22966
+ minimaxThinking: replayThinking
22967
+ },
22968
+ blocks: paddedBlocks
22969
+ } : {
22970
+ response: {
22971
+ visibleContentHash,
22972
+ minimaxThinking: replayThinking
22973
+ },
22974
+ blocks: paddedBlocks
22975
+ } : void 0;
20964
22976
  out.push({
20965
22977
  type: "finish",
20966
- reason: finishReasonFor$2(state)
22978
+ reason: finishReasonFor$2(state),
22979
+ ...replayState !== void 0 ? { replayState } : {}
20967
22980
  });
20968
22981
  return out;
20969
22982
  }
@@ -21034,8 +23047,40 @@ function processMinimaxStreamLine(line, state) {
21034
23047
  return out;
21035
23048
  }
21036
23049
  if (blockType === "thinking" || blockType === "redacted_thinking") {
21037
- out.push(...openTextBlock$1(state, "reasoning"));
21038
- state.contentIndexes.set(contentIndex, state.current.index);
23050
+ const index = state.blocks.length;
23051
+ const kind = blockType === "redacted_thinking" ? "redacted_thinking" : "thinking";
23052
+ const data = asString$8(block.data) ?? "";
23053
+ const signature = asString$8(block.signature) ?? "";
23054
+ const thinking = asString$8(block.thinking) ?? "";
23055
+ state.current = {
23056
+ index,
23057
+ type: "reasoning",
23058
+ text: kind === "thinking" ? thinking : ""
23059
+ };
23060
+ state.blocks.push({
23061
+ type: "reasoning",
23062
+ text: state.current.text
23063
+ });
23064
+ const initialReplay = isRecord$15(event.content_block) ? structuredClone(event.content_block) : { type: blockType };
23065
+ state.replayBlocks[index] = initialReplay;
23066
+ state.pendingThinking.set(contentIndex, {
23067
+ dshIndex: index,
23068
+ kind,
23069
+ text: thinking,
23070
+ signature,
23071
+ data
23072
+ });
23073
+ state.contentIndexes.set(contentIndex, index);
23074
+ out.push({
23075
+ type: "block-start",
23076
+ index,
23077
+ blockType: "reasoning"
23078
+ });
23079
+ if (state.current.text !== "") out.push({
23080
+ type: "reasoning-delta",
23081
+ index,
23082
+ text: state.current.text
23083
+ });
21039
23084
  return out;
21040
23085
  }
21041
23086
  out.push(...openTextBlock$1(state, "text"));
@@ -21061,8 +23106,37 @@ function processMinimaxStreamLine(line, state) {
21061
23106
  }
21062
23107
  return out;
21063
23108
  }
23109
+ if (deltaType === "signature_delta") {
23110
+ const signature = asString$8(delta.signature) ?? "";
23111
+ const pending = state.pendingThinking.get(contentIndex);
23112
+ if (pending !== void 0 && signature !== "") {
23113
+ pending.signature += signature;
23114
+ const replay = state.replayBlocks[pending.dshIndex];
23115
+ if (replay !== void 0 && replay !== null) replay.signature = (asString$8(replay.signature) ?? "") + signature;
23116
+ }
23117
+ return out;
23118
+ }
23119
+ if (deltaType === "data_delta") {
23120
+ const data = asString$8(delta.data) ?? "";
23121
+ const pending = state.pendingThinking.get(contentIndex);
23122
+ if (pending !== void 0 && data !== "") {
23123
+ pending.data = (pending.data ?? "") + data;
23124
+ const replay = state.replayBlocks[pending.dshIndex];
23125
+ if (replay !== void 0 && replay !== null) replay.data = (asString$8(replay.data) ?? "") + data;
23126
+ }
23127
+ return out;
23128
+ }
21064
23129
  const text = deltaType === "thinking_delta" ? asString$8(delta.thinking) : asString$8(delta.text);
21065
23130
  if (text !== void 0 && text !== "") {
23131
+ const pending = state.pendingThinking.get(contentIndex);
23132
+ if (pending !== void 0) {
23133
+ pending.text += text;
23134
+ const replay = state.replayBlocks[pending.dshIndex];
23135
+ if (replay !== void 0 && replay !== null) {
23136
+ const field = replay.type === "redacted_thinking" ? "data" : "thinking";
23137
+ replay[field] = (asString$8(replay[field]) ?? "") + text;
23138
+ }
23139
+ }
21066
23140
  const index = state.contentIndexes.get(contentIndex) ?? state.current?.index;
21067
23141
  const kind = deltaType === "thinking_delta" ? "reasoning" : "text";
21068
23142
  if (state.current === null || state.current.index !== index) {
@@ -21077,6 +23151,7 @@ function processMinimaxStreamLine(line, state) {
21077
23151
  type: kind,
21078
23152
  text: ""
21079
23153
  });
23154
+ state.replayBlocks[next] = null;
21080
23155
  state.contentIndexes.set(contentIndex, next);
21081
23156
  out.push({
21082
23157
  type: "block-start",
@@ -21159,7 +23234,7 @@ const MINIMAX_CODE_MODELS = [
21159
23234
  maxAttachments: null,
21160
23235
  maxImageBytes: null,
21161
23236
  maxVideoBytes: null,
21162
- supportsFilesApi: false,
23237
+ filesApiDocumented: false,
21163
23238
  description: "Thinking is always on and has no selectable level. Text input only."
21164
23239
  },
21165
23240
  {
@@ -21175,7 +23250,7 @@ const MINIMAX_CODE_MODELS = [
21175
23250
  maxAttachments: null,
21176
23251
  maxImageBytes: null,
21177
23252
  maxVideoBytes: null,
21178
- supportsFilesApi: false,
23253
+ filesApiDocumented: false,
21179
23254
  description: "The high-speed M2.7. Thinking is always on with no selectable level. Text input only."
21180
23255
  },
21181
23256
  {
@@ -21191,7 +23266,7 @@ const MINIMAX_CODE_MODELS = [
21191
23266
  maxAttachments: 9,
21192
23267
  maxImageBytes: 10 * 1024 * 1024,
21193
23268
  maxVideoBytes: 50 * 1024 * 1024,
21194
- supportsFilesApi: true,
23269
+ filesApiDocumented: true,
21195
23270
  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
23271
  },
21197
23272
  {
@@ -21214,7 +23289,7 @@ const MINIMAX_CODE_MODELS = [
21214
23289
  maxAttachments: 4,
21215
23290
  maxImageBytes: 10 * 1024 * 1024,
21216
23291
  maxVideoBytes: 50 * 1024 * 1024,
21217
- supportsFilesApi: true,
23292
+ filesApiDocumented: true,
21218
23293
  description: "Preview Flash model with a 1M context window. Thinking is forced on with a selectable effort level. Accepts up to 4 attachments."
21219
23294
  }
21220
23295
  ];
@@ -23399,11 +25474,6 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
23399
25474
  const contextWindow = contextWindowForModel(options.model, settings.contextWindowOverrides);
23400
25475
  const requestOptions = offloadOldestRequestImages$2(normalizeGenerateOptions(options), maxRequestImageBytes());
23401
25476
  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
25477
  let credentials;
23408
25478
  let accountId;
23409
25479
  try {
@@ -23414,6 +25484,19 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
23414
25484
  if (error instanceof MinimaxCodeUnauthorizedError) throw new LlmError(error.message, "INVALID_CREDENTIAL", { cause: error });
23415
25485
  throw new LlmError("MiniMax Code(编程订阅) credential could not be prepared: " + (error instanceof Error ? error.message : String(error)), "MISSING_CREDENTIAL", { cause: error });
23416
25486
  }
25487
+ let authOwner = computeMinimaxAuthOwner(credentials, accountId);
25488
+ const effectiveMax = clampOutputToContext(requestOptions.maxTokens ?? maxOutputTokensFor$1(options.model, contextWindow), contextWindow, estimatedInputTokens(requestOptions));
25489
+ const buildBodyForOwner = (owner) => {
25490
+ return assertRequestBodyFits(buildMinimaxRequest({
25491
+ ...requestOptions,
25492
+ maxTokens: effectiveMax
25493
+ }, images, {
25494
+ cacheControl: true,
25495
+ authOwner: owner,
25496
+ route: PROVIDER_ID$2
25497
+ }));
25498
+ };
25499
+ let body = buildBodyForOwner(authOwner);
23417
25500
  let response;
23418
25501
  for (let attempt = 0;; attempt += 1) {
23419
25502
  try {
@@ -23433,6 +25516,8 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
23433
25516
  const refused = credentials.accessToken;
23434
25517
  try {
23435
25518
  credentials = await this.renewCredential(accountId, fetchFn, signal, refused);
25519
+ authOwner = computeMinimaxAuthOwner(credentials, accountId);
25520
+ body = buildBodyForOwner(authOwner);
23436
25521
  } catch (error) {
23437
25522
  if (error instanceof MinimaxCodeUnauthorizedError) throw new LlmError(error.message, "INVALID_CREDENTIAL", { cause: error });
23438
25523
  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 +25536,12 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
23451
25536
  if (response.body === null) throw new LlmError("MiniMax Code(编程订阅) returned an empty response body", "PROVIDER_ERROR");
23452
25537
  const reader = response.body.getReader();
23453
25538
  const decoder = new TextDecoder();
23454
- const state = createStreamState();
25539
+ const state = createStreamState({
25540
+ model: options.model,
25541
+ authOwner,
25542
+ route: PROVIDER_ID$2,
25543
+ provider: PROVIDER_ID$2
25544
+ });
23455
25545
  let buffer = "";
23456
25546
  try {
23457
25547
  while (true) {
@@ -27717,6 +29807,7 @@ function createStreamState$2() {
27717
29807
  inputTokens: 0,
27718
29808
  outputTokens: 0,
27719
29809
  cacheReadTokens: 0,
29810
+ cacheWriteTokens: 0,
27720
29811
  reasoningTokens: 0,
27721
29812
  sawUsage: false
27722
29813
  };
@@ -27798,8 +29889,10 @@ function processStreamLine$1(line, state) {
27798
29889
  const prompt = numberOr$1(usage.prompt_tokens, 0);
27799
29890
  const details = isRecord$8(usage.prompt_tokens_details) ? usage.prompt_tokens_details : void 0;
27800
29891
  const cached = details ? numberOr$1(details.cached_tokens, 0) : 0;
27801
- state.inputTokens = Math.max(0, prompt - cached);
29892
+ const written = details ? numberOr$1(details.cache_write_tokens, 0) : 0;
29893
+ state.inputTokens = Math.max(0, prompt - cached - written);
27802
29894
  state.cacheReadTokens = cached;
29895
+ state.cacheWriteTokens = written;
27803
29896
  state.outputTokens = numberOr$1(usage.completion_tokens, state.outputTokens);
27804
29897
  if (isRecord$8(usage.completion_tokens_details)) state.reasoningTokens = numberOr$1(usage.completion_tokens_details.reasoning_tokens, state.reasoningTokens);
27805
29898
  }
@@ -27900,6 +29993,7 @@ function tokenUsage$1(state) {
27900
29993
  inputTokens: state.inputTokens,
27901
29994
  outputTokens: state.outputTokens,
27902
29995
  ...state.cacheReadTokens > 0 ? { cacheReadTokens: state.cacheReadTokens } : {},
29996
+ ...state.cacheWriteTokens > 0 ? { cacheWriteTokens: state.cacheWriteTokens } : {},
27903
29997
  ...state.reasoningTokens > 0 ? { reasoningTokens: state.reasoningTokens } : {}
27904
29998
  };
27905
29999
  }
@@ -39752,8 +41846,16 @@ function apply(ctx, pluginConfig = {}) {
39752
41846
  logger: ctx.logger
39753
41847
  });
39754
41848
  const proxyFetch = proxyManager.createFetch();
41849
+ const providerLimits = readModelRequestLimits();
41850
+ const modelFetch = Object.fromEntries(CONTROLLED_PROVIDERS.map((provider) => [provider, createControlledModelFetch(createDiagnosticFetch(proxyFetch, {
41851
+ provider,
41852
+ onRecord: (record) => ctx.logger.info("[provider-diagnostics] " + JSON.stringify(record))
41853
+ }), {
41854
+ provider,
41855
+ limits: providerLimits
41856
+ })]));
39755
41857
  const antigravityAdapter = new AntigravityAdapter(antigravityStore, antigravityModelSettings, antigravityPreferences, {
39756
- fetchFn: proxyFetch,
41858
+ fetchFn: modelFetch.antigravity,
39757
41859
  attachments: ctx.attachments
39758
41860
  }, antigravityAccountPool);
39759
41861
  let antigravityRegistration;
@@ -39780,7 +41882,7 @@ function apply(ctx, pluginConfig = {}) {
39780
41882
  conflict: () => commandCodeConflict
39781
41883
  }, commandCodeAccountPool);
39782
41884
  const commandCodeAdapter = new CommandCodeAdapter(commandCodeStore, commandCodeModelSettings, commandCodePreferences, {
39783
- fetchFn: proxyFetch,
41885
+ fetchFn: modelFetch["command-code"],
39784
41886
  attachments: ctx.attachments
39785
41887
  }, commandCodeAccountPool);
39786
41888
  let commandCodeRegistration;
@@ -39788,11 +41890,14 @@ function apply(ctx, pluginConfig = {}) {
39788
41890
  const ollamaStore = new FileCredentialStore$5();
39789
41891
  const ollamaModelSettings = new FileModelSettingsStore$5();
39790
41892
  const ollamaAccountPool = new OllamaAccountPool({ store: ollamaStore });
39791
- const ollamaAdapter = new OllamaAdapter(ollamaStore, ollamaModelSettings, { fetchFn: proxyFetch }, ollamaAccountPool);
41893
+ const ollamaAdapter = new OllamaAdapter(ollamaStore, ollamaModelSettings, {
41894
+ fetchFn: modelFetch.ollama,
41895
+ attachments: ctx.attachments
41896
+ }, ollamaAccountPool);
39792
41897
  let ollamaRegistration;
39793
41898
  let ollamaConflict = null;
39794
41899
  const kimiCodeAdapter = new KimiCodeAdapter(kimiCodeStore, kimiCodeModelSettings, kimiCodePreferences, {
39795
- fetchFn: proxyFetch,
41900
+ fetchFn: modelFetch["kimi-code"],
39796
41901
  attachments: ctx.attachments,
39797
41902
  videos: { readVideo: async (ref) => ({
39798
41903
  data: await readVideoBytes(ref),
@@ -39854,7 +41959,7 @@ function apply(ctx, pluginConfig = {}) {
39854
41959
  conflict: () => kimiCodeConflict
39855
41960
  }, kimiCodeAccountPool);
39856
41961
  const minimaxCodeAdapter = new MinimaxCodeAdapter(minimaxCodeStore, {
39857
- fetchFn: proxyFetch,
41962
+ fetchFn: modelFetch["minimax-code"],
39858
41963
  attachments: ctx.attachments,
39859
41964
  accountPool: minimaxCodeAccountPool
39860
41965
  }, minimaxCodeModelSettings, minimaxCodePreferences);
@@ -39904,7 +42009,7 @@ function apply(ctx, pluginConfig = {}) {
39904
42009
  checkin: minimaxCodeCheckin
39905
42010
  }, minimaxCodeModelSettings, minimaxCodePreferences);
39906
42011
  const workBuddyAdapter = new WorkBuddyAdapter(workBuddyStore, workBuddyModelSettings, workBuddyPreferences, {
39907
- fetchFn: proxyFetch,
42012
+ fetchFn: modelFetch.workbuddy,
39908
42013
  attachments: ctx.attachments,
39909
42014
  accountPool: workBuddyAccountPool
39910
42015
  });
@@ -39946,7 +42051,7 @@ function apply(ctx, pluginConfig = {}) {
39946
42051
  checkin: workBuddyCheckin
39947
42052
  });
39948
42053
  const claudeAdapter = new ClaudeAdapter(claudeStore, claudeModelSettings, claudePreferences, {
39949
- fetchFn: proxyFetch,
42054
+ fetchFn: modelFetch.claude,
39950
42055
  attachments: ctx.attachments,
39951
42056
  accountPool: claudeAccountPool
39952
42057
  });
@@ -39983,7 +42088,7 @@ function apply(ctx, pluginConfig = {}) {
39983
42088
  const usage = new UsageService(oauth, { fetchFn: proxyFetch });
39984
42089
  codexAccountPool.setQuotaBlockedUntil((account, now) => usage.blockedUntilFor(account.credentials, now));
39985
42090
  const adapter = new CodexChatGptAdapter(new ResponsesClient(oauth, ctx.attachments, {
39986
- fetchFn: proxyFetch,
42091
+ fetchFn: modelFetch["codex-chatgpt"],
39987
42092
  accountPool: codexAccountPool,
39988
42093
  localRawImages: { baseUrl: localWebServerBaseUrl(ctx.webServer.host, ctx.webServer.port) },
39989
42094
  onGenerationFinished: () => usage.invalidate(),