@exulu/backend 1.55.0 → 1.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1253,7 +1253,7 @@ var hydrateVariables = async (tool5) => {
1253
1253
  await Promise.all(promises2);
1254
1254
  return tool5;
1255
1255
  };
1256
- var convertExuluToolsToAiSdkTools = async (currentTools, approvedTools, allExuluTools, configs, providerapikey, contexts, rerankers, user, exuluConfig, sessionID, req, project, sessionItems, model, agent) => {
1256
+ var convertExuluToolsToAiSdkTools = async (currentTools, approvedTools, allExuluTools, configs, providerapikey, contexts, rerankers, user, exuluConfig, sessionID, req, project, sessionItems, model, agent, memoryItems) => {
1257
1257
  if (!currentTools) return {};
1258
1258
  if (!allExuluTools) {
1259
1259
  allExuluTools = [];
@@ -1309,7 +1309,8 @@ var convertExuluToolsToAiSdkTools = async (currentTools, approvedTools, allExulu
1309
1309
  user,
1310
1310
  role: user?.role?.id,
1311
1311
  model,
1312
- preselectedItemIds: sessionItems
1312
+ preselected: sessionItems,
1313
+ memoryItems
1313
1314
  });
1314
1315
  if (agenticSearchTool) {
1315
1316
  const index = currentTools.findIndex((tool5) => tool5.id === "agentic_context_search");
@@ -1443,6 +1444,8 @@ var convertExuluToolsToAiSdkTools = async (currentTools, approvedTools, allExulu
1443
1444
  ...inputs,
1444
1445
  model,
1445
1446
  sessionID,
1447
+ sessionItems,
1448
+ memory: memoryItems,
1446
1449
  req,
1447
1450
  // Convert config to object format if a config object
1448
1451
  // is available, after we added the .value property
@@ -1543,7 +1546,7 @@ var ExuluTool = class {
1543
1546
  });
1544
1547
  }
1545
1548
  execute = async ({
1546
- agent: agentId2,
1549
+ agent: agentId,
1547
1550
  config,
1548
1551
  user,
1549
1552
  inputs,
@@ -1551,14 +1554,14 @@ var ExuluTool = class {
1551
1554
  items
1552
1555
  }) => {
1553
1556
  console.log("[EXULU] Calling tool execute directly", {
1554
- agentId: agentId2,
1557
+ agentId,
1555
1558
  config,
1556
1559
  user,
1557
1560
  inputs,
1558
1561
  project,
1559
1562
  items
1560
1563
  });
1561
- const agent = await exuluApp.get().agent(agentId2);
1564
+ const agent = await exuluApp.get().agent(agentId);
1562
1565
  if (!agent) {
1563
1566
  throw new Error("Agent not found.");
1564
1567
  }
@@ -1630,6 +1633,95 @@ var ExuluTool = class {
1630
1633
  };
1631
1634
  };
1632
1635
 
1636
+ // ee/agentic-retrieval/v3/classifier.ts
1637
+ var import_ai2 = require("ai");
1638
+ var import_zod5 = require("zod");
1639
+
1640
+ // src/utils/with-retry.ts
1641
+ async function withRetry(generateFn, maxRetries = 3) {
1642
+ let lastError;
1643
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
1644
+ try {
1645
+ return await generateFn();
1646
+ } catch (error) {
1647
+ lastError = error;
1648
+ console.error(`[EXULU] generateText attempt ${attempt} failed:`, error);
1649
+ if (attempt === maxRetries) {
1650
+ throw error;
1651
+ }
1652
+ await new Promise((resolve3) => setTimeout(resolve3, Math.pow(2, attempt) * 1e3));
1653
+ }
1654
+ }
1655
+ throw lastError;
1656
+ }
1657
+
1658
+ // ee/agentic-retrieval/v3/classifier.ts
1659
+ async function classifyQuery(query, contexts, samples, model) {
1660
+ const contextDescriptions = contexts.map((ctx) => {
1661
+ const sample = samples.find((s) => s.contextId === ctx.id);
1662
+ const fieldList = sample?.fields.join(", ") ?? "name, external_id";
1663
+ return `
1664
+ <context>
1665
+ <id>
1666
+ ${ctx.id}
1667
+ </id>
1668
+ <name>
1669
+ ${ctx.name}
1670
+ </name>
1671
+ <description>
1672
+ ${ctx.description}
1673
+ </description>
1674
+ <fields>
1675
+ ${fieldList}
1676
+ </fields>
1677
+ <example_items>
1678
+ ${sample?.exampleItems.map((item) => JSON.stringify(item)).join("\n")}
1679
+ </example_items>
1680
+ </context>
1681
+ `;
1682
+ }).join("\n\n");
1683
+ const result = await withRetry(async () => {
1684
+ const result2 = await (0, import_ai2.generateText)({
1685
+ model,
1686
+ temperature: 0,
1687
+ output: import_ai2.Output.object({
1688
+ schema: import_zod5.z.object({
1689
+ queryType: import_zod5.z.enum(["aggregate", "list", "targeted", "exploratory"]).describe(
1690
+ "aggregate: ONLY use when the user explicitly asks to COUNT how many documents/items/tickets exist in the knowledge base (e.g. 'how many documents about X?', 'total number of tickets'). NEVER use for: real-world statistics stored in a document, intent statements, how-to questions, error/fault descriptions, configuration questions, or any query that does not explicitly ask for a count of knowledge base entries. When in doubt, choose targeted. list: user wants to enumerate matching items/documents (show me all, list documents about). targeted: use for almost everything \u2014 specific fact, answer, configuration, how-to, error/fault, feature/behavior question. Also use for intent statements and short commands describing a desired state (phrases that state what the user wants to do or achieve, even without an explicit question word). Real-world statistics stored in documents also go here. When in doubt, choose targeted over aggregate or exploratory. exploratory: only for broad conceptual questions needing multi-source synthesis (what is the process for Z, explain how X works, general overview of topic Y)."
1691
+ ),
1692
+ language: import_zod5.z.string().describe("ISO 639-3 language code of the query (e.g. eng, deu, fra)"),
1693
+ suggestedContextIds: import_zod5.z.array(import_zod5.z.enum(contexts.map((c) => c.id))).describe(
1694
+ "IDs of knowledge bases most likely to contain the answer. Return empty array to search all contexts."
1695
+ )
1696
+ })
1697
+ }),
1698
+ toolChoice: "none",
1699
+ system: `You are a query classifier for a multi-knowledge-base retrieval system.
1700
+ Classify the query and identify which knowledge bases are most relevant.
1701
+
1702
+ Available knowledge bases:
1703
+ ${contextDescriptions}
1704
+
1705
+ Guidelines for queryType:
1706
+ - Use "aggregate" ONLY when the query contains explicit counting language (e.g., "how many", "count", "total number", "wie viele"). Short statements, commands, or phrases without a question word are NEVER aggregate \u2014 classify them as targeted.
1707
+ - When in doubt between aggregate and targeted: always choose targeted.
1708
+
1709
+ Guidelines for suggestedContextIds:
1710
+ - Be conservative: only suggest contexts that are genuinely likely to contain the answer.
1711
+ Aim for 2\u20133 focused suggestions rather than listing everything.
1712
+ - Use each knowledge base's name and description (shown above) to judge relevance.
1713
+ - Return an empty array only if you truly cannot determine which contexts are relevant.`,
1714
+ prompt: `Query: ${query}`
1715
+ });
1716
+ return result2.output;
1717
+ }, 3);
1718
+ return result;
1719
+ }
1720
+
1721
+ // ee/agentic-retrieval/v3/tools.ts
1722
+ var import_zod6 = require("zod");
1723
+ var import_ai3 = require("ai");
1724
+
1633
1725
  // src/uppy/index.ts
1634
1726
  var import_express = require("express");
1635
1727
  var import_client_s32 = require("@aws-sdk/client-s3");
@@ -3481,6 +3573,45 @@ var promptFavoritesSchema = {
3481
3573
  }
3482
3574
  ]
3483
3575
  };
