@exulu/backend 1.56.0 → 1.57.0
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/index.cjs +182 -111
- package/dist/index.js +184 -112
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1546,7 +1546,7 @@ var ExuluTool = class {
|
|
|
1546
1546
|
});
|
|
1547
1547
|
}
|
|
1548
1548
|
execute = async ({
|
|
1549
|
-
agent:
|
|
1549
|
+
agent: agentId,
|
|
1550
1550
|
config,
|
|
1551
1551
|
user,
|
|
1552
1552
|
inputs,
|
|
@@ -1554,14 +1554,14 @@ var ExuluTool = class {
|
|
|
1554
1554
|
items
|
|
1555
1555
|
}) => {
|
|
1556
1556
|
console.log("[EXULU] Calling tool execute directly", {
|
|
1557
|
-
agentId
|
|
1557
|
+
agentId,
|
|
1558
1558
|
config,
|
|
1559
1559
|
user,
|
|
1560
1560
|
inputs,
|
|
1561
1561
|
project,
|
|
1562
1562
|
items
|
|
1563
1563
|
});
|
|
1564
|
-
const agent = await exuluApp.get().agent(
|
|
1564
|
+
const agent = await exuluApp.get().agent(agentId);
|
|
1565
1565
|
if (!agent) {
|
|
1566
1566
|
throw new Error("Agent not found.");
|
|
1567
1567
|
}
|
|
@@ -11838,27 +11838,141 @@ var providerRateLimiter = async (key, windowSeconds, limit, points) => {
|
|
|
11838
11838
|
// src/exulu/openai-gateway.ts
|
|
11839
11839
|
var import_express2 = require("express");
|
|
11840
11840
|
var import_ai9 = require("ai");
|
|
11841
|
+
|
|
11842
|
+
// src/exulu/openai-transformer.ts
|
|
11843
|
+
function transformStreamChunk(chunk, ctx) {
|
|
11844
|
+
const base = {
|
|
11845
|
+
id: ctx.completionId,
|
|
11846
|
+
object: "chat.completion.chunk",
|
|
11847
|
+
created: ctx.created,
|
|
11848
|
+
model: ctx.modelId
|
|
11849
|
+
};
|
|
11850
|
+
switch (chunk.type) {
|
|
11851
|
+
case "text-delta":
|
|
11852
|
+
return {
|
|
11853
|
+
...base,
|
|
11854
|
+
choices: [{ index: 0, delta: { content: chunk.text }, finish_reason: null }]
|
|
11855
|
+
};
|
|
11856
|
+
case "tool-input-start":
|
|
11857
|
+
return {
|
|
11858
|
+
...base,
|
|
11859
|
+
choices: [
|
|
11860
|
+
{
|
|
11861
|
+
index: 0,
|
|
11862
|
+
delta: {
|
|
11863
|
+
tool_calls: [
|
|
11864
|
+
{
|
|
11865
|
+
index: 0,
|
|
11866
|
+
id: chunk.id,
|
|
11867
|
+
type: "function",
|
|
11868
|
+
function: { name: chunk.toolName, arguments: "" }
|
|
11869
|
+
}
|
|
11870
|
+
]
|
|
11871
|
+
},
|
|
11872
|
+
finish_reason: null
|
|
11873
|
+
}
|
|
11874
|
+
]
|
|
11875
|
+
};
|
|
11876
|
+
case "tool-input-delta":
|
|
11877
|
+
return {
|
|
11878
|
+
...base,
|
|
11879
|
+
choices: [
|
|
11880
|
+
{
|
|
11881
|
+
index: 0,
|
|
11882
|
+
delta: { tool_calls: [{ index: 0, function: { arguments: chunk.delta } }] },
|
|
11883
|
+
finish_reason: null
|
|
11884
|
+
}
|
|
11885
|
+
]
|
|
11886
|
+
};
|
|
11887
|
+
case "finish": {
|
|
11888
|
+
const inputTokens = chunk.usage?.inputTokens ?? 0;
|
|
11889
|
+
const outputTokens = chunk.usage?.outputTokens ?? 0;
|
|
11890
|
+
const finishReason = chunk.finishReason === "tool-calls" ? "tool_calls" : "stop";
|
|
11891
|
+
return {
|
|
11892
|
+
...base,
|
|
11893
|
+
choices: [{ index: 0, delta: {}, finish_reason: finishReason }],
|
|
11894
|
+
usage: {
|
|
11895
|
+
prompt_tokens: inputTokens,
|
|
11896
|
+
completion_tokens: outputTokens,
|
|
11897
|
+
total_tokens: inputTokens + outputTokens
|
|
11898
|
+
}
|
|
11899
|
+
};
|
|
11900
|
+
}
|
|
11901
|
+
default:
|
|
11902
|
+
return null;
|
|
11903
|
+
}
|
|
11904
|
+
}
|
|
11905
|
+
function transformCompletion(text, inputTokens, outputTokens, ctx) {
|
|
11906
|
+
return {
|
|
11907
|
+
id: ctx.completionId,
|
|
11908
|
+
object: "chat.completion",
|
|
11909
|
+
created: ctx.created,
|
|
11910
|
+
model: ctx.modelId,
|
|
11911
|
+
choices: [
|
|
11912
|
+
{
|
|
11913
|
+
index: 0,
|
|
11914
|
+
message: { role: "assistant", content: text },
|
|
11915
|
+
finish_reason: "stop"
|
|
11916
|
+
}
|
|
11917
|
+
],
|
|
11918
|
+
usage: {
|
|
11919
|
+
prompt_tokens: inputTokens,
|
|
11920
|
+
completion_tokens: outputTokens,
|
|
11921
|
+
total_tokens: inputTokens + outputTokens
|
|
11922
|
+
}
|
|
11923
|
+
};
|
|
11924
|
+
}
|
|
11925
|
+
|
|
11926
|
+
// src/exulu/openai-gateway.ts
|
|
11841
11927
|
var import_node_crypto4 = require("crypto");
|
|
11842
11928
|
var import_crypto_js6 = __toESM(require("crypto-js"), 1);
|
|
11843
11929
|
var import_express3 = __toESM(require("express"), 1);
|
|
11844
|
-
function
|
|
11930
|
+
function convertOpenAIToolsToAiSdkTools(tools) {
|
|
11931
|
+
return Object.fromEntries(
|
|
11932
|
+
tools.map((t) => {
|
|
11933
|
+
const params = t.function.parameters ?? {};
|
|
11934
|
+
return [
|
|
11935
|
+
t.function.name,
|
|
11936
|
+
{
|
|
11937
|
+
description: t.function.description ?? "",
|
|
11938
|
+
inputSchema: (0, import_ai9.jsonSchema)({
|
|
11939
|
+
type: "object",
|
|
11940
|
+
properties: params.properties ?? {},
|
|
11941
|
+
...params.required ? { required: params.required } : {}
|
|
11942
|
+
})
|
|
11943
|
+
}
|
|
11944
|
+
];
|
|
11945
|
+
})
|
|
11946
|
+
);
|
|
11947
|
+
}
|
|
11948
|
+
function convertOpenAIMessagesToModelMessages(messages) {
|
|
11845
11949
|
const systemParts = [];
|
|
11846
11950
|
const coreMessages = [];
|
|
11951
|
+
const toolCallIdToName = /* @__PURE__ */ new Map();
|
|
11847
11952
|
for (const msg of messages) {
|
|
11848
11953
|
if (msg.role === "system") {
|
|
11849
11954
|
systemParts.push(typeof msg.content === "string" ? msg.content : "");
|
|
11850
11955
|
continue;
|
|
11851
11956
|
}
|
|
11852
11957
|
if (msg.role === "user") {
|
|
11958
|
+
const last = coreMessages[coreMessages.length - 1];
|
|
11853
11959
|
if (typeof msg.content === "string") {
|
|
11854
|
-
|
|
11960
|
+
if (last?.role === "user" && typeof last.content === "string") {
|
|
11961
|
+
last.content += "\n\n" + msg.content;
|
|
11962
|
+
} else {
|
|
11963
|
+
coreMessages.push({ role: "user", content: msg.content });
|
|
11964
|
+
}
|
|
11855
11965
|
} else if (Array.isArray(msg.content)) {
|
|
11856
11966
|
const parts = msg.content.flatMap((part) => {
|
|
11857
11967
|
if (part.type === "text") return [{ type: "text", text: part.text }];
|
|
11858
11968
|
if (part.type === "image_url") return [{ type: "image", image: part.image_url.url }];
|
|
11859
11969
|
return [];
|
|
11860
11970
|
});
|
|
11861
|
-
|
|
11971
|
+
if (last?.role === "user" && Array.isArray(last.content)) {
|
|
11972
|
+
last.content.push(...parts);
|
|
11973
|
+
} else {
|
|
11974
|
+
coreMessages.push({ role: "user", content: parts });
|
|
11975
|
+
}
|
|
11862
11976
|
}
|
|
11863
11977
|
continue;
|
|
11864
11978
|
}
|
|
@@ -11869,11 +11983,20 @@ function convertOpenAIMessagesToCoreMessages(messages) {
|
|
|
11869
11983
|
parts.push({ type: "text", text: msg.content });
|
|
11870
11984
|
}
|
|
11871
11985
|
for (const tc of msg.tool_calls) {
|
|
11986
|
+
toolCallIdToName.set(tc.id, tc.function.name);
|
|
11987
|
+
const rawArgs = tc.function.arguments;
|
|
11988
|
+
const input = rawArgs == null ? {} : typeof rawArgs === "object" ? rawArgs : (() => {
|
|
11989
|
+
try {
|
|
11990
|
+
return JSON.parse(rawArgs);
|
|
11991
|
+
} catch {
|
|
11992
|
+
return {};
|
|
11993
|
+
}
|
|
11994
|
+
})();
|
|
11872
11995
|
parts.push({
|
|
11873
11996
|
type: "tool-call",
|
|
11874
11997
|
toolCallId: tc.id,
|
|
11875
11998
|
toolName: tc.function.name,
|
|
11876
|
-
|
|
11999
|
+
input
|
|
11877
12000
|
});
|
|
11878
12001
|
}
|
|
11879
12002
|
coreMessages.push({ role: "assistant", content: parts });
|
|
@@ -11886,13 +12009,17 @@ function convertOpenAIMessagesToCoreMessages(messages) {
|
|
|
11886
12009
|
continue;
|
|
11887
12010
|
}
|
|
11888
12011
|
if (msg.role === "tool") {
|
|
12012
|
+
const toolCallId = msg.tool_call_id ?? "";
|
|
12013
|
+
const toolName = toolCallIdToName.get(toolCallId) ?? "unknown";
|
|
12014
|
+
const resultText = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
11889
12015
|
coreMessages.push({
|
|
11890
12016
|
role: "tool",
|
|
11891
12017
|
content: [
|
|
11892
12018
|
{
|
|
11893
12019
|
type: "tool-result",
|
|
11894
|
-
toolCallId
|
|
11895
|
-
|
|
12020
|
+
toolCallId,
|
|
12021
|
+
toolName,
|
|
12022
|
+
output: { type: "text", value: resultText }
|
|
11896
12023
|
}
|
|
11897
12024
|
]
|
|
11898
12025
|
});
|
|
@@ -12009,8 +12136,32 @@ var registerOpenAIGatewayRoutes = async (app, providers, tools, contexts, config
|
|
|
12009
12136
|
}
|
|
12010
12137
|
);
|
|
12011
12138
|
app.post(
|
|
12012
|
-
"/gateway/open-ai/v1/chat/completions",
|
|
12139
|
+
["/gateway/open-ai/v1/chat/completions", "/gateway/open-ai/v1/completions"],
|
|
12013
12140
|
import_express3.default.json({ limit: REQUEST_SIZE_LIMIT }),
|
|
12141
|
+
(req, _res, next) => {
|
|
12142
|
+
console.log("[OPENAI GATEWAY] incoming request:", {
|
|
12143
|
+
url: req.originalUrl,
|
|
12144
|
+
method: req.method,
|
|
12145
|
+
headers: {
|
|
12146
|
+
authorization: req.headers["authorization"] ? "[present]" : "[missing]",
|
|
12147
|
+
"x-api-key": req.headers["x-api-key"] ? "[present]" : "[missing]",
|
|
12148
|
+
"exulu-api-key": req.headers["exulu-api-key"] ? "[present]" : "[missing]",
|
|
12149
|
+
"content-type": req.headers["content-type"]
|
|
12150
|
+
},
|
|
12151
|
+
body: {
|
|
12152
|
+
model: req.body?.model,
|
|
12153
|
+
stream: req.body?.stream,
|
|
12154
|
+
messagesCount: req.body?.messages?.length,
|
|
12155
|
+
hasPrompt: typeof req.body?.prompt === "string",
|
|
12156
|
+
tools: req.body?.tools
|
|
12157
|
+
}
|
|
12158
|
+
});
|
|
12159
|
+
if (typeof req.body.prompt === "string") {
|
|
12160
|
+
req.body.messages = [{ role: "user", content: req.body.prompt }];
|
|
12161
|
+
delete req.body.prompt;
|
|
12162
|
+
}
|
|
12163
|
+
next();
|
|
12164
|
+
},
|
|
12014
12165
|
async (req, res) => {
|
|
12015
12166
|
try {
|
|
12016
12167
|
const { db: db2 } = await postgresClient();
|
|
@@ -12116,8 +12267,10 @@ var registerOpenAIGatewayRoutes = async (app, providers, tools, contexts, config
|
|
|
12116
12267
|
languageModel,
|
|
12117
12268
|
agent
|
|
12118
12269
|
);
|
|
12270
|
+
const clientTools = Array.isArray(req.body.tools) ? req.body.tools : [];
|
|
12271
|
+
const activeTools = clientTools.length > 0 ? convertOpenAIToolsToAiSdkTools(clientTools) : convertedTools;
|
|
12119
12272
|
const openaiMessages = req.body.messages ?? [];
|
|
12120
|
-
const { systemPrompt: requestSystemPrompt, coreMessages } =
|
|
12273
|
+
const { systemPrompt: requestSystemPrompt, coreMessages } = convertOpenAIMessagesToModelMessages(openaiMessages);
|
|
12121
12274
|
const agentInstructions = agent.instructions ?? "";
|
|
12122
12275
|
const systemParts = [
|
|
12123
12276
|
agentInstructions ? `You are an agent named: ${agent.name}
|
|
@@ -12129,7 +12282,8 @@ ${project.description}` : ""}` : "",
|
|
|
12129
12282
|
const systemPrompt = systemParts.join("\n\n");
|
|
12130
12283
|
const completionId = `chatcmpl-${(0, import_node_crypto4.randomUUID)()}`;
|
|
12131
12284
|
const created = Math.floor(Date.now() / 1e3);
|
|
12132
|
-
const hasTools = Object.keys(
|
|
12285
|
+
const hasTools = Object.keys(activeTools).length > 0;
|
|
12286
|
+
const ctx = { completionId, created, modelId };
|
|
12133
12287
|
if (req.body.stream === true) {
|
|
12134
12288
|
res.setHeader("Content-Type", "text/event-stream");
|
|
12135
12289
|
res.setHeader("Cache-Control", "no-cache");
|
|
@@ -12138,9 +12292,9 @@ ${project.description}` : ""}` : "",
|
|
|
12138
12292
|
model: languageModel,
|
|
12139
12293
|
system: systemPrompt || void 0,
|
|
12140
12294
|
messages: coreMessages,
|
|
12141
|
-
tools: hasTools ?
|
|
12295
|
+
tools: hasTools ? activeTools : void 0,
|
|
12142
12296
|
maxRetries: 2,
|
|
12143
|
-
stopWhen: [(0, import_ai9.stepCountIs)(5)],
|
|
12297
|
+
stopWhen: clientTools.length > 0 ? void 0 : [(0, import_ai9.stepCountIs)(5)],
|
|
12144
12298
|
onError: (error) => {
|
|
12145
12299
|
console.error("[OPENAI GATEWAY] stream error:", error);
|
|
12146
12300
|
}
|
|
@@ -12159,83 +12313,17 @@ ${project.description}` : ""}` : "",
|
|
|
12159
12313
|
let inputTokens = 0;
|
|
12160
12314
|
let outputTokens = 0;
|
|
12161
12315
|
for await (const chunk of result.fullStream) {
|
|
12162
|
-
|
|
12163
|
-
|
|
12164
|
-
|
|
12165
|
-
|
|
12166
|
-
|
|
12167
|
-
|
|
12168
|
-
|
|
12169
|
-
|
|
12170
|
-
|
|
12171
|
-
|
|
12172
|
-
`
|
|
12173
|
-
);
|
|
12174
|
-
} else if (chunk.type === "tool-input-start") {
|
|
12175
|
-
res.write(
|
|
12176
|
-
`data: ${JSON.stringify({
|
|
12177
|
-
id: completionId,
|
|
12178
|
-
object: "chat.completion.chunk",
|
|
12179
|
-
created,
|
|
12180
|
-
model: modelId,
|
|
12181
|
-
choices: [
|
|
12182
|
-
{
|
|
12183
|
-
index: 0,
|
|
12184
|
-
delta: {
|
|
12185
|
-
tool_calls: [
|
|
12186
|
-
{
|
|
12187
|
-
index: 0,
|
|
12188
|
-
id: chunk.id,
|
|
12189
|
-
type: "function",
|
|
12190
|
-
function: { name: chunk.toolName, arguments: "" }
|
|
12191
|
-
}
|
|
12192
|
-
]
|
|
12193
|
-
},
|
|
12194
|
-
finish_reason: null
|
|
12195
|
-
}
|
|
12196
|
-
]
|
|
12197
|
-
})}
|
|
12198
|
-
|
|
12199
|
-
`
|
|
12200
|
-
);
|
|
12201
|
-
} else if (chunk.type === "tool-input-delta") {
|
|
12202
|
-
res.write(
|
|
12203
|
-
`data: ${JSON.stringify({
|
|
12204
|
-
id: completionId,
|
|
12205
|
-
object: "chat.completion.chunk",
|
|
12206
|
-
created,
|
|
12207
|
-
model: modelId,
|
|
12208
|
-
choices: [
|
|
12209
|
-
{
|
|
12210
|
-
index: 0,
|
|
12211
|
-
delta: { tool_calls: [{ index: 0, function: { arguments: chunk.delta } }] },
|
|
12212
|
-
finish_reason: null
|
|
12213
|
-
}
|
|
12214
|
-
]
|
|
12215
|
-
})}
|
|
12216
|
-
|
|
12217
|
-
`
|
|
12218
|
-
);
|
|
12219
|
-
} else if (chunk.type === "finish") {
|
|
12220
|
-
inputTokens = chunk.usage?.inputTokens ?? 0;
|
|
12221
|
-
outputTokens = chunk.usage?.outputTokens ?? 0;
|
|
12222
|
-
const finishReason = chunk.finishReason === "tool-calls" ? "tool_calls" : "stop";
|
|
12223
|
-
res.write(
|
|
12224
|
-
`data: ${JSON.stringify({
|
|
12225
|
-
id: completionId,
|
|
12226
|
-
object: "chat.completion.chunk",
|
|
12227
|
-
created,
|
|
12228
|
-
model: modelId,
|
|
12229
|
-
choices: [{ index: 0, delta: {}, finish_reason: finishReason }],
|
|
12230
|
-
usage: {
|
|
12231
|
-
prompt_tokens: inputTokens,
|
|
12232
|
-
completion_tokens: outputTokens,
|
|
12233
|
-
total_tokens: inputTokens + outputTokens
|
|
12234
|
-
}
|
|
12235
|
-
})}
|
|
12316
|
+
console.log("[OPENAI GATEWAY] chunk:", chunk.type);
|
|
12317
|
+
const openAIChunk = transformStreamChunk(chunk, ctx);
|
|
12318
|
+
if (openAIChunk) {
|
|
12319
|
+
if (chunk.type === "finish") {
|
|
12320
|
+
inputTokens = chunk.usage?.inputTokens ?? 0;
|
|
12321
|
+
outputTokens = chunk.usage?.outputTokens ?? 0;
|
|
12322
|
+
console.log("[OPENAI GATEWAY] finish_reason:", openAIChunk.choices[0]?.finish_reason);
|
|
12323
|
+
}
|
|
12324
|
+
res.write(`data: ${JSON.stringify(openAIChunk)}
|
|
12236
12325
|
|
|
12237
|
-
`
|
|
12238
|
-
);
|
|
12326
|
+
`);
|
|
12239
12327
|
}
|
|
12240
12328
|
}
|
|
12241
12329
|
res.write("data: [DONE]\n\n");
|
|
@@ -12246,29 +12334,12 @@ ${project.description}` : ""}` : "",
|
|
|
12246
12334
|
model: languageModel,
|
|
12247
12335
|
system: systemPrompt || void 0,
|
|
12248
12336
|
messages: coreMessages,
|
|
12249
|
-
tools: hasTools ?
|
|
12337
|
+
tools: hasTools ? activeTools : void 0,
|
|
12250
12338
|
maxRetries: 2,
|
|
12251
|
-
stopWhen: [(0, import_ai9.stepCountIs)(5)]
|
|
12252
|
-
});
|
|
12253
|
-
res.json({
|
|
12254
|
-
id: completionId,
|
|
12255
|
-
object: "chat.completion",
|
|
12256
|
-
created,
|
|
12257
|
-
model: agentId,
|
|
12258
|
-
choices: [
|
|
12259
|
-
{
|
|
12260
|
-
index: 0,
|
|
12261
|
-
message: { role: "assistant", content: text },
|
|
12262
|
-
finish_reason: "stop"
|
|
12263
|
-
}
|
|
12264
|
-
],
|
|
12265
|
-
usage: {
|
|
12266
|
-
prompt_tokens: usage.promptTokens,
|
|
12267
|
-
completion_tokens: usage.completionTokens,
|
|
12268
|
-
total_tokens: usage.totalTokens
|
|
12269
|
-
}
|
|
12339
|
+
stopWhen: clientTools.length > 0 ? void 0 : [(0, import_ai9.stepCountIs)(5)]
|
|
12270
12340
|
});
|
|
12271
|
-
|
|
12341
|
+
res.json(transformCompletion(text, usage.inputTokens ?? 0, usage.outputTokens ?? 0, ctx));
|
|
12342
|
+
await writeStatistics(agent, project, user, usage.inputTokens ?? 0, usage.outputTokens ?? 0);
|
|
12272
12343
|
}
|
|
12273
12344
|
} catch (error) {
|
|
12274
12345
|
console.error("[OPENAI GATEWAY] /v1/chat/completions error:", error);
|
package/dist/index.js
CHANGED
|
@@ -1486,7 +1486,7 @@ var ExuluTool = class {
|
|
|
1486
1486
|
});
|
|
1487
1487
|
}
|
|
1488
1488
|
execute = async ({
|
|
1489
|
-
agent:
|
|
1489
|
+
agent: agentId,
|
|
1490
1490
|
config,
|
|
1491
1491
|
user,
|
|
1492
1492
|
inputs,
|
|
@@ -1494,14 +1494,14 @@ var ExuluTool = class {
|
|
|
1494
1494
|
items
|
|
1495
1495
|
}) => {
|
|
1496
1496
|
console.log("[EXULU] Calling tool execute directly", {
|
|
1497
|
-
agentId
|
|
1497
|
+
agentId,
|
|
1498
1498
|
config,
|
|
1499
1499
|
user,
|
|
1500
1500
|
inputs,
|
|
1501
1501
|
project,
|
|
1502
1502
|
items
|
|
1503
1503
|
});
|
|
1504
|
-
const agent = await exuluApp.get().agent(
|
|
1504
|
+
const agent = await exuluApp.get().agent(agentId);
|
|
1505
1505
|
if (!agent) {
|
|
1506
1506
|
throw new Error("Agent not found.");
|
|
1507
1507
|
}
|
|
@@ -11800,29 +11800,144 @@ import "express";
|
|
|
11800
11800
|
import {
|
|
11801
11801
|
streamText as streamText2,
|
|
11802
11802
|
generateText as generateText5,
|
|
11803
|
-
stepCountIs as stepCountIs3
|
|
11803
|
+
stepCountIs as stepCountIs3,
|
|
11804
|
+
jsonSchema
|
|
11804
11805
|
} from "ai";
|
|
11806
|
+
|
|
11807
|
+
// src/exulu/openai-transformer.ts
|
|
11808
|
+
function transformStreamChunk(chunk, ctx) {
|
|
11809
|
+
const base = {
|
|
11810
|
+
id: ctx.completionId,
|
|
11811
|
+
object: "chat.completion.chunk",
|
|
11812
|
+
created: ctx.created,
|
|
11813
|
+
model: ctx.modelId
|
|
11814
|
+
};
|
|
11815
|
+
switch (chunk.type) {
|
|
11816
|
+
case "text-delta":
|
|
11817
|
+
return {
|
|
11818
|
+
...base,
|
|
11819
|
+
choices: [{ index: 0, delta: { content: chunk.text }, finish_reason: null }]
|
|
11820
|
+
};
|
|
11821
|
+
case "tool-input-start":
|
|
11822
|
+
return {
|
|
11823
|
+
...base,
|
|
11824
|
+
choices: [
|
|
11825
|
+
{
|
|
11826
|
+
index: 0,
|
|
11827
|
+
delta: {
|
|
11828
|
+
tool_calls: [
|
|
11829
|
+
{
|
|
11830
|
+
index: 0,
|
|
11831
|
+
id: chunk.id,
|
|
11832
|
+
type: "function",
|
|
11833
|
+
function: { name: chunk.toolName, arguments: "" }
|
|
11834
|
+
}
|
|
11835
|
+
]
|
|
11836
|
+
},
|
|
11837
|
+
finish_reason: null
|
|
11838
|
+
}
|
|
11839
|
+
]
|
|
11840
|
+
};
|
|
11841
|
+
case "tool-input-delta":
|
|
11842
|
+
return {
|
|
11843
|
+
...base,
|
|
11844
|
+
choices: [
|
|
11845
|
+
{
|
|
11846
|
+
index: 0,
|
|
11847
|
+
delta: { tool_calls: [{ index: 0, function: { arguments: chunk.delta } }] },
|
|
11848
|
+
finish_reason: null
|
|
11849
|
+
}
|
|
11850
|
+
]
|
|
11851
|
+
};
|
|
11852
|
+
case "finish": {
|
|
11853
|
+
const inputTokens = chunk.usage?.inputTokens ?? 0;
|
|
11854
|
+
const outputTokens = chunk.usage?.outputTokens ?? 0;
|
|
11855
|
+
const finishReason = chunk.finishReason === "tool-calls" ? "tool_calls" : "stop";
|
|
11856
|
+
return {
|
|
11857
|
+
...base,
|
|
11858
|
+
choices: [{ index: 0, delta: {}, finish_reason: finishReason }],
|
|
11859
|
+
usage: {
|
|
11860
|
+
prompt_tokens: inputTokens,
|
|
11861
|
+
completion_tokens: outputTokens,
|
|
11862
|
+
total_tokens: inputTokens + outputTokens
|
|
11863
|
+
}
|
|
11864
|
+
};
|
|
11865
|
+
}
|
|
11866
|
+
default:
|
|
11867
|
+
return null;
|
|
11868
|
+
}
|
|
11869
|
+
}
|
|
11870
|
+
function transformCompletion(text, inputTokens, outputTokens, ctx) {
|
|
11871
|
+
return {
|
|
11872
|
+
id: ctx.completionId,
|
|
11873
|
+
object: "chat.completion",
|
|
11874
|
+
created: ctx.created,
|
|
11875
|
+
model: ctx.modelId,
|
|
11876
|
+
choices: [
|
|
11877
|
+
{
|
|
11878
|
+
index: 0,
|
|
11879
|
+
message: { role: "assistant", content: text },
|
|
11880
|
+
finish_reason: "stop"
|
|
11881
|
+
}
|
|
11882
|
+
],
|
|
11883
|
+
usage: {
|
|
11884
|
+
prompt_tokens: inputTokens,
|
|
11885
|
+
completion_tokens: outputTokens,
|
|
11886
|
+
total_tokens: inputTokens + outputTokens
|
|
11887
|
+
}
|
|
11888
|
+
};
|
|
11889
|
+
}
|
|
11890
|
+
|
|
11891
|
+
// src/exulu/openai-gateway.ts
|
|
11805
11892
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
11806
11893
|
import CryptoJS6 from "crypto-js";
|
|
11807
11894
|
import express from "express";
|
|
11808
|
-
function
|
|
11895
|
+
function convertOpenAIToolsToAiSdkTools(tools) {
|
|
11896
|
+
return Object.fromEntries(
|
|
11897
|
+
tools.map((t) => {
|
|
11898
|
+
const params = t.function.parameters ?? {};
|
|
11899
|
+
return [
|
|
11900
|
+
t.function.name,
|
|
11901
|
+
{
|
|
11902
|
+
description: t.function.description ?? "",
|
|
11903
|
+
inputSchema: jsonSchema({
|
|
11904
|
+
type: "object",
|
|
11905
|
+
properties: params.properties ?? {},
|
|
11906
|
+
...params.required ? { required: params.required } : {}
|
|
11907
|
+
})
|
|
11908
|
+
}
|
|
11909
|
+
];
|
|
11910
|
+
})
|
|
11911
|
+
);
|
|
11912
|
+
}
|
|
11913
|
+
function convertOpenAIMessagesToModelMessages(messages) {
|
|
11809
11914
|
const systemParts = [];
|
|
11810
11915
|
const coreMessages = [];
|
|
11916
|
+
const toolCallIdToName = /* @__PURE__ */ new Map();
|
|
11811
11917
|
for (const msg of messages) {
|
|
11812
11918
|
if (msg.role === "system") {
|
|
11813
11919
|
systemParts.push(typeof msg.content === "string" ? msg.content : "");
|
|
11814
11920
|
continue;
|
|
11815
11921
|
}
|
|
11816
11922
|
if (msg.role === "user") {
|
|
11923
|
+
const last = coreMessages[coreMessages.length - 1];
|
|
11817
11924
|
if (typeof msg.content === "string") {
|
|
11818
|
-
|
|
11925
|
+
if (last?.role === "user" && typeof last.content === "string") {
|
|
11926
|
+
last.content += "\n\n" + msg.content;
|
|
11927
|
+
} else {
|
|
11928
|
+
coreMessages.push({ role: "user", content: msg.content });
|
|
11929
|
+
}
|
|
11819
11930
|
} else if (Array.isArray(msg.content)) {
|
|
11820
11931
|
const parts = msg.content.flatMap((part) => {
|
|
11821
11932
|
if (part.type === "text") return [{ type: "text", text: part.text }];
|
|
11822
11933
|
if (part.type === "image_url") return [{ type: "image", image: part.image_url.url }];
|
|
11823
11934
|
return [];
|
|
11824
11935
|
});
|
|
11825
|
-
|
|
11936
|
+
if (last?.role === "user" && Array.isArray(last.content)) {
|
|
11937
|
+
last.content.push(...parts);
|
|
11938
|
+
} else {
|
|
11939
|
+
coreMessages.push({ role: "user", content: parts });
|
|
11940
|
+
}
|
|
11826
11941
|
}
|
|
11827
11942
|
continue;
|
|
11828
11943
|
}
|
|
@@ -11833,11 +11948,20 @@ function convertOpenAIMessagesToCoreMessages(messages) {
|
|
|
11833
11948
|
parts.push({ type: "text", text: msg.content });
|
|
11834
11949
|
}
|
|
11835
11950
|
for (const tc of msg.tool_calls) {
|
|
11951
|
+
toolCallIdToName.set(tc.id, tc.function.name);
|
|
11952
|
+
const rawArgs = tc.function.arguments;
|
|
11953
|
+
const input = rawArgs == null ? {} : typeof rawArgs === "object" ? rawArgs : (() => {
|
|
11954
|
+
try {
|
|
11955
|
+
return JSON.parse(rawArgs);
|
|
11956
|
+
} catch {
|
|
11957
|
+
return {};
|
|
11958
|
+
}
|
|
11959
|
+
})();
|
|
11836
11960
|
parts.push({
|
|
11837
11961
|
type: "tool-call",
|
|
11838
11962
|
toolCallId: tc.id,
|
|
11839
11963
|
toolName: tc.function.name,
|
|
11840
|
-
|
|
11964
|
+
input
|
|
11841
11965
|
});
|
|
11842
11966
|
}
|
|
11843
11967
|
coreMessages.push({ role: "assistant", content: parts });
|
|
@@ -11850,13 +11974,17 @@ function convertOpenAIMessagesToCoreMessages(messages) {
|
|
|
11850
11974
|
continue;
|
|
11851
11975
|
}
|
|
11852
11976
|
if (msg.role === "tool") {
|
|
11977
|
+
const toolCallId = msg.tool_call_id ?? "";
|
|
11978
|
+
const toolName = toolCallIdToName.get(toolCallId) ?? "unknown";
|
|
11979
|
+
const resultText = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
11853
11980
|
coreMessages.push({
|
|
11854
11981
|
role: "tool",
|
|
11855
11982
|
content: [
|
|
11856
11983
|
{
|
|
11857
11984
|
type: "tool-result",
|
|
11858
|
-
toolCallId
|
|
11859
|
-
|
|
11985
|
+
toolCallId,
|
|
11986
|
+
toolName,
|
|
11987
|
+
output: { type: "text", value: resultText }
|
|
11860
11988
|
}
|
|
11861
11989
|
]
|
|
11862
11990
|
});
|
|
@@ -11973,8 +12101,32 @@ var registerOpenAIGatewayRoutes = async (app, providers, tools, contexts, config
|
|
|
11973
12101
|
}
|
|
11974
12102
|
);
|
|
11975
12103
|
app.post(
|
|
11976
|
-
"/gateway/open-ai/v1/chat/completions",
|
|
12104
|
+
["/gateway/open-ai/v1/chat/completions", "/gateway/open-ai/v1/completions"],
|
|
11977
12105
|
express.json({ limit: REQUEST_SIZE_LIMIT }),
|
|
12106
|
+
(req, _res, next) => {
|
|
12107
|
+
console.log("[OPENAI GATEWAY] incoming request:", {
|
|
12108
|
+
url: req.originalUrl,
|
|
12109
|
+
method: req.method,
|
|
12110
|
+
headers: {
|
|
12111
|
+
authorization: req.headers["authorization"] ? "[present]" : "[missing]",
|
|
12112
|
+
"x-api-key": req.headers["x-api-key"] ? "[present]" : "[missing]",
|
|
12113
|
+
"exulu-api-key": req.headers["exulu-api-key"] ? "[present]" : "[missing]",
|
|
12114
|
+
"content-type": req.headers["content-type"]
|
|
12115
|
+
},
|
|
12116
|
+
body: {
|
|
12117
|
+
model: req.body?.model,
|
|
12118
|
+
stream: req.body?.stream,
|
|
12119
|
+
messagesCount: req.body?.messages?.length,
|
|
12120
|
+
hasPrompt: typeof req.body?.prompt === "string",
|
|
12121
|
+
tools: req.body?.tools
|
|
12122
|
+
}
|
|
12123
|
+
});
|
|
12124
|
+
if (typeof req.body.prompt === "string") {
|
|
12125
|
+
req.body.messages = [{ role: "user", content: req.body.prompt }];
|
|
12126
|
+
delete req.body.prompt;
|
|
12127
|
+
}
|
|
12128
|
+
next();
|
|
12129
|
+
},
|
|
11978
12130
|
async (req, res) => {
|
|
11979
12131
|
try {
|
|
11980
12132
|
const { db: db2 } = await postgresClient();
|
|
@@ -12080,8 +12232,10 @@ var registerOpenAIGatewayRoutes = async (app, providers, tools, contexts, config
|
|
|
12080
12232
|
languageModel,
|
|
12081
12233
|
agent
|
|
12082
12234
|
);
|
|
12235
|
+
const clientTools = Array.isArray(req.body.tools) ? req.body.tools : [];
|
|
12236
|
+
const activeTools = clientTools.length > 0 ? convertOpenAIToolsToAiSdkTools(clientTools) : convertedTools;
|
|
12083
12237
|
const openaiMessages = req.body.messages ?? [];
|
|
12084
|
-
const { systemPrompt: requestSystemPrompt, coreMessages } =
|
|
12238
|
+
const { systemPrompt: requestSystemPrompt, coreMessages } = convertOpenAIMessagesToModelMessages(openaiMessages);
|
|
12085
12239
|
const agentInstructions = agent.instructions ?? "";
|
|
12086
12240
|
const systemParts = [
|
|
12087
12241
|
agentInstructions ? `You are an agent named: ${agent.name}
|
|
@@ -12093,7 +12247,8 @@ ${project.description}` : ""}` : "",
|
|
|
12093
12247
|
const systemPrompt = systemParts.join("\n\n");
|
|
12094
12248
|
const completionId = `chatcmpl-${randomUUID4()}`;
|
|
12095
12249
|
const created = Math.floor(Date.now() / 1e3);
|
|
12096
|
-
const hasTools = Object.keys(
|
|
12250
|
+
const hasTools = Object.keys(activeTools).length > 0;
|
|
12251
|
+
const ctx = { completionId, created, modelId };
|
|
12097
12252
|
if (req.body.stream === true) {
|
|
12098
12253
|
res.setHeader("Content-Type", "text/event-stream");
|
|
12099
12254
|
res.setHeader("Cache-Control", "no-cache");
|
|
@@ -12102,9 +12257,9 @@ ${project.description}` : ""}` : "",
|
|
|
12102
12257
|
model: languageModel,
|
|
12103
12258
|
system: systemPrompt || void 0,
|
|
12104
12259
|
messages: coreMessages,
|
|
12105
|
-
tools: hasTools ?
|
|
12260
|
+
tools: hasTools ? activeTools : void 0,
|
|
12106
12261
|
maxRetries: 2,
|
|
12107
|
-
stopWhen: [stepCountIs3(5)],
|
|
12262
|
+
stopWhen: clientTools.length > 0 ? void 0 : [stepCountIs3(5)],
|
|
12108
12263
|
onError: (error) => {
|
|
12109
12264
|
console.error("[OPENAI GATEWAY] stream error:", error);
|
|
12110
12265
|
}
|
|
@@ -12123,83 +12278,17 @@ ${project.description}` : ""}` : "",
|
|
|
12123
12278
|
let inputTokens = 0;
|
|
12124
12279
|
let outputTokens = 0;
|
|
12125
12280
|
for await (const chunk of result.fullStream) {
|
|
12126
|
-
|
|
12127
|
-
|
|
12128
|
-
|
|
12129
|
-
|
|
12130
|
-
|
|
12131
|
-
|
|
12132
|
-
|
|
12133
|
-
|
|
12134
|
-
|
|
12135
|
-
|
|
12136
|
-
`
|
|
12137
|
-
);
|
|
12138
|
-
} else if (chunk.type === "tool-input-start") {
|
|
12139
|
-
res.write(
|
|
12140
|
-
`data: ${JSON.stringify({
|
|
12141
|
-
id: completionId,
|
|
12142
|
-
object: "chat.completion.chunk",
|
|
12143
|
-
created,
|
|
12144
|
-
model: modelId,
|
|
12145
|
-
choices: [
|
|
12146
|
-
{
|
|
12147
|
-
index: 0,
|
|
12148
|
-
delta: {
|
|
12149
|
-
tool_calls: [
|
|
12150
|
-
{
|
|
12151
|
-
index: 0,
|
|
12152
|
-
id: chunk.id,
|
|
12153
|
-
type: "function",
|
|
12154
|
-
function: { name: chunk.toolName, arguments: "" }
|
|
12155
|
-
}
|
|
12156
|
-
]
|
|
12157
|
-
},
|
|
12158
|
-
finish_reason: null
|
|
12159
|
-
}
|
|
12160
|
-
]
|
|
12161
|
-
})}
|
|
12162
|
-
|
|
12163
|
-
`
|
|
12164
|
-
);
|
|
12165
|
-
} else if (chunk.type === "tool-input-delta") {
|
|
12166
|
-
res.write(
|
|
12167
|
-
`data: ${JSON.stringify({
|
|
12168
|
-
id: completionId,
|
|
12169
|
-
object: "chat.completion.chunk",
|
|
12170
|
-
created,
|
|
12171
|
-
model: modelId,
|
|
12172
|
-
choices: [
|
|
12173
|
-
{
|
|
12174
|
-
index: 0,
|
|
12175
|
-
delta: { tool_calls: [{ index: 0, function: { arguments: chunk.delta } }] },
|
|
12176
|
-
finish_reason: null
|
|
12177
|
-
}
|
|
12178
|
-
]
|
|
12179
|
-
})}
|
|
12180
|
-
|
|
12181
|
-
`
|
|
12182
|
-
);
|
|
12183
|
-
} else if (chunk.type === "finish") {
|
|
12184
|
-
inputTokens = chunk.usage?.inputTokens ?? 0;
|
|
12185
|
-
outputTokens = chunk.usage?.outputTokens ?? 0;
|
|
12186
|
-
const finishReason = chunk.finishReason === "tool-calls" ? "tool_calls" : "stop";
|
|
12187
|
-
res.write(
|
|
12188
|
-
`data: ${JSON.stringify({
|
|
12189
|
-
id: completionId,
|
|
12190
|
-
object: "chat.completion.chunk",
|
|
12191
|
-
created,
|
|
12192
|
-
model: modelId,
|
|
12193
|
-
choices: [{ index: 0, delta: {}, finish_reason: finishReason }],
|
|
12194
|
-
usage: {
|
|
12195
|
-
prompt_tokens: inputTokens,
|
|
12196
|
-
completion_tokens: outputTokens,
|
|
12197
|
-
total_tokens: inputTokens + outputTokens
|
|
12198
|
-
}
|
|
12199
|
-
})}
|
|
12281
|
+
console.log("[OPENAI GATEWAY] chunk:", chunk.type);
|
|
12282
|
+
const openAIChunk = transformStreamChunk(chunk, ctx);
|
|
12283
|
+
if (openAIChunk) {
|
|
12284
|
+
if (chunk.type === "finish") {
|
|
12285
|
+
inputTokens = chunk.usage?.inputTokens ?? 0;
|
|
12286
|
+
outputTokens = chunk.usage?.outputTokens ?? 0;
|
|
12287
|
+
console.log("[OPENAI GATEWAY] finish_reason:", openAIChunk.choices[0]?.finish_reason);
|
|
12288
|
+
}
|
|
12289
|
+
res.write(`data: ${JSON.stringify(openAIChunk)}
|
|
12200
12290
|
|
|
12201
|
-
`
|
|
12202
|
-
);
|
|
12291
|
+
`);
|
|
12203
12292
|
}
|
|
12204
12293
|
}
|
|
12205
12294
|
res.write("data: [DONE]\n\n");
|
|
@@ -12210,29 +12299,12 @@ ${project.description}` : ""}` : "",
|
|
|
12210
12299
|
model: languageModel,
|
|
12211
12300
|
system: systemPrompt || void 0,
|
|
12212
12301
|
messages: coreMessages,
|
|
12213
|
-
tools: hasTools ?
|
|
12302
|
+
tools: hasTools ? activeTools : void 0,
|
|
12214
12303
|
maxRetries: 2,
|
|
12215
|
-
stopWhen: [stepCountIs3(5)]
|
|
12216
|
-
});
|
|
12217
|
-
res.json({
|
|
12218
|
-
id: completionId,
|
|
12219
|
-
object: "chat.completion",
|
|
12220
|
-
created,
|
|
12221
|
-
model: agentId,
|
|
12222
|
-
choices: [
|
|
12223
|
-
{
|
|
12224
|
-
index: 0,
|
|
12225
|
-
message: { role: "assistant", content: text },
|
|
12226
|
-
finish_reason: "stop"
|
|
12227
|
-
}
|
|
12228
|
-
],
|
|
12229
|
-
usage: {
|
|
12230
|
-
prompt_tokens: usage.promptTokens,
|
|
12231
|
-
completion_tokens: usage.completionTokens,
|
|
12232
|
-
total_tokens: usage.totalTokens
|
|
12233
|
-
}
|
|
12304
|
+
stopWhen: clientTools.length > 0 ? void 0 : [stepCountIs3(5)]
|
|
12234
12305
|
});
|
|
12235
|
-
|
|
12306
|
+
res.json(transformCompletion(text, usage.inputTokens ?? 0, usage.outputTokens ?? 0, ctx));
|
|
12307
|
+
await writeStatistics(agent, project, user, usage.inputTokens ?? 0, usage.outputTokens ?? 0);
|
|
12236
12308
|
}
|
|
12237
12309
|
} catch (error) {
|
|
12238
12310
|
console.error("[OPENAI GATEWAY] /v1/chat/completions error:", error);
|