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