@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.
- package/README.md +1 -1
- package/dist/{chunk-MVBA7ABV.js → chunk-EJONCU3B.js} +4 -4
- package/dist/chunk-EJONCU3B.js.map +1 -0
- package/dist/{chunk-SV2Y6OCD.js → chunk-I3I5LKZI.js} +238 -318
- package/dist/chunk-I3I5LKZI.js.map +1 -0
- package/dist/cli.js +341 -221
- package/dist/cli.js.map +1 -1
- package/dist/{provider-templates-6XYKAZB5.js → provider-templates-BPGB5V2L.js} +2 -2
- package/dist/ui/public/app.js +94 -13
- package/dist/ui/public/style.css +31 -0
- package/dist/{ui-command-DJZIIIWC.js → ui-command-GNCQS7F7.js} +33 -15
- package/dist/{ui-command-DJZIIIWC.js.map → ui-command-GNCQS7F7.js.map} +1 -1
- package/package.json +1 -1
- package/dist/chunk-MVBA7ABV.js.map +0 -1
- package/dist/chunk-SV2Y6OCD.js.map +0 -1
- /package/dist/{provider-templates-6XYKAZB5.js.map → provider-templates-BPGB5V2L.js.map} +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
getTemplateById,
|
|
4
4
|
init_provider_templates
|
|
5
|
-
} from "./chunk-
|
|
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.
|
|
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
|
|
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,
|
|
1878
|
-
const path = (
|
|
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
|
|
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
|
|
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: "
|
|
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
|
|
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
|
-
|
|
8683
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
9641
|
-
const storeName = isMac ? "macOS Keychain" :
|
|
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
|
-
`
|
|
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
|
-
|
|
10445
|
-
|
|
10446
|
-
|
|
10447
|
-
|
|
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 =
|
|
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
|
|
10496
|
-
relay-ai providers auth openai
|
|
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
|
|
10511
|
-
import { existsSync as
|
|
10512
|
-
import { homedir as
|
|
10513
|
-
import { join as
|
|
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
|
|
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
|
-
|
|
10453
|
+
join11(homedir7(), "Applications", `${name}.app`)
|
|
10533
10454
|
]);
|
|
10534
10455
|
}
|
|
10535
10456
|
function winLocalAppData() {
|
|
10536
|
-
return process.env.LOCALAPPDATA ??
|
|
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
|
-
|
|
10542
|
-
|
|
10543
|
-
|
|
10544
|
-
|
|
10545
|
-
|
|
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(
|
|
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(
|
|
10472
|
+
out.push(join11(base, `${name}.exe`));
|
|
10552
10473
|
}
|
|
10553
10474
|
try {
|
|
10554
|
-
if (
|
|
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(
|
|
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 &&
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
10578
|
+
execSync2(`open ${JSON.stringify(path)}`, { stdio: "inherit" });
|
|
10658
10579
|
} else {
|
|
10659
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10603
|
+
execSync2(`osascript -e 'tell application "Codex" to quit'`, { stdio: "pipe" });
|
|
10683
10604
|
} catch {
|
|
10684
|
-
|
|
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
|
|
10733
|
-
import { existsSync as
|
|
10734
|
-
import { homedir as
|
|
10735
|
-
import { join as
|
|
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
|
|
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
|
-
|
|
10673
|
+
join12(homedir8(), "Applications", "Claude.app")
|
|
10753
10674
|
];
|
|
10754
10675
|
}
|
|
10755
10676
|
function winLocalAppData2() {
|
|
10756
|
-
return process.env.LOCALAPPDATA ??
|
|
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
|
-
|
|
10762
|
-
|
|
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(
|
|
10687
|
+
out.push(join12(base, "Claude.exe"));
|
|
10767
10688
|
try {
|
|
10768
|
-
if (
|
|
10689
|
+
if (existsSync12(base)) {
|
|
10769
10690
|
for (const name of readdirSync2(base)) {
|
|
10770
10691
|
if (name.startsWith("app-")) {
|
|
10771
|
-
out.push(
|
|
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 &&
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
10784
|
+
execSync3(`open ${JSON.stringify(path)}`, { stdio: "inherit" });
|
|
10864
10785
|
} else {
|
|
10865
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10809
|
+
execSync3(`osascript -e 'tell application "Claude" to quit'`, { stdio: "pipe" });
|
|
10889
10810
|
} catch {
|
|
10890
|
-
|
|
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-
|
|
11066
|
+
//# sourceMappingURL=chunk-I3I5LKZI.js.map
|