3576
+ var contextPresetsSchema = {
3577
+ type: "context_presets",
3578
+ name: {
3579
+ plural: "context_presets",
3580
+ singular: "context_preset"
3581
+ },
3582
+ RBAC: true,
3583
+ fields: [
3584
+ {
3585
+ name: "name",
3586
+ type: "text",
3587
+ required: true,
3588
+ index: true
3589
+ },
3590
+ {
3591
+ name: "description",
3592
+ type: "text"
3593
+ },
3594
+ {
3595
+ name: "preset_items",
3596
+ type: "json",
3597
+ required: true
3598
+ },
3599
+ {
3600
+ name: "tags",
3601
+ type: "json"
3602
+ },
3603
+ {
3604
+ name: "usage_count",
3605
+ type: "number",
3606
+ default: 0
3607
+ },
3608
+ {
3609
+ name: "favorite_count",
3610
+ type: "number",
3611
+ default: 0
3612
+ }
3613
+ ]
3614
+ };
3484
3615
  var addCoreFields = (schema) => {
3485
3616
  schema.fields.forEach((field) => {
3486
3617
  if (field.type === "file") {
@@ -3530,7 +3661,8 @@ var coreSchemas = {
3530
3661
  platformConfigurationsSchema: () => addCoreFields(platformConfigurationsSchema),
3531
3662
  promptLibrarySchema: () => addCoreFields(promptLibrarySchema),
3532
3663
  embedderSettingsSchema: () => addCoreFields(embedderSettingsSchema),
3533
- promptFavoritesSchema: () => addCoreFields(promptFavoritesSchema)
3664
+ promptFavoritesSchema: () => addCoreFields(promptFavoritesSchema),
3665
+ contextPresetsSchema: () => addCoreFields(contextPresetsSchema)
3534
3666
  };
3535
3667
  if (license["agent-feedback"]) {
3536
3668
  schemas.feedbackSchema = () => addCoreFields(feedbackSchema);
@@ -4572,7 +4704,7 @@ var ExuluContext2 = class {
4572
4704
  job: jobs.length > 0 ? jobs.join(",") : void 0
4573
4705
  };
4574
4706
  };
4575
- updateItem = async (item, config, user, role, generateEmbeddingsOverwrite) => {
4707
+ updateItem = async (item, config, user, role, generateEmbeddingsOverwrite, runProcessorOverwrite) => {
4576
4708
  console.log("[EXULU] updating item", item);
4577
4709
  const { db: db2 } = await postgresClient();
4578
4710
  if (item.field) {
@@ -4598,7 +4730,7 @@ var ExuluContext2 = class {
4598
4730
  let shouldGenerateEmbeddings = this.embedder && generateEmbeddingsOverwrite !== false && (generateEmbeddingsOverwrite || this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always");
4599
4731
  if (this.processor) {
4600
4732
  const processor = this.processor;
4601
- if (processor && (processor?.config?.trigger === "onInsert" || processor?.config?.trigger === "onUpdate" || processor?.config?.trigger === "always")) {
4733
+ if (processor && runProcessorOverwrite !== false && (runProcessorOverwrite || processor?.config?.trigger === "onInsert" || processor?.config?.trigger === "onUpdate" || processor?.config?.trigger === "always")) {
4602
4734
  const { job: processorJob, result: processorResult } = await this.processField(
4603
4735
  "api",
4604
4736
  {
@@ -4864,120 +4996,7 @@ var ExuluContext2 = class {
4864
4996
  };
4865
4997
  };
4866
4998
 
4867
- // ee/agentic-retrieval/v3/context-sampler.ts
4868
- var CACHE_TTL_MS = 60 * 60 * 1e3;
4869
- var ContextSampler = class {
4870
- cache = /* @__PURE__ */ new Map();
4871
- async getSamples(contexts, user, role) {
4872
- return Promise.all(contexts.map((ctx) => this.getSample(ctx, user, role)));
4873
- }
4874
- async getSample(ctx, user, role) {
4875
- const cached = this.cache.get(ctx.id);
4876
- if (cached && Date.now() - cached.sampledAt < CACHE_TTL_MS) {
4877
- return cached;
4878
- }
4879
- const { db: db2 } = await postgresClient();
4880
- const tableName = getTableName(ctx.id);
4881
- const tableDefinition = convertContextToTableDefinition(ctx);
4882
- const customFieldNames = ctx.fields.map((f) => f.name);
4883
- const selectFields = ["id", "name", "external_id", ...customFieldNames];
4884
- let exampleItems = [];
4885
- try {
4886
- let query = db2(tableName).select(selectFields).whereNull("archived").limit(2);
4887
- query = applyAccessControl(tableDefinition, query, user, tableName);
4888
- exampleItems = await query;
4889
- } catch {
4890
- }
4891
- const sample = {
4892
- contextId: ctx.id,
4893
- contextName: ctx.name,
4894
- fields: ["name", "external_id", ...customFieldNames],
4895
- exampleItems,
4896
- sampledAt: Date.now()
4897
- };
4898
- this.cache.set(ctx.id, sample);
4899
- return sample;
4900
- }
4901
- /** Evict a context from cache so it's re-sampled on next use */
4902
- invalidate(contextId) {
4903
- this.cache.delete(contextId);
4904
- }
4905
- };
4906
-
4907
- // ee/agentic-retrieval/v3/classifier.ts
4908
- var import_ai2 = require("ai");
4909
- var import_zod5 = require("zod");
4910
-
4911
- // src/utils/with-retry.ts
4912
- async function withRetry(generateFn, maxRetries = 3) {
4913
- let lastError;
4914
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
4915
- try {
4916
- return await generateFn();
4917
- } catch (error) {
4918
- lastError = error;
4919
- console.error(`[EXULU] generateText attempt ${attempt} failed:`, error);
4920
- if (attempt === maxRetries) {
4921
- throw error;
4922
- }
4923
- await new Promise((resolve3) => setTimeout(resolve3, Math.pow(2, attempt) * 1e3));
4924
- }
4925
- }
4926
- throw lastError;
4927
- }
4928
-
4929
- // ee/agentic-retrieval/v3/classifier.ts
4930
- async function classifyQuery(query, contexts, samples, model) {
4931
- const contextDescriptions = contexts.map((ctx) => {
4932
- const sample = samples.find((s) => s.contextId === ctx.id);
4933
- const fieldList = sample?.fields.join(", ") ?? "name, external_id";
4934
- const exampleStr = sample?.exampleItems.length ? `
4935
- Example records: ${JSON.stringify(sample.exampleItems.slice(0, 2))}` : "";
4936
- return ` - ${ctx.id}: ${ctx.name}
4937
- Description: ${ctx.description}
4938
- Fields: ${fieldList}${exampleStr}`;
4939
- }).join("\n\n");
4940
- const result = await withRetry(async () => {
4941
- const result2 = await (0, import_ai2.generateText)({
4942
- model,
4943
- temperature: 0,
4944
- output: import_ai2.Output.object({
4945
- schema: import_zod5.z.object({
4946
- queryType: import_zod5.z.enum(["aggregate", "list", "targeted", "exploratory"]).describe(
4947
- "aggregate: ONLY use when the user explicitly asks to COUNT how many documents/items/tickets exist in the knowledge base (e.g. 'how many documents about X?', 'total number of tickets'). NEVER use for: real-world statistics stored in a document, intent statements, how-to questions, error/fault descriptions, configuration questions, or any query that does not explicitly ask for a count of knowledge base entries. When in doubt, choose targeted. list: user wants to enumerate matching items/documents (show me all, list documents about). targeted: use for almost everything \u2014 specific fact, answer, configuration, how-to, error/fault, feature/behavior question. Also use for intent statements and short commands describing a desired state (phrases that state what the user wants to do or achieve, even without an explicit question word). Real-world statistics stored in documents also go here. When in doubt, choose targeted over aggregate or exploratory. exploratory: only for broad conceptual questions needing multi-source synthesis (what is the process for Z, explain how X works, general overview of topic Y)."
4948
- ),
4949
- language: import_zod5.z.string().describe("ISO 639-3 language code of the query (e.g. eng, deu, fra)"),
4950
- suggestedContextIds: import_zod5.z.array(import_zod5.z.enum(contexts.map((c) => c.id))).describe(
4951
- "IDs of knowledge bases most likely to contain the answer. Return empty array to search all contexts."
4952
- )
4953
- })
4954
- }),
4955
- toolChoice: "none",
4956
- system: `You are a query classifier for a multi-knowledge-base retrieval system.
4957
- Classify the query and identify which knowledge bases are most relevant.
4958
-
4959
- Available knowledge bases:
4960
- ${contextDescriptions}
4961
-
4962
- Guidelines for queryType:
4963
- - Use "aggregate" ONLY when the query contains explicit counting language (e.g., "how many", "count", "total number", "wie viele"). Short statements, commands, or phrases without a question word are NEVER aggregate \u2014 classify them as targeted.
4964
- - When in doubt between aggregate and targeted: always choose targeted.
4965
-
4966
- Guidelines for suggestedContextIds:
4967
- - Be conservative: only suggest contexts that are genuinely likely to contain the answer.
4968
- Aim for 2\u20133 focused suggestions rather than listing everything.
4969
- - Use each knowledge base's name and description (shown above) to judge relevance.
4970
- - Return an empty array only if you truly cannot determine which contexts are relevant.`,
4971
- prompt: `Query: ${query}`
4972
- });
4973
- return result2.output;
4974
- }, 3);
4975
- return result;
4976
- }
4977
-
4978
4999
  // ee/agentic-retrieval/v3/tools.ts
4979
- var import_zod6 = require("zod");
4980
- var import_ai3 = require("ai");
4981
5000
  function buildContextEnum(contexts) {
4982
5001
  return import_zod6.z.array(import_zod6.z.enum(contexts.map((c) => c.id))).describe(
4983
5002
  contexts.map(
@@ -5019,7 +5038,7 @@ function parseGlobalItemIds(globalIds) {
5019
5038
  return map;
5020
5039
  }
5021
5040
  function createRetrievalTools(params) {
5022
- const { contexts, user, role, updateVirtualFiles, preselectedItemsByContext } = params;
5041
+ const { contexts, toolVariablesConfig, user, role, updateVirtualFiles, preselectedItemsByContext } = params;
5023
5042
  const ctxEnum = buildContextEnum(contexts);
5024
5043
  const count_items_or_chunks = (0, import_ai3.tool)({
5025
5044
  description: "Count items or chunks WITHOUT loading them into context. Use for 'how many', 'count', or 'total number of' queries.",
@@ -5152,7 +5171,7 @@ Use includeContent: true when you need the ACTUAL text to answer a question.
5152
5171
 
5153
5172
  For listing queries: always start with includeContent: false, then use dynamic tools to fetch specific pages.`,
5154
5173
  inputSchema: import_zod6.z.object({
5155
- query: import_zod6.z.string().describe("Search query about the content you're looking for"),
5174
+ userQuery: import_zod6.z.string().describe("The original unaltered question from the user"),
5156
5175
  knowledge_base_id: import_zod6.z.enum(contexts.map((c) => c.id)).describe(
5157
5176
  contexts.map(
5158
5177
  (c) => `<knowledge_base id="${c.id}" name="${c.name}">${c.description}</knowledge_base>`
@@ -5171,7 +5190,7 @@ For listing queries: always start with includeContent: false, then use dynamic t
5171
5190
  limit: import_zod6.z.number().default(20).describe("Max chunks with content (max 20). Without content, up to 200 are returned.")
5172
5191
  }),
5173
5192
  execute: async ({
5174
- query,
5193
+ userQuery,
5175
5194
  knowledge_base_id,
5176
5195
  keywords,
5177
5196
  searchMethod,
@@ -5182,7 +5201,8 @@ For listing queries: always start with includeContent: false, then use dynamic t
5182
5201
  limit
5183
5202
  }) => {
5184
5203
  const [ctx] = resolveContexts([knowledge_base_id], contexts);
5185
- const effectiveLimit = includeContent ? Math.min(limit ?? 20, 20) : Math.min((limit ?? 20) * 20, 400);
5204
+ const maxResults = toolVariablesConfig?.[`${ctx.id}_|_max_results`] || 20;
5205
+ const effectiveLimit = includeContent ? Math.min(limit ?? maxResults, maxResults) : Math.min((limit ?? maxResults) * maxResults, 400);
5186
5206
  const itemFilters = [];
5187
5207
  if (preselectedItemsByContext) {
5188
5208
  const contextItemIds = preselectedItemsByContext.get(knowledge_base_id);
@@ -5204,7 +5224,7 @@ For listing queries: always start with includeContent: false, then use dynamic t
5204
5224
  if (item_names)
5205
5225
  itemFilters.push({ name: { or: item_names.map((n) => ({ contains: n })) } });
5206
5226
  if (item_external_ids) itemFilters.push({ external_id: { in: item_external_ids } });
5207
- const effectiveQuery = query || keywords?.join(" ") || "";
5227
+ const effectiveQuery = userQuery || keywords?.join(" ") || "";
5208
5228
  let method = mapSearchMethod(searchMethod ?? "hybrid");
5209
5229
  if (method === "hybridSearch" || method === "cosineDistance") {
5210
5230
  if (!ctx.embedder) {
@@ -5212,6 +5232,7 @@ For listing queries: always start with includeContent: false, then use dynamic t
5212
5232
  method = "tsvector";
5213
5233
  }
5214
5234
  }
5235
+ const expandChunks = toolVariablesConfig?.[`${ctx.id}_|_expand_chunks`] || 0;
5215
5236
  try {
5216
5237
  const { chunks } = await ctx.search({
5217
5238
  query: effectiveQuery,
@@ -5224,7 +5245,11 @@ For listing queries: always start with includeContent: false, then use dynamic t
5224
5245
  sort: { field: "updatedAt", direction: "desc" },
5225
5246
  user,
5226
5247
  role,
5227
- trigger: "tool"
5248
+ trigger: "tool",
5249
+ expand: expandChunks > 0 ? {
5250
+ before: expandChunks,
5251
+ after: expandChunks
5252
+ } : void 0
5228
5253
  });
5229
5254
  return JSON.stringify(
5230
5255
  chunks.map(
@@ -5996,6 +6021,46 @@ var TrajectoryLogger = class {
5996
6021
  }
5997
6022
  };
5998
6023
 
6024
+ // ee/agentic-retrieval/v3/context-sampler.ts
6025
+ var CACHE_TTL_MS = 60 * 60 * 1e3;
6026
+ var ContextSampler = class {
6027
+ cache = /* @__PURE__ */ new Map();
6028
+ async getSamples(contexts, user, role) {
6029
+ return Promise.all(contexts.map((ctx) => this.getSample(ctx, user, role)));
6030
+ }
6031
+ async getSample(ctx, user, role) {
6032
+ const cached = this.cache.get(ctx.id);
6033
+ if (cached && Date.now() - cached.sampledAt < CACHE_TTL_MS) {
6034
+ return cached;
6035
+ }
6036
+ const { db: db2 } = await postgresClient();
6037
+ const tableName = getTableName(ctx.id);
6038
+ const tableDefinition = convertContextToTableDefinition(ctx);
6039
+ const customFieldNames = ctx.fields.map((f) => f.name);
6040
+ const selectFields = ["id", "name", "external_id", ...customFieldNames];
6041
+ let exampleItems = [];
6042
+ try {
6043
+ let query = db2(tableName).select(selectFields).whereNull("archived").limit(2);
6044
+ query = applyAccessControl(tableDefinition, query, user, tableName);
6045
+ exampleItems = await query;
6046
+ } catch {
6047
+ }
6048
+ const sample = {
6049
+ contextId: ctx.id,
6050
+ contextName: ctx.name,
6051
+ fields: ["name", "external_id", ...customFieldNames],
6052
+ exampleItems,
6053
+ sampledAt: Date.now()
6054
+ };
6055
+ this.cache.set(ctx.id, sample);
6056
+ return sample;
6057
+ }
6058
+ /** Evict a context from cache so it's re-sampled on next use */
6059
+ invalidate(contextId) {
6060
+ this.cache.delete(contextId);
6061
+ }
6062
+ };
6063
+
5999
6064
  // ee/agentic-retrieval/v3/index.ts
6000
6065
  var sampler = new ContextSampler();
6001
6066
  async function* executeV3({
@@ -6003,6 +6068,7 @@ async function* executeV3({
6003
6068
  contexts,
6004
6069
  reranker,
6005
6070
  model,
6071
+ toolVariablesConfig,
6006
6072
  user,
6007
6073
  role,
6008
6074
  customInstructions,
@@ -6028,15 +6094,39 @@ async function* executeV3({
6028
6094
  }
6029
6095
  console.log("[EXULU] v3 \u2014 classified as:", classification);
6030
6096
  const strategy = STRATEGIES[classification.queryType];
6097
+ const contextSpecificInstructions = activeContexts.map((ctx) => {
6098
+ const instructions = toolVariablesConfig?.[`${ctx.id}_|_instructions`] ?? "";
6099
+ if (instructions) {
6100
+ return `
6101
+ <${ctx.id}>
6102
+ ${instructions}
6103
+ </${ctx.id}>
6104
+ `;
6105
+ } else {
6106
+ return null;
6107
+ }
6108
+ }).filter(Boolean).join("\n");
6031
6109
  const suggestedIds = classification.suggestedContextIds;
6032
6110
  const fallbackIds = activeContexts.filter((c) => !suggestedIds.includes(c.id)).map((c) => c.id);
6033
- const contextBase = suggestedIds.length > 0 ? `Suggested priority contexts: [${suggestedIds.join(", ")}]. Also available: [${fallbackIds.join(", ")}]. Custom instructions may require searching additional or all contexts \u2014 follow them.` : `All contexts available: [${activeContexts.map((c) => c.id).join(", ")}].`;
6111
+ let contextBase = suggestedIds.length > 0 ? `
6112
+ Suggested priority contexts: [${suggestedIds.join(", ")}].
6113
+
6114
+ Also available: [${fallbackIds.join(", ")}].
6115
+
6116
+ Custom instructions may require searching additional or all contexts \u2014 follow them.` : `All contexts available: [${activeContexts.map((c) => c.id).join(", ")}].`;
6034
6117
  const preselectedNote = preselectedByContext?.size ? `
6035
6118
  SCOPE CONSTRAINT: Retrieval is scoped to preselected items/contexts. Per context: ${[...preselectedByContext.entries()].map(([ctx, ids]) => ids === null ? `${ctx} (full context)` : `${ctx} (${ids.length} item${ids.length === 1 ? "" : "s"})`).join(", ")}. All tools enforce this scope automatically. For full-context entries you may search freely; for item-restricted entries do NOT use search_items_by_name for discovery \u2014 go directly to search_content or save_search_results.` : "";
6119
+ if (contextSpecificInstructions?.length) {
6120
+ contextBase += `
6121
+ Context specific instructions:
6122
+ ${contextSpecificInstructions}
6123
+ `;
6124
+ }
6036
6125
  const contextGuidance = contextBase + preselectedNote;
6037
6126
  const bashToolkit = await (0, import_bash_tool.createBashTool)({ files: {} });
6038
6127
  const retrievalTools = createRetrievalTools({
6039
6128
  contexts: activeContexts,
6129
+ toolVariablesConfig,
6040
6130
  user,
6041
6131
  role,
6042
6132
  updateVirtualFiles: (files) => bashToolkit.sandbox.writeFiles(files),
@@ -6091,7 +6181,8 @@ function createAgenticRetrievalToolV3({
6091
6181
  user,
6092
6182
  role,
6093
6183
  model,
6094
- preselectedItemIds
6184
+ preselected,
6185
+ memoryItems
6095
6186
  }) {
6096
6187
  const license = checkLicense();
6097
6188
  if (!license["agentic-retrieval"]) {
@@ -6144,27 +6235,57 @@ function createAgenticRetrievalToolV3({
6144
6235
  default: false
6145
6236
  },
6146
6237
  {
6147
- name: "log_trajectories",
6238
+ name: "logging",
6148
6239
  description: "Save a detailed markdown + JSON log of every retrieval execution to disk. Useful for debugging and evaluation.",
6149
6240
  type: "boolean",
6150
6241
  default: false
6151
6242
  },
6152
6243
  ...contexts.map((ctx) => ({
6153
- name: ctx.id,
6244
+ name: ctx.id + "_|_enabled",
6154
6245
  description: `Enable search in "${ctx.name}". ${ctx.description}`,
6155
6246
  type: "boolean",
6156
6247
  default: true
6248
+ })),
6249
+ ...contexts.map((ctx) => ({
6250
+ name: `${ctx.id}_|_instructions`,
6251
+ description: `Instructions for the retrieval agent about how to search in the ${ctx.name} context`,
6252
+ type: "string",
6253
+ default: ""
6254
+ })),
6255
+ ...contexts.map((ctx) => ({
6256
+ name: `${ctx.id}_|_priority`,
6257
+ description: `Defines in which order the context should be searched in, the higher the number the higher the priority, if contexts have the same priority they are searched in parallel`,
6258
+ type: "number",
6259
+ default: 0
6260
+ })),
6261
+ ...contexts.map((ctx) => ({
6262
+ name: `${ctx.id}_|_max_results`,
6263
+ description: `Defines the maximum number of results to return for the ${ctx.name} context`,
6264
+ type: "number",
6265
+ default: 0
6266
+ })),
6267
+ ...contexts.map((ctx) => ({
6268
+ name: `${ctx.id}_|_max_steps`,
6269
+ description: `Defines the maximum number of steps the agent is allowed to take when searching the ${ctx.name} context`,
6270
+ type: "number",
6271
+ default: 0
6272
+ })),
6273
+ ...contexts.map((ctx) => ({
6274
+ name: `${ctx.id}_|_expand_chunks`,
6275
+ description: `Defines if the agent automatically retrieves nearby chunks around the matched chunks, usefull if relevant content might be split up`,
6276
+ type: "number",
6277
+ default: 0
6157
6278
  }))
6158
6279
  ],
6159
6280
  inputSchema: import_zod9.z.object({
6160
- query: import_zod9.z.string().describe("The question or query to answer"),
6281
+ userQuery: import_zod9.z.string().describe("The original unaltered question from the user"),
6161
6282
  userInstructions: import_zod9.z.string().optional().describe("Additional instructions from the user to guide retrieval"),
6162
6283
  confirmedContextIds: import_zod9.z.array(import_zod9.z.string()).optional().describe(
6163
6284
  "Knowledge base IDs explicitly confirmed by the user to be used in the retrieval. When presen only searches these contexts. "
6164
6285
  )
6165
6286
  }),
6166
6287
  execute: async function* ({
6167
- query,
6288
+ userQuery,
6168
6289
  userInstructions,
6169
6290
  confirmedContextIds,
6170
6291
  toolVariablesConfig,
@@ -6182,10 +6303,10 @@ function createAgenticRetrievalToolV3({
6182
6303
  let managedContextEnabled = false;
6183
6304
  if (toolVariablesConfig) {
6184
6305
  configInstructions = toolVariablesConfig["instructions"] ?? "";
6185
- logTrajectory = toolVariablesConfig["log_trajectories"] === true || toolVariablesConfig["log_trajectories"] === "true";
6306
+ logTrajectory = toolVariablesConfig["logging"] === true || toolVariablesConfig["logging"] === "true";
6186
6307
  managedContextEnabled = toolVariablesConfig["managed_context"] === true || toolVariablesConfig["managed_context"] === "true";
6187
6308
  activeContexts = contexts.filter(
6188
- (ctx) => toolVariablesConfig[ctx.id] === true || toolVariablesConfig[ctx.id] === "true" || toolVariablesConfig[ctx.id] === 1
6309
+ (ctx) => toolVariablesConfig[ctx.id + "_|_enabled"] === true || toolVariablesConfig[ctx.id + "_|_enabled"] === "true" || toolVariablesConfig[ctx.id + "_|_enabled"] === 1
6189
6310
  );
6190
6311
  if (activeContexts.length === 0) activeContexts = contexts;
6191
6312
  requiresPreselectedContexts = toolVariablesConfig["require_preselected_contexts"] === true || toolVariablesConfig["require_preselected_contexts"] === "true";
@@ -6195,13 +6316,13 @@ function createAgenticRetrievalToolV3({
6195
6316
  }
6196
6317
  }
6197
6318
  console.log("[EXULU] Managed context enabled:", managedContextEnabled);
6198
- console.log("[EXULU] Preselected item IDs:", preselectedItemIds);
6199
- if (managedContextEnabled && !preselectedItemIds?.length) {
6319
+ console.log("[EXULU] Preselected item IDs:", preselected);
6320
+ if (managedContextEnabled && !preselected?.length) {
6200
6321
  console.log("[EXULU] Managed context was enabled for the agentic retrieval tool. This means that the user must preselect items that the agentic retrieval tool will search in, please notify the user to preselect items before executing the tool.");
6201
6322
  yield { result: "Managed context was enabled for the agentic retrieval tool. This means that the user must preselect items that the agentic retrieval tool will search in, please notify the user to preselect items before executing the tool." };
6202
6323
  return;
6203
6324
  }
6204
- if (requiresPreselectedContexts && !confirmedContextIds?.length && !preselectedItemIds?.length) {
6325
+ if (requiresPreselectedContexts && !confirmedContextIds?.length && !preselected?.length) {
6205
6326
  console.log("[EXULU] The user must choose between the available contexts before executing the tool. The available contexts are: " + activeContexts.map((c) => c.id).join(", ") + ". If the question_ask tool is available use that to ask the user which contexts they want to search in, otherwise just ask them in plain text.");
6206
6327
  yield { result: "The user must choose between the available contexts before executing the tool, the available contexts are: " + activeContexts.map((c) => c.id).join(", ") + ". If the question_ask tool is available use that to ask the user which contexts they want to search in, otherwise just ask them in plain text." };
6207
6328
  return;
@@ -6212,21 +6333,43 @@ function createAgenticRetrievalToolV3({
6212
6333
  if (filtered.length > 0) activeContexts = filtered;
6213
6334
  }
6214
6335
  const combinedInstructions = [
6215
- configInstructions ? `Configuration instructions: ${configInstructions}` : "",
6216
- adminInstructions ? `Admin instructions: ${adminInstructions}` : "",
6217
- userInstructions ? `User instructions: ${userInstructions}` : ""
6336
+ configInstructions ? `
6337
+ Configuration instructions:
6338
+ <configuration_instructions>
6339
+ ${configInstructions}
6340
+ </configuration_instructions>
6341
+ ` : "",
6342
+ adminInstructions ? `
6343
+ Admin instructions:
6344
+ <admin_instructions>
6345
+ ${adminInstructions}
6346
+ </admin_instructions>
6347
+ ` : "",
6348
+ userInstructions ? `
6349
+ User instructions:
6350
+ <user_instructions>
6351
+ ${userInstructions}
6352
+ </user_instructions>
6353
+ ` : "",
6354
+ memoryItems ? `
6355
+ Relevant memories (these are items that the agent has retrieved from the memory context and are relevant to the query):
6356
+ <relevant_memories>
6357
+ ${memoryItems?.map((item) => JSON.stringify(item)).join("\n")}
6358
+ </relevant_memories>
6359
+ ` : ""
6218
6360
  ].filter(Boolean).join("\n");
6219
6361
  for await (const output of executeV3({
6220
- query,
6362
+ query: userQuery,
6221
6363
  contexts: activeContexts,
6222
6364
  reranker: configuredReranker,
6365
+ toolVariablesConfig,
6223
6366
  model,
6224
6367
  user,
6225
6368
  role,
6226
6369
  customInstructions: combinedInstructions || void 0,
6227
6370
  logTrajectory,
6228
6371
  sessionId: sessionID,
6229
- preselectedItemIds
6372
+ preselectedItemIds: preselected
6230
6373
  })) {
6231
6374
  yield { result: JSON.stringify(output) };
6232
6375
  }
@@ -11046,6 +11189,7 @@ var ExuluProvider = class {
11046
11189
  });
11047
11190
  }
11048
11191
  let memoryContext = "";
11192
+ let memoryItems;
11049
11193
  if (agent?.memory && contexts?.length && query) {
11050
11194
  const context = contexts.find((context2) => context2.id === agent?.memory);
11051
11195
  if (!context) {
@@ -11068,6 +11212,7 @@ var ExuluProvider = class {
11068
11212
  page: 1
11069
11213
  });
11070
11214
  if (result?.chunks?.length) {
11215
+ memoryItems = result.chunks;
11071
11216
  memoryContext = `
11072
11217
  Pre-fetched relevant information for this query:
11073
11218
 
@@ -11188,7 +11333,8 @@ When a tool execution is not approved by the user, do not retry it unless explic
11188
11333
  project,
11189
11334
  sessionItems,
11190
11335
  model,
11191
- agent
11336
+ agent,
11337
+ memoryItems
11192
11338
  ),
11193
11339
  stopWhen: [(0, import_ai8.stepCountIs)(maxStepCount || 5)]
11194
11340
  // make configurable
@@ -11265,7 +11411,8 @@ When a tool execution is not approved by the user, do not retry it unless explic
11265
11411
  project,
11266
11412
  sessionItems,
11267
11413
  model,
11268
- agent
11414
+ agent,
11415
+ memoryItems
11269
11416
  ),
11270
11417
  stopWhen: [(0, import_ai8.stepCountIs)(maxStepCount || 5)]
11271
11418
  });
@@ -11447,6 +11594,7 @@ ${extractedText}
11447
11594
  });
11448
11595
  }
11449
11596
  let memoryContext = "";
11597
+ let memoryItems;
11450
11598
  if (agent?.memory && contexts?.length && query) {
11451
11599
  const context = contexts.find((context2) => context2.id === agent?.memory);
11452
11600
  if (!context) {
@@ -11470,6 +11618,7 @@ ${extractedText}
11470
11618
  });
11471
11619
  import_fs.default.writeFileSync("pre-fetched-relevant-information.json", JSON.stringify(result2, null, 2));
11472
11620
  if (result2?.chunks?.length) {
11621
+ memoryItems = result2.chunks;
11473
11622
  memoryContext = `
11474
11623
  <pre-fetched relevant information for this query>:
11475
11624
 
@@ -11489,9 +11638,6 @@ ${extractedText}
11489
11638
  const genericContext = "IMPORTANT: \n\n The current date is " + (/* @__PURE__ */ new Date()).toLocaleDateString() + " and the current time is " + (/* @__PURE__ */ new Date()).toLocaleTimeString() + ". If the user does not explicitly provide the current date, for examle when saying ' this weekend', you should assume they are talking with the current date in mind as a reference.";
11490
11639
  let system = instructions || "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.";
11491
11640
  system += "\n\n" + genericContext;
11492
- if (memoryContext) {
11493
- system += "\n\n" + memoryContext;
11494
- }
11495
11641
  const includesContextSearchTool = currentTools?.some(
11496
11642
  (tool5) => tool5.name.toLowerCase().includes("context_search") || tool5.id.includes("context_search") || tool5.type === "context"
11497
11643
  );
@@ -11576,7 +11722,8 @@ When a tool execution is not approved by the user, do not retry it unless explic
11576
11722
  project,
11577
11723
  sessionItems,
11578
11724
  model,
11579
- agent
11725
+ agent,
11726
+ memoryItems
11580
11727
  ),
11581
11728
  onError: (error) => {
11582
11729
  console.error("[EXULU] chat stream error.", error);
@@ -11691,27 +11838,141 @@ var providerRateLimiter = async (key, windowSeconds, limit, points) => {
11691
11838
  // src/exulu/openai-gateway.ts
11692
11839
  var import_express2 = require("express");
11693
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
11694
11927
  var import_node_crypto4 = require("crypto");
11695
11928
  var import_crypto_js6 = __toESM(require("crypto-js"), 1);
11696
11929
  var import_express3 = __toESM(require("express"), 1);
11697
- 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) {
11698
11949
  const systemParts = [];
11699
11950
  const coreMessages = [];
11951
+ const toolCallIdToName = /* @__PURE__ */ new Map();
11700
11952
  for (const msg of messages) {
11701
11953
  if (msg.role === "system") {
11702
11954
  systemParts.push(typeof msg.content === "string" ? msg.content : "");
11703
11955
  continue;
11704
11956
  }
11705
11957
  if (msg.role === "user") {
11958
+ const last = coreMessages[coreMessages.length - 1];
11706
11959
  if (typeof msg.content === "string") {
11707
- 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
+ }
11708
11965
  } else if (Array.isArray(msg.content)) {
11709
11966
  const parts = msg.content.flatMap((part) => {
11710
11967
  if (part.type === "text") return [{ type: "text", text: part.text }];
11711
11968
  if (part.type === "image_url") return [{ type: "image", image: part.image_url.url }];
11712
11969
  return [];
11713
11970
  });
11714
- 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
+ }
11715
11976
  }
11716
11977
  continue;
11717
11978
  }
@@ -11722,11 +11983,20 @@ function convertOpenAIMessagesToCoreMessages(messages) {
11722
11983
  parts.push({ type: "text", text: msg.content });
11723
11984
  }
11724
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
+ })();
11725
11995
  parts.push({
11726
11996
  type: "tool-call",
11727
11997
  toolCallId: tc.id,
11728
11998
  toolName: tc.function.name,
11729
- args: JSON.parse(tc.function.arguments || "{}")
11999
+ input
11730
12000
  });
11731
12001
  }
11732
12002
  coreMessages.push({ role: "assistant", content: parts });
@@ -11739,13 +12009,17 @@ function convertOpenAIMessagesToCoreMessages(messages) {
11739
12009
  continue;
11740
12010
  }
11741
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);
11742
12015
  coreMessages.push({
11743
12016
  role: "tool",
11744
12017
  content: [
11745
12018
  {
11746
12019
  type: "tool-result",
11747
- toolCallId: msg.tool_call_id ?? "",
11748
- result: msg.content
12020
+ toolCallId,
12021
+ toolName,
12022
+ output: { type: "text", value: resultText }
11749
12023
  }
11750
12024
  ]
11751
12025
  });
@@ -11862,8 +12136,32 @@ var registerOpenAIGatewayRoutes = async (app, providers, tools, contexts, config
11862
12136
  }
11863
12137
  );
11864
12138
  app.post(
11865
- "/gateway/open-ai/v1/chat/completions",
12139
+ ["/gateway/open-ai/v1/chat/completions", "/gateway/open-ai/v1/completions"],
11866
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
+ },
11867
12165
  async (req, res) => {
11868
12166
  try {
11869
12167
  const { db: db2 } = await postgresClient();
@@ -11969,8 +12267,10 @@ var registerOpenAIGatewayRoutes = async (app, providers, tools, contexts, config
11969
12267
  languageModel,
11970
12268
  agent
11971
12269
  );
12270
+ const clientTools = Array.isArray(req.body.tools) ? req.body.tools : [];
12271
+ const activeTools = clientTools.length > 0 ? convertOpenAIToolsToAiSdkTools(clientTools) : convertedTools;
11972
12272
  const openaiMessages = req.body.messages ?? [];
11973
- const { systemPrompt: requestSystemPrompt, coreMessages } = convertOpenAIMessagesToCoreMessages(openaiMessages);
12273
+ const { systemPrompt: requestSystemPrompt, coreMessages } = convertOpenAIMessagesToModelMessages(openaiMessages);
11974
12274
  const agentInstructions = agent.instructions ?? "";
11975
12275
  const systemParts = [
11976
12276
  agentInstructions ? `You are an agent named: ${agent.name}
@@ -11982,7 +12282,8 @@ ${project.description}` : ""}` : "",
11982
12282
  const systemPrompt = systemParts.join("\n\n");
11983
12283
  const completionId = `chatcmpl-${(0, import_node_crypto4.randomUUID)()}`;
11984
12284
  const created = Math.floor(Date.now() / 1e3);
11985
- const hasTools = Object.keys(convertedTools).length > 0;
12285
+ const hasTools = Object.keys(activeTools).length > 0;
12286
+ const ctx = { completionId, created, modelId };
11986
12287
  if (req.body.stream === true) {
11987
12288
  res.setHeader("Content-Type", "text/event-stream");
11988
12289
  res.setHeader("Cache-Control", "no-cache");
@@ -11991,9 +12292,9 @@ ${project.description}` : ""}` : "",
11991
12292
  model: languageModel,
11992
12293
  system: systemPrompt || void 0,
11993
12294
  messages: coreMessages,
11994
- tools: hasTools ? convertedTools : void 0,
12295
+ tools: hasTools ? activeTools : void 0,
11995
12296
  maxRetries: 2,
11996
- stopWhen: [(0, import_ai9.stepCountIs)(5)],
12297
+ stopWhen: clientTools.length > 0 ? void 0 : [(0, import_ai9.stepCountIs)(5)],
11997
12298
  onError: (error) => {
11998
12299
  console.error("[OPENAI GATEWAY] stream error:", error);
11999
12300
  }
@@ -12012,83 +12313,17 @@ ${project.description}` : ""}` : "",
12012
12313
  let inputTokens = 0;
12013
12314
  let outputTokens = 0;
12014
12315
  for await (const chunk of result.fullStream) {
12015
- if (chunk.type === "text-delta") {
12016
- res.write(
12017
- `data: ${JSON.stringify({
12018
- id: completionId,
12019
- object: "chat.completion.chunk",
12020
- created,
12021
- model: modelId,
12022
- choices: [{ index: 0, delta: { content: chunk.text }, finish_reason: null }]
12023
- })}
12024
-
12025
- `
12026
- );
12027
- } else if (chunk.type === "tool-input-start") {
12028
- res.write(
12029
- `data: ${JSON.stringify({
12030
- id: completionId,
12031
- object: "chat.completion.chunk",
12032
- created,
12033
- model: modelId,
12034
- choices: [
12035
- {
12036
- index: 0,
12037
- delta: {
12038
- tool_calls: [
12039
- {
12040
- index: 0,
12041
- id: chunk.id,
12042
- type: "function",
12043
- function: { name: chunk.toolName, arguments: "" }
12044
- }
12045
- ]
12046
- },
12047
- finish_reason: null
12048
- }
12049
- ]
12050
- })}
12051
-
12052
- `
12053
- );
12054
- } else if (chunk.type === "tool-input-delta") {
12055
- res.write(
12056
- `data: ${JSON.stringify({
12057
- id: completionId,
12058
- object: "chat.completion.chunk",
12059
- created,
12060
- model: modelId,
12061
- choices: [
12062
- {
12063
- index: 0,
12064
- delta: { tool_calls: [{ index: 0, function: { arguments: chunk.delta } }] },
12065
- finish_reason: null
12066
- }
12067
- ]
12068
- })}
12069
-
12070
- `
12071
- );
12072
- } else if (chunk.type === "finish") {
12073
- inputTokens = chunk.usage?.inputTokens ?? 0;
12074
- outputTokens = chunk.usage?.outputTokens ?? 0;
12075
- const finishReason = chunk.finishReason === "tool-calls" ? "tool_calls" : "stop";
12076
- res.write(
12077
- `data: ${JSON.stringify({
12078
- id: completionId,
12079
- object: "chat.completion.chunk",
12080
- created,
12081
- model: modelId,
12082
- choices: [{ index: 0, delta: {}, finish_reason: finishReason }],
12083
- usage: {
12084
- prompt_tokens: inputTokens,
12085
- completion_tokens: outputTokens,
12086
- total_tokens: inputTokens + outputTokens
12087
- }
12088
- })}
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)}
12089
12325
 
12090
- `
12091
- );
12326
+ `);
12092
12327
  }
12093
12328
  }
12094
12329
  res.write("data: [DONE]\n\n");
@@ -12099,29 +12334,12 @@ ${project.description}` : ""}` : "",
12099
12334
  model: languageModel,
12100
12335
  system: systemPrompt || void 0,
12101
12336
  messages: coreMessages,
12102
- tools: hasTools ? convertedTools : void 0,
12337
+ tools: hasTools ? activeTools : void 0,
12103
12338
  maxRetries: 2,
12104
- stopWhen: [(0, import_ai9.stepCountIs)(5)]
12105
- });
12106
- res.json({
12107
- id: completionId,
12108
- object: "chat.completion",
12109
- created,
12110
- model: agentId,
12111
- choices: [
12112
- {
12113
- index: 0,
12114
- message: { role: "assistant", content: text },
12115
- finish_reason: "stop"
12116
- }
12117
- ],
12118
- usage: {
12119
- prompt_tokens: usage.promptTokens,
12120
- completion_tokens: usage.completionTokens,
12121
- total_tokens: usage.totalTokens
12122
- }
12339
+ stopWhen: clientTools.length > 0 ? void 0 : [(0, import_ai9.stepCountIs)(5)]
12123
12340
  });
12124
- 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);
12125
12343
  }
12126
12344
  } catch (error) {
12127
12345
  console.error("[OPENAI GATEWAY] /v1/chat/completions error:", error);
@@ -12169,6 +12387,7 @@ var {
12169
12387
  workflowTemplatesSchema: workflowTemplatesSchema2,
12170
12388
  rbacSchema: rbacSchema2,
12171
12389
  promptLibrarySchema: promptLibrarySchema2,
12390
+ contextPresetsSchema: contextPresetsSchema2,
12172
12391
  embedderSettingsSchema: embedderSettingsSchema2,
12173
12392
  promptFavoritesSchema: promptFavoritesSchema2,
12174
12393
  statisticsSchema: statisticsSchema2
@@ -12216,6 +12435,7 @@ var createExpressRoutes = async (app, providers, tools, contexts, config, evals,
12216
12435
  projectsSchema2(),
12217
12436
  jobResultsSchema2(),
12218
12437
  promptLibrarySchema2(),
12438
+ contextPresetsSchema2(),
12219
12439
  embedderSettingsSchema2(),
12220
12440
  promptFavoritesSchema2(),
12221
12441
  evalRunsSchema2(),
@@ -17282,6 +17502,7 @@ var {
17282
17502
  projectsSchema: projectsSchema3,
17283
17503
  jobResultsSchema: jobResultsSchema3,
17284
17504
  promptLibrarySchema: promptLibrarySchema3,
17505
+ contextPresetsSchema: contextPresetsSchema3,
17285
17506
  embedderSettingsSchema: embedderSettingsSchema3,
17286
17507
  promptFavoritesSchema: promptFavoritesSchema3
17287
17508
  } = coreSchemas.get();
@@ -17319,6 +17540,7 @@ var up = async function(knex) {
17319
17540
  projectsSchema3(),
17320
17541
  jobResultsSchema3(),
17321
17542
  promptLibrarySchema3(),
17543
+ contextPresetsSchema3(),
17322
17544
  embedderSettingsSchema3(),
17323
17545
  promptFavoritesSchema3(),
17324
17546
  rbacSchema3(),