@jacobbd/relay-ai 0.6.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,9 +17,9 @@
17
17
 
18
18
  📺 **Watch the Demos**
19
19
 
20
- | **Claude Code / Cowork / Desktop** | **Codex CLI & Desktop App** | **Gemini CLI** | **v0.4.1: UI & Antigravity** |
21
- |:---:|:---:|:---:|:---:|
22
- | [![Claude Demo](https://img.youtube.com/vi/IvsUPHLhX0o/mqdefault.jpg)](https://youtu.be/IvsUPHLhX0o) | [![Codex Demo](https://img.youtube.com/vi/42oiOB8IAu4/mqdefault.jpg)](https://youtu.be/42oiOB8IAu4) | [![Gemini Demo](https://img.youtube.com/vi/g7JKvqOHJl4/mqdefault.jpg)](https://www.youtube.com/watch?v=g7JKvqOHJl4) | [![UI & Antigravity Demo](https://img.youtube.com/vi/8vXJ0LfpdoY/mqdefault.jpg)](https://www.youtube.com/watch?v=8vXJ0LfpdoY) |
20
+ | **Claude Code / Cowork / Desktop** | **Codex CLI & Desktop App** | **Gemini CLI** | **v0.4.1: UI & Antigravity** | **Server Gateway** |
21
+ |:---:|:---:|:---:|:---:|:---:|
22
+ | [![Claude Demo](https://img.youtube.com/vi/IvsUPHLhX0o/mqdefault.jpg)](https://youtu.be/IvsUPHLhX0o) | [![Codex Demo](https://img.youtube.com/vi/42oiOB8IAu4/mqdefault.jpg)](https://youtu.be/42oiOB8IAu4) | [![Gemini Demo](https://img.youtube.com/vi/g7JKvqOHJl4/mqdefault.jpg)](https://www.youtube.com/watch?v=g7JKvqOHJl4) | [![UI & Antigravity Demo](https://img.youtube.com/vi/8vXJ0LfpdoY/mqdefault.jpg)](https://www.youtube.com/watch?v=8vXJ0LfpdoY) | [![Server Gateway Demo](https://img.youtube.com/vi/4CEw0MQUE9I/mqdefault.jpg)](https://www.youtube.com/watch?v=4CEw0MQUE9I) |
23
23
 
24
24
  **relay-ai** is an interactive CLI — and now a **visual launcher** — that connects AI coding tools to any provider and runs local API gateways on your machine. It supports **Claude Code**, **Claude Desktop (Cowork + Code)**, the **OpenAI Codex CLI**, the **ChatGPT desktop app in Codex mode (macOS + Windows)**, **Google Gemini CLI**, and experimental **Antigravity CLI / IDE** support.
25
25
 
@@ -455,8 +455,9 @@ Launch **Claude Desktop** (macOS or Windows) with registry providers:
455
455
  relay-ai claude-app
456
456
  ```
457
457
 
458
- This command automates the "Third-Party Inference" (Developer Mode) setup. It temporarily configures Claude Desktop to point at a local gateway, launches the app, and routes traffic to your chosen provider.
458
+ This command automates the "Third-Party Inference" (Developer Mode) setup. It temporarily configures Claude Desktop to point at a local gateway, launches the app, and exposes the selected model followed by your available saved favorites (up to 20 models total).
459
459
 
460
+ - **Selected + favorites:** The selected model is listed first; duplicates and unavailable favorites are skipped. Claude Desktop ultimately controls which discovered model it activates initially.
460
461
  - **Keep the terminal open:** The proxy runs in the foreground.
461
462
  - **Ctrl+C to restore:** When you're done, press `Ctrl+C` in the terminal to automatically restore Claude Desktop to its normal Anthropic cloud mode.
462
463
  - **Cleanup:** If the terminal crashes, run `relay-ai claude-app --restore`.
@@ -11,7 +11,7 @@ import { join } from "path";
11
11
  // package.json
12
12
  var package_default = {
13
13
  name: "@jacobbd/relay-ai",
14
- version: "0.6.1",
14
+ version: "0.6.3",
15
15
  publishConfig: {
16
16
  access: "public"
17
17
  },
@@ -46,7 +46,7 @@ var package_default = {
46
46
  node: ">=18"
47
47
  },
48
48
  scripts: {
49
- build: "tsup && mkdir -p dist/ui/public && cp -r src/ui/public/. dist/ui/public/",
49
+ build: "tsup && node scripts/copy-ui-assets.mjs",
50
50
  dev: "tsup --watch",
51
51
  test: "vitest run",
52
52
  "test:watch": "vitest",
@@ -139,6 +139,14 @@ var CONFLICTING_ENV_VARS = [
139
139
  "ANTHROPIC_DEFAULT_SONNET_MODEL",
140
140
  "ANTHROPIC_DEFAULT_HAIKU_MODEL"
141
141
  ];
142
+ var PARENT_SESSION_ENV_VARS = [
143
+ "CLAUDECODE",
144
+ "CLAUDE_CODE_CHILD_SESSION",
145
+ "CLAUDE_CODE_SESSION_ID",
146
+ "CLAUDE_CODE_HOST_SESSION_ID",
147
+ "CLAUDE_CODE_ENTRYPOINT",
148
+ "CLAUDE_PID"
149
+ ];
142
150
  var OPENCODE_CACHE_PATH = join(homedir(), ".cache", "opencode", "models.json");
143
151
  var MAX_MODEL_CATALOG = 20;
144
152
  var VERTEX_ANTHROPIC_NPM = "@ai-sdk/google-vertex/anthropic";
@@ -3045,6 +3053,9 @@ function buildChildEnv(baseUrl, model, apiKey, proxyPort, contextWindow, enableG
3045
3053
  for (const name of CONFLICTING_ENV_VARS) {
3046
3054
  delete env[name];
3047
3055
  }
3056
+ for (const name of PARENT_SESSION_ENV_VARS) {
3057
+ delete env[name];
3058
+ }
3048
3059
  env["ANTHROPIC_BASE_URL"] = proxyPort ? `http://127.0.0.1:${proxyPort}` : baseUrl;
3049
3060
  env["ANTHROPIC_API_KEY"] = apiKey;
3050
3061
  const bareModel = stripOneMContextSuffix(model);
@@ -4243,7 +4254,7 @@ function maskGatewayModelId(aliasId) {
4243
4254
  // src/server/models.ts
4244
4255
  var CREATED_AT_ISO = "2025-01-01T00:00:00Z";
4245
4256
  var CREATED_AT_UNIX = 1735689600;
4246
- function formatAnthropicModelEntry(id, displayName, contextWindow) {
4257
+ function formatAnthropicModelEntry(id, displayName, contextWindow, options) {
4247
4258
  const maxInput = resolveContextWindow(id, contextWindow);
4248
4259
  return {
4249
4260
  id,
@@ -4251,12 +4262,18 @@ function formatAnthropicModelEntry(id, displayName, contextWindow) {
4251
4262
  display_name: displayName,
4252
4263
  created_at: CREATED_AT_ISO,
4253
4264
  context_window: maxInput,
4254
- max_input_tokens: maxInput
4265
+ max_input_tokens: maxInput,
4266
+ ...options?.supportsOneM !== void 0 ? { supports_1m: options.supportsOneM } : {}
4255
4267
  };
4256
4268
  }
4257
4269
  function formatAnthropicModelList(entries) {
4258
4270
  return {
4259
- data: entries.map((entry) => formatAnthropicModelEntry(entry.id, entry.name, entry.contextWindow)),
4271
+ data: entries.map((entry) => formatAnthropicModelEntry(
4272
+ entry.id,
4273
+ entry.name,
4274
+ entry.contextWindow,
4275
+ { supportsOneM: entry.supportsOneM }
4276
+ )),
4260
4277
  has_more: false,
4261
4278
  first_id: entries[0]?.id ?? null,
4262
4279
  last_id: entries.at(-1)?.id ?? null
@@ -4271,31 +4288,65 @@ function gatewayProviderId(model) {
4271
4288
  function gatewayAliasId(model) {
4272
4289
  return aliasModelId(model.id, gatewayProviderId(model));
4273
4290
  }
4291
+ function openAiIdCollisions(models) {
4292
+ const counts = /* @__PURE__ */ new Map();
4293
+ for (const model of models) counts.set(model.id, (counts.get(model.id) ?? 0) + 1);
4294
+ const collisions = /* @__PURE__ */ new Set();
4295
+ for (const [id, count] of counts) if (count > 1) collisions.add(id);
4296
+ return collisions;
4297
+ }
4298
+ function openAiExposedId(model, collisions) {
4299
+ return collisions.has(model.id) ? `${gatewayProviderId(model)}/${model.id}` : model.id;
4300
+ }
4274
4301
  function exposedGatewayAliasId(model, opts) {
4275
- const alias = gatewayAliasId(model);
4276
- return opts?.maskGatewayIds ? maskGatewayModelId(alias) : alias;
4302
+ const singleOneM = usesSingleOneMEntry(model, opts);
4303
+ const alias = gatewayAliasId(singleOneM ? { ...model, id: stripOneMContextSuffix(model.id) } : model);
4304
+ const exposed = opts?.maskGatewayIds ? maskGatewayModelId(alias) : alias;
4305
+ return singleOneM ? `${stripOneMContextSuffix(exposed)}[1m]` : exposed;
4277
4306
  }
4278
4307
  function gatewayDisplayName(model, opts) {
4279
- if (!opts?.maskGatewayIds) return model.name;
4280
- return `${model.name} (${gatewayProviderLabel(model)})`;
4308
+ const name = opts?.maskGatewayIds ? `${model.name} (${gatewayProviderLabel(model)})` : model.name;
4309
+ return usesSingleOneMEntry(model, opts) && !/\b1m$/i.test(name) ? `${name} 1M` : name;
4281
4310
  }
4282
4311
  function formatGatewayAnthropicModels(models, opts) {
4283
4312
  return formatAnthropicModelList(
4284
4313
  models.map((model) => ({
4285
4314
  id: exposedGatewayAliasId(model, opts),
4286
4315
  name: gatewayDisplayName(model, opts),
4287
- contextWindow: model.contextWindow
4316
+ contextWindow: model.contextWindow,
4317
+ supportsOneM: usesSingleOneMEntry(model, opts) ? false : void 0
4288
4318
  }))
4289
4319
  );
4290
4320
  }
4321
+ function usesSingleOneMEntry(model, opts) {
4322
+ return opts?.longContextDisplay === "single-1m" && resolveContextWindow(stripOneMContextSuffix(model.id), model.contextWindow) >= 1e6;
4323
+ }
4291
4324
  function createGatewayModelCatalog(models, opts) {
4292
4325
  const byId = /* @__PURE__ */ new Map();
4326
+ const collisions = openAiIdCollisions(models);
4293
4327
  for (const model of models) {
4294
- byId.set(model.id, model);
4328
+ if (!byId.has(model.id)) byId.set(model.id, model);
4329
+ const scopedId = openAiExposedId(model, collisions);
4330
+ if (scopedId !== model.id) byId.set(scopedId, model);
4295
4331
  const alias = exposedGatewayAliasId(model, opts);
4296
4332
  if (alias !== model.id) byId.set(alias, model);
4333
+ const singleOneM = usesSingleOneMEntry(model, opts);
4334
+ if (singleOneM) {
4335
+ const bareModel = { ...model, id: stripOneMContextSuffix(model.id) };
4336
+ const rawBareAlias = gatewayAliasId(bareModel);
4337
+ const exposedBareAlias = opts?.maskGatewayIds ? maskGatewayModelId(rawBareAlias) : rawBareAlias;
4338
+ for (const compatibleId of [
4339
+ stripOneMContextSuffix(model.id),
4340
+ rawBareAlias,
4341
+ `${rawBareAlias}[1m]`,
4342
+ exposedBareAlias,
4343
+ `${exposedBareAlias}[1m]`
4344
+ ]) {
4345
+ byId.set(compatibleId, model);
4346
+ }
4347
+ }
4297
4348
  if (opts?.maskGatewayIds) {
4298
- const rawAlias = gatewayAliasId(model);
4349
+ const rawAlias = gatewayAliasId(singleOneM ? { ...model, id: stripOneMContextSuffix(model.id) } : model);
4299
4350
  if (rawAlias !== alias) byId.set(rawAlias, model);
4300
4351
  }
4301
4352
  }
@@ -4308,14 +4359,14 @@ function upstreamModelId(model) {
4308
4359
  const id = model.upstreamModelId ?? model.id;
4309
4360
  return id.replace(/\[1m\]$/i, "");
4310
4361
  }
4311
- function buildDedupedModelRows(models, opts) {
4362
+ function buildDedupedModelRows(models, opts, collisions = openAiIdCollisions(models)) {
4312
4363
  const seen = /* @__PURE__ */ new Set();
4313
4364
  const rows = [];
4314
4365
  for (const model of [...models].sort((a, b) => a.name.localeCompare(b.name))) {
4315
4366
  const row = {
4316
4367
  name: model.name,
4317
4368
  anthropicId: exposedGatewayAliasId(model, opts),
4318
- openaiId: model.id
4369
+ openaiId: openAiExposedId(model, collisions)
4319
4370
  };
4320
4371
  const key = `${row.name}\0${row.anthropicId}\0${row.openaiId}`;
4321
4372
  if (seen.has(key)) continue;
@@ -4328,10 +4379,11 @@ function supportsDirectOpenAIChatCompletions(model) {
4328
4379
  return model.modelFormat === "openai" && (!!model.completionsUrl || model.sourceBackend === "zen" || model.sourceBackend === "go");
4329
4380
  }
4330
4381
  function formatOpenAIModels(models) {
4382
+ const collisions = openAiIdCollisions(models);
4331
4383
  return {
4332
4384
  object: "list",
4333
4385
  data: models.map((model) => ({
4334
- id: model.id,
4386
+ id: openAiExposedId(model, collisions),
4335
4387
  object: "model",
4336
4388
  created: CREATED_AT_UNIX,
4337
4389
  owned_by: model.sourceBackend
@@ -8721,8 +8773,9 @@ function translateOpenAiRequest(body) {
8721
8773
  break;
8722
8774
  case "assistant": {
8723
8775
  const parts = [];
8724
- if (typeof msg.content === "string" && msg.content) {
8725
- parts.push({ type: "text", text: msg.content });
8776
+ const assistantText = typeof msg.content === "string" ? msg.content : Array.isArray(msg.content) ? msg.content.filter((p8) => p8?.type === "text" && typeof p8.text === "string").map((p8) => p8.text).join("") : "";
8777
+ if (assistantText) {
8778
+ parts.push({ type: "text", text: assistantText });
8726
8779
  }
8727
8780
  for (const tc of msg.tool_calls ?? []) {
8728
8781
  parts.push({
@@ -8783,14 +8836,31 @@ function translateOpenAiRequest(body) {
8783
8836
  maxOutputTokens: body.max_completion_tokens ?? body.max_tokens
8784
8837
  };
8785
8838
  }
8839
+ function toOpenAiFinishReason(reason) {
8840
+ switch (reason) {
8841
+ case "tool-calls":
8842
+ return "tool_calls";
8843
+ case "content-filter":
8844
+ return "content_filter";
8845
+ case "length":
8846
+ return "length";
8847
+ case "stop":
8848
+ return "stop";
8849
+ default:
8850
+ return "stop";
8851
+ }
8852
+ }
8786
8853
  async function generateOpenAiResponse(model, params, responseModelId) {
8787
8854
  const result = await generateText2({ model, ...params });
8788
8855
  const message = { role: "assistant", content: result.text || null };
8856
+ if (result.reasoningText || result.reasoning) {
8857
+ message.reasoning_content = result.reasoningText ?? result.reasoning;
8858
+ }
8789
8859
  if (result.toolCalls?.length) {
8790
8860
  message.tool_calls = result.toolCalls.map((tc) => ({
8791
8861
  id: tc.toolCallId,
8792
8862
  type: "function",
8793
- function: { name: tc.toolName, arguments: JSON.stringify(tc.args) }
8863
+ function: { name: tc.toolName, arguments: JSON.stringify(tc.input ?? tc.args ?? {}) }
8794
8864
  }));
8795
8865
  }
8796
8866
  return {
@@ -8798,7 +8868,7 @@ async function generateOpenAiResponse(model, params, responseModelId) {
8798
8868
  object: "chat.completion",
8799
8869
  created: Math.floor(Date.now() / 1e3),
8800
8870
  model: responseModelId,
8801
- choices: [{ index: 0, message, finish_reason: result.finishReason || "stop" }],
8871
+ choices: [{ index: 0, message, finish_reason: toOpenAiFinishReason(result.finishReason) }],
8802
8872
  usage: {
8803
8873
  prompt_tokens: result.usage?.promptTokens ?? 0,
8804
8874
  completion_tokens: result.usage?.completionTokens ?? 0,
@@ -8806,7 +8876,7 @@ async function generateOpenAiResponse(model, params, responseModelId) {
8806
8876
  }
8807
8877
  };
8808
8878
  }
8809
- async function streamOpenAiResponse(model, params, responseModelId, onChunk) {
8879
+ async function streamOpenAiResponse(model, params, responseModelId, onChunk, log7) {
8810
8880
  const { fullStream } = streamText2({ model, ...params });
8811
8881
  const baseData = {
8812
8882
  id: `chatcmpl-${Date.now()}`,
@@ -8817,23 +8887,68 @@ async function streamOpenAiResponse(model, params, responseModelId, onChunk) {
8817
8887
  const send = (delta, finish_reason = null) => onChunk(`data: ${JSON.stringify({ ...baseData, choices: [{ index: 0, delta, finish_reason }] })}
8818
8888
 
8819
8889
  `);
8890
+ const streamedToolIndex = /* @__PURE__ */ new Map();
8891
+ let nextToolIndex = 0;
8892
+ const seenPartTypes = /* @__PURE__ */ new Set();
8893
+ let toolCallChunksEmitted = 0;
8820
8894
  for await (const part of fullStream) {
8821
8895
  const p8 = part;
8896
+ seenPartTypes.add(p8.type);
8822
8897
  switch (p8.type) {
8823
8898
  case "text-delta":
8824
8899
  send({ role: "assistant", content: p8.textDelta ?? p8.text ?? "" });
8825
8900
  break;
8901
+ case "reasoning-delta":
8902
+ send({ role: "assistant", reasoning_content: p8.text ?? p8.delta ?? "" });
8903
+ break;
8826
8904
  case "tool-input-start":
8827
- case "tool-call-streaming-start":
8828
- send({ role: "assistant", tool_calls: [{ index: 0, id: p8.id ?? p8.toolCallId, type: "function", function: { name: p8.toolName, arguments: "" } }] });
8905
+ case "tool-call-streaming-start": {
8906
+ const id = p8.id ?? p8.toolCallId ?? "";
8907
+ const index = nextToolIndex++;
8908
+ streamedToolIndex.set(id, index);
8909
+ send({ role: "assistant", tool_calls: [{ index, id, type: "function", function: { name: p8.toolName, arguments: "" } }] });
8910
+ toolCallChunksEmitted++;
8829
8911
  break;
8912
+ }
8830
8913
  case "tool-input-delta":
8831
- case "tool-call-delta":
8832
- send({ tool_calls: [{ index: 0, function: { arguments: p8.delta ?? p8.text ?? p8.argsTextDelta ?? "" } }] });
8914
+ case "tool-call-delta": {
8915
+ const id = p8.id ?? p8.toolCallId ?? "";
8916
+ const index = streamedToolIndex.get(id) ?? 0;
8917
+ send({ tool_calls: [{ index, function: { arguments: p8.delta ?? p8.text ?? p8.argsTextDelta ?? "" } }] });
8918
+ break;
8919
+ }
8920
+ case "tool-call": {
8921
+ const id = p8.toolCallId ?? "";
8922
+ if (streamedToolIndex.has(id)) break;
8923
+ const index = nextToolIndex++;
8924
+ send({
8925
+ role: "assistant",
8926
+ tool_calls: [{ index, id, type: "function", function: { name: p8.toolName, arguments: JSON.stringify(p8.input ?? {}) } }]
8927
+ });
8928
+ toolCallChunksEmitted++;
8833
8929
  break;
8930
+ }
8834
8931
  case "finish":
8835
- send({}, p8.finishReason || "stop");
8932
+ log7?.(() => `openai stream parts=[${[...seenPartTypes].join(",")}] toolCallChunks=${toolCallChunksEmitted} finishReason=${p8.finishReason}`);
8933
+ send({}, toOpenAiFinishReason(p8.finishReason));
8836
8934
  break;
8935
+ case "error": {
8936
+ const errMsg = typeof p8.error === "string" ? p8.error : formatUpstreamError(p8.error);
8937
+ log7?.(() => `openai stream error parts=[${[...seenPartTypes].join(",")}]: ${errMsg}`);
8938
+ log7?.(() => {
8939
+ try {
8940
+ return `openai stream error raw: ${JSON.stringify(p8.error, Object.getOwnPropertyNames(p8.error ?? {})).slice(0, 3e3)}`;
8941
+ } catch {
8942
+ return `openai stream error raw: (unserializable) ${String(p8.error)}`;
8943
+ }
8944
+ });
8945
+ send({ role: "assistant", content: `
8946
+
8947
+ [relay-ai upstream error: ${errMsg}]` });
8948
+ send({}, "stop");
8949
+ onChunk("data: [DONE]\n\n");
8950
+ return;
8951
+ }
8837
8952
  }
8838
8953
  }
8839
8954
  onChunk("data: [DONE]\n\n");
@@ -9023,6 +9138,25 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
9023
9138
  sendJson(res, 400, { error: { message: "Invalid JSON body" } });
9024
9139
  return;
9025
9140
  }
9141
+ plog(() => `openai-chat-completions raw body: ${JSON.stringify(body).slice(0, 6e3)}`);
9142
+ plog(() => {
9143
+ const messages = Array.isArray(body.messages) ? body.messages : [];
9144
+ const summary = messages.map((m, i) => {
9145
+ const msg = m;
9146
+ const contentType = msg.content === null ? "null" : Array.isArray(msg.content) ? "array" : typeof msg.content;
9147
+ let partShapes = "";
9148
+ if (Array.isArray(msg.content)) {
9149
+ partShapes = " parts=[" + msg.content.map((p8) => {
9150
+ const part = p8;
9151
+ const keys = Object.keys(part).join(",");
9152
+ const textLen = typeof part.text === "string" ? part.text.length : void 0;
9153
+ return `{type=${part.type} keys=${keys}${textLen !== void 0 ? ` textLen=${textLen}` : ""}}`;
9154
+ }).join(",") + "]";
9155
+ }
9156
+ return `#${i} role=${msg.role} hasContent=${"content" in msg} contentType=${contentType} toolCalls=${Array.isArray(msg.tool_calls) ? msg.tool_calls.length : 0}${partShapes}`;
9157
+ });
9158
+ return `openai-chat-completions message shapes: [${summary.join(" | ")}]`;
9159
+ });
9026
9160
  const model = lookupModel(res, options.catalog, body.model);
9027
9161
  if (!model) return;
9028
9162
  if (supportsDirectOpenAIChatCompletions(model)) {
@@ -9032,7 +9166,9 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
9032
9166
  }
9033
9167
  const completionsUrl = model.completionsUrl ? model.completionsUrl : `${backendFor(options, model).baseUrl}/v1/chat/completions`;
9034
9168
  const apiKey2 = model.apiKey ?? options.apiKey;
9035
- await relayAnthropicMessages(res, completionsUrl, body, apiKey2, Boolean(body.stream));
9169
+ const forwardBody = { ...body, model: upstreamModelId(model) };
9170
+ plog(() => `openai-direct-passthrough \u2192 ${completionsUrl} model=${forwardBody.model} stream=${Boolean(body.stream)}`);
9171
+ await relayAnthropicMessages(res, completionsUrl, forwardBody, apiKey2, Boolean(body.stream), void 0, void 0, (message) => plog(message));
9036
9172
  return;
9037
9173
  }
9038
9174
  const npm = model.npm || (model.modelFormat === "anthropic" ? "@ai-sdk/anthropic" : void 0);
@@ -9054,7 +9190,7 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
9054
9190
  "Cache-Control": "no-cache",
9055
9191
  "Connection": "keep-alive"
9056
9192
  });
9057
- await streamOpenAiResponse(languageModel, params, responseModelId, (chunk) => res.write(chunk));
9193
+ await streamOpenAiResponse(languageModel, params, responseModelId, (chunk) => res.write(chunk), plog);
9058
9194
  res.end();
9059
9195
  } else {
9060
9196
  const response = await generateOpenAiResponse(languageModel, params, responseModelId);
@@ -9395,10 +9531,11 @@ function formatModelCatalogLines(models, gateway) {
9395
9531
  }
9396
9532
  list.push(model);
9397
9533
  }
9534
+ const collisions = openAiIdCollisions(models);
9398
9535
  const lines = ["Model catalog:", ""];
9399
9536
  const sortedGroups = [...groups.entries()].sort((a, b) => a[0].localeCompare(b[0]));
9400
9537
  for (const [label, groupModels] of sortedGroups) {
9401
- const rows = buildDedupedModelRows(groupModels, gateway);
9538
+ const rows = buildDedupedModelRows(groupModels, gateway, collisions);
9402
9539
  const hiddenDuplicates = groupModels.length - rows.length;
9403
9540
  const duplicateNote = hiddenDuplicates > 0 ? `, ${hiddenDuplicates} duplicate${hiddenDuplicates !== 1 ? "s" : ""} hidden` : "";
9404
9541
  const nameWidth = cappedWidth(rows.map((row) => row.name), "Model", 28);
@@ -10580,6 +10717,8 @@ function winLocalAppData2() {
10580
10717
  function winClaudeExeCandidates() {
10581
10718
  const local = winLocalAppData2();
10582
10719
  const bases = [
10720
+ // Squirrel install folder used by the Anthropic Claude desktop installer.
10721
+ join12(local, "AnthropicClaude"),
10583
10722
  join12(local, "Programs", "Claude"),
10584
10723
  join12(local, "Claude")
10585
10724
  ];
@@ -10877,6 +11016,7 @@ export {
10877
11016
  appendCodexBodyDump,
10878
11017
  getGeminiProxyDebugLogPath,
10879
11018
  getUiDebugLogPath,
11019
+ getServerDebugLogPath,
10880
11020
  makeTraceLogger,
10881
11021
  writeSecureLogLine,
10882
11022
  printTraceLog,
@@ -10892,6 +11032,7 @@ export {
10892
11032
  extractApiKey,
10893
11033
  sendJson,
10894
11034
  gatewayProviderLabel,
11035
+ openAiIdCollisions,
10895
11036
  createGatewayModelCatalog,
10896
11037
  buildDedupedModelRows,
10897
11038
  grabRoundTripSignature,
@@ -10962,4 +11103,4 @@ export {
10962
11103
  supportsClaudeTransparentMode,
10963
11104
  buildHttpProxyRoutes
10964
11105
  };
10965
- //# sourceMappingURL=chunk-P4S42QJK.js.map
11106
+ //# sourceMappingURL=chunk-WHEMJ7C3.js.map