@jacobbd/relay-ai 0.6.1 → 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/dist/{chunk-P4S42QJK.js → chunk-I3I5LKZI.js} +120 -19
- package/dist/{chunk-P4S42QJK.js.map → chunk-I3I5LKZI.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/ui/public/app.js +50 -10
- package/dist/ui/public/style.css +15 -0
- package/dist/{ui-command-EQJIZRZZ.js → ui-command-GNCQS7F7.js} +8 -4
- package/dist/{ui-command-EQJIZRZZ.js.map → ui-command-GNCQS7F7.js.map} +1 -1
- package/package.json +1 -1
|
@@ -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
|
},
|
|
@@ -4271,6 +4271,16 @@ function gatewayProviderId(model) {
|
|
|
4271
4271
|
function gatewayAliasId(model) {
|
|
4272
4272
|
return aliasModelId(model.id, gatewayProviderId(model));
|
|
4273
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
|
+
}
|
|
4274
4284
|
function exposedGatewayAliasId(model, opts) {
|
|
4275
4285
|
const alias = gatewayAliasId(model);
|
|
4276
4286
|
return opts?.maskGatewayIds ? maskGatewayModelId(alias) : alias;
|
|
@@ -4290,8 +4300,11 @@ function formatGatewayAnthropicModels(models, opts) {
|
|
|
4290
4300
|
}
|
|
4291
4301
|
function createGatewayModelCatalog(models, opts) {
|
|
4292
4302
|
const byId = /* @__PURE__ */ new Map();
|
|
4303
|
+
const collisions = openAiIdCollisions(models);
|
|
4293
4304
|
for (const model of models) {
|
|
4294
|
-
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);
|
|
4295
4308
|
const alias = exposedGatewayAliasId(model, opts);
|
|
4296
4309
|
if (alias !== model.id) byId.set(alias, model);
|
|
4297
4310
|
if (opts?.maskGatewayIds) {
|
|
@@ -4308,14 +4321,14 @@ function upstreamModelId(model) {
|
|
|
4308
4321
|
const id = model.upstreamModelId ?? model.id;
|
|
4309
4322
|
return id.replace(/\[1m\]$/i, "");
|
|
4310
4323
|
}
|
|
4311
|
-
function buildDedupedModelRows(models, opts) {
|
|
4324
|
+
function buildDedupedModelRows(models, opts, collisions = openAiIdCollisions(models)) {
|
|
4312
4325
|
const seen = /* @__PURE__ */ new Set();
|
|
4313
4326
|
const rows = [];
|
|
4314
4327
|
for (const model of [...models].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
4315
4328
|
const row = {
|
|
4316
4329
|
name: model.name,
|
|
4317
4330
|
anthropicId: exposedGatewayAliasId(model, opts),
|
|
4318
|
-
openaiId: model
|
|
4331
|
+
openaiId: openAiExposedId(model, collisions)
|
|
4319
4332
|
};
|
|
4320
4333
|
const key = `${row.name}\0${row.anthropicId}\0${row.openaiId}`;
|
|
4321
4334
|
if (seen.has(key)) continue;
|
|
@@ -4328,10 +4341,11 @@ function supportsDirectOpenAIChatCompletions(model) {
|
|
|
4328
4341
|
return model.modelFormat === "openai" && (!!model.completionsUrl || model.sourceBackend === "zen" || model.sourceBackend === "go");
|
|
4329
4342
|
}
|
|
4330
4343
|
function formatOpenAIModels(models) {
|
|
4344
|
+
const collisions = openAiIdCollisions(models);
|
|
4331
4345
|
return {
|
|
4332
4346
|
object: "list",
|
|
4333
4347
|
data: models.map((model) => ({
|
|
4334
|
-
id: model
|
|
4348
|
+
id: openAiExposedId(model, collisions),
|
|
4335
4349
|
object: "model",
|
|
4336
4350
|
created: CREATED_AT_UNIX,
|
|
4337
4351
|
owned_by: model.sourceBackend
|
|
@@ -8721,8 +8735,9 @@ function translateOpenAiRequest(body) {
|
|
|
8721
8735
|
break;
|
|
8722
8736
|
case "assistant": {
|
|
8723
8737
|
const parts = [];
|
|
8724
|
-
|
|
8725
|
-
|
|
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 });
|
|
8726
8741
|
}
|
|
8727
8742
|
for (const tc of msg.tool_calls ?? []) {
|
|
8728
8743
|
parts.push({
|
|
@@ -8783,14 +8798,31 @@ function translateOpenAiRequest(body) {
|
|
|
8783
8798
|
maxOutputTokens: body.max_completion_tokens ?? body.max_tokens
|
|
8784
8799
|
};
|
|
8785
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
|
+
}
|
|
8786
8815
|
async function generateOpenAiResponse(model, params, responseModelId) {
|
|
8787
8816
|
const result = await generateText2({ model, ...params });
|
|
8788
8817
|
const message = { role: "assistant", content: result.text || null };
|
|
8818
|
+
if (result.reasoningText || result.reasoning) {
|
|
8819
|
+
message.reasoning_content = result.reasoningText ?? result.reasoning;
|
|
8820
|
+
}
|
|
8789
8821
|
if (result.toolCalls?.length) {
|
|
8790
8822
|
message.tool_calls = result.toolCalls.map((tc) => ({
|
|
8791
8823
|
id: tc.toolCallId,
|
|
8792
8824
|
type: "function",
|
|
8793
|
-
function: { name: tc.toolName, arguments: JSON.stringify(tc.args) }
|
|
8825
|
+
function: { name: tc.toolName, arguments: JSON.stringify(tc.input ?? tc.args ?? {}) }
|
|
8794
8826
|
}));
|
|
8795
8827
|
}
|
|
8796
8828
|
return {
|
|
@@ -8798,7 +8830,7 @@ async function generateOpenAiResponse(model, params, responseModelId) {
|
|
|
8798
8830
|
object: "chat.completion",
|
|
8799
8831
|
created: Math.floor(Date.now() / 1e3),
|
|
8800
8832
|
model: responseModelId,
|
|
8801
|
-
choices: [{ index: 0, message, finish_reason: result.finishReason
|
|
8833
|
+
choices: [{ index: 0, message, finish_reason: toOpenAiFinishReason(result.finishReason) }],
|
|
8802
8834
|
usage: {
|
|
8803
8835
|
prompt_tokens: result.usage?.promptTokens ?? 0,
|
|
8804
8836
|
completion_tokens: result.usage?.completionTokens ?? 0,
|
|
@@ -8806,7 +8838,7 @@ async function generateOpenAiResponse(model, params, responseModelId) {
|
|
|
8806
8838
|
}
|
|
8807
8839
|
};
|
|
8808
8840
|
}
|
|
8809
|
-
async function streamOpenAiResponse(model, params, responseModelId, onChunk) {
|
|
8841
|
+
async function streamOpenAiResponse(model, params, responseModelId, onChunk, log7) {
|
|
8810
8842
|
const { fullStream } = streamText2({ model, ...params });
|
|
8811
8843
|
const baseData = {
|
|
8812
8844
|
id: `chatcmpl-${Date.now()}`,
|
|
@@ -8817,23 +8849,68 @@ async function streamOpenAiResponse(model, params, responseModelId, onChunk) {
|
|
|
8817
8849
|
const send = (delta, finish_reason = null) => onChunk(`data: ${JSON.stringify({ ...baseData, choices: [{ index: 0, delta, finish_reason }] })}
|
|
8818
8850
|
|
|
8819
8851
|
`);
|
|
8852
|
+
const streamedToolIndex = /* @__PURE__ */ new Map();
|
|
8853
|
+
let nextToolIndex = 0;
|
|
8854
|
+
const seenPartTypes = /* @__PURE__ */ new Set();
|
|
8855
|
+
let toolCallChunksEmitted = 0;
|
|
8820
8856
|
for await (const part of fullStream) {
|
|
8821
8857
|
const p8 = part;
|
|
8858
|
+
seenPartTypes.add(p8.type);
|
|
8822
8859
|
switch (p8.type) {
|
|
8823
8860
|
case "text-delta":
|
|
8824
8861
|
send({ role: "assistant", content: p8.textDelta ?? p8.text ?? "" });
|
|
8825
8862
|
break;
|
|
8863
|
+
case "reasoning-delta":
|
|
8864
|
+
send({ role: "assistant", reasoning_content: p8.text ?? p8.delta ?? "" });
|
|
8865
|
+
break;
|
|
8826
8866
|
case "tool-input-start":
|
|
8827
|
-
case "tool-call-streaming-start":
|
|
8828
|
-
|
|
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++;
|
|
8829
8873
|
break;
|
|
8874
|
+
}
|
|
8830
8875
|
case "tool-input-delta":
|
|
8831
|
-
case "tool-call-delta":
|
|
8832
|
-
|
|
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 ?? "" } }] });
|
|
8833
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++;
|
|
8891
|
+
break;
|
|
8892
|
+
}
|
|
8834
8893
|
case "finish":
|
|
8835
|
-
|
|
8894
|
+
log7?.(() => `openai stream parts=[${[...seenPartTypes].join(",")}] toolCallChunks=${toolCallChunksEmitted} finishReason=${p8.finishReason}`);
|
|
8895
|
+
send({}, toOpenAiFinishReason(p8.finishReason));
|
|
8836
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
|
+
}
|
|
8837
8914
|
}
|
|
8838
8915
|
}
|
|
8839
8916
|
onChunk("data: [DONE]\n\n");
|
|
@@ -9023,6 +9100,25 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
|
|
|
9023
9100
|
sendJson(res, 400, { error: { message: "Invalid JSON body" } });
|
|
9024
9101
|
return;
|
|
9025
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
|
+
});
|
|
9026
9122
|
const model = lookupModel(res, options.catalog, body.model);
|
|
9027
9123
|
if (!model) return;
|
|
9028
9124
|
if (supportsDirectOpenAIChatCompletions(model)) {
|
|
@@ -9032,7 +9128,9 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
|
|
|
9032
9128
|
}
|
|
9033
9129
|
const completionsUrl = model.completionsUrl ? model.completionsUrl : `${backendFor(options, model).baseUrl}/v1/chat/completions`;
|
|
9034
9130
|
const apiKey2 = model.apiKey ?? options.apiKey;
|
|
9035
|
-
|
|
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));
|
|
9036
9134
|
return;
|
|
9037
9135
|
}
|
|
9038
9136
|
const npm = model.npm || (model.modelFormat === "anthropic" ? "@ai-sdk/anthropic" : void 0);
|
|
@@ -9054,7 +9152,7 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
|
|
|
9054
9152
|
"Cache-Control": "no-cache",
|
|
9055
9153
|
"Connection": "keep-alive"
|
|
9056
9154
|
});
|
|
9057
|
-
await streamOpenAiResponse(languageModel, params, responseModelId, (chunk) => res.write(chunk));
|
|
9155
|
+
await streamOpenAiResponse(languageModel, params, responseModelId, (chunk) => res.write(chunk), plog);
|
|
9058
9156
|
res.end();
|
|
9059
9157
|
} else {
|
|
9060
9158
|
const response = await generateOpenAiResponse(languageModel, params, responseModelId);
|
|
@@ -9395,10 +9493,11 @@ function formatModelCatalogLines(models, gateway) {
|
|
|
9395
9493
|
}
|
|
9396
9494
|
list.push(model);
|
|
9397
9495
|
}
|
|
9496
|
+
const collisions = openAiIdCollisions(models);
|
|
9398
9497
|
const lines = ["Model catalog:", ""];
|
|
9399
9498
|
const sortedGroups = [...groups.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
9400
9499
|
for (const [label, groupModels] of sortedGroups) {
|
|
9401
|
-
const rows = buildDedupedModelRows(groupModels, gateway);
|
|
9500
|
+
const rows = buildDedupedModelRows(groupModels, gateway, collisions);
|
|
9402
9501
|
const hiddenDuplicates = groupModels.length - rows.length;
|
|
9403
9502
|
const duplicateNote = hiddenDuplicates > 0 ? `, ${hiddenDuplicates} duplicate${hiddenDuplicates !== 1 ? "s" : ""} hidden` : "";
|
|
9404
9503
|
const nameWidth = cappedWidth(rows.map((row) => row.name), "Model", 28);
|
|
@@ -10877,6 +10976,7 @@ export {
|
|
|
10877
10976
|
appendCodexBodyDump,
|
|
10878
10977
|
getGeminiProxyDebugLogPath,
|
|
10879
10978
|
getUiDebugLogPath,
|
|
10979
|
+
getServerDebugLogPath,
|
|
10880
10980
|
makeTraceLogger,
|
|
10881
10981
|
writeSecureLogLine,
|
|
10882
10982
|
printTraceLog,
|
|
@@ -10892,6 +10992,7 @@ export {
|
|
|
10892
10992
|
extractApiKey,
|
|
10893
10993
|
sendJson,
|
|
10894
10994
|
gatewayProviderLabel,
|
|
10995
|
+
openAiIdCollisions,
|
|
10895
10996
|
createGatewayModelCatalog,
|
|
10896
10997
|
buildDedupedModelRows,
|
|
10897
10998
|
grabRoundTripSignature,
|
|
@@ -10962,4 +11063,4 @@ export {
|
|
|
10962
11063
|
supportsClaudeTransparentMode,
|
|
10963
11064
|
buildHttpProxyRoutes
|
|
10964
11065
|
};
|
|
10965
|
-
//# sourceMappingURL=chunk-
|
|
11066
|
+
//# sourceMappingURL=chunk-I3I5LKZI.js.map
|