@exulu/backend 1.24.0 → 1.25.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/CHANGELOG.md CHANGED
@@ -1,7 +1,7 @@
1
- # [1.24.0](https://github.com/Qventu/exulu-backend/compare/v1.23.3...v1.24.0) (2025-09-19)
1
+ # [1.25.0](https://github.com/Qventu/exulu-backend/compare/v1.24.0...v1.25.0) (2025-09-30)
2
2
 
3
3
 
4
4
  ### Features
5
5
 
6
- * dynamic db name, agent instructions, agentToAgent, rights checks, token usage, uploads ([93a84a4](https://github.com/Qventu/exulu-backend/commit/93a84a4cef6ce5ea006ae862530e04e7dfd0108c))
7
- * project files ([24d34cc](https://github.com/Qventu/exulu-backend/commit/24d34ccb61777356653de2b5319064098cc5d877))
6
+ * switch to anthropic sdk for claude code endpoint ([4dcfcfd](https://github.com/Qventu/exulu-backend/commit/4dcfcfde031e5515e7329a217498dce4aeafd364))
7
+ * trying out different proxy methods for opencode and claude code ([7630bec](https://github.com/Qventu/exulu-backend/commit/7630bec940a55da58b3dfb210b3a56849cd31a54))
package/dist/index.cjs CHANGED
@@ -168,6 +168,9 @@ async function ensureDatabaseExists() {
168
168
  } else {
169
169
  console.log(`[EXULU] Database '${dbName}' already exists.`);
170
170
  }
171
+ } catch (error) {
172
+ console.error("[EXULU] Error while checking to ensure the database exists, this could be if the user running the server does not have database admin rights, it is fine to ignore this if you are sure the database exists.", error);
173
+ return;
171
174
  } finally {
172
175
  await defaultKnex.destroy();
173
176
  }
@@ -198,7 +201,11 @@ async function postgresClient() {
198
201
  ssl: process.env.POSTGRES_DB_SSL === "true" ? { rejectUnauthorized: false } : false
199
202
  }
200
203
  });
201
- await knex.schema.createExtensionIfNotExists("vector");
204
+ try {
205
+ await knex.schema.createExtensionIfNotExists("vector");
206
+ } catch (error) {
207
+ console.error("[EXULU] Error creating vector extension, this might be fine if you already activated the extension and the 'user' running this script does not have higher level database permissions.", error);
208
+ }
202
209
  db["exulu"] = knex;
203
210
  } catch (error) {
204
211
  console.error("[EXULU] Error initializing exulu database.", error);
@@ -461,7 +468,7 @@ var import_jose = require("jose");
461
468
  var getToken = async (authHeader) => {
462
469
  const token = authHeader.split(" ")[1];
463
470
  if (!token) {
464
- throw new Error("No token provided");
471
+ throw new Error("No token provided for user authentication in headers.");
465
472
  }
466
473
  if (!process.env.NEXTAUTH_SECRET) {
467
474
  throw new Error("No NEXTAUTH_SECRET provided");
@@ -2391,7 +2398,11 @@ var postprocessUpdate = async ({
2391
2398
  const { db: db3 } = await postgresClient();
2392
2399
  console.log("[EXULU] Deleting chunks for item", result.id);
2393
2400
  await db3.from(getChunksTableName(context.id)).where({ source: result.id }).delete();
2401
+ console.log("[EXULU] Deleted chunks for item", result.id);
2402
+ console.log("[EXULU] Embedder", context.embedder);
2403
+ console.log("[EXULU] Configuration", context.configuration);
2394
2404
  if (context.embedder && (context.configuration.calculateVectors === "onUpdate" || context.configuration.calculateVectors === "always")) {
2405
+ console.log("[EXULU] Generating embeddings for item", result.id);
2395
2406
  const { job } = await context.embeddings.generate.one({
2396
2407
  item: result,
2397
2408
  user,
@@ -3922,6 +3933,11 @@ var ExuluAgent2 = class {
3922
3933
  // prepareStep could be used here to set the model for the first step or change other params
3923
3934
  system,
3924
3935
  maxRetries: 2,
3936
+ providerOptions: {
3937
+ openai: {
3938
+ reasoningSummary: "auto"
3939
+ }
3940
+ },
3925
3941
  tools: convertToolsArrayToObject(
3926
3942
  currentTools,
3927
3943
  allExuluTools,
@@ -4521,8 +4537,8 @@ var ExuluContext = class {
4521
4537
  CREATE INDEX IF NOT EXISTS ${tableName}_embedding_hnsw_cosine
4522
4538
  ON ${tableName}
4523
4539
  USING hnsw (embedding vector_cosine_ops)
4524
- WHERE embedding IS NOT NULL
4525
4540
  WITH (m = 16, ef_construction = 64)
4541
+ WHERE embedding IS NOT NULL
4526
4542
  `);
4527
4543
  return;
4528
4544
  };
@@ -5073,6 +5089,13 @@ var createUppyRoutes = async (app, config) => {
5073
5089
  var import_utils4 = require("@apollo/utils.keyvaluecache");
5074
5090
  var import_body_parser = __toESM(require("body-parser"), 1);
5075
5091
  var import_crypto_js3 = __toESM(require("crypto-js"), 1);
5092
+ var import_openai = __toESM(require("openai"), 1);
5093
+ var import_fs = __toESM(require("fs"), 1);
5094
+ var import_node_crypto3 = require("crypto");
5095
+ var import_api = require("@opentelemetry/api");
5096
+ var import_ai2 = require("ai");
5097
+ var import_express_http_proxy = __toESM(require("express-http-proxy"), 1);
5098
+ var import_sdk = __toESM(require("@anthropic-ai/sdk"), 1);
5076
5099
 
5077
5100
  // src/registry/utils/claude-messages.ts
5078
5101
  var CLAUDE_MESSAGES = {
@@ -5097,13 +5120,6 @@ var CLAUDE_MESSAGES = {
5097
5120
  };
5098
5121
 
5099
5122
  // src/registry/routes.ts
5100
- var import_openai = __toESM(require("openai"), 1);
5101
- var import_fs = __toESM(require("fs"), 1);
5102
- var import_node_crypto3 = require("crypto");
5103
- var import_api = require("@opentelemetry/api");
5104
- var import_ai2 = require("ai");
5105
- var import_zod2 = require("zod");
5106
- var import_client_s33 = require("@aws-sdk/client-s3");
5107
5123
  var REQUEST_SIZE_LIMIT = "50mb";
5108
5124
  var global_queues = {
5109
5125
  logs_cleaner: "logs-cleaner"
@@ -5461,11 +5477,92 @@ Mood: friendly and intelligent.
5461
5477
  } else {
5462
5478
  console.log("[EXULU] skipping uppy file upload routes, because no S3 compatible region, key or secret is set in ExuluApp instance.");
5463
5479
  }
5464
- const TARGET_API = "https://api.anthropic.com";
5480
+ app.use("/xxx/anthropic/:id", import_express4.default.raw({ type: "*/*", limit: REQUEST_SIZE_LIMIT }), (0, import_express_http_proxy.default)(
5481
+ (req, res, next) => {
5482
+ return "https://api.anthropic.com";
5483
+ },
5484
+ {
5485
+ limit: "50mb",
5486
+ memoizeHost: false,
5487
+ preserveHostHdr: true,
5488
+ secure: false,
5489
+ reqAsBuffer: true,
5490
+ proxyReqBodyDecorator: function(bodyContent, srcReq) {
5491
+ return bodyContent;
5492
+ },
5493
+ userResDecorator: function(proxyRes, proxyResData, userReq, userRes) {
5494
+ console.log("[EXULU] Proxy response!", proxyResData);
5495
+ proxyResData = proxyResData.toString();
5496
+ console.log("[EXULU] Proxy response string!", proxyResData);
5497
+ return proxyResData;
5498
+ },
5499
+ proxyReqPathResolver: (req) => {
5500
+ const prefix = `/gateway/anthropic/${req.params.id}`;
5501
+ let path2 = req.url.startsWith(prefix) ? req.url.slice(prefix.length) : req.url;
5502
+ if (!path2.startsWith("/")) path2 = "/" + path2;
5503
+ console.log("[EXULU] Provider path:", path2);
5504
+ return path2;
5505
+ },
5506
+ proxyReqOptDecorator: function(proxyReqOpts, srcReq) {
5507
+ return new Promise(async (resolve, reject) => {
5508
+ try {
5509
+ const authenticationResult = await requestValidators.authenticate(srcReq);
5510
+ if (!authenticationResult.user?.id) {
5511
+ console.log("[EXULU] failed authentication result", authenticationResult);
5512
+ reject(authenticationResult.message);
5513
+ return;
5514
+ }
5515
+ console.log("[EXULU] Authenticated call", authenticationResult.user?.email);
5516
+ const { db: db3 } = await postgresClient();
5517
+ let query = db3("agents");
5518
+ query.select("*");
5519
+ query = applyAccessControl(agentsSchema2(), authenticationResult.user, query);
5520
+ query.where({ id: srcReq.params.id });
5521
+ const agent = await query.first();
5522
+ if (!agent) {
5523
+ reject(new Error("Agent with id " + srcReq.params.id + " not found."));
5524
+ return;
5525
+ }
5526
+ console.log("[EXULU] Agent loaded", agent.name);
5527
+ const backend = agents.find((x) => x.id === agent.backend);
5528
+ if (!process.env.NEXTAUTH_SECRET) {
5529
+ reject(new Error("Missing NEXTAUTH_SECRET"));
5530
+ return;
5531
+ }
5532
+ if (!agent.providerapikey) {
5533
+ reject(new Error("API Key not set for agent"));
5534
+ return;
5535
+ }
5536
+ const variableName = agent.providerapikey;
5537
+ const variable = await db3.from("variables").where({ name: variableName }).first();
5538
+ console.log("[EXULU] Variable loaded", variable);
5539
+ let providerapikey = variable.value;
5540
+ if (!variable.encrypted) {
5541
+ reject(new Error("API Key not encrypted for agent"));
5542
+ return;
5543
+ }
5544
+ if (variable.encrypted) {
5545
+ const bytes = import_crypto_js3.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
5546
+ providerapikey = bytes.toString(import_crypto_js3.default.enc.Utf8);
5547
+ }
5548
+ console.log("[EXULU] Provider API key", providerapikey);
5549
+ proxyReqOpts.headers["x-api-key"] = providerapikey;
5550
+ proxyReqOpts.rejectUnauthorized = false;
5551
+ delete proxyReqOpts.headers["provider"];
5552
+ const url = new URL("https://api.anthropic.com");
5553
+ proxyReqOpts.headers["host"] = url.host;
5554
+ proxyReqOpts.headers["anthropic-version"] = "2023-06-01";
5555
+ console.log("[EXULU] Proxy request headers", proxyReqOpts.headers);
5556
+ resolve(proxyReqOpts);
5557
+ } catch (error) {
5558
+ console.error("[EXULU] Proxy error", error);
5559
+ reject(error);
5560
+ }
5561
+ });
5562
+ }
5563
+ }
5564
+ ));
5465
5565
  app.use("/gateway/anthropic/:id", import_express4.default.raw({ type: "*/*", limit: REQUEST_SIZE_LIMIT }), async (req, res) => {
5466
- console.log("[EXULU] Coding request!!!");
5467
- const path2 = req.url;
5468
- const url = `${TARGET_API}${path2}`;
5469
5566
  try {
5470
5567
  if (!req.body.tools) {
5471
5568
  req.body.tools = [];
@@ -5476,7 +5573,6 @@ Mood: friendly and intelligent.
5476
5573
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
5477
5574
  return;
5478
5575
  }
5479
- console.log("[EXULU] Authenticated call", authenticationResult.user?.email);
5480
5576
  const { db: db3 } = await postgresClient();
5481
5577
  let query = db3("agents");
5482
5578
  query.select("*");
@@ -5491,17 +5587,7 @@ Mood: friendly and intelligent.
5491
5587
  res.end(Buffer.from(arrayBuffer));
5492
5588
  return;
5493
5589
  }
5494
- console.log("[EXULU] Agent loaded", agent.name);
5495
- const backend = agents.find((x) => x.id === agent.backend);
5496
- if (!backend) {
5497
- const arrayBuffer = createCustomAnthropicStreamingMessage(`
5498
- \x1B[41m -- Agent ${agent.name} does not have a exulu backend setup, or the exulu backend that was assigned no longer exists. --
5499
- \x1B[0m`);
5500
- res.setHeader("Content-Type", "application/json");
5501
- res.end(Buffer.from(arrayBuffer));
5502
- return;
5503
- }
5504
- console.log("[EXULU] Backend loaded", backend.id);
5590
+ console.log("[EXULU] anthropic proxy called for agent:", agent?.name);
5505
5591
  if (!process.env.NEXTAUTH_SECRET) {
5506
5592
  const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.missing_nextauth_secret);
5507
5593
  res.setHeader("Content-Type", "application/json");
@@ -5522,7 +5608,7 @@ Mood: friendly and intelligent.
5522
5608
  res.end(Buffer.from(arrayBuffer));
5523
5609
  return;
5524
5610
  }
5525
- let providerapikey = variable.value;
5611
+ let anthropicApiKey = variable.value;
5526
5612
  if (!variable.encrypted) {
5527
5613
  const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.anthropic_token_variable_not_encrypted);
5528
5614
  res.setHeader("Content-Type", "application/json");
@@ -5531,90 +5617,30 @@ Mood: friendly and intelligent.
5531
5617
  }
5532
5618
  if (variable.encrypted) {
5533
5619
  const bytes = import_crypto_js3.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
5534
- providerapikey = bytes.toString(import_crypto_js3.default.enc.Utf8);
5620
+ anthropicApiKey = bytes.toString(import_crypto_js3.default.enc.Utf8);
5535
5621
  }
5536
5622
  const headers = {
5537
- "x-api-key": providerapikey,
5623
+ "x-api-key": anthropicApiKey,
5538
5624
  "anthropic-version": "2023-06-01",
5539
5625
  "content-type": req.headers["content-type"] || "application/json"
5540
5626
  };
5541
5627
  if (req.headers["accept"]) headers["accept"] = req.headers["accept"];
5542
5628
  if (req.headers["user-agent"]) headers["user-agent"] = req.headers["user-agent"];
5543
- console.log("agent", agent.name);
5544
- const model = backend.model?.create({
5545
- apiKey: providerapikey
5546
- });
5547
- if (!model) {
5548
- const arrayBuffer = createCustomAnthropicStreamingMessage(`
5549
- \x1B[41m -- Could not create language model instance fro agent ${agent.name}. --
5550
- \x1B[0m`);
5551
- res.setHeader("Content-Type", "application/json");
5552
- res.end(Buffer.from(arrayBuffer));
5553
- return;
5554
- }
5555
- const systemMessagesConcatenated = req.body.system.map((x) => x.text).join("\n\n\n");
5556
- let messages = convertClaudeCodeMessagesToVercelAISdkMessages(
5557
- req.body.messages
5558
- );
5559
- const tools2 = convertClaudeCodeToolsToVercelAISdkTools(
5560
- req.body.tools
5561
- );
5562
- console.log("STREAMING TEXT");
5563
- const result = (0, import_ai2.streamText)({
5564
- model,
5565
- // Should be a LanguageModelV1
5566
- // TIP FOR DEBUGGING IF YOU RUN INTO ISSUES / ERRORS REGARDING THE MESSAGES FORMAT. STORE THE 'raw' VARIABLE TO A FILE (fs.write)
5567
- // AND COPY THE CONTENT INTO THE messages: BELOW, TYPESCRIPT WILL TELL YOU WHAT IS WRONG WHICH IS USUALLY EASIER TO READ THAN THE
5568
- // ERROR OUTPUT IN THE LOGS.
5569
- messages,
5570
- system: systemMessagesConcatenated || "",
5571
- // prepareStep could be used here to set the model for the first step or change other params
5572
- maxOutputTokens: req.body.max_tokens,
5573
- temperature: req.body.temperature,
5574
- maxRetries: 2,
5575
- providerOptions: {
5576
- metadata: {
5577
- user_id: req.body.metadata?.user_id
5578
- }
5579
- },
5580
- tools: tools2,
5581
- onFinish: (data) => console.log("[EXULU] Finished stream"),
5582
- onError: (error) => console.error("[EXULU] chat stream error.", error)
5583
- // stopWhen: [stepCountIs(1)],
5629
+ const client2 = new import_sdk.default({
5630
+ apiKey: anthropicApiKey
5584
5631
  });
5585
- let allChunks = [];
5586
- result.consumeStream();
5587
- const responses = [];
5588
- try {
5589
- for await (const uiMessage of (0, import_ai2.readUIMessageStream)({
5590
- stream: result.toUIMessageStream()
5591
- })) {
5592
- console.log("Streaming chunk:", uiMessage);
5593
- const message = {
5594
- type: "message",
5595
- role: uiMessage.role,
5596
- content: uiMessage.parts.map((part) => {
5597
- if (part.type.includes("tool-")) {
5598
- const type = part.type;
5599
- part.type = "tool_use";
5600
- part.name = type.replace("tool-", "");
5601
- part.id = part.toolCallId;
5602
- }
5603
- return part;
5604
- })
5605
- };
5606
- responses.push(message);
5607
- console.log("Wrote message to response", message);
5632
+ for await (const event of client2.messages.stream(req.body)) {
5633
+ console.log("[EXULU] Event", event);
5634
+ if (event.message?.usage) {
5608
5635
  }
5609
- } catch (err) {
5610
- console.error("Stream error:", err);
5611
- } finally {
5612
- const jsonString = JSON.stringify(responses[responses.length - 1]);
5613
- const arrayBuffer = new TextEncoder().encode(jsonString).buffer;
5614
- res.setHeader("Content-Type", "application/json");
5615
- res.end(Buffer.from(arrayBuffer));
5616
- return;
5636
+ const msg = `event: ${event.type}
5637
+ data: ${JSON.stringify(event)}
5638
+
5639
+ `;
5640
+ res.write(msg);
5617
5641
  }
5642
+ res.write("event: done\ndata: [DONE]\n\n");
5643
+ res.end();
5618
5644
  } catch (error) {
5619
5645
  console.error("[PROXY] Manual proxy error:", error);
5620
5646
  if (!res.headersSent) {
@@ -5629,97 +5655,6 @@ Mood: friendly and intelligent.
5629
5655
  app.use(import_express4.default.static("public"));
5630
5656
  return app;
5631
5657
  };
5632
- var convertClaudeCodeToolsToVercelAISdkTools = (tools) => {
5633
- const result = {};
5634
- for (const tool2 of tools) {
5635
- const mySchema = (0, import_ai2.jsonSchema)(tool2.input_schema);
5636
- tools[tool2.name] = {
5637
- id: tool2.name,
5638
- name: tool2.name,
5639
- description: tool2.description,
5640
- inputSchema: mySchema
5641
- };
5642
- }
5643
- return result;
5644
- };
5645
- var convertClaudeCodeMessagesToVercelAISdkMessages = (messages) => {
5646
- let raw = messages.map((msg) => {
5647
- if (!msg.role) {
5648
- msg.role = "assistant";
5649
- }
5650
- delete msg.id;
5651
- if (!Array.isArray(msg.content)) {
5652
- return {
5653
- role: msg.role,
5654
- content: msg.content
5655
- };
5656
- }
5657
- if (msg.content.some((part) => part.type === "tool_result")) {
5658
- msg.role = "tool";
5659
- }
5660
- let parts = msg.content.map((part) => {
5661
- if (part.type === "step-start") {
5662
- return void 0;
5663
- }
5664
- if (part.type === "reasoning") {
5665
- const content = part.text?.length > 1 ? part.text : part.content;
5666
- return {
5667
- type: "reasoning",
5668
- text: content || "No reasoning content provided"
5669
- };
5670
- }
5671
- if (part.type === "tool_use") {
5672
- part.type = "tool-call";
5673
- }
5674
- if (part.type === "tool_result") {
5675
- part.type = "tool-result";
5676
- part.output = {
5677
- type: "text",
5678
- value: part.text || part.content
5679
- };
5680
- part.text = null;
5681
- part.content = null;
5682
- if (!part.name && part.tool_use_id) {
5683
- const allParts = raw.map((x) => x.content).flat();
5684
- const result = allParts.find((x) => {
5685
- return x.toolCallId === part.tool_use_id && x.name;
5686
- });
5687
- console.log("FIND RESULT!!!!", result);
5688
- if (result) {
5689
- part.name = result.name;
5690
- } else {
5691
- part.name = "...";
5692
- }
5693
- }
5694
- }
5695
- if (part.tool_use_id) {
5696
- part.toolCallId = part.tool_use_id;
5697
- delete part.tool_use_id;
5698
- }
5699
- return {
5700
- type: part.type,
5701
- ...part.text || part.content ? { text: part.text || part.content } : {},
5702
- ...part.toolCallId ? { toolCallId: part.toolCallId } : {},
5703
- ...part.name ? { toolName: part.name } : {},
5704
- ...part.input ? { input: part.input } : {},
5705
- ...part.output ? { output: part.output } : {},
5706
- ...part.cache_control?.type ? {
5707
- providerOptions: {
5708
- anthropic: { cacheControl: { type: part.cache_control?.type } }
5709
- }
5710
- } : {}
5711
- };
5712
- });
5713
- parts = parts.filter((part) => part !== void 0);
5714
- return {
5715
- role: msg.role,
5716
- id: msg.id,
5717
- content: parts
5718
- };
5719
- });
5720
- raw = raw.filter((msg) => msg !== void 0);
5721
- return raw;
5722
- };
5723
5658
  var createCustomAnthropicStreamingMessage = (message) => {
5724
5659
  const responseData = {
5725
5660
  type: "message",
@@ -5782,8 +5717,6 @@ var createWorkers = async (queues2, logger, contexts, _logsDir, tracer) => {
5782
5717
  }, data.role, bullmqJob.id);
5783
5718
  return result;
5784
5719
  }
5785
- if (bullmqJob.data.type === "workflow") {
5786
- }
5787
5720
  } catch (error) {
5788
5721
  await db3.from("jobs").where({ redis: bullmqJob.id }).update({
5789
5722
  status: "failed",
@@ -5850,7 +5783,7 @@ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
5850
5783
  var import_node_crypto4 = require("crypto");
5851
5784
  var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
5852
5785
  var import_types = require("@modelcontextprotocol/sdk/types.js");
5853
- var import_zod3 = require("zod");
5786
+ var import_zod2 = require("zod");
5854
5787
  var import_express6 = require("express");
5855
5788
  var import_api3 = require("@opentelemetry/api");
5856
5789
  var SESSION_ID_HEADER = "mcp-session-id";
@@ -5907,7 +5840,7 @@ var ExuluMCP = class {
5907
5840
  {
5908
5841
  title: "Code Review",
5909
5842
  description: "Review code for best practices and potential issues",
5910
- argsSchema: { code: import_zod3.z.string() }
5843
+ argsSchema: { code: import_zod2.z.string() }
5911
5844
  },
5912
5845
  ({ code }) => ({
5913
5846
  messages: [{
package/dist/index.js CHANGED
@@ -117,6 +117,9 @@ async function ensureDatabaseExists() {
117
117
  } else {
118
118
  console.log(`[EXULU] Database '${dbName}' already exists.`);
119
119
  }
120
+ } catch (error) {
121
+ console.error("[EXULU] Error while checking to ensure the database exists, this could be if the user running the server does not have database admin rights, it is fine to ignore this if you are sure the database exists.", error);
122
+ return;
120
123
  } finally {
121
124
  await defaultKnex.destroy();
122
125
  }
@@ -147,7 +150,11 @@ async function postgresClient() {
147
150
  ssl: process.env.POSTGRES_DB_SSL === "true" ? { rejectUnauthorized: false } : false
148
151
  }
149
152
  });
150
- await knex.schema.createExtensionIfNotExists("vector");
153
+ try {
154
+ await knex.schema.createExtensionIfNotExists("vector");
155
+ } catch (error) {
156
+ console.error("[EXULU] Error creating vector extension, this might be fine if you already activated the extension and the 'user' running this script does not have higher level database permissions.", error);
157
+ }
151
158
  db["exulu"] = knex;
152
159
  } catch (error) {
153
160
  console.error("[EXULU] Error initializing exulu database.", error);
@@ -410,7 +417,7 @@ import { jwtVerify, importJWK } from "jose";
410
417
  var getToken = async (authHeader) => {
411
418
  const token = authHeader.split(" ")[1];
412
419
  if (!token) {
413
- throw new Error("No token provided");
420
+ throw new Error("No token provided for user authentication in headers.");
414
421
  }
415
422
  if (!process.env.NEXTAUTH_SECRET) {
416
423
  throw new Error("No NEXTAUTH_SECRET provided");
@@ -2340,7 +2347,11 @@ var postprocessUpdate = async ({
2340
2347
  const { db: db3 } = await postgresClient();
2341
2348
  console.log("[EXULU] Deleting chunks for item", result.id);
2342
2349
  await db3.from(getChunksTableName(context.id)).where({ source: result.id }).delete();
2350
+ console.log("[EXULU] Deleted chunks for item", result.id);
2351
+ console.log("[EXULU] Embedder", context.embedder);
2352
+ console.log("[EXULU] Configuration", context.configuration);
2343
2353
  if (context.embedder && (context.configuration.calculateVectors === "onUpdate" || context.configuration.calculateVectors === "always")) {
2354
+ console.log("[EXULU] Generating embeddings for item", result.id);
2344
2355
  const { job } = await context.embeddings.generate.one({
2345
2356
  item: result,
2346
2357
  user,
@@ -3875,6 +3886,11 @@ var ExuluAgent2 = class {
3875
3886
  // prepareStep could be used here to set the model for the first step or change other params
3876
3887
  system,
3877
3888
  maxRetries: 2,
3889
+ providerOptions: {
3890
+ openai: {
3891
+ reasoningSummary: "auto"
3892
+ }
3893
+ },
3878
3894
  tools: convertToolsArrayToObject(
3879
3895
  currentTools,
3880
3896
  allExuluTools,
@@ -4474,8 +4490,8 @@ var ExuluContext = class {
4474
4490
  CREATE INDEX IF NOT EXISTS ${tableName}_embedding_hnsw_cosine
4475
4491
  ON ${tableName}
4476
4492
  USING hnsw (embedding vector_cosine_ops)
4477
- WHERE embedding IS NOT NULL
4478
4493
  WITH (m = 16, ef_construction = 64)
4494
+ WHERE embedding IS NOT NULL
4479
4495
  `);
4480
4496
  return;
4481
4497
  };
@@ -5039,6 +5055,13 @@ var createUppyRoutes = async (app, config) => {
5039
5055
  import { InMemoryLRUCache } from "@apollo/utils.keyvaluecache";
5040
5056
  import bodyParser from "body-parser";
5041
5057
  import CryptoJS3 from "crypto-js";
5058
+ import OpenAI from "openai";
5059
+ import fs from "fs";
5060
+ import { randomUUID as randomUUID3 } from "crypto";
5061
+ import "@opentelemetry/api";
5062
+ import { jsonSchema } from "ai";
5063
+ import proxy from "express-http-proxy";
5064
+ import Anthropic from "@anthropic-ai/sdk";
5042
5065
 
5043
5066
  // src/registry/utils/claude-messages.ts
5044
5067
  var CLAUDE_MESSAGES = {
@@ -5063,13 +5086,6 @@ var CLAUDE_MESSAGES = {
5063
5086
  };
5064
5087
 
5065
5088
  // src/registry/routes.ts
5066
- import OpenAI from "openai";
5067
- import fs from "fs";
5068
- import { randomUUID as randomUUID3 } from "crypto";
5069
- import "@opentelemetry/api";
5070
- import { jsonSchema, readUIMessageStream, streamText as streamText2 } from "ai";
5071
- import "zod";
5072
- import "@aws-sdk/client-s3";
5073
5089
  var REQUEST_SIZE_LIMIT = "50mb";
5074
5090
  var global_queues = {
5075
5091
  logs_cleaner: "logs-cleaner"
@@ -5427,11 +5443,92 @@ Mood: friendly and intelligent.
5427
5443
  } else {
5428
5444
  console.log("[EXULU] skipping uppy file upload routes, because no S3 compatible region, key or secret is set in ExuluApp instance.");
5429
5445
  }
5430
- const TARGET_API = "https://api.anthropic.com";
5446
+ app.use("/xxx/anthropic/:id", express.raw({ type: "*/*", limit: REQUEST_SIZE_LIMIT }), proxy(
5447
+ (req, res, next) => {
5448
+ return "https://api.anthropic.com";
5449
+ },
5450
+ {
5451
+ limit: "50mb",
5452
+ memoizeHost: false,
5453
+ preserveHostHdr: true,
5454
+ secure: false,
5455
+ reqAsBuffer: true,
5456
+ proxyReqBodyDecorator: function(bodyContent, srcReq) {
5457
+ return bodyContent;
5458
+ },
5459
+ userResDecorator: function(proxyRes, proxyResData, userReq, userRes) {
5460
+ console.log("[EXULU] Proxy response!", proxyResData);
5461
+ proxyResData = proxyResData.toString();
5462
+ console.log("[EXULU] Proxy response string!", proxyResData);
5463
+ return proxyResData;
5464
+ },
5465
+ proxyReqPathResolver: (req) => {
5466
+ const prefix = `/gateway/anthropic/${req.params.id}`;
5467
+ let path2 = req.url.startsWith(prefix) ? req.url.slice(prefix.length) : req.url;
5468
+ if (!path2.startsWith("/")) path2 = "/" + path2;
5469
+ console.log("[EXULU] Provider path:", path2);
5470
+ return path2;
5471
+ },
5472
+ proxyReqOptDecorator: function(proxyReqOpts, srcReq) {
5473
+ return new Promise(async (resolve, reject) => {
5474
+ try {
5475
+ const authenticationResult = await requestValidators.authenticate(srcReq);
5476
+ if (!authenticationResult.user?.id) {
5477
+ console.log("[EXULU] failed authentication result", authenticationResult);
5478
+ reject(authenticationResult.message);
5479
+ return;
5480
+ }
5481
+ console.log("[EXULU] Authenticated call", authenticationResult.user?.email);
5482
+ const { db: db3 } = await postgresClient();
5483
+ let query = db3("agents");
5484
+ query.select("*");
5485
+ query = applyAccessControl(agentsSchema2(), authenticationResult.user, query);
5486
+ query.where({ id: srcReq.params.id });
5487
+ const agent = await query.first();
5488
+ if (!agent) {
5489
+ reject(new Error("Agent with id " + srcReq.params.id + " not found."));
5490
+ return;
5491
+ }
5492
+ console.log("[EXULU] Agent loaded", agent.name);
5493
+ const backend = agents.find((x) => x.id === agent.backend);
5494
+ if (!process.env.NEXTAUTH_SECRET) {
5495
+ reject(new Error("Missing NEXTAUTH_SECRET"));
5496
+ return;
5497
+ }
5498
+ if (!agent.providerapikey) {
5499
+ reject(new Error("API Key not set for agent"));
5500
+ return;
5501
+ }
5502
+ const variableName = agent.providerapikey;
5503
+ const variable = await db3.from("variables").where({ name: variableName }).first();
5504
+ console.log("[EXULU] Variable loaded", variable);
5505
+ let providerapikey = variable.value;
5506
+ if (!variable.encrypted) {
5507
+ reject(new Error("API Key not encrypted for agent"));
5508
+ return;
5509
+ }
5510
+ if (variable.encrypted) {
5511
+ const bytes = CryptoJS3.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
5512
+ providerapikey = bytes.toString(CryptoJS3.enc.Utf8);
5513
+ }
5514
+ console.log("[EXULU] Provider API key", providerapikey);
5515
+ proxyReqOpts.headers["x-api-key"] = providerapikey;
5516
+ proxyReqOpts.rejectUnauthorized = false;
5517
+ delete proxyReqOpts.headers["provider"];
5518
+ const url = new URL("https://api.anthropic.com");
5519
+ proxyReqOpts.headers["host"] = url.host;
5520
+ proxyReqOpts.headers["anthropic-version"] = "2023-06-01";
5521
+ console.log("[EXULU] Proxy request headers", proxyReqOpts.headers);
5522
+ resolve(proxyReqOpts);
5523
+ } catch (error) {
5524
+ console.error("[EXULU] Proxy error", error);
5525
+ reject(error);
5526
+ }
5527
+ });
5528
+ }
5529
+ }
5530
+ ));
5431
5531
  app.use("/gateway/anthropic/:id", express.raw({ type: "*/*", limit: REQUEST_SIZE_LIMIT }), async (req, res) => {
5432
- console.log("[EXULU] Coding request!!!");
5433
- const path2 = req.url;
5434
- const url = `${TARGET_API}${path2}`;
5435
5532
  try {
5436
5533
  if (!req.body.tools) {
5437
5534
  req.body.tools = [];
@@ -5442,7 +5539,6 @@ Mood: friendly and intelligent.
5442
5539
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
5443
5540
  return;
5444
5541
  }
5445
- console.log("[EXULU] Authenticated call", authenticationResult.user?.email);
5446
5542
  const { db: db3 } = await postgresClient();
5447
5543
  let query = db3("agents");
5448
5544
  query.select("*");
@@ -5457,17 +5553,7 @@ Mood: friendly and intelligent.
5457
5553
  res.end(Buffer.from(arrayBuffer));
5458
5554
  return;
5459
5555
  }
5460
- console.log("[EXULU] Agent loaded", agent.name);
5461
- const backend = agents.find((x) => x.id === agent.backend);
5462
- if (!backend) {
5463
- const arrayBuffer = createCustomAnthropicStreamingMessage(`
5464
- \x1B[41m -- Agent ${agent.name} does not have a exulu backend setup, or the exulu backend that was assigned no longer exists. --
5465
- \x1B[0m`);
5466
- res.setHeader("Content-Type", "application/json");
5467
- res.end(Buffer.from(arrayBuffer));
5468
- return;
5469
- }
5470
- console.log("[EXULU] Backend loaded", backend.id);
5556
+ console.log("[EXULU] anthropic proxy called for agent:", agent?.name);
5471
5557
  if (!process.env.NEXTAUTH_SECRET) {
5472
5558
  const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.missing_nextauth_secret);
5473
5559
  res.setHeader("Content-Type", "application/json");
@@ -5488,7 +5574,7 @@ Mood: friendly and intelligent.
5488
5574
  res.end(Buffer.from(arrayBuffer));
5489
5575
  return;
5490
5576
  }
5491
- let providerapikey = variable.value;
5577
+ let anthropicApiKey = variable.value;
5492
5578
  if (!variable.encrypted) {
5493
5579
  const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.anthropic_token_variable_not_encrypted);
5494
5580
  res.setHeader("Content-Type", "application/json");
@@ -5497,90 +5583,30 @@ Mood: friendly and intelligent.
5497
5583
  }
5498
5584
  if (variable.encrypted) {
5499
5585
  const bytes = CryptoJS3.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
5500
- providerapikey = bytes.toString(CryptoJS3.enc.Utf8);
5586
+ anthropicApiKey = bytes.toString(CryptoJS3.enc.Utf8);
5501
5587
  }
5502
5588
  const headers = {
5503
- "x-api-key": providerapikey,
5589
+ "x-api-key": anthropicApiKey,
5504
5590
  "anthropic-version": "2023-06-01",
5505
5591
  "content-type": req.headers["content-type"] || "application/json"
5506
5592
  };
5507
5593
  if (req.headers["accept"]) headers["accept"] = req.headers["accept"];
5508
5594
  if (req.headers["user-agent"]) headers["user-agent"] = req.headers["user-agent"];
5509
- console.log("agent", agent.name);
5510
- const model = backend.model?.create({
5511
- apiKey: providerapikey
5512
- });
5513
- if (!model) {
5514
- const arrayBuffer = createCustomAnthropicStreamingMessage(`
5515
- \x1B[41m -- Could not create language model instance fro agent ${agent.name}. --
5516
- \x1B[0m`);
5517
- res.setHeader("Content-Type", "application/json");
5518
- res.end(Buffer.from(arrayBuffer));
5519
- return;
5520
- }
5521
- const systemMessagesConcatenated = req.body.system.map((x) => x.text).join("\n\n\n");
5522
- let messages = convertClaudeCodeMessagesToVercelAISdkMessages(
5523
- req.body.messages
5524
- );
5525
- const tools2 = convertClaudeCodeToolsToVercelAISdkTools(
5526
- req.body.tools
5527
- );
5528
- console.log("STREAMING TEXT");
5529
- const result = streamText2({
5530
- model,
5531
- // Should be a LanguageModelV1
5532
- // TIP FOR DEBUGGING IF YOU RUN INTO ISSUES / ERRORS REGARDING THE MESSAGES FORMAT. STORE THE 'raw' VARIABLE TO A FILE (fs.write)
5533
- // AND COPY THE CONTENT INTO THE messages: BELOW, TYPESCRIPT WILL TELL YOU WHAT IS WRONG WHICH IS USUALLY EASIER TO READ THAN THE
5534
- // ERROR OUTPUT IN THE LOGS.
5535
- messages,
5536
- system: systemMessagesConcatenated || "",
5537
- // prepareStep could be used here to set the model for the first step or change other params
5538
- maxOutputTokens: req.body.max_tokens,
5539
- temperature: req.body.temperature,
5540
- maxRetries: 2,
5541
- providerOptions: {
5542
- metadata: {
5543
- user_id: req.body.metadata?.user_id
5544
- }
5545
- },
5546
- tools: tools2,
5547
- onFinish: (data) => console.log("[EXULU] Finished stream"),
5548
- onError: (error) => console.error("[EXULU] chat stream error.", error)
5549
- // stopWhen: [stepCountIs(1)],
5595
+ const client2 = new Anthropic({
5596
+ apiKey: anthropicApiKey
5550
5597
  });
5551
- let allChunks = [];
5552
- result.consumeStream();
5553
- const responses = [];
5554
- try {
5555
- for await (const uiMessage of readUIMessageStream({
5556
- stream: result.toUIMessageStream()
5557
- })) {
5558
- console.log("Streaming chunk:", uiMessage);
5559
- const message = {
5560
- type: "message",
5561
- role: uiMessage.role,
5562
- content: uiMessage.parts.map((part) => {
5563
- if (part.type.includes("tool-")) {
5564
- const type = part.type;
5565
- part.type = "tool_use";
5566
- part.name = type.replace("tool-", "");
5567
- part.id = part.toolCallId;
5568
- }
5569
- return part;
5570
- })
5571
- };
5572
- responses.push(message);
5573
- console.log("Wrote message to response", message);
5598
+ for await (const event of client2.messages.stream(req.body)) {
5599
+ console.log("[EXULU] Event", event);
5600
+ if (event.message?.usage) {
5574
5601
  }
5575
- } catch (err) {
5576
- console.error("Stream error:", err);
5577
- } finally {
5578
- const jsonString = JSON.stringify(responses[responses.length - 1]);
5579
- const arrayBuffer = new TextEncoder().encode(jsonString).buffer;
5580
- res.setHeader("Content-Type", "application/json");
5581
- res.end(Buffer.from(arrayBuffer));
5582
- return;
5602
+ const msg = `event: ${event.type}
5603
+ data: ${JSON.stringify(event)}
5604
+
5605
+ `;
5606
+ res.write(msg);
5583
5607
  }
5608
+ res.write("event: done\ndata: [DONE]\n\n");
5609
+ res.end();
5584
5610
  } catch (error) {
5585
5611
  console.error("[PROXY] Manual proxy error:", error);
5586
5612
  if (!res.headersSent) {
@@ -5595,97 +5621,6 @@ Mood: friendly and intelligent.
5595
5621
  app.use(express.static("public"));
5596
5622
  return app;
5597
5623
  };
5598
- var convertClaudeCodeToolsToVercelAISdkTools = (tools) => {
5599
- const result = {};
5600
- for (const tool2 of tools) {
5601
- const mySchema = jsonSchema(tool2.input_schema);
5602
- tools[tool2.name] = {
5603
- id: tool2.name,
5604
- name: tool2.name,
5605
- description: tool2.description,
5606
- inputSchema: mySchema
5607
- };
5608
- }
5609
- return result;
5610
- };
5611
- var convertClaudeCodeMessagesToVercelAISdkMessages = (messages) => {
5612
- let raw = messages.map((msg) => {
5613
- if (!msg.role) {
5614
- msg.role = "assistant";
5615
- }
5616
- delete msg.id;
5617
- if (!Array.isArray(msg.content)) {
5618
- return {
5619
- role: msg.role,
5620
- content: msg.content
5621
- };
5622
- }
5623
- if (msg.content.some((part) => part.type === "tool_result")) {
5624
- msg.role = "tool";
5625
- }
5626
- let parts = msg.content.map((part) => {
5627
- if (part.type === "step-start") {
5628
- return void 0;
5629
- }
5630
- if (part.type === "reasoning") {
5631
- const content = part.text?.length > 1 ? part.text : part.content;
5632
- return {
5633
- type: "reasoning",
5634
- text: content || "No reasoning content provided"
5635
- };
5636
- }
5637
- if (part.type === "tool_use") {
5638
- part.type = "tool-call";
5639
- }
5640
- if (part.type === "tool_result") {
5641
- part.type = "tool-result";
5642
- part.output = {
5643
- type: "text",
5644
- value: part.text || part.content
5645
- };
5646
- part.text = null;
5647
- part.content = null;
5648
- if (!part.name && part.tool_use_id) {
5649
- const allParts = raw.map((x) => x.content).flat();
5650
- const result = allParts.find((x) => {
5651
- return x.toolCallId === part.tool_use_id && x.name;
5652
- });
5653
- console.log("FIND RESULT!!!!", result);
5654
- if (result) {
5655
- part.name = result.name;
5656
- } else {
5657
- part.name = "...";
5658
- }
5659
- }
5660
- }
5661
- if (part.tool_use_id) {
5662
- part.toolCallId = part.tool_use_id;
5663
- delete part.tool_use_id;
5664
- }
5665
- return {
5666
- type: part.type,
5667
- ...part.text || part.content ? { text: part.text || part.content } : {},
5668
- ...part.toolCallId ? { toolCallId: part.toolCallId } : {},
5669
- ...part.name ? { toolName: part.name } : {},
5670
- ...part.input ? { input: part.input } : {},
5671
- ...part.output ? { output: part.output } : {},
5672
- ...part.cache_control?.type ? {
5673
- providerOptions: {
5674
- anthropic: { cacheControl: { type: part.cache_control?.type } }
5675
- }
5676
- } : {}
5677
- };
5678
- });
5679
- parts = parts.filter((part) => part !== void 0);
5680
- return {
5681
- role: msg.role,
5682
- id: msg.id,
5683
- content: parts
5684
- };
5685
- });
5686
- raw = raw.filter((msg) => msg !== void 0);
5687
- return raw;
5688
- };
5689
5624
  var createCustomAnthropicStreamingMessage = (message) => {
5690
5625
  const responseData = {
5691
5626
  type: "message",
@@ -5748,8 +5683,6 @@ var createWorkers = async (queues2, logger, contexts, _logsDir, tracer) => {
5748
5683
  }, data.role, bullmqJob.id);
5749
5684
  return result;
5750
5685
  }
5751
- if (bullmqJob.data.type === "workflow") {
5752
- }
5753
5686
  } catch (error) {
5754
5687
  await db3.from("jobs").where({ redis: bullmqJob.id }).update({
5755
5688
  status: "failed",
@@ -5816,7 +5749,7 @@ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mc
5816
5749
  import { randomUUID as randomUUID4 } from "crypto";
5817
5750
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
5818
5751
  import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
5819
- import { z as z3 } from "zod";
5752
+ import { z as z2 } from "zod";
5820
5753
  import "express";
5821
5754
  import "@opentelemetry/api";
5822
5755
  var SESSION_ID_HEADER = "mcp-session-id";
@@ -5873,7 +5806,7 @@ var ExuluMCP = class {
5873
5806
  {
5874
5807
  title: "Code Review",
5875
5808
  description: "Review code for best practices and potential issues",
5876
- argsSchema: { code: z3.string() }
5809
+ argsSchema: { code: z2.string() }
5877
5810
  },
5878
5811
  ({ code }) => ({
5879
5812
  messages: [{
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@exulu/backend",
3
3
  "author": "Qventu Bv.",
4
- "version": "1.24.0",
4
+ "version": "1.25.0",
5
5
  "main": "./dist/index.js",
6
6
  "private": false,
7
7
  "publishConfig": {
@@ -66,7 +66,7 @@
66
66
  "@opentelemetry/sdk-node": "^0.203.0",
67
67
  "@opentelemetry/semantic-conventions": "^1.36.0",
68
68
  "@opentelemetry/winston-transport": "^0.14.1",
69
- "ai": "^5.0.44",
69
+ "ai": "^5.0.56",
70
70
  "apollo-server": "^3.13.0",
71
71
  "bcryptjs": "^3.0.2",
72
72
  "body-parser": "^2.2.0",
@@ -78,6 +78,7 @@
78
78
  "csv-parse": "^5.6.0",
79
79
  "dotenv": "^16.5.0",
80
80
  "express": "^5.1.0",
81
+ "express-http-proxy": "^2.1.2",
81
82
  "graphql": "^16.11.0",
82
83
  "graphql-tools": "^9.0.18",
83
84
  "graphql-type-json": "^0.3.2",