@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.
Files changed (3) hide show
  1. package/dist/index.cjs +182 -111
  2. package/dist/index.js +184 -112
  3. 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: agentId2,
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: agentId2,
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(agentId2);
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 convertOpenAIMessagesToCoreMessages(messages) {
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
- coreMessages.push({ role: "user", content: msg.content });
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
- coreMessages.push({ role: "user", content: parts });
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
- args: JSON.parse(tc.function.arguments || "{}")
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: msg.tool_call_id ?? "",
11895
- result: msg.content
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 } = convertOpenAIMessagesToCoreMessages(openaiMessages);
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(convertedTools).length > 0;
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 ? convertedTools : void 0,
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
- if (chunk.type === "text-delta") {
12163
- res.write(
12164
- `data: ${JSON.stringify({
12165
- id: completionId,
12166
- object: "chat.completion.chunk",
12167
- created,
12168
- model: modelId,
12169
- choices: [{ index: 0, delta: { content: chunk.text }, finish_reason: null }]
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 ? convertedTools : void 0,
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
- await writeStatistics(agent, project, user, usage.promptTokens, usage.completionTokens);
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: agentId2,
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: agentId2,
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(agentId2);
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 convertOpenAIMessagesToCoreMessages(messages) {
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
- coreMessages.push({ role: "user", content: msg.content });
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
- coreMessages.push({ role: "user", content: parts });
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
- args: JSON.parse(tc.function.arguments || "{}")
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: msg.tool_call_id ?? "",
11859
- result: msg.content
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 } = convertOpenAIMessagesToCoreMessages(openaiMessages);
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(convertedTools).length > 0;
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 ? convertedTools : void 0,
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
- if (chunk.type === "text-delta") {
12127
- res.write(
12128
- `data: ${JSON.stringify({
12129
- id: completionId,
12130
- object: "chat.completion.chunk",
12131
- created,
12132
- model: modelId,
12133
- choices: [{ index: 0, delta: { content: chunk.text }, finish_reason: null }]
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 ? convertedTools : void 0,
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
- await writeStatistics(agent, project, user, usage.promptTokens, usage.completionTokens);
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);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@exulu/backend",
3
3
  "author": "Qventu Bv.",
4
- "version": "1.56.0",
4
+ "version": "1.57.0",
5
5
  "main": "./dist/index.js",
6
6
  "private": false,
7
7
  "publishConfig": {