@jacobbd/relay-ai 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getTemplateById,
4
4
  init_provider_templates
5
- } from "./chunk-MVBA7ABV.js";
5
+ } from "./chunk-EJONCU3B.js";
6
6
 
7
7
  // src/constants.ts
8
8
  import { homedir } from "os";
@@ -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.0",
14
+ version: "0.6.2",
15
15
  publishConfig: {
16
16
  access: "public"
17
17
  },
@@ -1784,6 +1784,10 @@ async function getSavedServerPassword() {
1784
1784
  }
1785
1785
  return null;
1786
1786
  }
1787
+ function getEnvServerPassword() {
1788
+ const value = process.env["RELAY_AI_SERVER_PASSWORD"]?.trim();
1789
+ return value || null;
1790
+ }
1787
1791
  async function setSavedServerPassword(password) {
1788
1792
  const keyring = await getServerPasswordKeyring();
1789
1793
  if (keyring) {
@@ -1867,15 +1871,15 @@ import { join as join4 } from "path";
1867
1871
  import { execFileSync } from "child_process";
1868
1872
  import { existsSync as existsSync2 } from "fs";
1869
1873
  function findBinaryOnPath(name, fallbackPaths, options = {}) {
1870
- const isWindows3 = options.isWindows ?? process.platform === "win32";
1874
+ const isWindows2 = options.isWindows ?? process.platform === "win32";
1871
1875
  const exists = options.exists ?? existsSync2;
1872
1876
  const runWhich = options.runWhich ?? ((binary, win) => execFileSync(win ? "where.exe" : "which", [binary], {
1873
1877
  encoding: "utf8",
1874
1878
  stdio: ["pipe", "pipe", "pipe"]
1875
1879
  }));
1876
1880
  try {
1877
- const lines = runWhich(name, isWindows3).trim().split("\n").map((line) => line.trim()).filter(Boolean);
1878
- const path = (isWindows3 ? lines.find((line) => line.toLowerCase().endsWith(".cmd")) : null) ?? lines[0];
1881
+ const lines = runWhich(name, isWindows2).trim().split("\n").map((line) => line.trim()).filter(Boolean);
1882
+ const path = (isWindows2 ? lines.find((line) => line.toLowerCase().endsWith(".cmd")) : null) ?? lines[0];
1879
1883
  if (path && (!options.verifyWhichResult || exists(path))) return path;
1880
1884
  } catch {
1881
1885
  }
@@ -4267,6 +4271,16 @@ function gatewayProviderId(model) {
4267
4271
  function gatewayAliasId(model) {
4268
4272
  return aliasModelId(model.id, gatewayProviderId(model));
4269
4273
  }
4274
+ function openAiIdCollisions(models) {
4275
+ const counts = /* @__PURE__ */ new Map();
4276
+ for (const model of models) counts.set(model.id, (counts.get(model.id) ?? 0) + 1);
4277
+ const collisions = /* @__PURE__ */ new Set();
4278
+ for (const [id, count] of counts) if (count > 1) collisions.add(id);
4279
+ return collisions;
4280
+ }
4281
+ function openAiExposedId(model, collisions) {
4282
+ return collisions.has(model.id) ? `${gatewayProviderId(model)}/${model.id}` : model.id;
4283
+ }
4270
4284
  function exposedGatewayAliasId(model, opts) {
4271
4285
  const alias = gatewayAliasId(model);
4272
4286
  return opts?.maskGatewayIds ? maskGatewayModelId(alias) : alias;
@@ -4286,8 +4300,11 @@ function formatGatewayAnthropicModels(models, opts) {
4286
4300
  }
4287
4301
  function createGatewayModelCatalog(models, opts) {
4288
4302
  const byId = /* @__PURE__ */ new Map();
4303
+ const collisions = openAiIdCollisions(models);
4289
4304
  for (const model of models) {
4290
- byId.set(model.id, model);
4305
+ if (!byId.has(model.id)) byId.set(model.id, model);
4306
+ const scopedId = openAiExposedId(model, collisions);
4307
+ if (scopedId !== model.id) byId.set(scopedId, model);
4291
4308
  const alias = exposedGatewayAliasId(model, opts);
4292
4309
  if (alias !== model.id) byId.set(alias, model);
4293
4310
  if (opts?.maskGatewayIds) {
@@ -4304,14 +4321,14 @@ function upstreamModelId(model) {
4304
4321
  const id = model.upstreamModelId ?? model.id;
4305
4322
  return id.replace(/\[1m\]$/i, "");
4306
4323
  }
4307
- function buildDedupedModelRows(models, opts) {
4324
+ function buildDedupedModelRows(models, opts, collisions = openAiIdCollisions(models)) {
4308
4325
  const seen = /* @__PURE__ */ new Set();
4309
4326
  const rows = [];
4310
4327
  for (const model of [...models].sort((a, b) => a.name.localeCompare(b.name))) {
4311
4328
  const row = {
4312
4329
  name: model.name,
4313
4330
  anthropicId: exposedGatewayAliasId(model, opts),
4314
- openaiId: model.id
4331
+ openaiId: openAiExposedId(model, collisions)
4315
4332
  };
4316
4333
  const key = `${row.name}\0${row.anthropicId}\0${row.openaiId}`;
4317
4334
  if (seen.has(key)) continue;
@@ -4324,10 +4341,11 @@ function supportsDirectOpenAIChatCompletions(model) {
4324
4341
  return model.modelFormat === "openai" && (!!model.completionsUrl || model.sourceBackend === "zen" || model.sourceBackend === "go");
4325
4342
  }
4326
4343
  function formatOpenAIModels(models) {
4344
+ const collisions = openAiIdCollisions(models);
4327
4345
  return {
4328
4346
  object: "list",
4329
4347
  data: models.map((model) => ({
4330
- id: model.id,
4348
+ id: openAiExposedId(model, collisions),
4331
4349
  object: "model",
4332
4350
  created: CREATED_AT_UNIX,
4333
4351
  owned_by: model.sourceBackend
@@ -7377,7 +7395,7 @@ async function fetchTemplateModels(template, apiKey, baseUrlOverride, extraHeade
7377
7395
  models: [],
7378
7396
  baseUrl: "",
7379
7397
  error: "This provider needs a base URL.",
7380
- hint: "Use relay-ai providers import from OpenCode for advanced setups."
7398
+ hint: template.urlPrompt ? "Enter the API base URL when adding this provider." : "This template is missing a default base URL \u2014 report a bug."
7381
7399
  };
7382
7400
  }
7383
7401
  if (template.modelSource === "static-seed") {
@@ -8233,7 +8251,7 @@ async function refreshProviderModels(providerId, apiKey, registry = loadRegistry
8233
8251
  }
8234
8252
  const source = resolveModelSource(provider);
8235
8253
  if (source === "manual-only") {
8236
- const hint = provider.templateId === "google-vertex" || provider.id === "google-vertex" || provider.api.npm === "@ai-sdk/google-vertex" ? "Vertex uses gcloud credentials \u2014 re-import from OpenCode or configure env auth." : "Manual-only provider \u2014 model list is not refreshed automatically.";
8254
+ const hint = provider.templateId === "google-vertex" || provider.id === "google-vertex" || provider.api.npm === "@ai-sdk/google-vertex" ? "Vertex uses gcloud credentials \u2014 use relay-ai server --vertex, or refresh after configuring ADC." : "Manual-only provider \u2014 model list is not refreshed automatically.";
8237
8255
  return {
8238
8256
  id: provider.id,
8239
8257
  name: provider.name,
@@ -8464,6 +8482,44 @@ async function ensureOpencodeCloudProviders(hasOpencodeKey) {
8464
8482
  }
8465
8483
  return { seeded, refreshed };
8466
8484
  }
8485
+ async function addOpencodeCloudFromApiKey(apiKey) {
8486
+ const trimmed = apiKey.trim();
8487
+ if (!trimmed) {
8488
+ return { added: false, error: "API key cannot be empty." };
8489
+ }
8490
+ const saved = await saveToCredentialStore(trimmed);
8491
+ if (!saved) {
8492
+ return {
8493
+ added: false,
8494
+ error: "Could not save API key.",
8495
+ hint: "Ensure RELAY_AI_HOME is writable (Docker uses secrets.json when no OS keyring)."
8496
+ };
8497
+ }
8498
+ process.env["OPENCODE_API_KEY"] = trimmed;
8499
+ const zenStub = addZenRegistryStub();
8500
+ const goStub = addGoRegistryStub();
8501
+ if (!zenStub.added && !goStub.added) {
8502
+ return {
8503
+ added: false,
8504
+ error: "OpenCode Zen / Go is already configured.",
8505
+ hint: "Remove zen or go first, or use Refresh on the provider cards."
8506
+ };
8507
+ }
8508
+ const registry = loadRegistry();
8509
+ const refreshResults = [
8510
+ await refreshProviderModels("zen", trimmed, registry),
8511
+ await refreshProviderModels("go", trimmed, registry)
8512
+ ];
8513
+ const modelCount = refreshResults.reduce((total, result) => total + (result.modelCount ?? 0), 0);
8514
+ const failed = refreshResults.filter((result) => !result.ok);
8515
+ return {
8516
+ added: true,
8517
+ modelCount,
8518
+ ...failed.length > 0 ? {
8519
+ hint: `Providers added, but ${failed.length} catalog refresh${failed.length === 1 ? "" : "es"} failed \u2014 try Refresh on the provider card.`
8520
+ } : {}
8521
+ };
8522
+ }
8467
8523
  function toggleProviderEnabled(id) {
8468
8524
  const registry = loadRegistry();
8469
8525
  const provider = registry.providers.find((p8) => p8.id === id);
@@ -8679,8 +8735,9 @@ function translateOpenAiRequest(body) {
8679
8735
  break;
8680
8736
  case "assistant": {
8681
8737
  const parts = [];
8682
- if (typeof msg.content === "string" && msg.content) {
8683
- parts.push({ type: "text", text: msg.content });
8738
+ 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("") : "";
8739
+ if (assistantText) {
8740
+ parts.push({ type: "text", text: assistantText });
8684
8741
  }
8685
8742
  for (const tc of msg.tool_calls ?? []) {
8686
8743
  parts.push({
@@ -8741,14 +8798,31 @@ function translateOpenAiRequest(body) {
8741
8798
  maxOutputTokens: body.max_completion_tokens ?? body.max_tokens
8742
8799
  };
8743
8800
  }
8801
+ function toOpenAiFinishReason(reason) {
8802
+ switch (reason) {
8803
+ case "tool-calls":
8804
+ return "tool_calls";
8805
+ case "content-filter":
8806
+ return "content_filter";
8807
+ case "length":
8808
+ return "length";
8809
+ case "stop":
8810
+ return "stop";
8811
+ default:
8812
+ return "stop";
8813
+ }
8814
+ }
8744
8815
  async function generateOpenAiResponse(model, params, responseModelId) {
8745
8816
  const result = await generateText2({ model, ...params });
8746
8817
  const message = { role: "assistant", content: result.text || null };
8818
+ if (result.reasoningText || result.reasoning) {
8819
+ message.reasoning_content = result.reasoningText ?? result.reasoning;
8820
+ }
8747
8821
  if (result.toolCalls?.length) {
8748
8822
  message.tool_calls = result.toolCalls.map((tc) => ({
8749
8823
  id: tc.toolCallId,
8750
8824
  type: "function",
8751
- function: { name: tc.toolName, arguments: JSON.stringify(tc.args) }
8825
+ function: { name: tc.toolName, arguments: JSON.stringify(tc.input ?? tc.args ?? {}) }
8752
8826
  }));
8753
8827
  }
8754
8828
  return {
@@ -8756,7 +8830,7 @@ async function generateOpenAiResponse(model, params, responseModelId) {
8756
8830
  object: "chat.completion",
8757
8831
  created: Math.floor(Date.now() / 1e3),
8758
8832
  model: responseModelId,
8759
- choices: [{ index: 0, message, finish_reason: result.finishReason || "stop" }],
8833
+ choices: [{ index: 0, message, finish_reason: toOpenAiFinishReason(result.finishReason) }],
8760
8834
  usage: {
8761
8835
  prompt_tokens: result.usage?.promptTokens ?? 0,
8762
8836
  completion_tokens: result.usage?.completionTokens ?? 0,
@@ -8764,7 +8838,7 @@ async function generateOpenAiResponse(model, params, responseModelId) {
8764
8838
  }
8765
8839
  };
8766
8840
  }
8767
- async function streamOpenAiResponse(model, params, responseModelId, onChunk) {
8841
+ async function streamOpenAiResponse(model, params, responseModelId, onChunk, log7) {
8768
8842
  const { fullStream } = streamText2({ model, ...params });
8769
8843
  const baseData = {
8770
8844
  id: `chatcmpl-${Date.now()}`,
@@ -8775,23 +8849,68 @@ async function streamOpenAiResponse(model, params, responseModelId, onChunk) {
8775
8849
  const send = (delta, finish_reason = null) => onChunk(`data: ${JSON.stringify({ ...baseData, choices: [{ index: 0, delta, finish_reason }] })}
8776
8850
 
8777
8851
  `);
8852
+ const streamedToolIndex = /* @__PURE__ */ new Map();
8853
+ let nextToolIndex = 0;
8854
+ const seenPartTypes = /* @__PURE__ */ new Set();
8855
+ let toolCallChunksEmitted = 0;
8778
8856
  for await (const part of fullStream) {
8779
8857
  const p8 = part;
8858
+ seenPartTypes.add(p8.type);
8780
8859
  switch (p8.type) {
8781
8860
  case "text-delta":
8782
8861
  send({ role: "assistant", content: p8.textDelta ?? p8.text ?? "" });
8783
8862
  break;
8863
+ case "reasoning-delta":
8864
+ send({ role: "assistant", reasoning_content: p8.text ?? p8.delta ?? "" });
8865
+ break;
8784
8866
  case "tool-input-start":
8785
- case "tool-call-streaming-start":
8786
- send({ role: "assistant", tool_calls: [{ index: 0, id: p8.id ?? p8.toolCallId, type: "function", function: { name: p8.toolName, arguments: "" } }] });
8867
+ case "tool-call-streaming-start": {
8868
+ const id = p8.id ?? p8.toolCallId ?? "";
8869
+ const index = nextToolIndex++;
8870
+ streamedToolIndex.set(id, index);
8871
+ send({ role: "assistant", tool_calls: [{ index, id, type: "function", function: { name: p8.toolName, arguments: "" } }] });
8872
+ toolCallChunksEmitted++;
8787
8873
  break;
8874
+ }
8788
8875
  case "tool-input-delta":
8789
- case "tool-call-delta":
8790
- send({ tool_calls: [{ index: 0, function: { arguments: p8.delta ?? p8.text ?? p8.argsTextDelta ?? "" } }] });
8876
+ case "tool-call-delta": {
8877
+ const id = p8.id ?? p8.toolCallId ?? "";
8878
+ const index = streamedToolIndex.get(id) ?? 0;
8879
+ send({ tool_calls: [{ index, function: { arguments: p8.delta ?? p8.text ?? p8.argsTextDelta ?? "" } }] });
8880
+ break;
8881
+ }
8882
+ case "tool-call": {
8883
+ const id = p8.toolCallId ?? "";
8884
+ if (streamedToolIndex.has(id)) break;
8885
+ const index = nextToolIndex++;
8886
+ send({
8887
+ role: "assistant",
8888
+ tool_calls: [{ index, id, type: "function", function: { name: p8.toolName, arguments: JSON.stringify(p8.input ?? {}) } }]
8889
+ });
8890
+ toolCallChunksEmitted++;
8791
8891
  break;
8892
+ }
8792
8893
  case "finish":
8793
- send({}, p8.finishReason || "stop");
8894
+ log7?.(() => `openai stream parts=[${[...seenPartTypes].join(",")}] toolCallChunks=${toolCallChunksEmitted} finishReason=${p8.finishReason}`);
8895
+ send({}, toOpenAiFinishReason(p8.finishReason));
8794
8896
  break;
8897
+ case "error": {
8898
+ const errMsg = typeof p8.error === "string" ? p8.error : formatUpstreamError(p8.error);
8899
+ log7?.(() => `openai stream error parts=[${[...seenPartTypes].join(",")}]: ${errMsg}`);
8900
+ log7?.(() => {
8901
+ try {
8902
+ return `openai stream error raw: ${JSON.stringify(p8.error, Object.getOwnPropertyNames(p8.error ?? {})).slice(0, 3e3)}`;
8903
+ } catch {
8904
+ return `openai stream error raw: (unserializable) ${String(p8.error)}`;
8905
+ }
8906
+ });
8907
+ send({ role: "assistant", content: `
8908
+
8909
+ [relay-ai upstream error: ${errMsg}]` });
8910
+ send({}, "stop");
8911
+ onChunk("data: [DONE]\n\n");
8912
+ return;
8913
+ }
8795
8914
  }
8796
8915
  }
8797
8916
  onChunk("data: [DONE]\n\n");
@@ -8981,6 +9100,25 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
8981
9100
  sendJson(res, 400, { error: { message: "Invalid JSON body" } });
8982
9101
  return;
8983
9102
  }
9103
+ plog(() => `openai-chat-completions raw body: ${JSON.stringify(body).slice(0, 6e3)}`);
9104
+ plog(() => {
9105
+ const messages = Array.isArray(body.messages) ? body.messages : [];
9106
+ const summary = messages.map((m, i) => {
9107
+ const msg = m;
9108
+ const contentType = msg.content === null ? "null" : Array.isArray(msg.content) ? "array" : typeof msg.content;
9109
+ let partShapes = "";
9110
+ if (Array.isArray(msg.content)) {
9111
+ partShapes = " parts=[" + msg.content.map((p8) => {
9112
+ const part = p8;
9113
+ const keys = Object.keys(part).join(",");
9114
+ const textLen = typeof part.text === "string" ? part.text.length : void 0;
9115
+ return `{type=${part.type} keys=${keys}${textLen !== void 0 ? ` textLen=${textLen}` : ""}}`;
9116
+ }).join(",") + "]";
9117
+ }
9118
+ return `#${i} role=${msg.role} hasContent=${"content" in msg} contentType=${contentType} toolCalls=${Array.isArray(msg.tool_calls) ? msg.tool_calls.length : 0}${partShapes}`;
9119
+ });
9120
+ return `openai-chat-completions message shapes: [${summary.join(" | ")}]`;
9121
+ });
8984
9122
  const model = lookupModel(res, options.catalog, body.model);
8985
9123
  if (!model) return;
8986
9124
  if (supportsDirectOpenAIChatCompletions(model)) {
@@ -8990,7 +9128,9 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
8990
9128
  }
8991
9129
  const completionsUrl = model.completionsUrl ? model.completionsUrl : `${backendFor(options, model).baseUrl}/v1/chat/completions`;
8992
9130
  const apiKey2 = model.apiKey ?? options.apiKey;
8993
- await relayAnthropicMessages(res, completionsUrl, body, apiKey2, Boolean(body.stream));
9131
+ const forwardBody = { ...body, model: upstreamModelId(model) };
9132
+ plog(() => `openai-direct-passthrough \u2192 ${completionsUrl} model=${forwardBody.model} stream=${Boolean(body.stream)}`);
9133
+ await relayAnthropicMessages(res, completionsUrl, forwardBody, apiKey2, Boolean(body.stream), void 0, void 0, (message) => plog(message));
8994
9134
  return;
8995
9135
  }
8996
9136
  const npm = model.npm || (model.modelFormat === "anthropic" ? "@ai-sdk/anthropic" : void 0);
@@ -9012,7 +9152,7 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
9012
9152
  "Cache-Control": "no-cache",
9013
9153
  "Connection": "keep-alive"
9014
9154
  });
9015
- await streamOpenAiResponse(languageModel, params, responseModelId, (chunk) => res.write(chunk));
9155
+ await streamOpenAiResponse(languageModel, params, responseModelId, (chunk) => res.write(chunk), plog);
9016
9156
  res.end();
9017
9157
  } else {
9018
9158
  const response = await generateOpenAiResponse(languageModel, params, responseModelId);
@@ -9353,10 +9493,11 @@ function formatModelCatalogLines(models, gateway) {
9353
9493
  }
9354
9494
  list.push(model);
9355
9495
  }
9496
+ const collisions = openAiIdCollisions(models);
9356
9497
  const lines = ["Model catalog:", ""];
9357
9498
  const sortedGroups = [...groups.entries()].sort((a, b) => a[0].localeCompare(b[0]));
9358
9499
  for (const [label, groupModels] of sortedGroups) {
9359
- const rows = buildDedupedModelRows(groupModels, gateway);
9500
+ const rows = buildDedupedModelRows(groupModels, gateway, collisions);
9360
9501
  const hiddenDuplicates = groupModels.length - rows.length;
9361
9502
  const duplicateNote = hiddenDuplicates > 0 ? `, ${hiddenDuplicates} duplicate${hiddenDuplicates !== 1 ? "s" : ""} hidden` : "";
9362
9503
  const nameWidth = cappedWidth(rows.map((row) => row.name), "Model", 28);
@@ -9484,7 +9625,7 @@ async function getServerPasswordForQuickMode(mode, passwordOverride) {
9484
9625
  if (mode === "local") return { password: null, wasSaved: false };
9485
9626
  const trimmedOverride = passwordOverride?.trim();
9486
9627
  if (trimmedOverride) return { password: trimmedOverride, wasSaved: false };
9487
- const fromEnv = process.env["RELAY_AI_SERVER_PASSWORD"]?.trim();
9628
+ const fromEnv = getEnvServerPassword();
9488
9629
  if (fromEnv) return { password: fromEnv, wasSaved: false };
9489
9630
  const savedPassword = await getSavedServerPassword();
9490
9631
  if (savedPassword) return { password: savedPassword, wasSaved: true };
@@ -9637,8 +9778,8 @@ async function resolveServerUpstreamApiKey() {
9637
9778
  }));
9638
9779
  if (apiKey) {
9639
9780
  const isMac = process.platform === "darwin";
9640
- const isWindows3 = process.platform === "win32";
9641
- const storeName = isMac ? "macOS Keychain" : isWindows3 ? "Windows Credential Manager" : "Secret Service";
9781
+ const isWindows2 = process.platform === "win32";
9782
+ const storeName = isMac ? "macOS Keychain" : isWindows2 ? "Windows Credential Manager" : "Secret Service";
9642
9783
  p4.log.success(`Found key in ${storeName}`);
9643
9784
  return apiKey;
9644
9785
  }
@@ -9961,97 +10102,6 @@ function buildHttpProxyRoutes(providers, favorites, selected, max = MAX_MODEL_CA
9961
10102
  return { routes, unavailable, unsupported };
9962
10103
  }
9963
10104
 
9964
- // src/opencode-serve.ts
9965
- import { execSync as execSync2, spawn as spawn2 } from "child_process";
9966
- import { existsSync as existsSync11 } from "fs";
9967
- import { homedir as homedir7 } from "os";
9968
- import { join as join11 } from "path";
9969
- var isWindows2 = process.platform === "win32";
9970
- var OPENCODE_FALLBACK_PATHS = isWindows2 ? [
9971
- join11(process.env["APPDATA"] ?? homedir7(), "npm", "opencode.cmd"),
9972
- join11(process.env["APPDATA"] ?? homedir7(), "npm", "opencode"),
9973
- join11(homedir7(), "AppData", "Roaming", "npm", "opencode.cmd")
9974
- ] : [
9975
- join11(homedir7(), ".opencode", "bin", "opencode"),
9976
- join11(homedir7(), ".local", "bin", "opencode"),
9977
- join11(homedir7(), ".npm", "bin", "opencode"),
9978
- "/usr/local/bin/opencode",
9979
- "/opt/homebrew/bin/opencode"
9980
- ];
9981
- function findOpencodeBinary() {
9982
- try {
9983
- const result = execSync2(isWindows2 ? "where.exe opencode" : "which opencode", {
9984
- encoding: "utf8",
9985
- stdio: ["pipe", "pipe", "pipe"]
9986
- });
9987
- const lines = result.trim().split("\n").map((l) => l.trim()).filter(Boolean);
9988
- const path = (isWindows2 ? lines.find((l) => l.toLowerCase().endsWith(".cmd")) : null) ?? lines[0];
9989
- if (path) return path;
9990
- } catch {
9991
- }
9992
- for (const path of OPENCODE_FALLBACK_PATHS) {
9993
- if (existsSync11(path)) return path;
9994
- }
9995
- return null;
9996
- }
9997
- async function fetchRawOpencodeProviders() {
9998
- const binary = findOpencodeBinary();
9999
- if (!binary) return null;
10000
- return new Promise((resolve) => {
10001
- let child = null;
10002
- let settled = false;
10003
- const TIMEOUT_MS = 1e4;
10004
- const finish = (value) => {
10005
- if (settled) return;
10006
- settled = true;
10007
- clearTimeout(timer);
10008
- try {
10009
- child?.kill();
10010
- } catch {
10011
- }
10012
- resolve(value);
10013
- };
10014
- const timer = setTimeout(() => {
10015
- finish(null);
10016
- }, TIMEOUT_MS);
10017
- try {
10018
- child = isWindows2 ? spawn2("cmd.exe", ["/c", binary, "serve", "--port", "0"], { stdio: ["pipe", "pipe", "pipe"] }) : spawn2(binary, ["serve", "--port", "0"], { stdio: ["pipe", "pipe", "pipe"] });
10019
- } catch {
10020
- finish(null);
10021
- return;
10022
- }
10023
- const portRegex = /opencode server listening on http:\/\/127\.0\.0\.1:(\d+)/;
10024
- let portFound = false;
10025
- let stdoutBuf = "";
10026
- const onData = (chunk) => {
10027
- if (portFound) return;
10028
- stdoutBuf += chunk.toString();
10029
- const match = portRegex.exec(stdoutBuf);
10030
- if (!match) return;
10031
- portFound = true;
10032
- const port = match[1];
10033
- fetch(`http://127.0.0.1:${port}/config/providers`).then((res) => res.json()).then((data) => {
10034
- const raw = data.providers;
10035
- if (!Array.isArray(raw)) {
10036
- finish(null);
10037
- return;
10038
- }
10039
- finish(raw);
10040
- }).catch(() => {
10041
- finish(null);
10042
- });
10043
- };
10044
- child.stdout?.on("data", onData);
10045
- child.stderr?.on("data", onData);
10046
- child.on("error", () => {
10047
- finish(null);
10048
- });
10049
- child.on("exit", () => {
10050
- if (!settled) finish(null);
10051
- });
10052
- });
10053
- }
10054
-
10055
10105
  // src/registry/add-template.ts
10056
10106
  async function probeTemplatePackage(template) {
10057
10107
  if (!template.supported) return template.unsupportedReason ?? "Provider is not supported yet.";
@@ -10075,14 +10125,17 @@ function filterAnonymousFreeModels(models, template) {
10075
10125
  })));
10076
10126
  }
10077
10127
  async function addProviderFromTemplate(template, apiKey, opts) {
10078
- const packageError = await probeTemplatePackage(template);
10079
- if (packageError) {
10080
- return { added: false, error: packageError };
10081
- }
10082
10128
  const trimmedKey = apiKey.trim();
10083
10129
  if (!trimmedKey && !template.apiKeyOptional) {
10084
10130
  return { added: false, error: "API key cannot be empty." };
10085
10131
  }
10132
+ if (template.modelSource === "zen-go-api") {
10133
+ return addOpencodeCloudFromApiKey(trimmedKey);
10134
+ }
10135
+ const packageError = await probeTemplatePackage(template);
10136
+ if (packageError) {
10137
+ return { added: false, error: packageError };
10138
+ }
10086
10139
  const registry = loadRegistry();
10087
10140
  const existing = registry.providers.find((p8) => p8.id === template.id);
10088
10141
  if (existing && !opts?.replaceExisting) {
@@ -10159,80 +10212,6 @@ import pc6 from "picocolors";
10159
10212
  import * as p5 from "@clack/prompts";
10160
10213
  import open3 from "open";
10161
10214
  init_provider_templates();
10162
-
10163
- // src/registry/auth-broker.ts
10164
- import { spawn as spawn3 } from "child_process";
10165
- async function runOpencodeAuthBroker(providerId, options = {}) {
10166
- const binary = findOpencodeBinary();
10167
- if (!binary) {
10168
- throw new Error("OpenCode CLI not found. Install from https://opencode.ai or use native device-code auth.");
10169
- }
10170
- const args = ["auth", "login", "--provider", providerId];
10171
- if (options.method) args.push("-m", options.method);
10172
- const exitCode = await new Promise((resolve, reject) => {
10173
- const child = spawn3(binary, args, { stdio: "inherit" });
10174
- child.on("error", reject);
10175
- child.on("exit", (code) => resolve(code ?? 1));
10176
- });
10177
- if (exitCode !== 0) {
10178
- throw new Error(`OpenCode auth login failed (exit ${exitCode})`);
10179
- }
10180
- const authFile = readOpencodeAuthFile();
10181
- const entry = authFile?.entries[providerId];
10182
- if (!isOpencodeOAuth(entry)) {
10183
- throw new Error(`No OAuth token found for "${providerId}" after OpenCode login`);
10184
- }
10185
- return entry;
10186
- }
10187
-
10188
- // src/registry/convert.ts
10189
- function modelToCached(model) {
10190
- return {
10191
- id: model.id,
10192
- name: model.name,
10193
- upstreamModelId: model.upstreamModelId,
10194
- family: model.family,
10195
- brand: model.brand,
10196
- contextWindow: model.contextWindow,
10197
- cost: model.cost,
10198
- isFree: model.isFree,
10199
- freeStatus: model.freeStatus,
10200
- modelFormat: model.modelFormat,
10201
- npm: model.npm,
10202
- apiUrl: model.apiBaseUrl,
10203
- supportedParameters: model.supportedParameters,
10204
- reasoning: model.reasoning,
10205
- interleavedReasoningField: model.interleavedReasoningField,
10206
- useResponsesLite: model.useResponsesLite,
10207
- preferWebSockets: model.preferWebSockets
10208
- };
10209
- }
10210
- function localProviderToRegistry(provider, opts) {
10211
- if (!isValidProviderId(provider.id)) return null;
10212
- if (provider.models.length === 0) return null;
10213
- const first = provider.models[0];
10214
- const apiUrl = (first.apiBaseUrl ?? first.baseUrl)?.trim();
10215
- const authType = opts?.authType ?? "api";
10216
- return {
10217
- id: provider.id,
10218
- templateId: opts?.templateId ?? provider.id,
10219
- name: provider.name,
10220
- enabled: true,
10221
- authRef: opts?.authRef ?? `keyring:provider:${provider.id}`,
10222
- authType,
10223
- api: {
10224
- npm: first.npm,
10225
- ...apiUrl ? { url: apiUrl } : {}
10226
- },
10227
- addedAt: (/* @__PURE__ */ new Date()).toISOString(),
10228
- modelsCache: {
10229
- fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
10230
- models: provider.models.map(modelToCached)
10231
- }
10232
- };
10233
- }
10234
-
10235
- // src/registry/provider-auth.ts
10236
10215
  var OPENAI_DISPLAY = "OpenAI ChatGPT Plus/Pro";
10237
10216
  var PROVIDER_DISPLAY = {
10238
10217
  xai: "xAI Grok (SuperGrok)",
@@ -10366,30 +10345,8 @@ async function upsertOAuthProvider(providerId, cred) {
10366
10345
  const templateId = providerId.replace(/-oauth$/, "") || providerId;
10367
10346
  const registry = loadRegistry();
10368
10347
  const authRef = oauthAuthRef(registryId);
10369
- const template = getTemplateById(templateId);
10348
+ const template = getTemplateById(templateId) ?? getTemplateById(registryId);
10370
10349
  let entry = registry.providers.find((pr) => pr.id === registryId);
10371
- if (!entry) {
10372
- const raw = await fetchRawOpencodeProviders();
10373
- if (raw) {
10374
- const { providers } = buildImportProviderList(raw, { [providerId]: cred });
10375
- const lp = providers.find((pr) => pr.id === registryId || pr.id === providerId);
10376
- if (lp) {
10377
- const converted = localProviderToRegistry(lp, { authType: "oauth", authRef });
10378
- if (converted) {
10379
- entry = {
10380
- ...converted,
10381
- id: registryId,
10382
- templateId,
10383
- name: oauthDisplayName(registryId, converted.name),
10384
- api: {
10385
- ...converted.api,
10386
- ...template?.headers ? { headers: { ...template.headers, ...converted.api.headers } } : {}
10387
- }
10388
- };
10389
- }
10390
- }
10391
- }
10392
- }
10393
10350
  if (!entry) {
10394
10351
  if (!template) {
10395
10352
  throw new Error(`Provider "${providerId}" is not in your registry and has no template`);
@@ -10397,7 +10354,7 @@ async function upsertOAuthProvider(providerId, cred) {
10397
10354
  const displayName = oauthDisplayName(registryId, template.name);
10398
10355
  entry = {
10399
10356
  id: registryId,
10400
- templateId,
10357
+ templateId: template.id,
10401
10358
  name: displayName,
10402
10359
  enabled: true,
10403
10360
  authRef,
@@ -10410,7 +10367,7 @@ async function upsertOAuthProvider(providerId, cred) {
10410
10367
  addedAt: (/* @__PURE__ */ new Date()).toISOString()
10411
10368
  };
10412
10369
  } else {
10413
- entry = { ...entry, authType: "oauth", authRef, templateId };
10370
+ entry = { ...entry, authType: "oauth", authRef, templateId: entry.templateId ?? templateId };
10414
10371
  }
10415
10372
  const idx = registry.providers.findIndex((pr) => pr.id === registryId);
10416
10373
  if (idx >= 0) registry.providers[idx] = entry;
@@ -10421,50 +10378,16 @@ async function upsertOAuthProvider(providerId, cred) {
10421
10378
  async function authenticateProvider(providerId, options = {}) {
10422
10379
  const registryId = toOAuthRegistryId(providerId);
10423
10380
  if (!supportsNativeOAuth(providerId)) {
10424
- if (findOpencodeBinary()) {
10425
- const cred2 = await runOpencodeAuthBroker(providerId, { method: options.brokerMethod });
10426
- let brokerDiagMsg = "";
10427
- const saved2 = await saveProviderCredential(
10428
- oauthAuthRef(registryId),
10429
- oauthCredentialToKeychainJson(cred2),
10430
- (msg) => {
10431
- brokerDiagMsg = msg;
10432
- }
10433
- );
10434
- if (!saved2) {
10435
- p5.log.warn(`Could not save OAuth tokens \u2014 ${brokerDiagMsg || "session may not persist."}`);
10436
- }
10437
- const registryProvider2 = await upsertOAuthProvider(providerId, cred2);
10438
- return { providerId: registryId, credential: cred2, registryProvider: registryProvider2 };
10439
- }
10440
10381
  throw new Error(
10441
- `Native OAuth is only built in for xai and openai. Install OpenCode for other OAuth providers.`
10382
+ `OAuth for "${providerId}" is not built into relay-ai. Add an API-key provider with relay-ai providers add, or run relay-ai providers import if you already configured it in the OpenCode CLI.`
10442
10383
  );
10443
10384
  }
10444
- let method = options.method;
10445
- if (isBrowserRedirectOAuth(providerId)) {
10446
- if (method === "broker") {
10447
- throw new Error(`Via OpenCode is not supported for "${providerId}". Use the built-in OAuth flow.`);
10448
- }
10449
- method = "native";
10450
- }
10451
- if (!method) {
10452
- const hasOpencode = findOpencodeBinary() !== null;
10453
- if (hasOpencode) {
10454
- const choice = await p5.select({
10455
- message: "How would you like to sign in?",
10456
- options: [
10457
- { value: "native", label: "Device code (recommended)", hint: "Works on SSH/VPS \u2014 open URL on any device" },
10458
- { value: "broker", label: "Via OpenCode", hint: "Uses opencode auth login" }
10459
- ]
10460
- });
10461
- if (p5.isCancel(choice)) throw new Error("Cancelled");
10462
- method = choice;
10463
- } else {
10464
- method = "native";
10465
- }
10385
+ if (options.method === "broker") {
10386
+ throw new Error(
10387
+ "OpenCode auth broker is no longer used for providers. Use the built-in OAuth flow, or relay-ai providers import for OpenCode CLI configs."
10388
+ );
10466
10389
  }
10467
- const cred = method === "broker" ? await runOpencodeAuthBroker(providerId, { method: options.brokerMethod }) : isBrowserRedirectOAuth(providerId) ? await runNativeBrowserOAuth(providerId) : await runNativeDeviceCode(providerId);
10390
+ const cred = isBrowserRedirectOAuth(providerId) ? await runNativeBrowserOAuth(providerId) : await runNativeDeviceCode(providerId);
10468
10391
  let nativeDiagMsg = "";
10469
10392
  const saved = await saveProviderCredential(
10470
10393
  oauthAuthRef(registryId),
@@ -10492,25 +10415,23 @@ function providerAuthHelpText() {
10492
10415
 
10493
10416
  ${pc6.bold("Usage:")}
10494
10417
  relay-ai providers auth <id>
10495
- relay-ai providers auth xai-oauth --native
10496
- relay-ai providers auth openai --broker
10418
+ relay-ai providers auth xai-oauth
10419
+ relay-ai providers auth openai-oauth
10497
10420
  relay-ai providers auth github-copilot
10498
10421
 
10499
- ${pc6.bold("Options:")}
10500
- --native Use built-in OAuth flow
10501
- --broker Delegate to OpenCode auth login
10502
-
10503
10422
  ${pc6.bold("Device code (works on SSH/VPS):")}
10504
10423
  xai-oauth SuperGrok / X Premium (device code at x.ai/device)
10505
10424
  openai-oauth ChatGPT Plus/Pro (device code at auth.openai.com/codex/device)
10506
- github-copilot GitHub Copilot Free or paid (device code at github.com/login/device)`;
10425
+ github-copilot GitHub Copilot Free or paid (device code at github.com/login/device)
10426
+
10427
+ ${pc6.dim("OpenCode CLI configs: use")} relay-ai providers import${pc6.dim(" (optional one-time migration).")}`;
10507
10428
  }
10508
10429
 
10509
10430
  // src/codex/app-launch.ts
10510
- import { execSync as execSync3, spawn as spawn4 } from "child_process";
10511
- import { existsSync as existsSync12, readdirSync, statSync as statSync3 } from "fs";
10512
- import { homedir as homedir8 } from "os";
10513
- import { join as join12 } from "path";
10431
+ import { execSync as execSync2, spawn as spawn2 } from "child_process";
10432
+ import { existsSync as existsSync11, readdirSync, statSync as statSync3 } from "fs";
10433
+ import { homedir as homedir7 } from "os";
10434
+ import { join as join11 } from "path";
10514
10435
  import * as p6 from "@clack/prompts";
10515
10436
  var CODEX_BUNDLE_ID = "com.openai.codex";
10516
10437
  var DARWIN_APP_NAMES = ["ChatGPT", "Codex"];
@@ -10521,7 +10442,7 @@ function codexAppSupported() {
10521
10442
  }
10522
10443
  }
10523
10444
  function run(cmd, encoding = "utf8") {
10524
- return execSync3(cmd, { encoding, stdio: ["pipe", "pipe", "pipe"] }).trim();
10445
+ return execSync2(cmd, { encoding, stdio: ["pipe", "pipe", "pipe"] }).trim();
10525
10446
  }
10526
10447
  function runPowerShell(script) {
10527
10448
  return run(`powershell.exe -NoProfile -Command ${JSON.stringify(script)}`);
@@ -10529,33 +10450,33 @@ function runPowerShell(script) {
10529
10450
  function darwinAppCandidates() {
10530
10451
  return DARWIN_APP_NAMES.flatMap((name) => [
10531
10452
  `/Applications/${name}.app`,
10532
- join12(homedir8(), "Applications", `${name}.app`)
10453
+ join11(homedir7(), "Applications", `${name}.app`)
10533
10454
  ]);
10534
10455
  }
10535
10456
  function winLocalAppData() {
10536
- return process.env.LOCALAPPDATA ?? join12(homedir8(), "AppData", "Local");
10457
+ return process.env.LOCALAPPDATA ?? join11(homedir7(), "AppData", "Local");
10537
10458
  }
10538
10459
  function winCodexExeCandidates() {
10539
10460
  const local = winLocalAppData();
10540
10461
  const bases = WIN_APP_NAMES.flatMap((name) => [
10541
- join12(local, "Programs", name),
10542
- join12(local, "Programs", `OpenAI ${name}`),
10543
- join12(local, name),
10544
- join12(local, `OpenAI ${name}`),
10545
- join12(local, "OpenAI", name)
10462
+ join11(local, "Programs", name),
10463
+ join11(local, "Programs", `OpenAI ${name}`),
10464
+ join11(local, name),
10465
+ join11(local, `OpenAI ${name}`),
10466
+ join11(local, "OpenAI", name)
10546
10467
  ]);
10547
- bases.push(join12(local, "openai-codex-electron"), join12(local, "openai-chatgpt-electron"));
10468
+ bases.push(join11(local, "openai-codex-electron"), join11(local, "openai-chatgpt-electron"));
10548
10469
  const out = [];
10549
10470
  for (const base of bases) {
10550
10471
  for (const name of WIN_APP_NAMES) {
10551
- out.push(join12(base, `${name}.exe`));
10472
+ out.push(join11(base, `${name}.exe`));
10552
10473
  }
10553
10474
  try {
10554
- if (existsSync12(base)) {
10475
+ if (existsSync11(base)) {
10555
10476
  for (const dir of readdirSync(base)) {
10556
10477
  if (dir.startsWith("app-")) {
10557
10478
  for (const name of WIN_APP_NAMES) {
10558
- out.push(join12(base, dir, `${name}.exe`));
10479
+ out.push(join11(base, dir, `${name}.exe`));
10559
10480
  }
10560
10481
  }
10561
10482
  }
@@ -10569,7 +10490,7 @@ function mdfindCodexApp() {
10569
10490
  try {
10570
10491
  const out = run(`mdfind "kMDItemCFBundleIdentifier == '${CODEX_BUNDLE_ID}'"`);
10571
10492
  const first = out.split("\n").map((l) => l.trim()).find(Boolean);
10572
- return first && existsSync12(first) ? first : null;
10493
+ return first && existsSync11(first) ? first : null;
10573
10494
  } catch {
10574
10495
  return null;
10575
10496
  }
@@ -10577,14 +10498,14 @@ function mdfindCodexApp() {
10577
10498
  function findCodexApp() {
10578
10499
  if (process.platform === "darwin") {
10579
10500
  for (const path of darwinAppCandidates()) {
10580
- if (existsSync12(path)) return path;
10501
+ if (existsSync11(path)) return path;
10581
10502
  }
10582
10503
  return mdfindCodexApp();
10583
10504
  }
10584
10505
  if (process.platform === "win32") {
10585
10506
  for (const path of winCodexExeCandidates()) {
10586
10507
  try {
10587
- if (existsSync12(path) && statSync3(path).isFile()) return path;
10508
+ if (existsSync11(path) && statSync3(path).isFile()) return path;
10588
10509
  } catch {
10589
10510
  }
10590
10511
  }
@@ -10654,15 +10575,15 @@ async function waitForQuit(timeoutMs) {
10654
10575
  function openCodexAppAt(path) {
10655
10576
  if (process.platform === "darwin") {
10656
10577
  if (path.endsWith(".app")) {
10657
- execSync3(`open ${JSON.stringify(path)}`, { stdio: "inherit" });
10578
+ execSync2(`open ${JSON.stringify(path)}`, { stdio: "inherit" });
10658
10579
  } else {
10659
- execSync3(`open -b ${CODEX_BUNDLE_ID}`, { stdio: "inherit" });
10580
+ execSync2(`open -b ${CODEX_BUNDLE_ID}`, { stdio: "inherit" });
10660
10581
  }
10661
10582
  return;
10662
10583
  }
10663
10584
  if (process.platform === "win32") {
10664
10585
  if (path.startsWith("shell:AppsFolder\\")) {
10665
- spawn4("cmd.exe", ["/c", "start", "", path], { stdio: "ignore", detached: true }).unref();
10586
+ spawn2("cmd.exe", ["/c", "start", "", path], { stdio: "ignore", detached: true }).unref();
10666
10587
  } else {
10667
10588
  runPowerShell(`Start-Process -FilePath '${path.replace(/'/g, "''")}'`);
10668
10589
  }
@@ -10679,9 +10600,9 @@ function openCodexApp() {
10679
10600
  }
10680
10601
  function darwinQuit() {
10681
10602
  try {
10682
- execSync3(`osascript -e 'tell application "Codex" to quit'`, { stdio: "pipe" });
10603
+ execSync2(`osascript -e 'tell application "Codex" to quit'`, { stdio: "pipe" });
10683
10604
  } catch {
10684
- execSync3(`osascript -e 'tell application id "${CODEX_BUNDLE_ID}" to quit'`, { stdio: "pipe" });
10605
+ execSync2(`osascript -e 'tell application id "${CODEX_BUNDLE_ID}" to quit'`, { stdio: "pipe" });
10685
10606
  }
10686
10607
  }
10687
10608
  function winQuitGraceful() {
@@ -10729,10 +10650,10 @@ function codexAppInstallHint() {
10729
10650
  }
10730
10651
 
10731
10652
  // src/claude-desktop/app-launch.ts
10732
- import { execSync as execSync4, spawn as spawn5 } from "child_process";
10733
- import { existsSync as existsSync13, readdirSync as readdirSync2, statSync as statSync4 } from "fs";
10734
- import { homedir as homedir9 } from "os";
10735
- import { join as join13 } from "path";
10653
+ import { execSync as execSync3, spawn as spawn3 } from "child_process";
10654
+ import { existsSync as existsSync12, readdirSync as readdirSync2, statSync as statSync4 } from "fs";
10655
+ import { homedir as homedir8 } from "os";
10656
+ import { join as join12 } from "path";
10736
10657
  import * as p7 from "@clack/prompts";
10737
10658
  var CLAUDE_BUNDLE_ID = "com.anthropic.claudefordesktop";
10738
10659
  function claudeAppSupported() {
@@ -10741,7 +10662,7 @@ function claudeAppSupported() {
10741
10662
  }
10742
10663
  }
10743
10664
  function run2(cmd, encoding = "utf8") {
10744
- return execSync4(cmd, { encoding, stdio: ["pipe", "pipe", "pipe"] }).trim();
10665
+ return execSync3(cmd, { encoding, stdio: ["pipe", "pipe", "pipe"] }).trim();
10745
10666
  }
10746
10667
  function runPowerShell2(script) {
10747
10668
  return run2(`powershell.exe -NoProfile -Command ${JSON.stringify(script)}`);
@@ -10749,26 +10670,26 @@ function runPowerShell2(script) {
10749
10670
  function darwinAppCandidates2() {
10750
10671
  return [
10751
10672
  "/Applications/Claude.app",
10752
- join13(homedir9(), "Applications", "Claude.app")
10673
+ join12(homedir8(), "Applications", "Claude.app")
10753
10674
  ];
10754
10675
  }
10755
10676
  function winLocalAppData2() {
10756
- return process.env.LOCALAPPDATA ?? join13(homedir9(), "AppData", "Local");
10677
+ return process.env.LOCALAPPDATA ?? join12(homedir8(), "AppData", "Local");
10757
10678
  }
10758
10679
  function winClaudeExeCandidates() {
10759
10680
  const local = winLocalAppData2();
10760
10681
  const bases = [
10761
- join13(local, "Programs", "Claude"),
10762
- join13(local, "Claude")
10682
+ join12(local, "Programs", "Claude"),
10683
+ join12(local, "Claude")
10763
10684
  ];
10764
10685
  const out = [];
10765
10686
  for (const base of bases) {
10766
- out.push(join13(base, "Claude.exe"));
10687
+ out.push(join12(base, "Claude.exe"));
10767
10688
  try {
10768
- if (existsSync13(base)) {
10689
+ if (existsSync12(base)) {
10769
10690
  for (const name of readdirSync2(base)) {
10770
10691
  if (name.startsWith("app-")) {
10771
- out.push(join13(base, name, "Claude.exe"));
10692
+ out.push(join12(base, name, "Claude.exe"));
10772
10693
  }
10773
10694
  }
10774
10695
  }
@@ -10781,7 +10702,7 @@ function mdfindClaudeApp() {
10781
10702
  try {
10782
10703
  const out = run2(`mdfind "kMDItemCFBundleIdentifier == '${CLAUDE_BUNDLE_ID}'"`);
10783
10704
  const first = out.split("\n").map((l) => l.trim()).find(Boolean);
10784
- return first && existsSync13(first) ? first : null;
10705
+ return first && existsSync12(first) ? first : null;
10785
10706
  } catch {
10786
10707
  return null;
10787
10708
  }
@@ -10789,14 +10710,14 @@ function mdfindClaudeApp() {
10789
10710
  function findClaudeApp() {
10790
10711
  if (process.platform === "darwin") {
10791
10712
  for (const path of darwinAppCandidates2()) {
10792
- if (existsSync13(path)) return path;
10713
+ if (existsSync12(path)) return path;
10793
10714
  }
10794
10715
  return mdfindClaudeApp();
10795
10716
  }
10796
10717
  if (process.platform === "win32") {
10797
10718
  for (const path of winClaudeExeCandidates()) {
10798
10719
  try {
10799
- if (existsSync13(path) && statSync4(path).isFile()) return path;
10720
+ if (existsSync12(path) && statSync4(path).isFile()) return path;
10800
10721
  } catch {
10801
10722
  }
10802
10723
  }
@@ -10860,15 +10781,15 @@ async function waitForQuit2(timeoutMs) {
10860
10781
  function openClaudeAppAt(path) {
10861
10782
  if (process.platform === "darwin") {
10862
10783
  if (path.endsWith(".app")) {
10863
- execSync4(`open ${JSON.stringify(path)}`, { stdio: "inherit" });
10784
+ execSync3(`open ${JSON.stringify(path)}`, { stdio: "inherit" });
10864
10785
  } else {
10865
- execSync4(`open -b ${CLAUDE_BUNDLE_ID}`, { stdio: "inherit" });
10786
+ execSync3(`open -b ${CLAUDE_BUNDLE_ID}`, { stdio: "inherit" });
10866
10787
  }
10867
10788
  return;
10868
10789
  }
10869
10790
  if (process.platform === "win32") {
10870
10791
  if (path.startsWith("shell:AppsFolder\\")) {
10871
- spawn5("cmd.exe", ["/c", "start", "", path], { stdio: "ignore", detached: true }).unref();
10792
+ spawn3("cmd.exe", ["/c", "start", "", path], { stdio: "ignore", detached: true }).unref();
10872
10793
  } else {
10873
10794
  runPowerShell2(`Start-Process -FilePath '${path.replace(/'/g, "''")}'`);
10874
10795
  }
@@ -10885,9 +10806,9 @@ function openClaudeApp() {
10885
10806
  }
10886
10807
  function darwinQuit2() {
10887
10808
  try {
10888
- execSync4(`osascript -e 'tell application "Claude" to quit'`, { stdio: "pipe" });
10809
+ execSync3(`osascript -e 'tell application "Claude" to quit'`, { stdio: "pipe" });
10889
10810
  } catch {
10890
- execSync4(`osascript -e 'tell application id "${CLAUDE_BUNDLE_ID}" to quit'`, { stdio: "pipe" });
10811
+ execSync3(`osascript -e 'tell application id "${CLAUDE_BUNDLE_ID}" to quit'`, { stdio: "pipe" });
10891
10812
  }
10892
10813
  }
10893
10814
  function winQuitGraceful2() {
@@ -10991,6 +10912,7 @@ export {
10991
10912
  recordLaunchFolder,
10992
10913
  recordLaunchSelection,
10993
10914
  getSavedServerPassword,
10915
+ getEnvServerPassword,
10994
10916
  setSavedServerPassword,
10995
10917
  getServerExposedProviders,
10996
10918
  setServerExposedProviders,
@@ -11039,10 +10961,7 @@ export {
11039
10961
  freeStatusLabel,
11040
10962
  refreshModelsDevCacheAsync,
11041
10963
  shouldHideModel,
11042
- findOpencodeBinary,
11043
- fetchRawOpencodeProviders,
11044
10964
  zenRegistryStub,
11045
- localProviderToRegistry,
11046
10965
  isLikelyPlaceholderKey,
11047
10966
  resolveRefreshCredential,
11048
10967
  oauthAuthRef,
@@ -11057,6 +10976,7 @@ export {
11057
10976
  appendCodexBodyDump,
11058
10977
  getGeminiProxyDebugLogPath,
11059
10978
  getUiDebugLogPath,
10979
+ getServerDebugLogPath,
11060
10980
  makeTraceLogger,
11061
10981
  writeSecureLogLine,
11062
10982
  printTraceLog,
@@ -11072,6 +10992,7 @@ export {
11072
10992
  extractApiKey,
11073
10993
  sendJson,
11074
10994
  gatewayProviderLabel,
10995
+ openAiIdCollisions,
11075
10996
  createGatewayModelCatalog,
11076
10997
  buildDedupedModelRows,
11077
10998
  grabRoundTripSignature,
@@ -11106,9 +11027,8 @@ export {
11106
11027
  refreshProviderModels,
11107
11028
  refreshAllProviderModels,
11108
11029
  removeProviderFromRegistry,
11109
- addZenRegistryStub,
11110
- addGoRegistryStub,
11111
11030
  ensureOpencodeCloudProviders,
11031
+ addOpencodeCloudFromApiKey,
11112
11032
  toggleProviderEnabled,
11113
11033
  startServer,
11114
11034
  filterServerModelsByProviders,
@@ -11143,4 +11063,4 @@ export {
11143
11063
  supportsClaudeTransparentMode,
11144
11064
  buildHttpProxyRoutes
11145
11065
  };
11146
- //# sourceMappingURL=chunk-SV2Y6OCD.js.map
11066
+ //# sourceMappingURL=chunk-I3I5LKZI.js.map