@exulu/backend 1.8.1 → 1.9.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,9 +1,9 @@
1
- ## [1.8.1](https://github.com/Qventu/exulu-backend/compare/v1.8.0...v1.8.1) (2025-08-06)
1
+ # [1.9.0](https://github.com/Qventu/exulu-backend/compare/v1.8.1...v1.9.0) (2025-08-13)
2
2
 
3
3
 
4
- ### Bug Fixes
4
+ ### Features
5
5
 
6
- * json import syntax ([9eff3aa](https://github.com/Qventu/exulu-backend/commit/9eff3aabebe649ccae8a4cd6e1df81aef0799ae5))
6
+ * improve scroll behaviour in chat and add apiKeyProvider to agents ([505155b](https://github.com/Qventu/exulu-backend/commit/505155b853707cefb1a5205f78064845391473d9))
7
7
 
8
8
  # [1.1.0](https://github.com/Qventu/exulu-backend/compare/v1.0.1...v1.1.0) (2025-07-30)
9
9
 
package/dist/index.cjs CHANGED
@@ -446,37 +446,6 @@ var ExuluEvalUtils = {
446
446
  }
447
447
  };
448
448
 
449
- // src/registry/utils/claude-messages.ts
450
- var CLAUDE_MESSAGES = {
451
- anthropic_token_variable_not_encrypted: `
452
- \x1B[41m -- Anthropic token variable set by your admin is not encrypted. This poses a security risk. Please contact your admin to fix the variable used for your key. --
453
- \x1B[0m`,
454
- anthropic_token_variable_not_found: `
455
- \x1B[41m -- Anthropic token variable not found. Please contact to fix the variable used for your key. --
456
- \x1B[0m`,
457
- authentication_error: `
458
- \x1B[41m -- Authentication error please check your IMP token and try again. --
459
- \x1B[0m`,
460
- missing_body: `
461
- \x1B[41m -- Missing body Anthropic response. --
462
- \x1B[0m`,
463
- missing_nextauth_secret: `
464
- \x1B[41m -- Missing NEXTAUTH_SECRET in environment variables on the server. --
465
- \x1B[0m`,
466
- not_enabled: `
467
- \x1B[31m
468
- \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557
469
- \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
470
- \u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
471
- \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
472
- \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2554\u255D \u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
473
- \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D
474
- Intelligence Management Platform
475
- \x1B[0m
476
- \x1B[41m -- Your account has not been enabled to use Claude Code, please contact your admin or enable Claude Code in the user settings. --
477
- \x1B[0m`
478
- };
479
-
480
449
  // src/registry/classes.ts
481
450
  var import_crypto_js = __toESM(require("crypto-js"), 1);
482
451
  function sanitizeToolName(name) {
@@ -488,12 +457,13 @@ function sanitizeToolName(name) {
488
457
  }
489
458
  return sanitized;
490
459
  }
491
- var convertToolsArrayToObject = (tools, configs) => {
460
+ var convertToolsArrayToObject = (tools, configs, providerApiKey) => {
492
461
  if (!tools) return {};
493
462
  const sanitizedTools = tools ? tools.map((tool2) => ({
494
463
  ...tool2,
495
464
  name: sanitizeToolName(tool2.name)
496
465
  })) : [];
466
+ console.log("[EXULU] Sanitized tools", sanitizedTools);
497
467
  const askForConfirmation = {
498
468
  description: "Ask the user for confirmation.",
499
469
  parameters: import_zod2.z.object({
@@ -515,11 +485,13 @@ var convertToolsArrayToObject = (tools, configs) => {
515
485
  if (config) {
516
486
  config = await hydrateVariables(config || []);
517
487
  }
488
+ console.log("[EXULU] Config", config);
518
489
  return await cur.tool.execute({
519
490
  ...inputs,
520
491
  // Convert config to object format if a config object
521
492
  // is available, after we added the .value property
522
493
  // by hydrating it from the variables table.
494
+ providerApiKey,
523
495
  config: config ? config.config.reduce((acc, curr) => {
524
496
  acc[curr.name] = curr.value;
525
497
  return acc;
@@ -616,9 +588,10 @@ var ExuluAgent = class {
616
588
  }),
617
589
  description: `A function that calls an AI agent named: ${this.name}. The agent does the following: ${this.description}.`,
618
590
  config: [],
619
- execute: async ({ prompt }) => {
591
+ execute: async ({ prompt, config, providerApiKey }) => {
620
592
  return await this.generateSync({
621
593
  prompt,
594
+ providerApiKey,
622
595
  statistics: {
623
596
  label: "",
624
597
  trigger: "tool"
@@ -627,7 +600,7 @@ var ExuluAgent = class {
627
600
  }
628
601
  });
629
602
  };
630
- generateSync = async ({ messages, prompt, tools, statistics, configs }) => {
603
+ generateSync = async ({ messages, prompt, tools, statistics, toolConfigs, providerApiKey }) => {
631
604
  if (!this.model) {
632
605
  throw new Error("Model is required for streaming.");
633
606
  }
@@ -637,13 +610,17 @@ var ExuluAgent = class {
637
610
  if (prompt && messages) {
638
611
  throw new Error("Prompt and messages cannot be provided at the same time.");
639
612
  }
613
+ const model = this.model.create({
614
+ apiKey: providerApiKey
615
+ });
640
616
  const { text } = await (0, import_ai.generateText)({
641
- model: this.model,
617
+ model,
618
+ // Should be a LanguageModelV1
642
619
  system: "You are a helpful assistant. When you use a tool to answer a question do not explicitly comment on the result of the tool call unless the user has explicitly you to do something with the result.",
643
620
  messages,
644
621
  prompt,
645
622
  maxRetries: 2,
646
- tools: convertToolsArrayToObject(tools, configs),
623
+ tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey),
647
624
  maxSteps: 5
648
625
  });
649
626
  if (statistics) {
@@ -657,7 +634,7 @@ var ExuluAgent = class {
657
634
  }
658
635
  return text;
659
636
  };
660
- generateStream = ({ messages, prompt, tools, statistics, configs }) => {
637
+ generateStream = ({ messages, prompt, tools, statistics, toolConfigs, providerApiKey }) => {
661
638
  if (!this.model) {
662
639
  throw new Error("Model is required for streaming.");
663
640
  }
@@ -667,13 +644,19 @@ var ExuluAgent = class {
667
644
  if (prompt && messages) {
668
645
  throw new Error("Prompt and messages cannot be provided at the same time.");
669
646
  }
647
+ const model = this.model.create({
648
+ apiKey: providerApiKey
649
+ });
650
+ console.log("[EXULU] Model provider key", providerApiKey);
651
+ console.log("[EXULU] Tool configs", toolConfigs);
670
652
  return (0, import_ai.streamText)({
671
- model: this.model,
653
+ model,
654
+ // Should be a LanguageModelV1
672
655
  messages,
673
656
  prompt,
674
657
  system: "You are a helpful assistant. When you use a tool to answer a question do not explicitly comment on the result of the tool call unless the user has explicitly you to do something with the result.",
675
658
  maxRetries: 2,
676
- tools: convertToolsArrayToObject(tools, configs),
659
+ tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey),
677
660
  maxSteps: 5,
678
661
  onError: (error) => console.error("[EXULU] chat stream error.", error),
679
662
  onFinish: async ({ response, usage }) => {
@@ -930,8 +913,23 @@ var ExuluEval = class {
930
913
  if (!data.prompt) {
931
914
  throw new Error("Prompt is required for running an agent.");
932
915
  }
916
+ const { db: db4 } = await postgresClient();
917
+ const variableName = runner.agent.providerApiKey;
918
+ const variable = await db4.from("variables").where({ name: variableName }).first();
919
+ if (!variable) {
920
+ throw new Error(`Provider API key for variable "${runner.agent.providerApiKey}" not found.`);
921
+ }
922
+ let providerApiKey = variable.value;
923
+ if (!variable.encrypted) {
924
+ throw new Error(`Provider API key for variable "${runner.agent.providerApiKey}" is not encrypted, for security reasons you are only allowed to use encrypted variables for provider API keys.`);
925
+ }
926
+ if (variable.encrypted) {
927
+ const bytes = import_crypto_js.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
928
+ providerApiKey = bytes.toString(import_crypto_js.default.enc.Utf8);
929
+ }
933
930
  const result = await runner.agent.generateSync({
934
- prompt: data.prompt
931
+ prompt: data.prompt,
932
+ providerApiKey
935
933
  });
936
934
  data.result = result;
937
935
  }
@@ -2675,6 +2673,10 @@ var agentsSchema = {
2675
2673
  name: "description",
2676
2674
  type: "text"
2677
2675
  },
2676
+ {
2677
+ name: "providerApiKey",
2678
+ type: "text"
2679
+ },
2678
2680
  {
2679
2681
  name: "extensions",
2680
2682
  type: "json"
@@ -3122,6 +3124,39 @@ var createUppyRoutes = async (app) => {
3122
3124
  var import_utils2 = require("@apollo/utils.keyvaluecache");
3123
3125
  var import_body_parser = __toESM(require("body-parser"), 1);
3124
3126
  var import_crypto_js3 = __toESM(require("crypto-js"), 1);
3127
+
3128
+ // src/registry/utils/claude-messages.ts
3129
+ var CLAUDE_MESSAGES = {
3130
+ anthropic_token_variable_not_encrypted: `
3131
+ \x1B[41m -- Anthropic token variable set by your admin is not encrypted. This poses a security risk. Please contact your admin to fix the variable used for your key. --
3132
+ \x1B[0m`,
3133
+ anthropic_token_variable_not_found: `
3134
+ \x1B[41m -- Anthropic token variable not found. Please contact to fix the variable used for your key. --
3135
+ \x1B[0m`,
3136
+ authentication_error: `
3137
+ \x1B[41m -- Authentication error please check your IMP token and try again. --
3138
+ \x1B[0m`,
3139
+ missing_body: `
3140
+ \x1B[41m -- Missing body Anthropic response. --
3141
+ \x1B[0m`,
3142
+ missing_nextauth_secret: `
3143
+ \x1B[41m -- Missing NEXTAUTH_SECRET in environment variables on the server. --
3144
+ \x1B[0m`,
3145
+ not_enabled: `
3146
+ \x1B[31m
3147
+ \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557
3148
+ \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
3149
+ \u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
3150
+ \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
3151
+ \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2554\u255D \u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
3152
+ \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D
3153
+ Intelligence Management Platform
3154
+ \x1B[0m
3155
+ \x1B[41m -- Your account has not been enabled to use Claude Code, please contact your admin or enable Claude Code in the user settings. --
3156
+ \x1B[0m`
3157
+ };
3158
+
3159
+ // src/registry/routes.ts
3125
3160
  var REQUEST_SIZE_LIMIT = "50mb";
3126
3161
  var global_queues = {
3127
3162
  logs_cleaner: "logs-cleaner"
@@ -3324,8 +3359,8 @@ var createExpressRoutes = async (app, agents, tools, workflows, contexts) => {
3324
3359
  name: agent.name,
3325
3360
  id: agent.id,
3326
3361
  description: agent.description,
3327
- provider: backend?.model?.provider,
3328
- model: backend?.model?.modelId,
3362
+ provider: backend?.model?.create({ apiKey: "" }).provider,
3363
+ model: backend?.model?.create({ apiKey: "" }).modelId,
3329
3364
  active: agent.active,
3330
3365
  public: agent.public,
3331
3366
  type: agent.type,
@@ -3333,6 +3368,7 @@ var createExpressRoutes = async (app, agents, tools, workflows, contexts) => {
3333
3368
  rateLimit: backend?.rateLimit,
3334
3369
  streaming: backend?.streaming,
3335
3370
  capabilities: backend?.capabilities,
3371
+ providerApiKey: agent.providerApiKey,
3336
3372
  // todo add contexts
3337
3373
  availableTools: tools,
3338
3374
  enabledTools: agent.tools
@@ -4057,13 +4093,33 @@ var createExpressRoutes = async (app, agents, tools, workflows, contexts) => {
4057
4093
  return;
4058
4094
  }
4059
4095
  console.log("[EXULU] agent tools", agentInstance.tools);
4060
- const enabledTools = agentInstance.tools.map((tool2) => tools.find(({ id }) => id === tool2)).filter(Boolean);
4096
+ const enabledTools = agentInstance.tools.map(({ config, toolId }) => tools.find(({ id }) => id === toolId)).filter(Boolean);
4061
4097
  console.log("[EXULU] enabled tools", enabledTools);
4098
+ const variableName = agentInstance.providerApiKey;
4099
+ const variable = await db3.from("variables").where({ name: variableName }).first();
4100
+ if (!variable) {
4101
+ res.status(400).json({
4102
+ message: "Provider API key variable not found."
4103
+ });
4104
+ return;
4105
+ }
4106
+ let providerApiKey = variable.value;
4107
+ if (!variable.encrypted) {
4108
+ res.status(400).json({
4109
+ message: "Provider API key variable not encrypted, for security reasons you are only allowed to use encrypted variables for provider API keys."
4110
+ });
4111
+ return;
4112
+ }
4113
+ if (variable.encrypted) {
4114
+ const bytes = import_crypto_js3.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
4115
+ providerApiKey = bytes.toString(import_crypto_js3.default.enc.Utf8);
4116
+ }
4062
4117
  if (!!stream) {
4063
4118
  const result = agent.generateStream({
4064
4119
  messages: req.body.messages,
4065
4120
  tools: enabledTools,
4066
- configs: agentInstance.tools,
4121
+ providerApiKey,
4122
+ toolConfigs: agentInstance.tools,
4067
4123
  statistics: {
4068
4124
  label: agent.name,
4069
4125
  trigger: "agent"
@@ -4075,7 +4131,8 @@ var createExpressRoutes = async (app, agents, tools, workflows, contexts) => {
4075
4131
  const response = await agent.generateSync({
4076
4132
  messages: req.body.messages,
4077
4133
  tools: enabledTools.map((tool2) => tool2.tool),
4078
- configs: agentInstance.tools,
4134
+ providerApiKey,
4135
+ toolConfigs: agentInstance.tools,
4079
4136
  statistics: {
4080
4137
  label: agent.name,
4081
4138
  trigger: "agent"
@@ -4690,7 +4747,6 @@ var claudeCodeAgent = new ExuluAgent({
4690
4747
 
4691
4748
  // src/templates/agents/claude-opus-4.ts
4692
4749
  var import_anthropic = require("@ai-sdk/anthropic");
4693
- var import_zod4 = require("zod");
4694
4750
  var agentId2 = "5434-5678-9143-2590";
4695
4751
  var defaultAgent = new ExuluAgent({
4696
4752
  id: `${agentId2}-default-claude-4-opus-agent`,
@@ -4708,17 +4764,21 @@ var defaultAgent = new ExuluAgent({
4708
4764
  config: {
4709
4765
  name: `Default agent`,
4710
4766
  instructions: "You are a helpful assistant.",
4711
- model: (0, import_anthropic.anthropic)("claude-4-opus-20250514"),
4712
- // todo add a field of type string that adds a dropdown list from which the user can select the model
4713
- // todo for each model, check which provider is used, and require the admin to add one or multiple
4714
- // API keys for the provider (which we can then auto-rotate).
4715
- // todo also add custom fields for rate limiting, so the admin can set custom rate limits for the agent
4716
- // and allow him/her to decide if the rate limit is per user or per agent.
4717
- // todo finally allow switching on or off immutable audit logs on the agent. Which then enables OTEL
4718
- // and stores the logs into the pre-defined storage.
4719
- custom: import_zod4.z.object({
4720
- apiKey: import_zod4.z.string()
4721
- })
4767
+ model: {
4768
+ create: ({ apiKey }) => {
4769
+ const anthropic2 = (0, import_anthropic.createAnthropic)({
4770
+ apiKey
4771
+ });
4772
+ return anthropic2("claude-4-opus-20250514");
4773
+ }
4774
+ // todo add a field of type string that adds a dropdown list from which the user can select the model
4775
+ // todo for each model, check which provider is used, and require the admin to add one or multiple
4776
+ // API keys for the provider (which we can then auto-rotate).
4777
+ // todo also add custom fields for rate limiting, so the admin can set custom rate limits for the agent
4778
+ // and allow him/her to decide if the rate limit is per user or per agent.
4779
+ // todo finally allow switching on or off immutable audit logs on the agent. Which then enables OTEL
4780
+ // and stores the logs into the pre-defined storage.
4781
+ }
4722
4782
  }
4723
4783
  });
4724
4784
 
package/dist/index.d.cts CHANGED
@@ -91,9 +91,16 @@ declare const ExuluZodFileType: ({ name, label, description, allowedFileTypes }:
91
91
  type ExuluAgentConfig = {
92
92
  name: string;
93
93
  instructions: string;
94
- model: LanguageModelV1;
94
+ model: {
95
+ create: ({ apiKey }: {
96
+ apiKey: string;
97
+ }) => LanguageModelV1;
98
+ };
95
99
  outputSchema?: ZodSchema;
96
- custom?: ZodSchema;
100
+ custom?: {
101
+ name: string;
102
+ description: string;
103
+ }[];
97
104
  memory?: {
98
105
  lastMessages: number;
99
106
  vector: boolean;
@@ -141,7 +148,11 @@ declare class ExuluAgent {
141
148
  rateLimit?: RateLimiterRule;
142
149
  config?: ExuluAgentConfig | undefined;
143
150
  evals?: ExuluAgentEval[];
144
- model?: LanguageModelV1;
151
+ model?: {
152
+ create: ({ apiKey }: {
153
+ apiKey: string;
154
+ }) => LanguageModelV1;
155
+ };
145
156
  capabilities: {
146
157
  images: string[];
147
158
  files: string[];
@@ -150,19 +161,21 @@ declare class ExuluAgent {
150
161
  };
151
162
  constructor({ id, name, description, config, rateLimit, capabilities, type, evals }: ExuluAgentParams);
152
163
  tool: () => ExuluTool;
153
- generateSync: ({ messages, prompt, tools, statistics, configs }: {
164
+ generateSync: ({ messages, prompt, tools, statistics, toolConfigs, providerApiKey }: {
154
165
  messages?: Message[];
155
166
  prompt?: string;
156
167
  tools?: ExuluTool[];
157
168
  statistics?: ExuluStatisticParams;
158
- configs?: ExuluAgentToolConfig[];
169
+ toolConfigs?: ExuluAgentToolConfig[];
170
+ providerApiKey: string;
159
171
  }) => Promise<string>;
160
- generateStream: ({ messages, prompt, tools, statistics, configs }: {
172
+ generateStream: ({ messages, prompt, tools, statistics, toolConfigs, providerApiKey }: {
161
173
  messages?: Message[];
162
174
  prompt?: string;
163
175
  tools?: ExuluTool[];
164
176
  statistics?: ExuluStatisticParams;
165
- configs?: ExuluAgentToolConfig[];
177
+ toolConfigs?: ExuluAgentToolConfig[];
178
+ providerApiKey: string;
166
179
  }) => ai.StreamTextResult<Record<string, Tool>, never>;
167
180
  }
168
181
  type VectorOperationResponse = Promise<{
@@ -268,7 +281,9 @@ type ExuluEvalRunnerInstance = {
268
281
  type ExuluEvalRunner = ({ data, runner }: {
269
282
  data: ExuluEvalInput;
270
283
  runner: {
271
- agent?: ExuluAgent;
284
+ agent?: ExuluAgent & {
285
+ providerApiKey: string;
286
+ };
272
287
  workflow?: ExuluWorkflow;
273
288
  };
274
289
  }) => Promise<{
package/dist/index.d.ts CHANGED
@@ -91,9 +91,16 @@ declare const ExuluZodFileType: ({ name, label, description, allowedFileTypes }:
91
91
  type ExuluAgentConfig = {
92
92
  name: string;
93
93
  instructions: string;
94
- model: LanguageModelV1;
94
+ model: {
95
+ create: ({ apiKey }: {
96
+ apiKey: string;
97
+ }) => LanguageModelV1;
98
+ };
95
99
  outputSchema?: ZodSchema;
96
- custom?: ZodSchema;
100
+ custom?: {
101
+ name: string;
102
+ description: string;
103
+ }[];
97
104
  memory?: {
98
105
  lastMessages: number;
99
106
  vector: boolean;
@@ -141,7 +148,11 @@ declare class ExuluAgent {
141
148
  rateLimit?: RateLimiterRule;
142
149
  config?: ExuluAgentConfig | undefined;
143
150
  evals?: ExuluAgentEval[];
144
- model?: LanguageModelV1;
151
+ model?: {
152
+ create: ({ apiKey }: {
153
+ apiKey: string;
154
+ }) => LanguageModelV1;
155
+ };
145
156
  capabilities: {
146
157
  images: string[];
147
158
  files: string[];
@@ -150,19 +161,21 @@ declare class ExuluAgent {
150
161
  };
151
162
  constructor({ id, name, description, config, rateLimit, capabilities, type, evals }: ExuluAgentParams);
152
163
  tool: () => ExuluTool;
153
- generateSync: ({ messages, prompt, tools, statistics, configs }: {
164
+ generateSync: ({ messages, prompt, tools, statistics, toolConfigs, providerApiKey }: {
154
165
  messages?: Message[];
155
166
  prompt?: string;
156
167
  tools?: ExuluTool[];
157
168
  statistics?: ExuluStatisticParams;
158
- configs?: ExuluAgentToolConfig[];
169
+ toolConfigs?: ExuluAgentToolConfig[];
170
+ providerApiKey: string;
159
171
  }) => Promise<string>;
160
- generateStream: ({ messages, prompt, tools, statistics, configs }: {
172
+ generateStream: ({ messages, prompt, tools, statistics, toolConfigs, providerApiKey }: {
161
173
  messages?: Message[];
162
174
  prompt?: string;
163
175
  tools?: ExuluTool[];
164
176
  statistics?: ExuluStatisticParams;
165
- configs?: ExuluAgentToolConfig[];
177
+ toolConfigs?: ExuluAgentToolConfig[];
178
+ providerApiKey: string;
166
179
  }) => ai.StreamTextResult<Record<string, Tool>, never>;
167
180
  }
168
181
  type VectorOperationResponse = Promise<{
@@ -268,7 +281,9 @@ type ExuluEvalRunnerInstance = {
268
281
  type ExuluEvalRunner = ({ data, runner }: {
269
282
  data: ExuluEvalInput;
270
283
  runner: {
271
- agent?: ExuluAgent;
284
+ agent?: ExuluAgent & {
285
+ providerApiKey: string;
286
+ };
272
287
  workflow?: ExuluWorkflow;
273
288
  };
274
289
  }) => Promise<{
package/dist/index.js CHANGED
@@ -403,37 +403,6 @@ var ExuluEvalUtils = {
403
403
  }
404
404
  };
405
405
 
406
- // src/registry/utils/claude-messages.ts
407
- var CLAUDE_MESSAGES = {
408
- anthropic_token_variable_not_encrypted: `
409
- \x1B[41m -- Anthropic token variable set by your admin is not encrypted. This poses a security risk. Please contact your admin to fix the variable used for your key. --
410
- \x1B[0m`,
411
- anthropic_token_variable_not_found: `
412
- \x1B[41m -- Anthropic token variable not found. Please contact to fix the variable used for your key. --
413
- \x1B[0m`,
414
- authentication_error: `
415
- \x1B[41m -- Authentication error please check your IMP token and try again. --
416
- \x1B[0m`,
417
- missing_body: `
418
- \x1B[41m -- Missing body Anthropic response. --
419
- \x1B[0m`,
420
- missing_nextauth_secret: `
421
- \x1B[41m -- Missing NEXTAUTH_SECRET in environment variables on the server. --
422
- \x1B[0m`,
423
- not_enabled: `
424
- \x1B[31m
425
- \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557
426
- \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
427
- \u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
428
- \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
429
- \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2554\u255D \u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
430
- \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D
431
- Intelligence Management Platform
432
- \x1B[0m
433
- \x1B[41m -- Your account has not been enabled to use Claude Code, please contact your admin or enable Claude Code in the user settings. --
434
- \x1B[0m`
435
- };
436
-
437
406
  // src/registry/classes.ts
438
407
  import CryptoJS from "crypto-js";
439
408
  function sanitizeToolName(name) {
@@ -445,12 +414,13 @@ function sanitizeToolName(name) {
445
414
  }
446
415
  return sanitized;
447
416
  }
448
- var convertToolsArrayToObject = (tools, configs) => {
417
+ var convertToolsArrayToObject = (tools, configs, providerApiKey) => {
449
418
  if (!tools) return {};
450
419
  const sanitizedTools = tools ? tools.map((tool2) => ({
451
420
  ...tool2,
452
421
  name: sanitizeToolName(tool2.name)
453
422
  })) : [];
423
+ console.log("[EXULU] Sanitized tools", sanitizedTools);
454
424
  const askForConfirmation = {
455
425
  description: "Ask the user for confirmation.",
456
426
  parameters: z.object({
@@ -472,11 +442,13 @@ var convertToolsArrayToObject = (tools, configs) => {
472
442
  if (config) {
473
443
  config = await hydrateVariables(config || []);
474
444
  }
445
+ console.log("[EXULU] Config", config);
475
446
  return await cur.tool.execute({
476
447
  ...inputs,
477
448
  // Convert config to object format if a config object
478
449
  // is available, after we added the .value property
479
450
  // by hydrating it from the variables table.
451
+ providerApiKey,
480
452
  config: config ? config.config.reduce((acc, curr) => {
481
453
  acc[curr.name] = curr.value;
482
454
  return acc;
@@ -573,9 +545,10 @@ var ExuluAgent = class {
573
545
  }),
574
546
  description: `A function that calls an AI agent named: ${this.name}. The agent does the following: ${this.description}.`,
575
547
  config: [],
576
- execute: async ({ prompt }) => {
548
+ execute: async ({ prompt, config, providerApiKey }) => {
577
549
  return await this.generateSync({
578
550
  prompt,
551
+ providerApiKey,
579
552
  statistics: {
580
553
  label: "",
581
554
  trigger: "tool"
@@ -584,7 +557,7 @@ var ExuluAgent = class {
584
557
  }
585
558
  });
586
559
  };
587
- generateSync = async ({ messages, prompt, tools, statistics, configs }) => {
560
+ generateSync = async ({ messages, prompt, tools, statistics, toolConfigs, providerApiKey }) => {
588
561
  if (!this.model) {
589
562
  throw new Error("Model is required for streaming.");
590
563
  }
@@ -594,13 +567,17 @@ var ExuluAgent = class {
594
567
  if (prompt && messages) {
595
568
  throw new Error("Prompt and messages cannot be provided at the same time.");
596
569
  }
570
+ const model = this.model.create({
571
+ apiKey: providerApiKey
572
+ });
597
573
  const { text } = await generateText({
598
- model: this.model,
574
+ model,
575
+ // Should be a LanguageModelV1
599
576
  system: "You are a helpful assistant. When you use a tool to answer a question do not explicitly comment on the result of the tool call unless the user has explicitly you to do something with the result.",
600
577
  messages,
601
578
  prompt,
602
579
  maxRetries: 2,
603
- tools: convertToolsArrayToObject(tools, configs),
580
+ tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey),
604
581
  maxSteps: 5
605
582
  });
606
583
  if (statistics) {
@@ -614,7 +591,7 @@ var ExuluAgent = class {
614
591
  }
615
592
  return text;
616
593
  };
617
- generateStream = ({ messages, prompt, tools, statistics, configs }) => {
594
+ generateStream = ({ messages, prompt, tools, statistics, toolConfigs, providerApiKey }) => {
618
595
  if (!this.model) {
619
596
  throw new Error("Model is required for streaming.");
620
597
  }
@@ -624,13 +601,19 @@ var ExuluAgent = class {
624
601
  if (prompt && messages) {
625
602
  throw new Error("Prompt and messages cannot be provided at the same time.");
626
603
  }
604
+ const model = this.model.create({
605
+ apiKey: providerApiKey
606
+ });
607
+ console.log("[EXULU] Model provider key", providerApiKey);
608
+ console.log("[EXULU] Tool configs", toolConfigs);
627
609
  return streamText({
628
- model: this.model,
610
+ model,
611
+ // Should be a LanguageModelV1
629
612
  messages,
630
613
  prompt,
631
614
  system: "You are a helpful assistant. When you use a tool to answer a question do not explicitly comment on the result of the tool call unless the user has explicitly you to do something with the result.",
632
615
  maxRetries: 2,
633
- tools: convertToolsArrayToObject(tools, configs),
616
+ tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey),
634
617
  maxSteps: 5,
635
618
  onError: (error) => console.error("[EXULU] chat stream error.", error),
636
619
  onFinish: async ({ response, usage }) => {
@@ -887,8 +870,23 @@ var ExuluEval = class {
887
870
  if (!data.prompt) {
888
871
  throw new Error("Prompt is required for running an agent.");
889
872
  }
873
+ const { db: db4 } = await postgresClient();
874
+ const variableName = runner.agent.providerApiKey;
875
+ const variable = await db4.from("variables").where({ name: variableName }).first();
876
+ if (!variable) {
877
+ throw new Error(`Provider API key for variable "${runner.agent.providerApiKey}" not found.`);
878
+ }
879
+ let providerApiKey = variable.value;
880
+ if (!variable.encrypted) {
881
+ throw new Error(`Provider API key for variable "${runner.agent.providerApiKey}" is not encrypted, for security reasons you are only allowed to use encrypted variables for provider API keys.`);
882
+ }
883
+ if (variable.encrypted) {
884
+ const bytes = CryptoJS.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
885
+ providerApiKey = bytes.toString(CryptoJS.enc.Utf8);
886
+ }
890
887
  const result = await runner.agent.generateSync({
891
- prompt: data.prompt
888
+ prompt: data.prompt,
889
+ providerApiKey
892
890
  });
893
891
  data.result = result;
894
892
  }
@@ -2632,6 +2630,10 @@ var agentsSchema = {
2632
2630
  name: "description",
2633
2631
  type: "text"
2634
2632
  },
2633
+ {
2634
+ name: "providerApiKey",
2635
+ type: "text"
2636
+ },
2635
2637
  {
2636
2638
  name: "extensions",
2637
2639
  type: "json"
@@ -3079,6 +3081,39 @@ var createUppyRoutes = async (app) => {
3079
3081
  import { InMemoryLRUCache } from "@apollo/utils.keyvaluecache";
3080
3082
  import bodyParser from "body-parser";
3081
3083
  import CryptoJS3 from "crypto-js";
3084
+
3085
+ // src/registry/utils/claude-messages.ts
3086
+ var CLAUDE_MESSAGES = {
3087
+ anthropic_token_variable_not_encrypted: `
3088
+ \x1B[41m -- Anthropic token variable set by your admin is not encrypted. This poses a security risk. Please contact your admin to fix the variable used for your key. --
3089
+ \x1B[0m`,
3090
+ anthropic_token_variable_not_found: `
3091
+ \x1B[41m -- Anthropic token variable not found. Please contact to fix the variable used for your key. --
3092
+ \x1B[0m`,
3093
+ authentication_error: `
3094
+ \x1B[41m -- Authentication error please check your IMP token and try again. --
3095
+ \x1B[0m`,
3096
+ missing_body: `
3097
+ \x1B[41m -- Missing body Anthropic response. --
3098
+ \x1B[0m`,
3099
+ missing_nextauth_secret: `
3100
+ \x1B[41m -- Missing NEXTAUTH_SECRET in environment variables on the server. --
3101
+ \x1B[0m`,
3102
+ not_enabled: `
3103
+ \x1B[31m
3104
+ \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557
3105
+ \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
3106
+ \u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
3107
+ \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
3108
+ \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2554\u255D \u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
3109
+ \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D
3110
+ Intelligence Management Platform
3111
+ \x1B[0m
3112
+ \x1B[41m -- Your account has not been enabled to use Claude Code, please contact your admin or enable Claude Code in the user settings. --
3113
+ \x1B[0m`
3114
+ };
3115
+
3116
+ // src/registry/routes.ts
3082
3117
  var REQUEST_SIZE_LIMIT = "50mb";
3083
3118
  var global_queues = {
3084
3119
  logs_cleaner: "logs-cleaner"
@@ -3281,8 +3316,8 @@ var createExpressRoutes = async (app, agents, tools, workflows, contexts) => {
3281
3316
  name: agent.name,
3282
3317
  id: agent.id,
3283
3318
  description: agent.description,
3284
- provider: backend?.model?.provider,
3285
- model: backend?.model?.modelId,
3319
+ provider: backend?.model?.create({ apiKey: "" }).provider,
3320
+ model: backend?.model?.create({ apiKey: "" }).modelId,
3286
3321
  active: agent.active,
3287
3322
  public: agent.public,
3288
3323
  type: agent.type,
@@ -3290,6 +3325,7 @@ var createExpressRoutes = async (app, agents, tools, workflows, contexts) => {
3290
3325
  rateLimit: backend?.rateLimit,
3291
3326
  streaming: backend?.streaming,
3292
3327
  capabilities: backend?.capabilities,
3328
+ providerApiKey: agent.providerApiKey,
3293
3329
  // todo add contexts
3294
3330
  availableTools: tools,
3295
3331
  enabledTools: agent.tools
@@ -4014,13 +4050,33 @@ var createExpressRoutes = async (app, agents, tools, workflows, contexts) => {
4014
4050
  return;
4015
4051
  }
4016
4052
  console.log("[EXULU] agent tools", agentInstance.tools);
4017
- const enabledTools = agentInstance.tools.map((tool2) => tools.find(({ id }) => id === tool2)).filter(Boolean);
4053
+ const enabledTools = agentInstance.tools.map(({ config, toolId }) => tools.find(({ id }) => id === toolId)).filter(Boolean);
4018
4054
  console.log("[EXULU] enabled tools", enabledTools);
4055
+ const variableName = agentInstance.providerApiKey;
4056
+ const variable = await db3.from("variables").where({ name: variableName }).first();
4057
+ if (!variable) {
4058
+ res.status(400).json({
4059
+ message: "Provider API key variable not found."
4060
+ });
4061
+ return;
4062
+ }
4063
+ let providerApiKey = variable.value;
4064
+ if (!variable.encrypted) {
4065
+ res.status(400).json({
4066
+ message: "Provider API key variable not encrypted, for security reasons you are only allowed to use encrypted variables for provider API keys."
4067
+ });
4068
+ return;
4069
+ }
4070
+ if (variable.encrypted) {
4071
+ const bytes = CryptoJS3.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
4072
+ providerApiKey = bytes.toString(CryptoJS3.enc.Utf8);
4073
+ }
4019
4074
  if (!!stream) {
4020
4075
  const result = agent.generateStream({
4021
4076
  messages: req.body.messages,
4022
4077
  tools: enabledTools,
4023
- configs: agentInstance.tools,
4078
+ providerApiKey,
4079
+ toolConfigs: agentInstance.tools,
4024
4080
  statistics: {
4025
4081
  label: agent.name,
4026
4082
  trigger: "agent"
@@ -4032,7 +4088,8 @@ var createExpressRoutes = async (app, agents, tools, workflows, contexts) => {
4032
4088
  const response = await agent.generateSync({
4033
4089
  messages: req.body.messages,
4034
4090
  tools: enabledTools.map((tool2) => tool2.tool),
4035
- configs: agentInstance.tools,
4091
+ providerApiKey,
4092
+ toolConfigs: agentInstance.tools,
4036
4093
  statistics: {
4037
4094
  label: agent.name,
4038
4095
  trigger: "agent"
@@ -4646,8 +4703,7 @@ var claudeCodeAgent = new ExuluAgent({
4646
4703
  });
4647
4704
 
4648
4705
  // src/templates/agents/claude-opus-4.ts
4649
- import { anthropic } from "@ai-sdk/anthropic";
4650
- import { z as z3 } from "zod";
4706
+ import { createAnthropic } from "@ai-sdk/anthropic";
4651
4707
  var agentId2 = "5434-5678-9143-2590";
4652
4708
  var defaultAgent = new ExuluAgent({
4653
4709
  id: `${agentId2}-default-claude-4-opus-agent`,
@@ -4665,17 +4721,21 @@ var defaultAgent = new ExuluAgent({
4665
4721
  config: {
4666
4722
  name: `Default agent`,
4667
4723
  instructions: "You are a helpful assistant.",
4668
- model: anthropic("claude-4-opus-20250514"),
4669
- // todo add a field of type string that adds a dropdown list from which the user can select the model
4670
- // todo for each model, check which provider is used, and require the admin to add one or multiple
4671
- // API keys for the provider (which we can then auto-rotate).
4672
- // todo also add custom fields for rate limiting, so the admin can set custom rate limits for the agent
4673
- // and allow him/her to decide if the rate limit is per user or per agent.
4674
- // todo finally allow switching on or off immutable audit logs on the agent. Which then enables OTEL
4675
- // and stores the logs into the pre-defined storage.
4676
- custom: z3.object({
4677
- apiKey: z3.string()
4678
- })
4724
+ model: {
4725
+ create: ({ apiKey }) => {
4726
+ const anthropic2 = createAnthropic({
4727
+ apiKey
4728
+ });
4729
+ return anthropic2("claude-4-opus-20250514");
4730
+ }
4731
+ // todo add a field of type string that adds a dropdown list from which the user can select the model
4732
+ // todo for each model, check which provider is used, and require the admin to add one or multiple
4733
+ // API keys for the provider (which we can then auto-rotate).
4734
+ // todo also add custom fields for rate limiting, so the admin can set custom rate limits for the agent
4735
+ // and allow him/her to decide if the rate limit is per user or per agent.
4736
+ // todo finally allow switching on or off immutable audit logs on the agent. Which then enables OTEL
4737
+ // and stores the logs into the pre-defined storage.
4738
+ }
4679
4739
  }
4680
4740
  });
4681
4741
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@exulu/backend",
3
3
  "author": "Qventu Bv.",
4
- "version": "1.8.1",
4
+ "version": "1.9.0",
5
5
  "main": "./dist/index.js",
6
6
  "private": false,
7
7
  "publishConfig": {