@exulu/backend 2.0.0 → 2.1.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/{chunk-IJ4HNHOT.js → chunk-RVZWZNWG.js} +568 -274
- package/dist/{convert-exulu-tools-to-ai-sdk-tools-2PEDFZ2X.js → convert-exulu-tools-to-ai-sdk-tools-K3RHHLN6.js} +1 -1
- package/dist/index.cjs +1274 -437
- package/dist/index.d.cts +37 -2
- package/dist/index.d.ts +37 -2
- package/dist/index.js +720 -211
- package/ee/agentic-retrieval/pipeline/config.test.ts +15 -0
- package/ee/agentic-retrieval/pipeline/config.ts +6 -0
- package/ee/agentic-retrieval/pipeline/global-ids.ts +30 -0
- package/ee/agentic-retrieval/pipeline/index.test.ts +86 -1
- package/ee/agentic-retrieval/pipeline/index.ts +96 -46
- package/ee/agentic-retrieval/pipeline/project-scope.test.ts +73 -0
- package/ee/agentic-retrieval/pipeline/project-scope.ts +77 -0
- package/ee/agentic-retrieval/pipeline/search.test.ts +27 -0
- package/ee/agentic-retrieval/pipeline/search.ts +7 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2875,126 +2875,14 @@ var init_wrap_execute = __esm({
|
|
|
2875
2875
|
}
|
|
2876
2876
|
});
|
|
2877
2877
|
|
|
2878
|
-
// src/templates/tools/project-retrieval-tool.ts
|
|
2879
|
-
var import_zod2, createProjectItemsRetrievalTool;
|
|
2880
|
-
var init_project_retrieval_tool = __esm({
|
|
2881
|
-
"src/templates/tools/project-retrieval-tool.ts"() {
|
|
2882
|
-
"use strict";
|
|
2883
|
-
init_cjs_shims();
|
|
2884
|
-
init_tool();
|
|
2885
|
-
import_zod2 = require("zod");
|
|
2886
|
-
init_client();
|
|
2887
|
-
createProjectItemsRetrievalTool = async ({
|
|
2888
|
-
user,
|
|
2889
|
-
role,
|
|
2890
|
-
contexts,
|
|
2891
|
-
projectId
|
|
2892
|
-
}) => {
|
|
2893
|
-
let project;
|
|
2894
|
-
const { db: db2 } = await postgresClient();
|
|
2895
|
-
project = await db2.from("projects").where("id", projectId).first();
|
|
2896
|
-
if (!project) {
|
|
2897
|
-
return;
|
|
2898
|
-
}
|
|
2899
|
-
console.log("[EXULU] Project search tool created for project", project);
|
|
2900
|
-
if (!project.project_items?.length) {
|
|
2901
|
-
return;
|
|
2902
|
-
}
|
|
2903
|
-
const projectRetrievalTool = ExuluTool.internal({
|
|
2904
|
-
id: "context_search_in_knowledge_items_added_to_project_" + projectId,
|
|
2905
|
-
name: "context_search in knowledge items added to project " + project.name,
|
|
2906
|
-
description: "This tool retrieves information about a project from conversations and items that were added to the project " + project.name + ".",
|
|
2907
|
-
inputSchema: import_zod2.z.object({
|
|
2908
|
-
query: import_zod2.z.string().describe("The query to retrieve information about the project " + project.name + "."),
|
|
2909
|
-
keywords: import_zod2.z.array(import_zod2.z.string()).describe(
|
|
2910
|
-
"The most relevant keywords in the query, such as names of people, companies, products, etc. in the project " + project.name + "."
|
|
2911
|
-
)
|
|
2912
|
-
}),
|
|
2913
|
-
type: "context",
|
|
2914
|
-
category: "project",
|
|
2915
|
-
config: [],
|
|
2916
|
-
execute: async ({ query }) => {
|
|
2917
|
-
console.log("[EXULU] Project search tool searching for project", project);
|
|
2918
|
-
const items = project.project_items;
|
|
2919
|
-
const set = {};
|
|
2920
|
-
for (const item of items) {
|
|
2921
|
-
const context = item.split("/")[0];
|
|
2922
|
-
if (!context) {
|
|
2923
|
-
throw new Error(
|
|
2924
|
-
"The item added to the project does not have a valid gid with the context id as the prefix before the first slash."
|
|
2925
|
-
);
|
|
2926
|
-
}
|
|
2927
|
-
const id = item.split("/").slice(1).join("/");
|
|
2928
|
-
if (set[context]) {
|
|
2929
|
-
set[context].push(id);
|
|
2930
|
-
} else {
|
|
2931
|
-
set[context] = [id];
|
|
2932
|
-
}
|
|
2933
|
-
}
|
|
2934
|
-
console.log("[EXULU] Project search tool searching through contexts", Object.keys(set));
|
|
2935
|
-
const results = await Promise.all(
|
|
2936
|
-
Object.keys(set).map(async (contextName) => {
|
|
2937
|
-
const context = contexts.find((context2) => context2.id === contextName);
|
|
2938
|
-
if (!context) {
|
|
2939
|
-
console.error(
|
|
2940
|
-
"[EXULU] Context not found for project information retrieval tool.",
|
|
2941
|
-
contextName
|
|
2942
|
-
);
|
|
2943
|
-
return [];
|
|
2944
|
-
}
|
|
2945
|
-
const itemIds = set[contextName];
|
|
2946
|
-
console.log("[EXULU] Project search tool searching through items", itemIds);
|
|
2947
|
-
const result = await context.search({
|
|
2948
|
-
query,
|
|
2949
|
-
itemFilters: [
|
|
2950
|
-
{
|
|
2951
|
-
id: {
|
|
2952
|
-
in: itemIds
|
|
2953
|
-
}
|
|
2954
|
-
}
|
|
2955
|
-
],
|
|
2956
|
-
chunkFilters: [],
|
|
2957
|
-
user,
|
|
2958
|
-
role,
|
|
2959
|
-
method: "hybridSearch",
|
|
2960
|
-
sort: {
|
|
2961
|
-
field: "updatedAt",
|
|
2962
|
-
direction: "desc"
|
|
2963
|
-
},
|
|
2964
|
-
trigger: "tool",
|
|
2965
|
-
limit: 10,
|
|
2966
|
-
page: 1
|
|
2967
|
-
});
|
|
2968
|
-
return {
|
|
2969
|
-
result: result.chunks.map((chunk) => ({
|
|
2970
|
-
...chunk,
|
|
2971
|
-
context: {
|
|
2972
|
-
name: context.name,
|
|
2973
|
-
id: context.id
|
|
2974
|
-
}
|
|
2975
|
-
}))
|
|
2976
|
-
};
|
|
2977
|
-
})
|
|
2978
|
-
);
|
|
2979
|
-
console.log("[EXULU] Project search tool results", results);
|
|
2980
|
-
return {
|
|
2981
|
-
result: JSON.stringify(results.flat())
|
|
2982
|
-
};
|
|
2983
|
-
}
|
|
2984
|
-
});
|
|
2985
|
-
return projectRetrievalTool;
|
|
2986
|
-
};
|
|
2987
|
-
}
|
|
2988
|
-
});
|
|
2989
|
-
|
|
2990
2878
|
// src/templates/tools/session-items-retrieval-tool.ts
|
|
2991
|
-
var
|
|
2879
|
+
var import_zod2, createSessionItemsRetrievalTool;
|
|
2992
2880
|
var init_session_items_retrieval_tool = __esm({
|
|
2993
2881
|
"src/templates/tools/session-items-retrieval-tool.ts"() {
|
|
2994
2882
|
"use strict";
|
|
2995
2883
|
init_cjs_shims();
|
|
2996
2884
|
init_tool();
|
|
2997
|
-
|
|
2885
|
+
import_zod2 = require("zod");
|
|
2998
2886
|
createSessionItemsRetrievalTool = async ({
|
|
2999
2887
|
user,
|
|
3000
2888
|
role,
|
|
@@ -3006,8 +2894,8 @@ var init_session_items_retrieval_tool = __esm({
|
|
|
3006
2894
|
id: "session_items_information_context_search",
|
|
3007
2895
|
name: "context_search in knowledge items added to session.",
|
|
3008
2896
|
description: "Context search in knowledge items added to session.",
|
|
3009
|
-
inputSchema:
|
|
3010
|
-
query:
|
|
2897
|
+
inputSchema: import_zod2.z.object({
|
|
2898
|
+
query: import_zod2.z.string().describe("The query to retrieve information from knowledge items added to the session.")
|
|
3011
2899
|
}),
|
|
3012
2900
|
type: "context",
|
|
3013
2901
|
category: "session",
|
|
@@ -3079,6 +2967,80 @@ var init_session_items_retrieval_tool = __esm({
|
|
|
3079
2967
|
}
|
|
3080
2968
|
});
|
|
3081
2969
|
|
|
2970
|
+
// ee/agentic-retrieval/pipeline/global-ids.ts
|
|
2971
|
+
function parsePreselectedItems(globalIds) {
|
|
2972
|
+
const map = /* @__PURE__ */ new Map();
|
|
2973
|
+
for (const gid of globalIds) {
|
|
2974
|
+
const slashIdx = gid.indexOf("/");
|
|
2975
|
+
if (slashIdx === -1) {
|
|
2976
|
+
if (gid) map.set(gid, null);
|
|
2977
|
+
continue;
|
|
2978
|
+
}
|
|
2979
|
+
const contextId = gid.slice(0, slashIdx);
|
|
2980
|
+
const itemId = gid.slice(slashIdx + 1);
|
|
2981
|
+
if (!contextId || !itemId) continue;
|
|
2982
|
+
if (map.get(contextId) === null) continue;
|
|
2983
|
+
const existing = map.get(contextId) ?? [];
|
|
2984
|
+
existing.push(itemId);
|
|
2985
|
+
map.set(contextId, existing);
|
|
2986
|
+
}
|
|
2987
|
+
return map;
|
|
2988
|
+
}
|
|
2989
|
+
var init_global_ids = __esm({
|
|
2990
|
+
"ee/agentic-retrieval/pipeline/global-ids.ts"() {
|
|
2991
|
+
"use strict";
|
|
2992
|
+
init_cjs_shims();
|
|
2993
|
+
}
|
|
2994
|
+
});
|
|
2995
|
+
|
|
2996
|
+
// ee/agentic-retrieval/pipeline/project-scope.ts
|
|
2997
|
+
function resolveProjectScope(opts) {
|
|
2998
|
+
const { scope, enabledContextIds, availableContextIds } = opts;
|
|
2999
|
+
if (!scope || scope.items.length === 0) return void 0;
|
|
3000
|
+
const itemsByContext = parsePreselectedItems(scope.items);
|
|
3001
|
+
const pinsByContext = /* @__PURE__ */ new Map();
|
|
3002
|
+
const scopedItemsByContext = /* @__PURE__ */ new Map();
|
|
3003
|
+
const addedContextIds = [];
|
|
3004
|
+
const allProjectContextIds = [];
|
|
3005
|
+
for (const [ctxId, itemIds] of itemsByContext) {
|
|
3006
|
+
if (!availableContextIds.has(ctxId)) {
|
|
3007
|
+
console.warn(
|
|
3008
|
+
`[EXULU pipeline] project "${scope.name}" references unknown context "${ctxId}" \u2014 skipping those items.`
|
|
3009
|
+
);
|
|
3010
|
+
continue;
|
|
3011
|
+
}
|
|
3012
|
+
allProjectContextIds.push(ctxId);
|
|
3013
|
+
if (enabledContextIds.has(ctxId)) {
|
|
3014
|
+
if (itemIds && itemIds.length > 0) pinsByContext.set(ctxId, new Set(itemIds));
|
|
3015
|
+
} else {
|
|
3016
|
+
scopedItemsByContext.set(ctxId, itemIds);
|
|
3017
|
+
addedContextIds.push(ctxId);
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
if (allProjectContextIds.length === 0) return void 0;
|
|
3021
|
+
return { pinsByContext, scopedItemsByContext, addedContextIds, allProjectContextIds };
|
|
3022
|
+
}
|
|
3023
|
+
function buildProjectKbProfileDefaults(items) {
|
|
3024
|
+
const defaults = {};
|
|
3025
|
+
for (const gid of items) {
|
|
3026
|
+
const slashIdx = gid.indexOf("/");
|
|
3027
|
+
const ctxId = slashIdx === -1 ? gid : gid.slice(0, slashIdx);
|
|
3028
|
+
if (ctxId === TRANSCRIPTIONS_CONTEXT_ID && !defaults[ctxId]) {
|
|
3029
|
+
defaults[ctxId] = { enabled: true, kind: "conversations", instructions: "", overrides: {} };
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
return defaults;
|
|
3033
|
+
}
|
|
3034
|
+
var TRANSCRIPTIONS_CONTEXT_ID;
|
|
3035
|
+
var init_project_scope = __esm({
|
|
3036
|
+
"ee/agentic-retrieval/pipeline/project-scope.ts"() {
|
|
3037
|
+
"use strict";
|
|
3038
|
+
init_cjs_shims();
|
|
3039
|
+
init_global_ids();
|
|
3040
|
+
TRANSCRIPTIONS_CONTEXT_ID = "transcriptions";
|
|
3041
|
+
}
|
|
3042
|
+
});
|
|
3043
|
+
|
|
3082
3044
|
// src/utils/sanitize-tool-name.ts
|
|
3083
3045
|
function sanitizeToolName(name) {
|
|
3084
3046
|
if (typeof name !== "string") return "";
|
|
@@ -3106,19 +3068,19 @@ var init_sanitize_tool_name = __esm({
|
|
|
3106
3068
|
});
|
|
3107
3069
|
|
|
3108
3070
|
// src/templates/tools/memory-tool.ts
|
|
3109
|
-
var
|
|
3071
|
+
var import_zod3, createNewMemoryItemTool;
|
|
3110
3072
|
var init_memory_tool = __esm({
|
|
3111
3073
|
"src/templates/tools/memory-tool.ts"() {
|
|
3112
3074
|
"use strict";
|
|
3113
3075
|
init_cjs_shims();
|
|
3114
3076
|
init_tool();
|
|
3115
|
-
|
|
3077
|
+
import_zod3 = require("zod");
|
|
3116
3078
|
init_sanitize_name();
|
|
3117
3079
|
createNewMemoryItemTool = (agent, context) => {
|
|
3118
3080
|
const fields = {
|
|
3119
|
-
name:
|
|
3120
|
-
description:
|
|
3121
|
-
surroundingContext:
|
|
3081
|
+
name: import_zod3.z.string().describe("The name of the item to create"),
|
|
3082
|
+
description: import_zod3.z.string().describe("The description of the item to create"),
|
|
3083
|
+
surroundingContext: import_zod3.z.string().describe("A description of the context surrounding this memory, for example if it relates to a question a user asked, a specific product, or entity etc...")
|
|
3122
3084
|
};
|
|
3123
3085
|
for (const field of context.fields) {
|
|
3124
3086
|
switch (field.type) {
|
|
@@ -3126,47 +3088,47 @@ var init_memory_tool = __esm({
|
|
|
3126
3088
|
case "longText":
|
|
3127
3089
|
case "shortText":
|
|
3128
3090
|
case "code":
|
|
3129
|
-
fields[field.name] =
|
|
3091
|
+
fields[field.name] = import_zod3.z.string().describe("The " + field.name + " of the item to create");
|
|
3130
3092
|
break;
|
|
3131
3093
|
case "enum":
|
|
3132
3094
|
if (field.enumValues && field.enumValues.length > 0) {
|
|
3133
3095
|
const enumValues = field.enumValues;
|
|
3134
|
-
fields[field.name] =
|
|
3096
|
+
fields[field.name] = import_zod3.z.preprocess(
|
|
3135
3097
|
(v) => typeof v === "string" ? v.toUpperCase() : v,
|
|
3136
|
-
|
|
3098
|
+
import_zod3.z.enum(enumValues)
|
|
3137
3099
|
).describe(
|
|
3138
3100
|
"The " + field.name + " of the item to create. Must be one of: " + field.enumValues.join(", ")
|
|
3139
3101
|
);
|
|
3140
3102
|
} else {
|
|
3141
|
-
fields[field.name] =
|
|
3103
|
+
fields[field.name] = import_zod3.z.string().describe("The " + field.name + " of the item to create");
|
|
3142
3104
|
}
|
|
3143
3105
|
break;
|
|
3144
3106
|
case "json":
|
|
3145
|
-
fields[field.name] =
|
|
3107
|
+
fields[field.name] = import_zod3.z.string({}).describe(
|
|
3146
3108
|
"The " + field.name + " of the item to create, it should be a valid JSON string."
|
|
3147
3109
|
);
|
|
3148
3110
|
break;
|
|
3149
3111
|
case "markdown":
|
|
3150
|
-
fields[field.name] =
|
|
3112
|
+
fields[field.name] = import_zod3.z.string().describe(
|
|
3151
3113
|
"The " + field.name + " of the item to create, it should be a valid Markdown string."
|
|
3152
3114
|
);
|
|
3153
3115
|
break;
|
|
3154
3116
|
case "number":
|
|
3155
|
-
fields[field.name] =
|
|
3117
|
+
fields[field.name] = import_zod3.z.number().describe("The " + field.name + " of the item to create");
|
|
3156
3118
|
break;
|
|
3157
3119
|
case "boolean":
|
|
3158
|
-
fields[field.name] =
|
|
3120
|
+
fields[field.name] = import_zod3.z.boolean().describe("The " + field.name + " of the item to create");
|
|
3159
3121
|
break;
|
|
3160
3122
|
case "file":
|
|
3161
3123
|
case "uuid":
|
|
3162
3124
|
case "date":
|
|
3163
3125
|
break;
|
|
3164
3126
|
default:
|
|
3165
|
-
fields[field.name] =
|
|
3127
|
+
fields[field.name] = import_zod3.z.string().describe("The " + field.name + " of the item to create");
|
|
3166
3128
|
break;
|
|
3167
3129
|
}
|
|
3168
3130
|
}
|
|
3169
|
-
fields["visibility"] =
|
|
3131
|
+
fields["visibility"] = import_zod3.z.enum(["private", "public"]).optional().describe(
|
|
3170
3132
|
"Whether this memory is private to the user or shared (public). Ask the user if unknown."
|
|
3171
3133
|
);
|
|
3172
3134
|
const toolName = "create_" + sanitizeName(context.name) + "_memory_item";
|
|
@@ -3176,7 +3138,7 @@ var init_memory_tool = __esm({
|
|
|
3176
3138
|
category: agent.name + "_memory",
|
|
3177
3139
|
description: "Create a new memory item in the " + agent.name + " memory context",
|
|
3178
3140
|
type: "function",
|
|
3179
|
-
inputSchema:
|
|
3141
|
+
inputSchema: import_zod3.z.object(fields),
|
|
3180
3142
|
config: [],
|
|
3181
3143
|
execute: async (params) => {
|
|
3182
3144
|
const { name, description, surroundingContext, information, visibility, exuluConfig, user } = params;
|
|
@@ -3747,9 +3709,9 @@ Probe error: ${probe.reason ?? "(no detail)"}`
|
|
|
3747
3709
|
});
|
|
3748
3710
|
const writeFileTool = (0, import_ai2.tool)({
|
|
3749
3711
|
description: 'Write content to a file in the sandbox. Creates parent directories if needed. Paths are always resolved against the session sandbox root \u2014 both relative paths ("skills/foo.md") and leading-slash paths ("/skills/foo.md") work and reach the same file. When the path is under the session artifact tree, the file is also uploaded to S3 and a short-lived presigned URL is returned in the tool output.',
|
|
3750
|
-
inputSchema:
|
|
3751
|
-
path:
|
|
3752
|
-
content:
|
|
3712
|
+
inputSchema: import_zod4.z.object({
|
|
3713
|
+
path: import_zod4.z.string().describe("The path where the file should be written. Relative paths and leading-slash paths are both resolved against the session sandbox root."),
|
|
3714
|
+
content: import_zod4.z.string().describe("The content to write to the file")
|
|
3753
3715
|
}),
|
|
3754
3716
|
execute: async ({ path: path2, content }) => {
|
|
3755
3717
|
const resolvedPath = resolveSessionPath(path2, sessionDir);
|
|
@@ -3768,8 +3730,8 @@ Probe error: ${probe.reason ?? "(no detail)"}`
|
|
|
3768
3730
|
});
|
|
3769
3731
|
const readFileTool = (0, import_ai2.tool)({
|
|
3770
3732
|
description: 'Read the contents of a file from the sandbox. Paths are always resolved against the session sandbox root \u2014 both relative paths ("skills/foo.md") and leading-slash paths ("/skills/foo.md") work and reach the same file. If the file does not exist, the error message is surfaced verbatim.',
|
|
3771
|
-
inputSchema:
|
|
3772
|
-
path:
|
|
3733
|
+
inputSchema: import_zod4.z.object({
|
|
3734
|
+
path: import_zod4.z.string().describe("The path of the file to read. Relative paths and leading-slash paths are both resolved against the session sandbox root.")
|
|
3773
3735
|
}),
|
|
3774
3736
|
execute: async ({ path: path2 }) => {
|
|
3775
3737
|
const resolvedPath = resolveSessionPath(path2, sessionDir);
|
|
@@ -3780,8 +3742,8 @@ Probe error: ${probe.reason ?? "(no detail)"}`
|
|
|
3780
3742
|
const originalBashTool = tools.bash;
|
|
3781
3743
|
const bashTool = (0, import_ai2.tool)({
|
|
3782
3744
|
description: originalBashTool.description ?? "",
|
|
3783
|
-
inputSchema:
|
|
3784
|
-
command:
|
|
3745
|
+
inputSchema: import_zod4.z.object({
|
|
3746
|
+
command: import_zod4.z.string().describe("The bash command to execute.")
|
|
3785
3747
|
}),
|
|
3786
3748
|
execute: async (args, opts) => {
|
|
3787
3749
|
const before = persistenceEnabled ? await snapshotSessionArtifacts() : null;
|
|
@@ -3851,7 +3813,7 @@ ${lines.join("\n")}`;
|
|
|
3851
3813
|
sandboxCache.set(sessionId, { handle, installedSkills });
|
|
3852
3814
|
return handle;
|
|
3853
3815
|
}
|
|
3854
|
-
var import_sandbox_runtime, import_promises, import_node_fs5, import_node_path4, import_node_child_process3, import_node_util2, import_bash_tool, import_ai2,
|
|
3816
|
+
var import_sandbox_runtime, import_promises, import_node_fs5, import_node_path4, import_node_child_process3, import_node_util2, import_bash_tool, import_ai2, import_zod4, import_crypto_js4, getAllExuluVariables, execAsync2, EXEC_MAX_BUFFER, sandboxProbePromise, SANDBOX_FALLBACK_INSTRUCTIONS, degradedModeLogged, sandboxCache;
|
|
3855
3817
|
var init_create_sandbox = __esm({
|
|
3856
3818
|
"ee/invoke-skills/create-sandbox.ts"() {
|
|
3857
3819
|
"use strict";
|
|
@@ -3866,7 +3828,7 @@ var init_create_sandbox = __esm({
|
|
|
3866
3828
|
init_system_dependencies();
|
|
3867
3829
|
import_bash_tool = require("bash-tool");
|
|
3868
3830
|
import_ai2 = require("ai");
|
|
3869
|
-
|
|
3831
|
+
import_zod4 = require("zod");
|
|
3870
3832
|
init_variable();
|
|
3871
3833
|
import_crypto_js4 = __toESM(require("crypto-js"), 1);
|
|
3872
3834
|
init_client();
|
|
@@ -3910,9 +3872,9 @@ var init_truncate_tool_output = __esm({
|
|
|
3910
3872
|
"src/utils/truncate-tool-output.ts"() {
|
|
3911
3873
|
"use strict";
|
|
3912
3874
|
init_cjs_shims();
|
|
3913
|
-
truncateToolOutput = (output, maxContextLength, toolName, tailFraction = 0.1) => {
|
|
3875
|
+
truncateToolOutput = (output, maxContextLength, toolName, tailFraction = 0.1, charLimitOverride) => {
|
|
3914
3876
|
const effectiveCtx = maxContextLength != null && maxContextLength > 0 ? maxContextLength : 128e3;
|
|
3915
|
-
const charLimit = Math.floor(effectiveCtx * 0.25 * 4);
|
|
3877
|
+
const charLimit = charLimitOverride != null && charLimitOverride > 0 ? charLimitOverride : Math.floor(effectiveCtx * 0.25 * 4);
|
|
3916
3878
|
const clampedTail = Math.min(1, Math.max(0, tailFraction));
|
|
3917
3879
|
if (output.length <= charLimit) return output;
|
|
3918
3880
|
const headChars = Math.floor(charLimit * (1 - clampedTail));
|
|
@@ -3934,13 +3896,257 @@ var init_truncate_tool_output = __esm({
|
|
|
3934
3896
|
}
|
|
3935
3897
|
});
|
|
3936
3898
|
|
|
3899
|
+
// src/exulu/context-budget.ts
|
|
3900
|
+
var DEFAULT_CONTEXT_WINDOW, deriveContextBudget, estimateTokens, estimateMessageTokens, getCompaction, sliceHistoryAtCheckpoint, contextOccupancy, CONTEXT_COMPACTION_REQUIRED, COMPACTION_INSUFFICIENT, ContextCompactionRequiredError, PROVIDER_CONTEXT_ERROR_PATTERNS, isProviderContextLengthError, mapStreamErrorMessage;
|
|
3901
|
+
var init_context_budget = __esm({
|
|
3902
|
+
"src/exulu/context-budget.ts"() {
|
|
3903
|
+
"use strict";
|
|
3904
|
+
init_cjs_shims();
|
|
3905
|
+
DEFAULT_CONTEXT_WINDOW = 128e3;
|
|
3906
|
+
deriveContextBudget = (contextWindowInput) => {
|
|
3907
|
+
const contextWindow = contextWindowInput != null && contextWindowInput > 0 ? contextWindowInput : DEFAULT_CONTEXT_WINDOW;
|
|
3908
|
+
const outputReserve = Math.min(32e3, Math.floor(contextWindow * 0.2));
|
|
3909
|
+
const usableWindow = contextWindow - outputReserve;
|
|
3910
|
+
return {
|
|
3911
|
+
contextWindow,
|
|
3912
|
+
outputReserve,
|
|
3913
|
+
usableWindow,
|
|
3914
|
+
warnThreshold: Math.floor(usableWindow * 0.8),
|
|
3915
|
+
blockThreshold: Math.floor(usableWindow * 0.95),
|
|
3916
|
+
toolOutputCapTokens: Math.min(25e3, Math.max(4e3, Math.floor(contextWindow * 0.1))),
|
|
3917
|
+
compactionTailTokens: Math.floor(usableWindow * 0.1),
|
|
3918
|
+
summaryBudgetTokens: Math.min(8e3, Math.floor(usableWindow * 0.05))
|
|
3919
|
+
};
|
|
3920
|
+
};
|
|
3921
|
+
estimateTokens = (text) => text ? Math.ceil(text.length / 4) : 0;
|
|
3922
|
+
estimateMessageTokens = (message) => estimateTokens(JSON.stringify(message));
|
|
3923
|
+
getCompaction = (message) => message.metadata?.compaction;
|
|
3924
|
+
sliceHistoryAtCheckpoint = (messages) => {
|
|
3925
|
+
let checkpointIdx = -1;
|
|
3926
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
3927
|
+
if (getCompaction(messages[i])) {
|
|
3928
|
+
checkpointIdx = i;
|
|
3929
|
+
break;
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
if (checkpointIdx === -1) return messages;
|
|
3933
|
+
const checkpoint = messages[checkpointIdx];
|
|
3934
|
+
const coversUpTo = getCompaction(checkpoint).coversUpTo;
|
|
3935
|
+
const coversIdx = messages.findIndex((m) => m.id === coversUpTo);
|
|
3936
|
+
const boundary = coversIdx === -1 ? checkpointIdx : coversIdx;
|
|
3937
|
+
const after = messages.filter((m, i) => i > boundary && i !== checkpointIdx);
|
|
3938
|
+
return [checkpoint, ...after];
|
|
3939
|
+
};
|
|
3940
|
+
contextOccupancy = (messages) => {
|
|
3941
|
+
let anchorIdx = -1;
|
|
3942
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
3943
|
+
const m = messages[i];
|
|
3944
|
+
const meta = m.metadata;
|
|
3945
|
+
if (getCompaction(m) || m.role === "assistant" && (typeof meta?.inputTokens === "number" || typeof meta?.lastStepInputTokens === "number")) {
|
|
3946
|
+
anchorIdx = i;
|
|
3947
|
+
break;
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
let total = 0;
|
|
3951
|
+
let rest = messages;
|
|
3952
|
+
if (anchorIdx !== -1) {
|
|
3953
|
+
const anchor = messages[anchorIdx];
|
|
3954
|
+
const compaction = getCompaction(anchor);
|
|
3955
|
+
if (compaction) {
|
|
3956
|
+
total = compaction.occupancyEstimate;
|
|
3957
|
+
} else {
|
|
3958
|
+
const meta = anchor.metadata;
|
|
3959
|
+
total = typeof meta.lastStepInputTokens === "number" ? meta.lastStepInputTokens + (meta.lastStepOutputTokens ?? 0) : (meta.inputTokens ?? 0) + (meta.outputTokens ?? 0);
|
|
3960
|
+
}
|
|
3961
|
+
rest = messages.slice(anchorIdx + 1);
|
|
3962
|
+
}
|
|
3963
|
+
for (const m of rest) total += estimateMessageTokens(m);
|
|
3964
|
+
return total;
|
|
3965
|
+
};
|
|
3966
|
+
CONTEXT_COMPACTION_REQUIRED = "CONTEXT_COMPACTION_REQUIRED";
|
|
3967
|
+
COMPACTION_INSUFFICIENT = "COMPACTION_INSUFFICIENT";
|
|
3968
|
+
ContextCompactionRequiredError = class extends Error {
|
|
3969
|
+
constructor(occupancy, budget) {
|
|
3970
|
+
super(
|
|
3971
|
+
JSON.stringify({
|
|
3972
|
+
code: CONTEXT_COMPACTION_REQUIRED,
|
|
3973
|
+
message: `This conversation no longer fits the model's context window (~${occupancy.toLocaleString("en-US")} of ${budget.usableWindow.toLocaleString("en-US")} usable tokens). Compact the conversation to continue.`,
|
|
3974
|
+
occupancy,
|
|
3975
|
+
usableWindow: budget.usableWindow,
|
|
3976
|
+
contextWindow: budget.contextWindow
|
|
3977
|
+
})
|
|
3978
|
+
);
|
|
3979
|
+
this.occupancy = occupancy;
|
|
3980
|
+
this.budget = budget;
|
|
3981
|
+
this.name = "ContextCompactionRequiredError";
|
|
3982
|
+
}
|
|
3983
|
+
};
|
|
3984
|
+
PROVIDER_CONTEXT_ERROR_PATTERNS = [
|
|
3985
|
+
/ContextWindowExceededError/i,
|
|
3986
|
+
/context.?window/i,
|
|
3987
|
+
/context.?length/i,
|
|
3988
|
+
/maximum context/i,
|
|
3989
|
+
/prompt is too long/i,
|
|
3990
|
+
/input is too long/i,
|
|
3991
|
+
/token count exceeds/i,
|
|
3992
|
+
/too many tokens/i
|
|
3993
|
+
];
|
|
3994
|
+
isProviderContextLengthError = (message) => PROVIDER_CONTEXT_ERROR_PATTERNS.some((re) => re.test(message));
|
|
3995
|
+
mapStreamErrorMessage = (message) => isProviderContextLengthError(message) ? JSON.stringify({
|
|
3996
|
+
code: CONTEXT_COMPACTION_REQUIRED,
|
|
3997
|
+
message: "The model rejected the request because the conversation exceeds its context window. Compact the conversation to continue.",
|
|
3998
|
+
providerMessage: message.slice(0, 500)
|
|
3999
|
+
}) : message;
|
|
4000
|
+
}
|
|
4001
|
+
});
|
|
4002
|
+
|
|
4003
|
+
// src/exulu/tool-output-offload.ts
|
|
4004
|
+
var import_node_crypto3, PREVIEW_CHARS, storeAsSessionFile, buildNotice, guardToolOutput, guardExtractedFileText;
|
|
4005
|
+
var init_tool_output_offload = __esm({
|
|
4006
|
+
"src/exulu/tool-output-offload.ts"() {
|
|
4007
|
+
"use strict";
|
|
4008
|
+
init_cjs_shims();
|
|
4009
|
+
import_node_crypto3 = require("crypto");
|
|
4010
|
+
init_uppy();
|
|
4011
|
+
init_context_budget();
|
|
4012
|
+
PREVIEW_CHARS = 4e3;
|
|
4013
|
+
storeAsSessionFile = async (serialized, ctx) => {
|
|
4014
|
+
if (!ctx.sessionID || !ctx.exuluConfig?.fileUploads) return void 0;
|
|
4015
|
+
const safeTool = ctx.toolName.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 40);
|
|
4016
|
+
const name = `tool-output-${safeTool}-${(0, import_node_crypto3.randomUUID)().slice(0, 8)}.txt`;
|
|
4017
|
+
try {
|
|
4018
|
+
await uploadFile(
|
|
4019
|
+
Buffer.from(serialized, "utf-8"),
|
|
4020
|
+
`sessions/${ctx.sessionID}/${name}`,
|
|
4021
|
+
ctx.exuluConfig,
|
|
4022
|
+
{ contentType: "text/plain" },
|
|
4023
|
+
ctx.user?.id
|
|
4024
|
+
);
|
|
4025
|
+
return name;
|
|
4026
|
+
} catch (err) {
|
|
4027
|
+
console.error("[EXULU] Failed to offload oversized tool output to session files.", err);
|
|
4028
|
+
return void 0;
|
|
4029
|
+
}
|
|
4030
|
+
};
|
|
4031
|
+
buildNotice = (tokens, capTokens, sessionFile) => sessionFile ? `Tool output truncated: ~${tokens.toLocaleString("en-US")} tokens (limit ${capTokens.toLocaleString("en-US")}). The FULL output is saved as session file "${sessionFile}" \u2014 call read_session_file with { filename: "${sessionFile}", offset, limit } to read specific line ranges.` : `Tool output truncated: ~${tokens.toLocaleString("en-US")} tokens (limit ${capTokens.toLocaleString("en-US")}). The remainder was discarded \u2014 re-run the tool with narrower arguments.`;
|
|
4032
|
+
guardToolOutput = async (value, ctx) => {
|
|
4033
|
+
if (value == null) return value;
|
|
4034
|
+
let serialized;
|
|
4035
|
+
try {
|
|
4036
|
+
serialized = typeof value === "string" ? value : JSON.stringify(value);
|
|
4037
|
+
} catch {
|
|
4038
|
+
return value;
|
|
4039
|
+
}
|
|
4040
|
+
if (typeof serialized !== "string") return value;
|
|
4041
|
+
const budget = deriveContextBudget(ctx.contextWindow);
|
|
4042
|
+
const tokens = estimateTokens(serialized);
|
|
4043
|
+
if (tokens <= budget.toolOutputCapTokens) return value;
|
|
4044
|
+
const sessionFile = await storeAsSessionFile(serialized, ctx);
|
|
4045
|
+
const result = {
|
|
4046
|
+
truncated: true,
|
|
4047
|
+
notice: buildNotice(tokens, budget.toolOutputCapTokens, sessionFile),
|
|
4048
|
+
...sessionFile ? { sessionFile } : {},
|
|
4049
|
+
preview: serialized.slice(0, PREVIEW_CHARS)
|
|
4050
|
+
};
|
|
4051
|
+
return result;
|
|
4052
|
+
};
|
|
4053
|
+
guardExtractedFileText = async (filename, text, ctx) => {
|
|
4054
|
+
const budget = deriveContextBudget(ctx.contextWindow);
|
|
4055
|
+
const tokens = estimateTokens(text);
|
|
4056
|
+
if (tokens <= budget.toolOutputCapTokens) return text;
|
|
4057
|
+
const sessionFile = await storeAsSessionFile(text, { ...ctx, toolName: `upload-${filename}` });
|
|
4058
|
+
const notice = sessionFile ? `[Document "${filename}" truncated: ~${tokens.toLocaleString("en-US")} tokens. The full extracted text is saved as session file "${sessionFile}" \u2014 read specific parts with read_session_file (offset/limit).]` : `[Document "${filename}" truncated: ~${tokens.toLocaleString("en-US")} tokens \u2014 the remainder is unavailable.]`;
|
|
4059
|
+
return `${text.slice(0, PREVIEW_CHARS)}
|
|
4060
|
+
|
|
4061
|
+
${notice}`;
|
|
4062
|
+
};
|
|
4063
|
+
}
|
|
4064
|
+
});
|
|
4065
|
+
|
|
4066
|
+
// src/templates/tools/session-file-read-tool.ts
|
|
4067
|
+
var import_zod5, DEFAULT_LIMIT, MAX_CONTENT_CHARS, createSessionFileReadTool;
|
|
4068
|
+
var init_session_file_read_tool = __esm({
|
|
4069
|
+
"src/templates/tools/session-file-read-tool.ts"() {
|
|
4070
|
+
"use strict";
|
|
4071
|
+
init_cjs_shims();
|
|
4072
|
+
import_zod5 = require("zod");
|
|
4073
|
+
init_tool();
|
|
4074
|
+
init_uppy();
|
|
4075
|
+
DEFAULT_LIMIT = 250;
|
|
4076
|
+
MAX_CONTENT_CHARS = 16e3;
|
|
4077
|
+
createSessionFileReadTool = ({
|
|
4078
|
+
sessionID,
|
|
4079
|
+
user,
|
|
4080
|
+
exuluConfig
|
|
4081
|
+
}) => {
|
|
4082
|
+
if (!sessionID || !exuluConfig?.fileUploads?.s3Bucket) return void 0;
|
|
4083
|
+
const readSessionFileExecute = async ({ filename, offset, limit }) => {
|
|
4084
|
+
const safeName = String(filename ?? "").trim();
|
|
4085
|
+
if (!safeName || safeName.includes("..") || safeName.includes("/") || safeName.includes("\\")) {
|
|
4086
|
+
return {
|
|
4087
|
+
error: "Invalid filename \u2014 pass the bare file name exactly as listed in the session files (no paths)."
|
|
4088
|
+
};
|
|
4089
|
+
}
|
|
4090
|
+
const uploads = exuluConfig.fileUploads;
|
|
4091
|
+
const generalPrefix = uploads.s3prefix ? `${uploads.s3prefix.replace(/\/$/, "")}/` : "";
|
|
4092
|
+
const key = `${generalPrefix}user_${user?.id ?? "api"}/sessions/${sessionID}/${safeName}`;
|
|
4093
|
+
try {
|
|
4094
|
+
const url = await getPresignedUrl(uploads.s3Bucket, key, exuluConfig);
|
|
4095
|
+
const res = await fetch(url);
|
|
4096
|
+
if (!res.ok) {
|
|
4097
|
+
return { error: `Could not read session file "${safeName}" (status ${res.status}). Check the exact file name.` };
|
|
4098
|
+
}
|
|
4099
|
+
const textBody = await res.text();
|
|
4100
|
+
const lines = textBody.split("\n");
|
|
4101
|
+
const start = (offset ?? 1) - 1;
|
|
4102
|
+
const requested = limit ?? DEFAULT_LIMIT;
|
|
4103
|
+
const sliced = lines.slice(start, start + requested);
|
|
4104
|
+
let content = sliced.join("\n");
|
|
4105
|
+
let linesReturned = sliced.length;
|
|
4106
|
+
if (content.length > MAX_CONTENT_CHARS) {
|
|
4107
|
+
content = content.slice(0, MAX_CONTENT_CHARS);
|
|
4108
|
+
linesReturned = Math.max(1, content.split("\n").length - 1);
|
|
4109
|
+
content = content + "\n[slice truncated \u2014 request fewer lines]";
|
|
4110
|
+
}
|
|
4111
|
+
return {
|
|
4112
|
+
content,
|
|
4113
|
+
totalLines: lines.length,
|
|
4114
|
+
offset: start + 1,
|
|
4115
|
+
linesReturned
|
|
4116
|
+
};
|
|
4117
|
+
} catch (err) {
|
|
4118
|
+
return { error: `Failed to read session file "${safeName}": ${err instanceof Error ? err.message : "unknown error"}` };
|
|
4119
|
+
}
|
|
4120
|
+
};
|
|
4121
|
+
return ExuluTool.internal({
|
|
4122
|
+
id: "read_session_file",
|
|
4123
|
+
name: "read_session_file",
|
|
4124
|
+
needsApproval: false,
|
|
4125
|
+
description: "Read a line range from a file stored in this session's files \u2014 including offloaded tool outputs (tool-output-*.txt) and uploaded documents. Use offset (1-based line number) and limit to page through large files instead of reading everything at once.",
|
|
4126
|
+
inputSchema: import_zod5.z.object({
|
|
4127
|
+
filename: import_zod5.z.string().describe('Exact session file name as referenced in a truncation notice, e.g. "tool-output-web_search-a1b2c3d4.txt"'),
|
|
4128
|
+
offset: import_zod5.z.number().int().min(1).optional().describe("1-based first line to read (default 1)"),
|
|
4129
|
+
limit: import_zod5.z.number().int().min(1).max(1e3).optional().describe(`Number of lines to read (default ${DEFAULT_LIMIT})`)
|
|
4130
|
+
}),
|
|
4131
|
+
type: "function",
|
|
4132
|
+
category: "session",
|
|
4133
|
+
config: [],
|
|
4134
|
+
// ExuluTool's execute type is modeled on retrieval tools ({result/job/items});
|
|
4135
|
+
// internal utility tools return richer shapes (memory-tool has the same
|
|
4136
|
+
// mismatch). The AI SDK passes the object through verbatim, so cast.
|
|
4137
|
+
execute: readSessionFileExecute
|
|
4138
|
+
});
|
|
4139
|
+
};
|
|
4140
|
+
}
|
|
4141
|
+
});
|
|
4142
|
+
|
|
3937
4143
|
// src/templates/tools/convert-exulu-tools-to-ai-sdk-tools.ts
|
|
3938
4144
|
var convert_exulu_tools_to_ai_sdk_tools_exports = {};
|
|
3939
4145
|
__export(convert_exulu_tools_to_ai_sdk_tools_exports, {
|
|
3940
4146
|
convertExuluToolsToAiSdkTools: () => convertExuluToolsToAiSdkTools,
|
|
3941
4147
|
hydrateVariables: () => hydrateVariables
|
|
3942
4148
|
});
|
|
3943
|
-
var import_client_s32, import_crypto_js5,
|
|
4149
|
+
var import_client_s32, import_crypto_js5, import_node_crypto4, OUTPUT_OFFLOAD_EXEMPT_TOOL_IDS, generateS3Key, s3Client2, getMimeType, hydrateVariables, convertExuluToolsToAiSdkTools;
|
|
3944
4150
|
var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
|
|
3945
4151
|
"src/templates/tools/convert-exulu-tools-to-ai-sdk-tools.ts"() {
|
|
3946
4152
|
"use strict";
|
|
@@ -3950,17 +4156,21 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
|
|
|
3950
4156
|
init_statistics2();
|
|
3951
4157
|
init_client();
|
|
3952
4158
|
import_crypto_js5 = __toESM(require("crypto-js"), 1);
|
|
3953
|
-
init_project_retrieval_tool();
|
|
3954
4159
|
init_session_items_retrieval_tool();
|
|
3955
4160
|
init_pipeline();
|
|
4161
|
+
init_project_scope();
|
|
3956
4162
|
init_sanitize_tool_name();
|
|
3957
|
-
|
|
4163
|
+
import_node_crypto4 = require("crypto");
|
|
3958
4164
|
init_statistics();
|
|
3959
4165
|
init_memory_tool();
|
|
3960
4166
|
init_create_sandbox();
|
|
3961
4167
|
init_uppy();
|
|
3962
4168
|
init_truncate_tool_output();
|
|
3963
|
-
|
|
4169
|
+
init_tool_output_offload();
|
|
4170
|
+
init_session_file_read_tool();
|
|
4171
|
+
init_context_budget();
|
|
4172
|
+
OUTPUT_OFFLOAD_EXEMPT_TOOL_IDS = /* @__PURE__ */ new Set(["agentic_context_search"]);
|
|
4173
|
+
generateS3Key = (filename) => `${(0, import_node_crypto4.randomUUID)()}-${filename}`;
|
|
3964
4174
|
getMimeType = (type) => {
|
|
3965
4175
|
switch (type) {
|
|
3966
4176
|
case ".png":
|
|
@@ -4057,7 +4267,7 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
|
|
|
4057
4267
|
await Promise.all(promises2);
|
|
4058
4268
|
return tool4;
|
|
4059
4269
|
};
|
|
4060
|
-
convertExuluToolsToAiSdkTools = async (currentTools, currentSkills, approvedTools, allExuluTools, configs, providerapikey, contexts, user, exuluConfig, sessionID, req, project, sessionItems, model, agent, memoryItems) => {
|
|
4270
|
+
convertExuluToolsToAiSdkTools = async (currentTools, currentSkills, approvedTools, allExuluTools, configs, providerapikey, contexts, user, exuluConfig, sessionID, req, project, sessionItems, model, agent, memoryItems, contextWindow, disabledTools) => {
|
|
4061
4271
|
if (!currentTools) return {};
|
|
4062
4272
|
if (!allExuluTools) {
|
|
4063
4273
|
allExuluTools = [];
|
|
@@ -4065,6 +4275,8 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
|
|
|
4065
4275
|
if (!contexts) {
|
|
4066
4276
|
contexts = [];
|
|
4067
4277
|
}
|
|
4278
|
+
const budget = deriveContextBudget(contextWindow);
|
|
4279
|
+
const toolOutputCharLimit = budget.toolOutputCapTokens * 4;
|
|
4068
4280
|
let sharedSessionSandbox;
|
|
4069
4281
|
if (sessionID && exuluConfig && agent?.sandbox_enabled === true) {
|
|
4070
4282
|
try {
|
|
@@ -4081,16 +4293,28 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
|
|
|
4081
4293
|
);
|
|
4082
4294
|
}
|
|
4083
4295
|
}
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4296
|
+
const disabled = new Set(disabledTools ?? []);
|
|
4297
|
+
let projectScope;
|
|
4298
|
+
if (project && !disabled.has("agentic_context_search")) {
|
|
4299
|
+
const { db: db2 } = await postgresClient();
|
|
4300
|
+
const projectRow = await db2.from("projects").where("id", project).first();
|
|
4301
|
+
let rawItems = projectRow?.project_items;
|
|
4302
|
+
if (typeof rawItems === "string") {
|
|
4303
|
+
try {
|
|
4304
|
+
rawItems = JSON.parse(rawItems);
|
|
4305
|
+
} catch {
|
|
4306
|
+
rawItems = void 0;
|
|
4307
|
+
}
|
|
4308
|
+
}
|
|
4309
|
+
if (projectRow && Array.isArray(rawItems) && rawItems.length > 0) {
|
|
4310
|
+
projectScope = {
|
|
4311
|
+
id: projectRow.id,
|
|
4312
|
+
name: projectRow.name,
|
|
4313
|
+
description: projectRow.description ?? void 0,
|
|
4314
|
+
customInstructions: projectRow.custom_instructions ?? void 0,
|
|
4315
|
+
items: rawItems,
|
|
4316
|
+
kbProfileDefaults: buildProjectKbProfileDefaults(rawItems)
|
|
4317
|
+
};
|
|
4094
4318
|
}
|
|
4095
4319
|
}
|
|
4096
4320
|
if (agent?.memory && contexts?.length) {
|
|
@@ -4101,7 +4325,7 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
|
|
|
4101
4325
|
);
|
|
4102
4326
|
}
|
|
4103
4327
|
const createNewMemoryTool = createNewMemoryItemTool(agent, context);
|
|
4104
|
-
if (createNewMemoryTool) {
|
|
4328
|
+
if (createNewMemoryTool && !disabled.has(createNewMemoryTool.id)) {
|
|
4105
4329
|
if (!currentTools) {
|
|
4106
4330
|
currentTools = [];
|
|
4107
4331
|
}
|
|
@@ -4116,31 +4340,62 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
|
|
|
4116
4340
|
contexts,
|
|
4117
4341
|
items: sessionItems
|
|
4118
4342
|
});
|
|
4119
|
-
if (sessionItemsRetrievalTool) {
|
|
4343
|
+
if (sessionItemsRetrievalTool && !disabled.has(sessionItemsRetrievalTool.id)) {
|
|
4120
4344
|
currentTools.push(sessionItemsRetrievalTool);
|
|
4121
4345
|
}
|
|
4122
4346
|
}
|
|
4347
|
+
const sessionFileReadTool = createSessionFileReadTool({ sessionID, user, exuluConfig });
|
|
4348
|
+
if (sessionFileReadTool && !disabled.has(sessionFileReadTool.id)) {
|
|
4349
|
+
currentTools.push(sessionFileReadTool);
|
|
4350
|
+
}
|
|
4123
4351
|
console.log("[EXULU] Creating agentic search tool", contexts?.length, model);
|
|
4124
|
-
if (contexts?.length && model) {
|
|
4125
|
-
const
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4352
|
+
if (contexts?.length && model && !disabled.has("agentic_context_search")) {
|
|
4353
|
+
const index = currentTools.findIndex((tool4) => tool4.id === "agentic_context_search");
|
|
4354
|
+
const memoryContext = agent?.memory ? contexts.find((c) => c.id === agent.memory) : void 0;
|
|
4355
|
+
if (index !== -1) {
|
|
4356
|
+
const agenticSearchTool = createAgenticRetrievalTool({
|
|
4357
|
+
contexts: contexts.filter((context) => context.id !== agent?.memory),
|
|
4358
|
+
// memory is searched by the memory phase, not as a KB
|
|
4359
|
+
memoryContext,
|
|
4360
|
+
user,
|
|
4361
|
+
role: user?.role?.id,
|
|
4362
|
+
model,
|
|
4363
|
+
preselected: sessionItems,
|
|
4364
|
+
memoryItems,
|
|
4365
|
+
projectScope
|
|
4366
|
+
});
|
|
4367
|
+
if (agenticSearchTool) {
|
|
4138
4368
|
currentTools[index] = {
|
|
4139
4369
|
...currentTools[index],
|
|
4140
4370
|
// important to keep the original tool config
|
|
4141
4371
|
...agenticSearchTool
|
|
4142
4372
|
};
|
|
4143
4373
|
}
|
|
4374
|
+
} else if (projectScope) {
|
|
4375
|
+
const projectContextIds = new Set(
|
|
4376
|
+
projectScope.items.map((gid) => {
|
|
4377
|
+
const i = gid.indexOf("/");
|
|
4378
|
+
return i === -1 ? gid : gid.slice(0, i);
|
|
4379
|
+
})
|
|
4380
|
+
);
|
|
4381
|
+
const scopedContexts = contexts.filter(
|
|
4382
|
+
(c) => projectContextIds.has(c.id) && c.id !== agent?.memory
|
|
4383
|
+
);
|
|
4384
|
+
if (scopedContexts.length > 0) {
|
|
4385
|
+
const projectSearchTool = createAgenticRetrievalTool({
|
|
4386
|
+
contexts: scopedContexts,
|
|
4387
|
+
memoryContext,
|
|
4388
|
+
user,
|
|
4389
|
+
role: user?.role?.id,
|
|
4390
|
+
model,
|
|
4391
|
+
preselected: [...sessionItems ?? [], ...projectScope.items],
|
|
4392
|
+
memoryItems,
|
|
4393
|
+
projectScope
|
|
4394
|
+
});
|
|
4395
|
+
if (projectSearchTool) {
|
|
4396
|
+
currentTools.push(projectSearchTool);
|
|
4397
|
+
}
|
|
4398
|
+
}
|
|
4144
4399
|
}
|
|
4145
4400
|
} else {
|
|
4146
4401
|
const agenticSearchTool = currentTools.find((tool4) => tool4.id === "agentic_context_search");
|
|
@@ -4168,7 +4423,7 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
|
|
|
4168
4423
|
if (typeof result?.content === "string") {
|
|
4169
4424
|
return {
|
|
4170
4425
|
...result,
|
|
4171
|
-
content: truncateToolOutput(result.content,
|
|
4426
|
+
content: truncateToolOutput(result.content, budget.contextWindow, "readFile", 0.05, toolOutputCharLimit)
|
|
4172
4427
|
};
|
|
4173
4428
|
}
|
|
4174
4429
|
return result;
|
|
@@ -4185,10 +4440,10 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
|
|
|
4185
4440
|
return {
|
|
4186
4441
|
...result,
|
|
4187
4442
|
...typeof result?.stdout === "string" && {
|
|
4188
|
-
stdout: truncateToolOutput(result.stdout,
|
|
4443
|
+
stdout: truncateToolOutput(result.stdout, budget.contextWindow, "bash", 0.1, toolOutputCharLimit)
|
|
4189
4444
|
},
|
|
4190
4445
|
...typeof result?.stderr === "string" && {
|
|
4191
|
-
stderr: truncateToolOutput(result.stderr,
|
|
4446
|
+
stderr: truncateToolOutput(result.stderr, budget.contextWindow, "bash stderr", 0.4, toolOutputCharLimit)
|
|
4192
4447
|
}
|
|
4193
4448
|
};
|
|
4194
4449
|
}
|
|
@@ -4324,16 +4579,30 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
|
|
|
4324
4579
|
user: user?.id,
|
|
4325
4580
|
role: user?.role?.id
|
|
4326
4581
|
});
|
|
4582
|
+
const guardCtx = {
|
|
4583
|
+
toolName: cur.name,
|
|
4584
|
+
contextWindow,
|
|
4585
|
+
sessionID,
|
|
4586
|
+
user,
|
|
4587
|
+
exuluConfig
|
|
4588
|
+
};
|
|
4589
|
+
const offloadExempt = OUTPUT_OFFLOAD_EXEMPT_TOOL_IDS.has(cur.id);
|
|
4327
4590
|
if (response && typeof response === "object" && Symbol.asyncIterator in response) {
|
|
4328
4591
|
let lastValue;
|
|
4329
4592
|
for await (const value of response) {
|
|
4330
4593
|
yield value;
|
|
4331
4594
|
lastValue = value;
|
|
4332
4595
|
}
|
|
4333
|
-
return lastValue;
|
|
4596
|
+
if (offloadExempt) return lastValue;
|
|
4597
|
+
const guarded = await guardToolOutput(lastValue, guardCtx);
|
|
4598
|
+
if (guarded !== lastValue) {
|
|
4599
|
+
yield guarded;
|
|
4600
|
+
}
|
|
4601
|
+
return guarded;
|
|
4334
4602
|
} else {
|
|
4335
|
-
|
|
4336
|
-
|
|
4603
|
+
const guarded = offloadExempt ? response : await guardToolOutput(response, guardCtx);
|
|
4604
|
+
yield guarded;
|
|
4605
|
+
return guarded;
|
|
4337
4606
|
}
|
|
4338
4607
|
}
|
|
4339
4608
|
}
|
|
@@ -4346,7 +4615,7 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
|
|
|
4346
4615
|
});
|
|
4347
4616
|
|
|
4348
4617
|
// src/exulu/tool.ts
|
|
4349
|
-
var import_ai3, import_zod6,
|
|
4618
|
+
var import_ai3, import_zod6, import_node_crypto5, PUBLIC_TOOL_TYPES, ExuluTool;
|
|
4350
4619
|
var init_tool = __esm({
|
|
4351
4620
|
"src/exulu/tool.ts"() {
|
|
4352
4621
|
"use strict";
|
|
@@ -4354,7 +4623,7 @@ var init_tool = __esm({
|
|
|
4354
4623
|
import_ai3 = require("ai");
|
|
4355
4624
|
import_zod6 = require("zod");
|
|
4356
4625
|
init_sanitize_name();
|
|
4357
|
-
|
|
4626
|
+
import_node_crypto5 = require("crypto");
|
|
4358
4627
|
init_singleton();
|
|
4359
4628
|
init_resolve_model();
|
|
4360
4629
|
init_validate();
|
|
@@ -4481,7 +4750,7 @@ var init_tool = __esm({
|
|
|
4481
4750
|
throw new Error("Tool " + sanitizeName(this.name) + " not found in " + JSON.stringify(tools));
|
|
4482
4751
|
}
|
|
4483
4752
|
console.log("[EXULU] Tool found", this.name);
|
|
4484
|
-
const toolCallId = this.id + "_" + (0,
|
|
4753
|
+
const toolCallId = this.id + "_" + (0, import_node_crypto5.randomUUID)();
|
|
4485
4754
|
console.log("[EXULU] Calling tool execute", {
|
|
4486
4755
|
inputs,
|
|
4487
4756
|
toolCallId,
|
|
@@ -4675,6 +4944,7 @@ function parsePipelineConfig(raw) {
|
|
|
4675
4944
|
managedContext: boolVal(r["managed_context"]),
|
|
4676
4945
|
requirePreselectedContexts: boolVal(r["require_preselected_contexts"]),
|
|
4677
4946
|
logging: boolVal(r["logging"]),
|
|
4947
|
+
projectSearch: r["project_search"] === void 0 || r["project_search"] === "" ? true : boolVal(r["project_search"]),
|
|
4678
4948
|
utilityModel: strVal(r["utility_model"], ""),
|
|
4679
4949
|
knowledgeBases: jsonVal("knowledge_bases", knowledgeBasesSchema, r["knowledge_bases"]),
|
|
4680
4950
|
routing: jsonVal("routing", routingSchema, r["routing"]),
|
|
@@ -6002,6 +6272,7 @@ async function searchContexts(opts) {
|
|
|
6002
6272
|
role,
|
|
6003
6273
|
model,
|
|
6004
6274
|
preselectedItems,
|
|
6275
|
+
scopedItemsByContext,
|
|
6005
6276
|
identifierPinsByContext,
|
|
6006
6277
|
memoryPinnedItemIds,
|
|
6007
6278
|
userPinnedItemIdsByContext,
|
|
@@ -6028,6 +6299,8 @@ async function searchContexts(opts) {
|
|
|
6028
6299
|
let pinnedItemIds;
|
|
6029
6300
|
if (hasPreselection) {
|
|
6030
6301
|
pinnedItemIds = preselectedItems.get(ctxId) ?? [];
|
|
6302
|
+
} else if (scopedItemsByContext?.has(ctxId)) {
|
|
6303
|
+
pinnedItemIds = scopedItemsByContext.get(ctxId) ?? [];
|
|
6031
6304
|
} else if (!skipPrefilter) {
|
|
6032
6305
|
const identifierPins = identifierPinsByContext.get(ctxId) ?? /* @__PURE__ */ new Set();
|
|
6033
6306
|
let pins = new Set(identifierPins);
|
|
@@ -6256,24 +6529,6 @@ var init_rerank = __esm({
|
|
|
6256
6529
|
});
|
|
6257
6530
|
|
|
6258
6531
|
// ee/agentic-retrieval/pipeline/index.ts
|
|
6259
|
-
function parsePreselectedItems(globalIds) {
|
|
6260
|
-
const map = /* @__PURE__ */ new Map();
|
|
6261
|
-
for (const gid of globalIds) {
|
|
6262
|
-
const slashIdx = gid.indexOf("/");
|
|
6263
|
-
if (slashIdx === -1) {
|
|
6264
|
-
if (gid) map.set(gid, null);
|
|
6265
|
-
continue;
|
|
6266
|
-
}
|
|
6267
|
-
const contextId = gid.slice(0, slashIdx);
|
|
6268
|
-
const itemId = gid.slice(slashIdx + 1);
|
|
6269
|
-
if (!contextId || !itemId) continue;
|
|
6270
|
-
if (map.get(contextId) === null) continue;
|
|
6271
|
-
const existing = map.get(contextId) ?? [];
|
|
6272
|
-
existing.push(itemId);
|
|
6273
|
-
map.set(contextId, existing);
|
|
6274
|
-
}
|
|
6275
|
-
return map;
|
|
6276
|
-
}
|
|
6277
6532
|
function addChunks(result, chunks) {
|
|
6278
6533
|
const seen = new Set(result.chunks.map((c) => c.chunk_id));
|
|
6279
6534
|
for (const chunk of chunks) {
|
|
@@ -6303,7 +6558,8 @@ function createAgenticRetrievalTool(opts) {
|
|
|
6303
6558
|
model,
|
|
6304
6559
|
instructions: adminInstructions,
|
|
6305
6560
|
preselected,
|
|
6306
|
-
memoryItems
|
|
6561
|
+
memoryItems,
|
|
6562
|
+
projectScope
|
|
6307
6563
|
} = opts;
|
|
6308
6564
|
const license = checkLicense();
|
|
6309
6565
|
if (!license["agentic-retrieval"]) {
|
|
@@ -6313,7 +6569,9 @@ function createAgenticRetrievalTool(opts) {
|
|
|
6313
6569
|
return ExuluTool.internal({
|
|
6314
6570
|
id: "agentic_context_search",
|
|
6315
6571
|
name: "Context Search",
|
|
6316
|
-
description: `Intelligent knowledge search across the available knowledge bases: ${contexts.map((c) => c.name || c.id).join(", ")}. Routes the question to the right sources, searches them with query expansion, and returns reranked passages. Results are exhaustive for the given query: do NOT repeat the call with a rephrased version of the same question \u2014 re-call only with genuinely new information (a different product or model, an explicitly named source or document, or new details from the user)
|
|
6572
|
+
description: `Intelligent knowledge search across the available knowledge bases: ${contexts.map((c) => c.name || c.id).join(", ")}. Routes the question to the right sources, searches them with query expansion, and returns reranked passages. Results are exhaustive for the given query: do NOT repeat the call with a rephrased version of the same question \u2014 re-call only with genuinely new information (a different product or model, an explicitly named source or document, or new details from the user).` + // Note: the description suffix intentionally remains even when the per-agent project_search
|
|
6573
|
+
// config is off — the config is only known at execute time, not at factory time.
|
|
6574
|
+
(projectScope ? ` Also searches the knowledge items attached to the project "${projectScope.name}".` : ""),
|
|
6317
6575
|
category: "contexts",
|
|
6318
6576
|
needsApproval: false,
|
|
6319
6577
|
type: "context",
|
|
@@ -6356,10 +6614,16 @@ function createAgenticRetrievalTool(opts) {
|
|
|
6356
6614
|
},
|
|
6357
6615
|
{
|
|
6358
6616
|
name: "max_steps",
|
|
6359
|
-
description: "Maximum
|
|
6617
|
+
description: "Maximum knowledge searches the agent may run for one message. Once spent, the search tool is disabled for the rest of the turn. 0 = no search-specific cap (the agent's overall tool-step budget still applies).",
|
|
6360
6618
|
type: "number",
|
|
6361
6619
|
default: 0
|
|
6362
6620
|
},
|
|
6621
|
+
{
|
|
6622
|
+
name: "project_search",
|
|
6623
|
+
description: "Automatically include items attached to the chat's project as an additional knowledge source (boosts them in shared sources, adds scoped search for others).",
|
|
6624
|
+
type: "boolean",
|
|
6625
|
+
default: true
|
|
6626
|
+
},
|
|
6363
6627
|
{
|
|
6364
6628
|
name: "knowledge_bases",
|
|
6365
6629
|
description: "Per-knowledge-base profiles: enabled, kind (documents | conversations | records), instructions, and per-KB overrides (limit, expand, multiQuery, hyde). JSON object keyed by context id.",
|
|
@@ -6469,6 +6733,23 @@ function createAgenticRetrievalTool(opts) {
|
|
|
6469
6733
|
}
|
|
6470
6734
|
}
|
|
6471
6735
|
const preselectedItems = parsePreselectedItems(preselected ?? []);
|
|
6736
|
+
const availableContextsById = new Map(contexts.map((c) => [c.id, c]));
|
|
6737
|
+
const resolvedProject = cfg.projectSearch ? resolveProjectScope({
|
|
6738
|
+
scope: projectScope,
|
|
6739
|
+
enabledContextIds: new Set(enabledContexts.map((c) => c.id)),
|
|
6740
|
+
availableContextIds: new Set(availableContextsById.keys())
|
|
6741
|
+
}) : void 0;
|
|
6742
|
+
if (resolvedProject) {
|
|
6743
|
+
if (projectScope?.kbProfileDefaults) {
|
|
6744
|
+
for (const [ctxId, profile] of Object.entries(projectScope.kbProfileDefaults)) {
|
|
6745
|
+
if (!cfg.knowledgeBases[ctxId]) cfg.knowledgeBases[ctxId] = profile;
|
|
6746
|
+
}
|
|
6747
|
+
}
|
|
6748
|
+
enabledContexts = [
|
|
6749
|
+
...enabledContexts,
|
|
6750
|
+
...resolvedProject.addedContextIds.map((id) => availableContextsById.get(id)).filter((c) => Boolean(c))
|
|
6751
|
+
];
|
|
6752
|
+
}
|
|
6472
6753
|
const contextsById = new Map(enabledContexts.map((c) => [c.id, c]));
|
|
6473
6754
|
const kbKindById = new Map(
|
|
6474
6755
|
enabledContexts.map((c) => [
|
|
@@ -6479,7 +6760,12 @@ function createAgenticRetrievalTool(opts) {
|
|
|
6479
6760
|
const documentContexts = enabledContexts.filter(
|
|
6480
6761
|
(c) => (cfg.knowledgeBases[c.id]?.kind ?? "documents") === "documents"
|
|
6481
6762
|
);
|
|
6482
|
-
const extraInstructions = [
|
|
6763
|
+
const extraInstructions = [
|
|
6764
|
+
cfg.instructions,
|
|
6765
|
+
adminInstructions,
|
|
6766
|
+
resolvedProject && projectScope?.customInstructions ? `Instructions for the attached project "${projectScope.name}":
|
|
6767
|
+
${projectScope.customInstructions}` : ""
|
|
6768
|
+
].filter(Boolean).join("\n");
|
|
6483
6769
|
const [memResult, routResult] = await Promise.all([
|
|
6484
6770
|
runMemoryPhase({
|
|
6485
6771
|
memoryChunks: memoryItems ?? [],
|
|
@@ -6525,6 +6811,30 @@ function createAgenticRetrievalTool(opts) {
|
|
|
6525
6811
|
yield { result: "The user has requested to search in knowledge bases that are not part of the preselected knowledge bases: " + missing.join(", ") };
|
|
6526
6812
|
return;
|
|
6527
6813
|
}
|
|
6814
|
+
let effectiveMainContexts = mainContexts;
|
|
6815
|
+
if (resolvedProject) {
|
|
6816
|
+
const mainSet = new Set(mainContexts);
|
|
6817
|
+
const appended = resolvedProject.allProjectContextIds.filter(
|
|
6818
|
+
(id) => !mainSet.has(id) && contextsById.has(id)
|
|
6819
|
+
);
|
|
6820
|
+
if (appended.length > 0) {
|
|
6821
|
+
effectiveMainContexts = [...mainContexts, ...appended];
|
|
6822
|
+
result.steps.push({
|
|
6823
|
+
stepNumber: 1,
|
|
6824
|
+
text: `Including sources from project "${projectScope.name}": ${appended.join(", ")}`,
|
|
6825
|
+
toolCalls: [],
|
|
6826
|
+
chunks: [],
|
|
6827
|
+
tokens: 0
|
|
6828
|
+
});
|
|
6829
|
+
result.reasoning.push({
|
|
6830
|
+
text: `Including project sources: ${appended.join(", ")}`,
|
|
6831
|
+
tools: []
|
|
6832
|
+
});
|
|
6833
|
+
}
|
|
6834
|
+
}
|
|
6835
|
+
const fallbackContextsToSearch = fallbackContexts.filter(
|
|
6836
|
+
(id) => !effectiveMainContexts.includes(id)
|
|
6837
|
+
);
|
|
6528
6838
|
const {
|
|
6529
6839
|
updatedQuestion,
|
|
6530
6840
|
updatedKeywords,
|
|
@@ -6551,7 +6861,7 @@ function createAgenticRetrievalTool(opts) {
|
|
|
6551
6861
|
}
|
|
6552
6862
|
const [mainSearch, speculativeFallbackSearch] = await Promise.all([
|
|
6553
6863
|
searchContexts({
|
|
6554
|
-
contextIds:
|
|
6864
|
+
contextIds: effectiveMainContexts,
|
|
6555
6865
|
contextsById,
|
|
6556
6866
|
kbProfiles: cfg.knowledgeBases,
|
|
6557
6867
|
question: updatedQuestion,
|
|
@@ -6564,13 +6874,14 @@ function createAgenticRetrievalTool(opts) {
|
|
|
6564
6874
|
identifierPinsByContext,
|
|
6565
6875
|
memoryPinnedItemIds,
|
|
6566
6876
|
userPinnedItemIdsByContext,
|
|
6877
|
+
scopedItemsByContext: resolvedProject?.scopedItemsByContext,
|
|
6567
6878
|
rewrites: cfg.vocabulary.rewrites,
|
|
6568
6879
|
styleHint: cfg.vocabulary.styleHint,
|
|
6569
6880
|
maxQueries: cfg.tuning.maxQueriesPerContext,
|
|
6570
6881
|
skipPrefilter: false
|
|
6571
6882
|
}),
|
|
6572
|
-
|
|
6573
|
-
contextIds:
|
|
6883
|
+
fallbackContextsToSearch.length > 0 && !hasExplicitDocAndPage ? searchContexts({
|
|
6884
|
+
contextIds: fallbackContextsToSearch,
|
|
6574
6885
|
contextsById,
|
|
6575
6886
|
kbProfiles: cfg.knowledgeBases,
|
|
6576
6887
|
question: updatedQuestion,
|
|
@@ -6583,6 +6894,7 @@ function createAgenticRetrievalTool(opts) {
|
|
|
6583
6894
|
identifierPinsByContext,
|
|
6584
6895
|
memoryPinnedItemIds,
|
|
6585
6896
|
userPinnedItemIdsByContext,
|
|
6897
|
+
scopedItemsByContext: resolvedProject?.scopedItemsByContext,
|
|
6586
6898
|
rewrites: cfg.vocabulary.rewrites,
|
|
6587
6899
|
styleHint: cfg.vocabulary.styleHint,
|
|
6588
6900
|
maxQueries: cfg.tuning.maxQueriesPerContext,
|
|
@@ -6596,6 +6908,9 @@ function createAgenticRetrievalTool(opts) {
|
|
|
6596
6908
|
})(),
|
|
6597
6909
|
...(function* () {
|
|
6598
6910
|
for (const s of userPinnedItemIdsByContext.values()) yield* s;
|
|
6911
|
+
})(),
|
|
6912
|
+
...(function* () {
|
|
6913
|
+
if (resolvedProject) for (const s of resolvedProject.pinsByContext.values()) yield* s;
|
|
6599
6914
|
})()
|
|
6600
6915
|
]);
|
|
6601
6916
|
const userPinnedItemIds = new Set(
|
|
@@ -6663,15 +6978,15 @@ function createAgenticRetrievalTool(opts) {
|
|
|
6663
6978
|
result.reasoning.push({ text: "Literal lookup satisfied; skipping fallback.", tools: [] });
|
|
6664
6979
|
yield { result: serializeOutput(result) };
|
|
6665
6980
|
}
|
|
6666
|
-
if (!literalLookupSatisfied &&
|
|
6981
|
+
if (!literalLookupSatisfied && fallbackContextsToSearch.length > 0 && (reranker ? mainRerank.rerank_score_max_genuine < cfg.tuning.fallbackThreshold : mainRerank.limited_results.length < cfg.tuning.topK)) {
|
|
6667
6982
|
result.steps.push({
|
|
6668
6983
|
stepNumber: 1,
|
|
6669
|
-
text: `Using fallback search in ${
|
|
6984
|
+
text: `Using fallback search in ${fallbackContextsToSearch.join(", ")}`,
|
|
6670
6985
|
toolCalls: [],
|
|
6671
6986
|
chunks: [],
|
|
6672
6987
|
tokens: 0
|
|
6673
6988
|
});
|
|
6674
|
-
result.reasoning.push({ text: `Fallback search in ${
|
|
6989
|
+
result.reasoning.push({ text: `Fallback search in ${fallbackContextsToSearch.join(", ")}`, tools: [] });
|
|
6675
6990
|
yield { result: serializeOutput(result) };
|
|
6676
6991
|
const fallbackRerank = await rerankResults({
|
|
6677
6992
|
chunks: speculativeFallbackSearch.chunks,
|
|
@@ -6747,11 +7062,14 @@ var init_pipeline = __esm({
|
|
|
6747
7062
|
init_resolve_model();
|
|
6748
7063
|
init_singleton();
|
|
6749
7064
|
init_config();
|
|
7065
|
+
init_project_scope();
|
|
6750
7066
|
init_routing();
|
|
6751
7067
|
init_memory();
|
|
6752
7068
|
init_prefilter();
|
|
6753
7069
|
init_search();
|
|
6754
7070
|
init_rerank();
|
|
7071
|
+
init_global_ids();
|
|
7072
|
+
init_global_ids();
|
|
6755
7073
|
}
|
|
6756
7074
|
});
|
|
6757
7075
|
|
|
@@ -9086,6 +9404,13 @@ var agentsSchema = {
|
|
|
9086
9404
|
name: "sandbox_enabled",
|
|
9087
9405
|
type: "boolean",
|
|
9088
9406
|
default: false
|
|
9407
|
+
},
|
|
9408
|
+
{
|
|
9409
|
+
// Per-turn budget for ALL tool steps on one chat message (bash, files,
|
|
9410
|
+
// knowledge search, integrations). 0/null = platform default
|
|
9411
|
+
// (DEFAULT_MAX_STEPS in resolve-max-steps.ts). Auto-ALTERed on boot.
|
|
9412
|
+
name: "max_tool_steps",
|
|
9413
|
+
type: "number"
|
|
9089
9414
|
}
|
|
9090
9415
|
]
|
|
9091
9416
|
};
|
|
@@ -11148,7 +11473,7 @@ var ExuluContext2 = class {
|
|
|
11148
11473
|
embedder,
|
|
11149
11474
|
chunker,
|
|
11150
11475
|
processor,
|
|
11151
|
-
active,
|
|
11476
|
+
active: active2,
|
|
11152
11477
|
fields,
|
|
11153
11478
|
queryRewriter,
|
|
11154
11479
|
resultReranker,
|
|
@@ -11179,7 +11504,7 @@ var ExuluContext2 = class {
|
|
|
11179
11504
|
this.description = description;
|
|
11180
11505
|
this.embedder = embedder;
|
|
11181
11506
|
this.chunker = chunker;
|
|
11182
|
-
this.active =
|
|
11507
|
+
this.active = active2;
|
|
11183
11508
|
this.queryRewriter = queryRewriter;
|
|
11184
11509
|
this.resultReranker = resultReranker;
|
|
11185
11510
|
this.entities = entities;
|
|
@@ -12179,7 +12504,6 @@ init_cjs_shims();
|
|
|
12179
12504
|
init_pipeline();
|
|
12180
12505
|
init_check_record_access();
|
|
12181
12506
|
init_client();
|
|
12182
|
-
init_project_retrieval_tool();
|
|
12183
12507
|
init_singleton();
|
|
12184
12508
|
init_supervisor();
|
|
12185
12509
|
init_catalog();
|
|
@@ -12326,14 +12650,26 @@ var addProviderFields = async (args, requestedFields, providers, result, tools,
|
|
|
12326
12650
|
)
|
|
12327
12651
|
);
|
|
12328
12652
|
if (args.project) {
|
|
12329
|
-
const
|
|
12330
|
-
|
|
12331
|
-
|
|
12332
|
-
|
|
12333
|
-
|
|
12334
|
-
|
|
12335
|
-
|
|
12336
|
-
|
|
12653
|
+
const hasAgentic = result.tools.some(
|
|
12654
|
+
(tool4) => tool4?.id === "agentic_context_search"
|
|
12655
|
+
);
|
|
12656
|
+
if (!hasAgentic) {
|
|
12657
|
+
const instance2 = createAgenticRetrievalTool({
|
|
12658
|
+
contexts: [],
|
|
12659
|
+
user,
|
|
12660
|
+
role: user.role?.id,
|
|
12661
|
+
model: void 0
|
|
12662
|
+
});
|
|
12663
|
+
if (instance2) {
|
|
12664
|
+
result.tools.unshift({
|
|
12665
|
+
id: instance2.id,
|
|
12666
|
+
name: instance2.name,
|
|
12667
|
+
description: instance2.description,
|
|
12668
|
+
category: instance2.category,
|
|
12669
|
+
type: instance2.type,
|
|
12670
|
+
config: []
|
|
12671
|
+
});
|
|
12672
|
+
}
|
|
12337
12673
|
}
|
|
12338
12674
|
}
|
|
12339
12675
|
result.tools = result.tools.filter((tool4) => tool4 !== null);
|
|
@@ -15830,7 +16166,7 @@ var durationFromSegments = (segments) => {
|
|
|
15830
16166
|
|
|
15831
16167
|
// src/exulu/recall/service.ts
|
|
15832
16168
|
var TABLE3 = "transcription_jobs";
|
|
15833
|
-
var DEFAULT_BOT_NAME = "
|
|
16169
|
+
var DEFAULT_BOT_NAME = "Company Notetaker";
|
|
15834
16170
|
var log4 = (msg) => console.log(`[EXULU-RECALL] ${msg}`);
|
|
15835
16171
|
var parseJson = (v) => {
|
|
15836
16172
|
if (v == null) return null;
|
|
@@ -18259,11 +18595,11 @@ var import_body_parser = __toESM(require("body-parser"), 1);
|
|
|
18259
18595
|
var import_crypto_js9 = __toESM(require("crypto-js"), 1);
|
|
18260
18596
|
var import_openai = __toESM(require("openai"), 1);
|
|
18261
18597
|
var import_fs3 = __toESM(require("fs"), 1);
|
|
18262
|
-
var
|
|
18598
|
+
var import_node_crypto9 = require("crypto");
|
|
18263
18599
|
var import_api2 = require("@opentelemetry/api");
|
|
18264
18600
|
init_check_record_access();
|
|
18265
18601
|
var import_jszip2 = __toESM(require("jszip"), 1);
|
|
18266
|
-
var
|
|
18602
|
+
var import_ai15 = require("ai");
|
|
18267
18603
|
var import_cookie_parser = __toESM(require("cookie-parser"), 1);
|
|
18268
18604
|
init_statistics2();
|
|
18269
18605
|
|
|
@@ -18272,7 +18608,8 @@ init_cjs_shims();
|
|
|
18272
18608
|
|
|
18273
18609
|
// src/exulu/resolve-max-steps.ts
|
|
18274
18610
|
init_cjs_shims();
|
|
18275
|
-
|
|
18611
|
+
var DEFAULT_MAX_STEPS = 10;
|
|
18612
|
+
function resolveRetrievalCallBudget(toolConfigs) {
|
|
18276
18613
|
const agentic = toolConfigs?.find((t) => t.id === "agentic_context_search");
|
|
18277
18614
|
if (!agentic?.config) return void 0;
|
|
18278
18615
|
const entry = agentic.config.find((c) => c.name === "max_steps");
|
|
@@ -18281,10 +18618,152 @@ function resolveMaxStepsFromToolConfigs(toolConfigs) {
|
|
|
18281
18618
|
const n = typeof raw === "number" ? raw : parseInt(String(raw ?? ""), 10);
|
|
18282
18619
|
return Number.isFinite(n) && n > 0 ? Math.floor(n) : void 0;
|
|
18283
18620
|
}
|
|
18621
|
+
function resolveTurnStepBudget(maxStepCount, agent) {
|
|
18622
|
+
if (typeof maxStepCount === "number" && Number.isFinite(maxStepCount) && maxStepCount > 0) {
|
|
18623
|
+
return Math.floor(maxStepCount);
|
|
18624
|
+
}
|
|
18625
|
+
const raw = agent?.max_tool_steps;
|
|
18626
|
+
const n = typeof raw === "number" ? raw : parseInt(String(raw ?? ""), 10);
|
|
18627
|
+
if (Number.isFinite(n) && n > 0) {
|
|
18628
|
+
return Math.floor(n);
|
|
18629
|
+
}
|
|
18630
|
+
return DEFAULT_MAX_STEPS;
|
|
18631
|
+
}
|
|
18632
|
+
function flattenPart(part) {
|
|
18633
|
+
const p = part;
|
|
18634
|
+
if (p?.type === "text") return p.text ?? "";
|
|
18635
|
+
if (p?.type === "tool-call") {
|
|
18636
|
+
return `Earlier, the assistant ran the "${p.toolName}" tool with input: ${JSON.stringify(p.input ?? {}).slice(0, 300)}`;
|
|
18637
|
+
}
|
|
18638
|
+
if (p?.type === "tool-result") {
|
|
18639
|
+
const out = p.output?.value ?? p.output;
|
|
18640
|
+
return `The "${p.toolName}" tool returned: ${typeof out === "string" ? out : JSON.stringify(out ?? "")}`;
|
|
18641
|
+
}
|
|
18642
|
+
return "";
|
|
18643
|
+
}
|
|
18644
|
+
function flattenToolHistory(messages) {
|
|
18645
|
+
return messages.map((m) => {
|
|
18646
|
+
const msg = m;
|
|
18647
|
+
if (msg.role === "tool") {
|
|
18648
|
+
const text = (Array.isArray(msg.content) ? msg.content : []).map(flattenPart).filter(Boolean).join("\n");
|
|
18649
|
+
return { role: "user", content: text || "(tool results)" };
|
|
18650
|
+
}
|
|
18651
|
+
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
18652
|
+
const text = msg.content.map(flattenPart).filter(Boolean).join("\n");
|
|
18653
|
+
return { role: "assistant", content: text || "(searching)" };
|
|
18654
|
+
}
|
|
18655
|
+
return m;
|
|
18656
|
+
});
|
|
18657
|
+
}
|
|
18658
|
+
var FINAL_ANSWER_INSTRUCTION = `This is your last step for this turn. Answer the user's original question now, in plain text, using only the information gathered above. If you could not finish the task, tell the user you reached the maximum number of tool steps, summarize what you found and did so far, and say what remains \u2014 they can ask you to continue. Do not attempt any further tool calls. Write your answer as normal prose for the user: do not output tool-call syntax, JSON commands, or bracketed lines such as "[called tool ...]" \u2014 describe anything you did or still plan to do in plain language.`;
|
|
18284
18659
|
function finalAnswerGuard(maxSteps) {
|
|
18285
|
-
return ({ stepNumber }) => stepNumber >= maxSteps - 1 ? {
|
|
18660
|
+
return ({ stepNumber, messages }) => stepNumber >= maxSteps - 1 ? {
|
|
18661
|
+
toolChoice: "none",
|
|
18662
|
+
activeTools: [],
|
|
18663
|
+
...Array.isArray(messages) ? {
|
|
18664
|
+
messages: [
|
|
18665
|
+
...flattenToolHistory(messages),
|
|
18666
|
+
{ role: "user", content: FINAL_ANSWER_INSTRUCTION }
|
|
18667
|
+
]
|
|
18668
|
+
} : {}
|
|
18669
|
+
} : void 0;
|
|
18670
|
+
}
|
|
18671
|
+
function retrievalBudgetGuard(limit, agenticToolKey, allToolKeys) {
|
|
18672
|
+
if (limit == null || limit <= 0 || !agenticToolKey || !allToolKeys.includes(agenticToolKey)) {
|
|
18673
|
+
return () => void 0;
|
|
18674
|
+
}
|
|
18675
|
+
const remainingTools = allToolKeys.filter((k) => k !== agenticToolKey);
|
|
18676
|
+
return ({ steps }) => {
|
|
18677
|
+
const calls = (steps ?? []).flatMap((s) => s?.toolCalls ?? []).filter((c) => c?.toolName === agenticToolKey).length;
|
|
18678
|
+
if (calls < limit) return void 0;
|
|
18679
|
+
return { activeTools: remainingTools };
|
|
18680
|
+
};
|
|
18681
|
+
}
|
|
18682
|
+
|
|
18683
|
+
// src/exulu/context-guard.ts
|
|
18684
|
+
init_cjs_shims();
|
|
18685
|
+
init_context_budget();
|
|
18686
|
+
var KEEP_RECENT_TOOL_MESSAGES = 2;
|
|
18687
|
+
var COLLAPSE_KEEP_CHARS = 400;
|
|
18688
|
+
var COLLAPSE_MARKER = " \u2026[older tool output collapsed mid-response to fit the context window \u2014 the full output is in the session file named in the notice above, if one was saved]";
|
|
18689
|
+
function contextGuard(contextWindow) {
|
|
18690
|
+
const budget = deriveContextBudget(contextWindow);
|
|
18691
|
+
return async ({ messages }) => {
|
|
18692
|
+
if (!Array.isArray(messages) || messages.length === 0) return void 0;
|
|
18693
|
+
const tokens = estimateTokens(JSON.stringify(messages));
|
|
18694
|
+
if (tokens < budget.usableWindow) return void 0;
|
|
18695
|
+
const toolIndices = messages.map((m, i) => m?.role === "tool" ? i : -1).filter((i) => i !== -1);
|
|
18696
|
+
const collapsible = new Set(toolIndices.slice(0, Math.max(0, toolIndices.length - KEEP_RECENT_TOOL_MESSAGES)));
|
|
18697
|
+
if (collapsible.size === 0) return void 0;
|
|
18698
|
+
let changed = false;
|
|
18699
|
+
const next = messages.map((m, i) => {
|
|
18700
|
+
if (!collapsible.has(i)) return m;
|
|
18701
|
+
const msg = m;
|
|
18702
|
+
if (!Array.isArray(msg.content)) return m;
|
|
18703
|
+
const content = msg.content.map((part) => {
|
|
18704
|
+
const p = part;
|
|
18705
|
+
if (p?.type !== "tool-result") return part;
|
|
18706
|
+
const out = p.output?.value ?? p.output;
|
|
18707
|
+
const asText = typeof out === "string" ? out : JSON.stringify(out ?? "");
|
|
18708
|
+
if (asText.length <= COLLAPSE_KEEP_CHARS + COLLAPSE_MARKER.length) return part;
|
|
18709
|
+
changed = true;
|
|
18710
|
+
return { ...part, output: { type: "text", value: asText.slice(0, COLLAPSE_KEEP_CHARS) + COLLAPSE_MARKER } };
|
|
18711
|
+
});
|
|
18712
|
+
return { ...m, content };
|
|
18713
|
+
});
|
|
18714
|
+
return changed ? { messages: next } : void 0;
|
|
18715
|
+
};
|
|
18716
|
+
}
|
|
18717
|
+
function composePrepareSteps(...guards) {
|
|
18718
|
+
return async (opts) => {
|
|
18719
|
+
let merged;
|
|
18720
|
+
let messages = opts.messages;
|
|
18721
|
+
for (const guard of guards) {
|
|
18722
|
+
const result = await guard({ ...opts, messages });
|
|
18723
|
+
if (!result) continue;
|
|
18724
|
+
merged = { ...merged ?? {}, ...result };
|
|
18725
|
+
if (Array.isArray(result.messages)) {
|
|
18726
|
+
messages = result.messages;
|
|
18727
|
+
}
|
|
18728
|
+
}
|
|
18729
|
+
if (merged && messages && !("messages" in merged)) {
|
|
18730
|
+
merged.messages = messages;
|
|
18731
|
+
}
|
|
18732
|
+
return merged;
|
|
18733
|
+
};
|
|
18286
18734
|
}
|
|
18287
18735
|
|
|
18736
|
+
// src/exulu/provider.ts
|
|
18737
|
+
init_sanitize_tool_name();
|
|
18738
|
+
|
|
18739
|
+
// src/exulu/auto-decline-stale-approvals.ts
|
|
18740
|
+
init_cjs_shims();
|
|
18741
|
+
var AUTO_DECLINE_REASON = "Automatically declined because the user sent a new message instead of responding to the approval request.";
|
|
18742
|
+
var isPendingApprovalToolPart = (part) => {
|
|
18743
|
+
const candidate = part;
|
|
18744
|
+
return (candidate?.type === "dynamic-tool" || typeof candidate?.type === "string" && candidate.type.startsWith("tool-")) && candidate?.state === "approval-requested" && typeof candidate?.approval?.id === "string";
|
|
18745
|
+
};
|
|
18746
|
+
var autoDeclineStaleApprovals = (messages) => {
|
|
18747
|
+
const declined = [];
|
|
18748
|
+
const reconciled = messages.map((message, index) => {
|
|
18749
|
+
if (index === messages.length - 1 || message.role !== "assistant") return message;
|
|
18750
|
+
if (!message.parts?.some(isPendingApprovalToolPart)) return message;
|
|
18751
|
+
const updated = {
|
|
18752
|
+
...message,
|
|
18753
|
+
parts: message.parts.map(
|
|
18754
|
+
(part) => isPendingApprovalToolPart(part) ? {
|
|
18755
|
+
...part,
|
|
18756
|
+
state: "output-denied",
|
|
18757
|
+
approval: { id: part.approval.id, approved: false, reason: AUTO_DECLINE_REASON }
|
|
18758
|
+
} : part
|
|
18759
|
+
)
|
|
18760
|
+
};
|
|
18761
|
+
declined.push(updated);
|
|
18762
|
+
return updated;
|
|
18763
|
+
});
|
|
18764
|
+
return { messages: reconciled, declined };
|
|
18765
|
+
};
|
|
18766
|
+
|
|
18288
18767
|
// src/exulu/provider.ts
|
|
18289
18768
|
var import_zod12 = require("zod");
|
|
18290
18769
|
init_tool();
|
|
@@ -18355,6 +18834,8 @@ async function clearSessionCurrentTask(session) {
|
|
|
18355
18834
|
}
|
|
18356
18835
|
|
|
18357
18836
|
// src/exulu/provider.ts
|
|
18837
|
+
init_context_budget();
|
|
18838
|
+
init_tool_output_offload();
|
|
18358
18839
|
var ExuluProvider = class {
|
|
18359
18840
|
// Must begin with a letter (a-z) or underscore (_). Subsequent characters in a name can be letters, digits (0-9), or
|
|
18360
18841
|
// underscores and be a max length of 80 characters and at least 5 characters long.
|
|
@@ -18525,7 +19006,9 @@ var ExuluProvider = class {
|
|
|
18525
19006
|
agent,
|
|
18526
19007
|
instructions,
|
|
18527
19008
|
maxStepCount,
|
|
18528
|
-
onTokenUsage
|
|
19009
|
+
onTokenUsage,
|
|
19010
|
+
contextWindow,
|
|
19011
|
+
disabledTools
|
|
18529
19012
|
}) => {
|
|
18530
19013
|
console.log(
|
|
18531
19014
|
"[EXULU] Called generate sync for agent: " + this.name,
|
|
@@ -18553,9 +19036,7 @@ var ExuluProvider = class {
|
|
|
18553
19036
|
if (messages && session && user) {
|
|
18554
19037
|
const previousMessages = await getAgentMessages({
|
|
18555
19038
|
session,
|
|
18556
|
-
user: user.id
|
|
18557
|
-
limit: 50,
|
|
18558
|
-
page: 1
|
|
19039
|
+
user: user.id
|
|
18559
19040
|
});
|
|
18560
19041
|
const previousMessagesContent = previousMessages.map(
|
|
18561
19042
|
(message) => JSON.parse(message.content)
|
|
@@ -18564,6 +19045,12 @@ var ExuluProvider = class {
|
|
|
18564
19045
|
// append the new message to the previous messages:
|
|
18565
19046
|
messages: [...previousMessagesContent, ...messages]
|
|
18566
19047
|
});
|
|
19048
|
+
const contextBudget = deriveContextBudget(contextWindow);
|
|
19049
|
+
const occupancy = contextOccupancy(messages);
|
|
19050
|
+
if (occupancy >= contextBudget.blockThreshold) {
|
|
19051
|
+
throw new ContextCompactionRequiredError(occupancy, contextBudget);
|
|
19052
|
+
}
|
|
19053
|
+
messages = sliceHistoryAtCheckpoint(messages);
|
|
18567
19054
|
}
|
|
18568
19055
|
console.log(
|
|
18569
19056
|
"[EXULU] Message count for agent: " + this.name,
|
|
@@ -18628,6 +19115,34 @@ var ExuluProvider = class {
|
|
|
18628
19115
|
if (memoryContext) {
|
|
18629
19116
|
system += "\n\n" + memoryContext;
|
|
18630
19117
|
}
|
|
19118
|
+
const tools = await convertExuluToolsToAiSdkTools(
|
|
19119
|
+
currentTools,
|
|
19120
|
+
currentSkills,
|
|
19121
|
+
approvedTools,
|
|
19122
|
+
allExuluTools,
|
|
19123
|
+
toolConfigs,
|
|
19124
|
+
providerapikey,
|
|
19125
|
+
contexts,
|
|
19126
|
+
user,
|
|
19127
|
+
exuluConfig,
|
|
19128
|
+
session,
|
|
19129
|
+
req,
|
|
19130
|
+
project,
|
|
19131
|
+
sessionItems,
|
|
19132
|
+
model,
|
|
19133
|
+
agent,
|
|
19134
|
+
memoryItems,
|
|
19135
|
+
contextWindow,
|
|
19136
|
+
disabledTools
|
|
19137
|
+
);
|
|
19138
|
+
const agenticEntry = currentTools?.find((t) => t.id === "agentic_context_search");
|
|
19139
|
+
const agenticToolKey = agenticEntry ? sanitizeToolName(agenticEntry.name) : void 0;
|
|
19140
|
+
const retrievalGuard = retrievalBudgetGuard(
|
|
19141
|
+
resolveRetrievalCallBudget(toolConfigs),
|
|
19142
|
+
agenticToolKey,
|
|
19143
|
+
Object.keys(tools)
|
|
19144
|
+
);
|
|
19145
|
+
const turnBudget = resolveTurnStepBudget(maxStepCount, agent);
|
|
18631
19146
|
const includesContextSearchTool = currentTools?.some(
|
|
18632
19147
|
(tool4) => tool4.name.toLowerCase().includes("context_search") || tool4.id.includes("context_search") || tool4.type === "context"
|
|
18633
19148
|
);
|
|
@@ -18640,12 +19155,12 @@ var ExuluProvider = class {
|
|
|
18640
19155
|
system += `
|
|
18641
19156
|
|
|
18642
19157
|
|
|
18643
|
-
|
|
19158
|
+
|
|
18644
19159
|
When you use a context search tool, you will include references to the items
|
|
18645
19160
|
retrieved from the tool call result inline in the response using this exact JSON format
|
|
18646
19161
|
(all on one line, no line breaks):
|
|
18647
19162
|
{item_name: <item_name>, item_id: <item_id>, context: <context_id>, chunk_id: <chunk_id>, chunk_index: <chunk_index>}
|
|
18648
|
-
|
|
19163
|
+
|
|
18649
19164
|
IMPORTANT formatting rules:
|
|
18650
19165
|
- Do NOT reference just chunks like "Looking at chunk_index 5 and chunk_index 0 from the search result", always use the JSON format above.
|
|
18651
19166
|
- Use the exact format shown above, all on ONE line
|
|
@@ -18653,9 +19168,9 @@ var ExuluProvider = class {
|
|
|
18653
19168
|
- Use the context ID from the tool result
|
|
18654
19169
|
- Include the file/item name, not the full path
|
|
18655
19170
|
- Separate multiple citations with spaces
|
|
18656
|
-
|
|
19171
|
+
|
|
18657
19172
|
Example: {item_name: document.pdf, item_id: abc123, context: my-context-id, chunk_id: chunk_456, chunk_index: 0}
|
|
18658
|
-
|
|
19173
|
+
|
|
18659
19174
|
The citations will be rendered as interactive badges in the UI.
|
|
18660
19175
|
`;
|
|
18661
19176
|
}
|
|
@@ -18666,12 +19181,12 @@ var ExuluProvider = class {
|
|
|
18666
19181
|
When you use a web search tool, you will include references to the results of the tool call result inline in the response using this exact JSON format
|
|
18667
19182
|
(all on one line, no line breaks):
|
|
18668
19183
|
{url: <url>, title: <title>, snippet: <snippet>}
|
|
18669
|
-
|
|
19184
|
+
|
|
18670
19185
|
IMPORTANT formatting rules:
|
|
18671
19186
|
- Use the exact format shown above, all on ONE line
|
|
18672
19187
|
- Do NOT use quotes around field names or values
|
|
18673
19188
|
- Separate multiple results with spaces
|
|
18674
|
-
|
|
19189
|
+
|
|
18675
19190
|
Example: {url: https://www.google.com, title: Google, snippet: The result of the web search.}
|
|
18676
19191
|
`;
|
|
18677
19192
|
}
|
|
@@ -18704,29 +19219,12 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
18704
19219
|
system,
|
|
18705
19220
|
prompt,
|
|
18706
19221
|
maxRetries: 2,
|
|
18707
|
-
tools
|
|
18708
|
-
currentTools,
|
|
18709
|
-
currentSkills,
|
|
18710
|
-
approvedTools,
|
|
18711
|
-
allExuluTools,
|
|
18712
|
-
toolConfigs,
|
|
18713
|
-
providerapikey,
|
|
18714
|
-
contexts,
|
|
18715
|
-
user,
|
|
18716
|
-
exuluConfig,
|
|
18717
|
-
session,
|
|
18718
|
-
req,
|
|
18719
|
-
project,
|
|
18720
|
-
sessionItems,
|
|
18721
|
-
model,
|
|
18722
|
-
agent,
|
|
18723
|
-
memoryItems
|
|
18724
|
-
),
|
|
19222
|
+
tools,
|
|
18725
19223
|
// Stop after the image_generation tool fires — the widget IS the
|
|
18726
19224
|
// assistant's response, no follow-up text turn is wanted (same
|
|
18727
19225
|
// reasoning as question_ask: the UI artifact is the message).
|
|
18728
|
-
prepareStep:
|
|
18729
|
-
stopWhen: [(0, import_ai11.stepCountIs)(
|
|
19226
|
+
prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget)),
|
|
19227
|
+
stopWhen: [(0, import_ai11.stepCountIs)(turnBudget), (0, import_ai11.hasToolCall)("image_generation")]
|
|
18730
19228
|
});
|
|
18731
19229
|
console.log("[EXULU] Output: " + JSON.stringify(output, null, 2));
|
|
18732
19230
|
const {
|
|
@@ -18787,26 +19285,9 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
18787
19285
|
ignoreIncompleteToolCalls: true
|
|
18788
19286
|
}),
|
|
18789
19287
|
maxRetries: 2,
|
|
18790
|
-
tools
|
|
18791
|
-
|
|
18792
|
-
|
|
18793
|
-
approvedTools,
|
|
18794
|
-
allExuluTools,
|
|
18795
|
-
toolConfigs,
|
|
18796
|
-
providerapikey,
|
|
18797
|
-
contexts,
|
|
18798
|
-
user,
|
|
18799
|
-
exuluConfig,
|
|
18800
|
-
session,
|
|
18801
|
-
req,
|
|
18802
|
-
project,
|
|
18803
|
-
sessionItems,
|
|
18804
|
-
model,
|
|
18805
|
-
agent,
|
|
18806
|
-
memoryItems
|
|
18807
|
-
),
|
|
18808
|
-
prepareStep: finalAnswerGuard(maxStepCount ?? resolveMaxStepsFromToolConfigs(toolConfigs) ?? 5),
|
|
18809
|
-
stopWhen: [(0, import_ai11.stepCountIs)(maxStepCount ?? resolveMaxStepsFromToolConfigs(toolConfigs) ?? 5), (0, import_ai11.hasToolCall)("image_generation")]
|
|
19288
|
+
tools,
|
|
19289
|
+
prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget)),
|
|
19290
|
+
stopWhen: [(0, import_ai11.stepCountIs)(turnBudget), (0, import_ai11.hasToolCall)("image_generation")]
|
|
18810
19291
|
});
|
|
18811
19292
|
if (statistics) {
|
|
18812
19293
|
await Promise.all([
|
|
@@ -18858,7 +19339,7 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
18858
19339
|
* - Document files (PDF, DOCX, etc.) -> text parts with extracted content using officeparser
|
|
18859
19340
|
* - Image files -> image parts (which ARE supported by Responses API)
|
|
18860
19341
|
*/
|
|
18861
|
-
async processFilePartsInMessages(messages) {
|
|
19342
|
+
async processFilePartsInMessages(messages, offloadCtx) {
|
|
18862
19343
|
const processedMessages = await Promise.all(
|
|
18863
19344
|
messages.map(async (message) => {
|
|
18864
19345
|
if (message.role !== "user" || !Array.isArray(message.parts)) {
|
|
@@ -18903,10 +19384,11 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
18903
19384
|
outputErrorToConsole: false,
|
|
18904
19385
|
newlineDelimiter: "\n"
|
|
18905
19386
|
});
|
|
19387
|
+
const guardedText = await guardExtractedFileText(filename, String(extractedText), offloadCtx);
|
|
18906
19388
|
return {
|
|
18907
19389
|
type: "text",
|
|
18908
19390
|
text: `<file file name = "${filename}" >
|
|
18909
|
-
${
|
|
19391
|
+
${guardedText}
|
|
18910
19392
|
</file>`
|
|
18911
19393
|
};
|
|
18912
19394
|
} catch (error) {
|
|
@@ -18922,7 +19404,6 @@ ${extractedText}
|
|
|
18922
19404
|
...message,
|
|
18923
19405
|
parts: processedParts
|
|
18924
19406
|
};
|
|
18925
|
-
console.log("[EXULU] Result: " + JSON.stringify(result, null, 2));
|
|
18926
19407
|
return result;
|
|
18927
19408
|
})
|
|
18928
19409
|
);
|
|
@@ -18945,7 +19426,9 @@ ${extractedText}
|
|
|
18945
19426
|
exuluConfig,
|
|
18946
19427
|
instructions,
|
|
18947
19428
|
req,
|
|
18948
|
-
maxStepCount
|
|
19429
|
+
maxStepCount,
|
|
19430
|
+
contextWindow,
|
|
19431
|
+
disabledTools
|
|
18949
19432
|
}) => {
|
|
18950
19433
|
if (!this.config) {
|
|
18951
19434
|
console.error("[EXULU] Config is required for streaming.");
|
|
@@ -18966,9 +19449,7 @@ ${extractedText}
|
|
|
18966
19449
|
console.log("[EXULU] loading previous messages from session: " + session);
|
|
18967
19450
|
const previousMessages2 = await getAgentMessages({
|
|
18968
19451
|
session,
|
|
18969
|
-
user: user?.id
|
|
18970
|
-
limit: 50,
|
|
18971
|
-
page: 1
|
|
19452
|
+
user: user?.id
|
|
18972
19453
|
});
|
|
18973
19454
|
previousMessagesContent = previousMessages2.map((message2) => JSON.parse(message2.content));
|
|
18974
19455
|
}
|
|
@@ -19023,63 +19504,36 @@ ${extractedText}
|
|
|
19023
19504
|
</pre-fetched relevant information for this query>`;
|
|
19024
19505
|
}
|
|
19025
19506
|
}
|
|
19026
|
-
messages = messages.filter(
|
|
19027
|
-
(message2, index, self) => index === self.findLastIndex((t) => t.id === message2.id)
|
|
19028
|
-
);
|
|
19029
|
-
messages =
|
|
19030
|
-
|
|
19031
|
-
|
|
19032
|
-
|
|
19033
|
-
|
|
19034
|
-
|
|
19035
|
-
|
|
19036
|
-
|
|
19037
|
-
|
|
19038
|
-
|
|
19039
|
-
|
|
19040
|
-
|
|
19041
|
-
);
|
|
19042
|
-
|
|
19043
|
-
|
|
19044
|
-
|
|
19045
|
-
|
|
19046
|
-
|
|
19047
|
-
|
|
19048
|
-
|
|
19049
|
-
|
|
19050
|
-
|
|
19051
|
-
|
|
19052
|
-
|
|
19053
|
-
|
|
19054
|
-
|
|
19055
|
-
|
|
19056
|
-
- Use the exact format shown above, all on ONE line
|
|
19057
|
-
- Do NOT use quotes around field names or values
|
|
19058
|
-
- Use the context ID from the tool result
|
|
19059
|
-
- Include the file/item name, not the full path
|
|
19060
|
-
- Separate multiple citations with spaces
|
|
19061
|
-
|
|
19062
|
-
Example: {item_name: document.pdf, item_id: abc123, context: my-context-id, chunk_id: chunk_456, chunk_index: 0}
|
|
19063
|
-
|
|
19064
|
-
The citations will be rendered as interactive badges in the UI.
|
|
19065
|
-
`;
|
|
19066
|
-
}
|
|
19067
|
-
if (includesWebSearchTool) {
|
|
19068
|
-
system += `
|
|
19069
|
-
|
|
19070
|
-
|
|
19071
|
-
When you use a web search tool, you will include references to the results of the tool call result inline in the response using this exact JSON format
|
|
19072
|
-
(all on one line, no line breaks):
|
|
19073
|
-
{url: <url>, title: <title>, snippet: <snippet>}
|
|
19074
|
-
|
|
19075
|
-
IMPORTANT formatting rules:
|
|
19076
|
-
- Use the exact format shown above, all on ONE line
|
|
19077
|
-
- Do NOT use quotes around field names or values
|
|
19078
|
-
- Separate multiple results with spaces
|
|
19079
|
-
|
|
19080
|
-
Example: {url: https://www.google.com, title: Google, snippet: The result of the web search.}
|
|
19081
|
-
`;
|
|
19082
|
-
}
|
|
19507
|
+
messages = messages.filter(
|
|
19508
|
+
(message2, index, self) => index === self.findLastIndex((t) => t.id === message2.id)
|
|
19509
|
+
);
|
|
19510
|
+
const { messages: reconciledMessages, declined } = autoDeclineStaleApprovals(messages);
|
|
19511
|
+
messages = reconciledMessages;
|
|
19512
|
+
if (declined.length && session && user) {
|
|
19513
|
+
await saveChat({ session, user: user.id, messages: declined });
|
|
19514
|
+
}
|
|
19515
|
+
messages = await this.processFilePartsInMessages(messages, {
|
|
19516
|
+
contextWindow,
|
|
19517
|
+
sessionID: session,
|
|
19518
|
+
user,
|
|
19519
|
+
exuluConfig
|
|
19520
|
+
});
|
|
19521
|
+
const chronologicalMessages = messages;
|
|
19522
|
+
const contextBudget = deriveContextBudget(contextWindow);
|
|
19523
|
+
const occupancy = contextOccupancy(chronologicalMessages);
|
|
19524
|
+
if (occupancy >= contextBudget.blockThreshold) {
|
|
19525
|
+
console.warn(
|
|
19526
|
+
`[EXULU] Blocking request: occupancy ${occupancy} >= blockThreshold ${contextBudget.blockThreshold} (window ${contextBudget.contextWindow}).`
|
|
19527
|
+
);
|
|
19528
|
+
throw new ContextCompactionRequiredError(occupancy, contextBudget);
|
|
19529
|
+
}
|
|
19530
|
+
messages = sliceHistoryAtCheckpoint(chronologicalMessages);
|
|
19531
|
+
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.";
|
|
19532
|
+
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.";
|
|
19533
|
+
if (user?.personal_system_prompt?.trim()) {
|
|
19534
|
+
system += "\n\nUser preferences:\n" + user.personal_system_prompt.trim();
|
|
19535
|
+
}
|
|
19536
|
+
system += "\n\n" + genericContext;
|
|
19083
19537
|
if (currentSkills?.length) {
|
|
19084
19538
|
const skillsList = currentSkills.map((skill) => {
|
|
19085
19539
|
const description = (skill.description ?? "").trim();
|
|
@@ -19134,6 +19588,11 @@ ${skillsList}
|
|
|
19134
19588
|
read them with the readFile tool. Files you produce yourself (via writeFile or via shell
|
|
19135
19589
|
commands like \`node create_doc.js\`) live in the same place. These files are scoped to
|
|
19136
19590
|
this single session; they are NOT visible in other sessions, projects, or knowledge bases.
|
|
19591
|
+
|
|
19592
|
+
Note on large outputs: oversized tool outputs and large uploaded documents are automatically
|
|
19593
|
+
truncated in the conversation; the FULL content is saved as a session file (named in the
|
|
19594
|
+
truncation notice, e.g. tool-output-*.txt). Use the read_session_file tool with offset/limit
|
|
19595
|
+
to page through it \u2014 do not ask the user to re-upload.
|
|
19137
19596
|
`;
|
|
19138
19597
|
system += `
|
|
19139
19598
|
|
|
@@ -19157,9 +19616,66 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
19157
19616
|
sessionItems,
|
|
19158
19617
|
model,
|
|
19159
19618
|
agent,
|
|
19160
|
-
memoryItems
|
|
19619
|
+
memoryItems,
|
|
19620
|
+
contextWindow,
|
|
19621
|
+
disabledTools
|
|
19161
19622
|
);
|
|
19162
19623
|
console.log("[EXULU] Converted tools", Object.keys(tools));
|
|
19624
|
+
const includesContextSearchTool = currentTools?.some(
|
|
19625
|
+
(tool4) => tool4.name.toLowerCase().includes("context_search") || tool4.id.includes("context_search") || tool4.type === "context"
|
|
19626
|
+
);
|
|
19627
|
+
const includesWebSearchTool = currentTools?.some(
|
|
19628
|
+
(tool4) => tool4.name.toLowerCase().includes("web_search") || tool4.id.includes("web_search") || tool4.type === "web_search"
|
|
19629
|
+
);
|
|
19630
|
+
console.log("[EXULU] Current tools: " + currentTools?.map((tool4) => tool4.name).join("\n"));
|
|
19631
|
+
console.log("[EXULU] Includes context search tool: " + includesContextSearchTool);
|
|
19632
|
+
console.log("[EXULU] Includes web search tool: " + includesWebSearchTool);
|
|
19633
|
+
if (includesContextSearchTool) {
|
|
19634
|
+
system += `
|
|
19635
|
+
|
|
19636
|
+
|
|
19637
|
+
|
|
19638
|
+
When you use a context search tool, you will include references to the items
|
|
19639
|
+
retrieved from the tool call result inline in the response using this exact JSON format
|
|
19640
|
+
(all on one line, no line breaks):
|
|
19641
|
+
{item_name: <item_name>, item_id: <item_id>, context: <context_id>, chunk_id: <chunk_id>, chunk_index: <chunk_index>}
|
|
19642
|
+
|
|
19643
|
+
IMPORTANT formatting rules:
|
|
19644
|
+
- Use the exact format shown above, all on ONE line
|
|
19645
|
+
- Do NOT use quotes around field names or values
|
|
19646
|
+
- Use the context ID from the tool result
|
|
19647
|
+
- Include the file/item name, not the full path
|
|
19648
|
+
- Separate multiple citations with spaces
|
|
19649
|
+
|
|
19650
|
+
Example: {item_name: document.pdf, item_id: abc123, context: my-context-id, chunk_id: chunk_456, chunk_index: 0}
|
|
19651
|
+
|
|
19652
|
+
The citations will be rendered as interactive badges in the UI.
|
|
19653
|
+
`;
|
|
19654
|
+
}
|
|
19655
|
+
if (includesWebSearchTool) {
|
|
19656
|
+
system += `
|
|
19657
|
+
|
|
19658
|
+
|
|
19659
|
+
When you use a web search tool, you will include references to the results of the tool call result inline in the response using this exact JSON format
|
|
19660
|
+
(all on one line, no line breaks):
|
|
19661
|
+
{url: <url>, title: <title>, snippet: <snippet>}
|
|
19662
|
+
|
|
19663
|
+
IMPORTANT formatting rules:
|
|
19664
|
+
- Use the exact format shown above, all on ONE line
|
|
19665
|
+
- Do NOT use quotes around field names or values
|
|
19666
|
+
- Separate multiple results with spaces
|
|
19667
|
+
|
|
19668
|
+
Example: {url: https://www.google.com, title: Google, snippet: The result of the web search.}
|
|
19669
|
+
`;
|
|
19670
|
+
}
|
|
19671
|
+
const agenticEntry = currentTools?.find((t) => t.id === "agentic_context_search");
|
|
19672
|
+
const agenticToolKey = agenticEntry ? sanitizeToolName(agenticEntry.name) : void 0;
|
|
19673
|
+
const retrievalGuard = retrievalBudgetGuard(
|
|
19674
|
+
resolveRetrievalCallBudget(toolConfigs),
|
|
19675
|
+
agenticToolKey,
|
|
19676
|
+
Object.keys(tools)
|
|
19677
|
+
);
|
|
19678
|
+
const turnBudget = resolveTurnStepBudget(maxStepCount, agent);
|
|
19163
19679
|
const result = (0, import_ai11.streamText)({
|
|
19164
19680
|
temperature: 0,
|
|
19165
19681
|
// TODO Make this configurable
|
|
@@ -19184,10 +19700,9 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
19184
19700
|
`Chat stream error: ${error instanceof Error ? error.message : JSON.stringify(error)}`
|
|
19185
19701
|
);
|
|
19186
19702
|
},
|
|
19187
|
-
//
|
|
19188
|
-
|
|
19189
|
-
|
|
19190
|
-
stopWhen: [(0, import_ai11.stepCountIs)(maxStepCount ?? resolveMaxStepsFromToolConfigs(toolConfigs) ?? (currentSkills?.length ? 10 : 5)), (0, import_ai11.hasToolCall)("image_generation")]
|
|
19703
|
+
// todo allow configuring the step budget per skill
|
|
19704
|
+
prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget)),
|
|
19705
|
+
stopWhen: [(0, import_ai11.stepCountIs)(turnBudget), (0, import_ai11.hasToolCall)("image_generation")]
|
|
19191
19706
|
});
|
|
19192
19707
|
return {
|
|
19193
19708
|
stream: result,
|
|
@@ -19198,19 +19713,14 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
19198
19713
|
};
|
|
19199
19714
|
var getAgentMessages = async ({
|
|
19200
19715
|
session,
|
|
19201
|
-
user
|
|
19202
|
-
limit,
|
|
19203
|
-
page
|
|
19716
|
+
user
|
|
19204
19717
|
}) => {
|
|
19205
19718
|
const { db: db2 } = await postgresClient();
|
|
19206
|
-
console.log(
|
|
19207
|
-
|
|
19208
|
-
|
|
19209
|
-
|
|
19210
|
-
|
|
19211
|
-
query.offset((page - 1) * limit);
|
|
19212
|
-
}
|
|
19213
|
-
const messages = await query;
|
|
19719
|
+
console.log("[EXULU] getting agent messages for session: " + session + " and user: " + user);
|
|
19720
|
+
const messages = await db2.from("agent_messages").where({ session, user: user || null }).orderBy([
|
|
19721
|
+
{ column: "createdAt", order: "asc" },
|
|
19722
|
+
{ column: "id", order: "asc" }
|
|
19723
|
+
]);
|
|
19214
19724
|
return messages;
|
|
19215
19725
|
};
|
|
19216
19726
|
var getSession = async ({ sessionID }) => {
|
|
@@ -19314,6 +19824,161 @@ ${SUGGESTIONS_SYSTEM_PROMPT}` : SUGGESTIONS_SYSTEM_PROMPT;
|
|
|
19314
19824
|
init_resolve_model();
|
|
19315
19825
|
init_supervisor();
|
|
19316
19826
|
|
|
19827
|
+
// src/exulu/resolve-context-window.ts
|
|
19828
|
+
init_cjs_shims();
|
|
19829
|
+
init_catalog();
|
|
19830
|
+
init_context_budget();
|
|
19831
|
+
var resolveContextWindow = async ({
|
|
19832
|
+
modelId,
|
|
19833
|
+
exuluProvider
|
|
19834
|
+
}) => {
|
|
19835
|
+
if (process.env.EXULU_USE_LITELLM === "true") {
|
|
19836
|
+
const entry = await findLiteLLMModel(modelId);
|
|
19837
|
+
const fromCatalog = entry?.max_input_tokens ?? entry?.max_tokens;
|
|
19838
|
+
if (fromCatalog != null && fromCatalog > 0) return fromCatalog;
|
|
19839
|
+
} else if (exuluProvider) {
|
|
19840
|
+
try {
|
|
19841
|
+
const fromProvider = exuluProvider.maxContextLength;
|
|
19842
|
+
if (fromProvider != null && fromProvider > 0) return fromProvider;
|
|
19843
|
+
} catch {
|
|
19844
|
+
}
|
|
19845
|
+
}
|
|
19846
|
+
console.warn(
|
|
19847
|
+
`[EXULU] Unknown context window for model "${modelId}" \u2014 assuming ${DEFAULT_CONTEXT_WINDOW}. Check the LiteLLM catalog / provider template metadata.`
|
|
19848
|
+
);
|
|
19849
|
+
return DEFAULT_CONTEXT_WINDOW;
|
|
19850
|
+
};
|
|
19851
|
+
|
|
19852
|
+
// src/exulu/routes.ts
|
|
19853
|
+
init_context_budget();
|
|
19854
|
+
|
|
19855
|
+
// src/exulu/active-streams.ts
|
|
19856
|
+
init_cjs_shims();
|
|
19857
|
+
var active = /* @__PURE__ */ new Set();
|
|
19858
|
+
var markStreamActive = (sessionID) => {
|
|
19859
|
+
active.add(sessionID);
|
|
19860
|
+
};
|
|
19861
|
+
var clearStreamActive = (sessionID) => {
|
|
19862
|
+
active.delete(sessionID);
|
|
19863
|
+
};
|
|
19864
|
+
var isStreamActive = (sessionID) => active.has(sessionID);
|
|
19865
|
+
|
|
19866
|
+
// src/exulu/compact-session.ts
|
|
19867
|
+
init_cjs_shims();
|
|
19868
|
+
var import_node_crypto6 = require("crypto");
|
|
19869
|
+
var import_ai13 = require("ai");
|
|
19870
|
+
init_truncate_tool_output();
|
|
19871
|
+
init_context_budget();
|
|
19872
|
+
var CompactionInsufficientError = class extends Error {
|
|
19873
|
+
constructor(reason) {
|
|
19874
|
+
super(JSON.stringify({ code: COMPACTION_INSUFFICIENT, message: reason }));
|
|
19875
|
+
this.name = "CompactionInsufficientError";
|
|
19876
|
+
}
|
|
19877
|
+
};
|
|
19878
|
+
var MIN_TAIL_MESSAGES = 2;
|
|
19879
|
+
var SUMMARY_TOOL_OUTPUT_SLICE = 1500;
|
|
19880
|
+
var SUMMARY_TOOL_INPUT_SLICE = 200;
|
|
19881
|
+
var SUMMARY_SYSTEM = `You compress chat histories for an AI assistant. Produce a dense, factual summary of the conversation below. Preserve:
|
|
19882
|
+
- the user's intent and any outstanding requests
|
|
19883
|
+
- key facts, decisions, and constraints
|
|
19884
|
+
- files, artifacts, and session files touched \u2014 ALWAYS keep exact file and item names so they stay retrievable
|
|
19885
|
+
- errors encountered and how they were resolved
|
|
19886
|
+
- pending tasks and the current state of the work
|
|
19887
|
+
Do not invent information. Do not include pleasantries. Write compact prose or bullet points.`;
|
|
19888
|
+
var splitTail = (messages, tailTokenBudget) => {
|
|
19889
|
+
const minTail = Math.min(MIN_TAIL_MESSAGES, messages.length);
|
|
19890
|
+
let cut = messages.length;
|
|
19891
|
+
let tokens = 0;
|
|
19892
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
19893
|
+
const t = estimateMessageTokens(messages[i]);
|
|
19894
|
+
const tailCount = messages.length - i;
|
|
19895
|
+
if (tailCount > minTail && tokens + t > tailTokenBudget) break;
|
|
19896
|
+
tokens += t;
|
|
19897
|
+
cut = i;
|
|
19898
|
+
}
|
|
19899
|
+
return { head: messages.slice(0, cut), tail: messages.slice(cut) };
|
|
19900
|
+
};
|
|
19901
|
+
var serializeForSummary = (messages) => messages.map((m) => {
|
|
19902
|
+
const parts = (m.parts ?? []).map((part) => {
|
|
19903
|
+
const p = part;
|
|
19904
|
+
if (p.type === "text") return p.text ?? "";
|
|
19905
|
+
if (p.type === "file") return `[file: ${p.filename ?? p.url ?? "attachment"}]`;
|
|
19906
|
+
if (p.type === "reasoning" || p.type === "step-start") return "";
|
|
19907
|
+
if (p.type?.startsWith("tool-") || p.type === "dynamic-tool") {
|
|
19908
|
+
const out = p.output?.value ?? p.output;
|
|
19909
|
+
const outText = typeof out === "string" ? out : JSON.stringify(out ?? "");
|
|
19910
|
+
return `[tool ${p.type}: ${JSON.stringify(p.input ?? {}).slice(0, SUMMARY_TOOL_INPUT_SLICE)}] \u2192 ${outText.slice(0, SUMMARY_TOOL_OUTPUT_SLICE)}`;
|
|
19911
|
+
}
|
|
19912
|
+
return "";
|
|
19913
|
+
}).filter(Boolean).join("\n");
|
|
19914
|
+
return `${m.role.toUpperCase()}:
|
|
19915
|
+
${parts}`;
|
|
19916
|
+
}).join("\n\n");
|
|
19917
|
+
var compactSession = async ({
|
|
19918
|
+
sessionID,
|
|
19919
|
+
user,
|
|
19920
|
+
languageModel,
|
|
19921
|
+
contextWindow,
|
|
19922
|
+
steer,
|
|
19923
|
+
modelId,
|
|
19924
|
+
summarize
|
|
19925
|
+
}) => {
|
|
19926
|
+
const budget = deriveContextBudget(contextWindow);
|
|
19927
|
+
const rows = await getAgentMessages({ session: sessionID, user: user.id });
|
|
19928
|
+
const all = await (0, import_ai13.validateUIMessages)({ messages: rows.map((r) => JSON.parse(r.content)) });
|
|
19929
|
+
const history = sliceHistoryAtCheckpoint(all);
|
|
19930
|
+
const { head, tail } = splitTail(history, budget.compactionTailTokens);
|
|
19931
|
+
if (head.length === 0) {
|
|
19932
|
+
throw new CompactionInsufficientError(
|
|
19933
|
+
"There is nothing left to compact \u2014 the recent messages already form the whole context. Start a new chat instead."
|
|
19934
|
+
);
|
|
19935
|
+
}
|
|
19936
|
+
let corpus = serializeForSummary(head);
|
|
19937
|
+
const originalTokens = estimateTokens(corpus);
|
|
19938
|
+
corpus = truncateToolOutput(corpus, contextWindow, "history", 0.3, Math.floor(budget.usableWindow * 0.8) * 4);
|
|
19939
|
+
const system = steer?.trim() ? `${SUMMARY_SYSTEM}
|
|
19940
|
+
|
|
19941
|
+
Focus especially on: ${steer.trim()}` : SUMMARY_SYSTEM;
|
|
19942
|
+
const doSummarize = summarize ?? (async ({ system: sys, prompt, maxOutputTokens }) => {
|
|
19943
|
+
const { text } = await (0, import_ai13.generateText)({
|
|
19944
|
+
model: languageModel,
|
|
19945
|
+
system: sys,
|
|
19946
|
+
prompt,
|
|
19947
|
+
temperature: 0,
|
|
19948
|
+
maxRetries: 2,
|
|
19949
|
+
maxOutputTokens
|
|
19950
|
+
});
|
|
19951
|
+
return text;
|
|
19952
|
+
});
|
|
19953
|
+
const summary = await doSummarize({ system, prompt: corpus, maxOutputTokens: budget.summaryBudgetTokens });
|
|
19954
|
+
const summaryTokens = estimateTokens(summary);
|
|
19955
|
+
let tailTokens = 0;
|
|
19956
|
+
for (const m of tail) tailTokens += estimateMessageTokens(m);
|
|
19957
|
+
const occupancyEstimate = summaryTokens + tailTokens;
|
|
19958
|
+
if (occupancyEstimate >= budget.blockThreshold) {
|
|
19959
|
+
throw new CompactionInsufficientError(
|
|
19960
|
+
"Compacting cannot shrink this conversation below the context limit \u2014 a recent message or output is too large by itself. Start a new chat."
|
|
19961
|
+
);
|
|
19962
|
+
}
|
|
19963
|
+
const compaction = {
|
|
19964
|
+
coversUpTo: head[head.length - 1].id,
|
|
19965
|
+
originalTokens,
|
|
19966
|
+
summaryTokens,
|
|
19967
|
+
occupancyEstimate,
|
|
19968
|
+
...steer?.trim() ? { steer: steer.trim() } : {}
|
|
19969
|
+
};
|
|
19970
|
+
const checkpoint = {
|
|
19971
|
+
id: `compaction_${(0, import_node_crypto6.randomUUID)()}`,
|
|
19972
|
+
role: "user",
|
|
19973
|
+
parts: [{ type: "text", text: `[Conversation summary \u2014 earlier messages were compacted]
|
|
19974
|
+
|
|
19975
|
+
${summary}` }],
|
|
19976
|
+
metadata: { compaction }
|
|
19977
|
+
};
|
|
19978
|
+
await saveChat({ session: sessionID, user: user.id, messages: [checkpoint], ...modelId ? { model: modelId } : {} });
|
|
19979
|
+
return { checkpoint, occupancyEstimate, originalTokens, summaryTokens };
|
|
19980
|
+
};
|
|
19981
|
+
|
|
19317
19982
|
// src/exulu/transcribe.ts
|
|
19318
19983
|
init_cjs_shims();
|
|
19319
19984
|
var TranscriptionError = class extends Error {
|
|
@@ -19688,7 +20353,7 @@ function checkApiKeyScope(user, agentId) {
|
|
|
19688
20353
|
// src/exulu/openai-gateway.ts
|
|
19689
20354
|
init_cjs_shims();
|
|
19690
20355
|
var import_express2 = require("express");
|
|
19691
|
-
var
|
|
20356
|
+
var import_ai14 = require("ai");
|
|
19692
20357
|
|
|
19693
20358
|
// src/exulu/openai-transformer.ts
|
|
19694
20359
|
init_cjs_shims();
|
|
@@ -19776,7 +20441,7 @@ function transformCompletion(text, inputTokens, outputTokens, ctx) {
|
|
|
19776
20441
|
}
|
|
19777
20442
|
|
|
19778
20443
|
// src/exulu/openai-gateway.ts
|
|
19779
|
-
var
|
|
20444
|
+
var import_node_crypto7 = require("crypto");
|
|
19780
20445
|
var import_crypto_js8 = require("crypto-js");
|
|
19781
20446
|
var import_express3 = __toESM(require("express"), 1);
|
|
19782
20447
|
init_client();
|
|
@@ -19784,6 +20449,9 @@ init_convert_exulu_tools_to_ai_sdk_tools();
|
|
|
19784
20449
|
init_statistics2();
|
|
19785
20450
|
init_statistics();
|
|
19786
20451
|
init_resolve_model();
|
|
20452
|
+
init_sanitize_tool_name();
|
|
20453
|
+
init_context_budget();
|
|
20454
|
+
init_supervisor();
|
|
19787
20455
|
function convertOpenAIToolsToAiSdkTools(tools) {
|
|
19788
20456
|
return Object.fromEntries(
|
|
19789
20457
|
tools.map((t) => {
|
|
@@ -19792,7 +20460,7 @@ function convertOpenAIToolsToAiSdkTools(tools) {
|
|
|
19792
20460
|
t.function.name,
|
|
19793
20461
|
{
|
|
19794
20462
|
description: t.function.description ?? "",
|
|
19795
|
-
inputSchema: (0,
|
|
20463
|
+
inputSchema: (0, import_ai14.jsonSchema)({
|
|
19796
20464
|
type: "object",
|
|
19797
20465
|
properties: params.properties ?? {},
|
|
19798
20466
|
...params.required ? { required: params.required } : {}
|
|
@@ -20095,6 +20763,10 @@ var registerOpenAIGatewayRoutes = async (app, providers, tools, contexts, config
|
|
|
20095
20763
|
}
|
|
20096
20764
|
const providerapikey = resolved.apiKey;
|
|
20097
20765
|
const languageModel = resolved.languageModel;
|
|
20766
|
+
const contextWindow = await resolveContextWindow({
|
|
20767
|
+
modelId: resolved.model.id,
|
|
20768
|
+
exuluProvider: isLiteLLMEnabled() ? void 0 : resolved.exuluProvider
|
|
20769
|
+
});
|
|
20098
20770
|
const disabledTools = req.body.disabledTools ?? [];
|
|
20099
20771
|
const enabledTools = await getEnabledTools(
|
|
20100
20772
|
agent,
|
|
@@ -20119,12 +20791,50 @@ var registerOpenAIGatewayRoutes = async (app, providers, tools, contexts, config
|
|
|
20119
20791
|
project?.id,
|
|
20120
20792
|
void 0,
|
|
20121
20793
|
languageModel,
|
|
20122
|
-
agent
|
|
20794
|
+
agent,
|
|
20795
|
+
void 0,
|
|
20796
|
+
contextWindow,
|
|
20797
|
+
disabledTools
|
|
20798
|
+
);
|
|
20799
|
+
const gatewayAgenticEntry = enabledTools?.find((t) => t.id === "agentic_context_search");
|
|
20800
|
+
const gatewayAgenticKey = gatewayAgenticEntry ? sanitizeToolName(gatewayAgenticEntry.name) : void 0;
|
|
20801
|
+
const gatewayRetrievalGuard = retrievalBudgetGuard(
|
|
20802
|
+
resolveRetrievalCallBudget(agent.tools),
|
|
20803
|
+
gatewayAgenticKey,
|
|
20804
|
+
Object.keys(convertedTools)
|
|
20123
20805
|
);
|
|
20806
|
+
const turnBudget = resolveTurnStepBudget(void 0, agent);
|
|
20124
20807
|
const clientTools = Array.isArray(req.body.tools) ? req.body.tools : [];
|
|
20125
20808
|
const activeTools = clientTools.length > 0 ? convertOpenAIToolsToAiSdkTools(clientTools) : convertedTools;
|
|
20126
20809
|
const openaiMessages = req.body.messages ?? [];
|
|
20127
20810
|
const { systemPrompt: requestSystemPrompt, coreMessages } = convertOpenAIMessagesToModelMessages(openaiMessages);
|
|
20811
|
+
const gatewayBudget = deriveContextBudget(contextWindow);
|
|
20812
|
+
const IMAGE_TOKEN_ALLOWANCE = 1e3;
|
|
20813
|
+
let imageCount = 0;
|
|
20814
|
+
const textOnlyMessages = openaiMessages.map((m) => {
|
|
20815
|
+
if (!Array.isArray(m.content)) return m;
|
|
20816
|
+
return {
|
|
20817
|
+
...m,
|
|
20818
|
+
content: m.content.map((p) => {
|
|
20819
|
+
if (p && typeof p === "object" && "image_url" in p && p.image_url) {
|
|
20820
|
+
imageCount += 1;
|
|
20821
|
+
return { ...p, image_url: { url: "[image]" } };
|
|
20822
|
+
}
|
|
20823
|
+
return p;
|
|
20824
|
+
})
|
|
20825
|
+
};
|
|
20826
|
+
});
|
|
20827
|
+
const promptTokens = estimateTokens(JSON.stringify(textOnlyMessages)) + imageCount * IMAGE_TOKEN_ALLOWANCE;
|
|
20828
|
+
if (promptTokens >= gatewayBudget.blockThreshold) {
|
|
20829
|
+
res.status(400).json({
|
|
20830
|
+
error: {
|
|
20831
|
+
message: `This request is ~${promptTokens.toLocaleString("en-US")} tokens, which exceeds the model's usable context window (${gatewayBudget.usableWindow.toLocaleString("en-US")} tokens). Reduce the conversation history.`,
|
|
20832
|
+
type: "invalid_request_error",
|
|
20833
|
+
code: "context_length_exceeded"
|
|
20834
|
+
}
|
|
20835
|
+
});
|
|
20836
|
+
return;
|
|
20837
|
+
}
|
|
20128
20838
|
const agentInstructions = agent.instructions ?? "";
|
|
20129
20839
|
const systemParts = [
|
|
20130
20840
|
agentInstructions ? `You are an agent named: ${agent.name}
|
|
@@ -20134,7 +20844,7 @@ ${project.description}` : ""}` : "",
|
|
|
20134
20844
|
requestSystemPrompt
|
|
20135
20845
|
].filter(Boolean);
|
|
20136
20846
|
const systemPrompt = systemParts.join("\n\n");
|
|
20137
|
-
const completionId = `chatcmpl-${(0,
|
|
20847
|
+
const completionId = `chatcmpl-${(0, import_node_crypto7.randomUUID)()}`;
|
|
20138
20848
|
const created = Math.floor(Date.now() / 1e3);
|
|
20139
20849
|
const hasTools = Object.keys(activeTools).length > 0;
|
|
20140
20850
|
const ctx = { completionId, created, modelId };
|
|
@@ -20142,13 +20852,14 @@ ${project.description}` : ""}` : "",
|
|
|
20142
20852
|
res.setHeader("Content-Type", "text/event-stream");
|
|
20143
20853
|
res.setHeader("Cache-Control", "no-cache");
|
|
20144
20854
|
res.setHeader("Connection", "keep-alive");
|
|
20145
|
-
const result = (0,
|
|
20855
|
+
const result = (0, import_ai14.streamText)({
|
|
20146
20856
|
model: languageModel,
|
|
20147
20857
|
system: systemPrompt || void 0,
|
|
20148
20858
|
messages: coreMessages,
|
|
20149
20859
|
tools: hasTools ? activeTools : void 0,
|
|
20150
20860
|
maxRetries: 2,
|
|
20151
|
-
|
|
20861
|
+
prepareStep: clientTools.length > 0 ? void 0 : composePrepareSteps(contextGuard(contextWindow), gatewayRetrievalGuard, finalAnswerGuard(turnBudget)),
|
|
20862
|
+
stopWhen: clientTools.length > 0 ? void 0 : [(0, import_ai14.stepCountIs)(turnBudget)],
|
|
20152
20863
|
onError: (error) => {
|
|
20153
20864
|
console.error("[OPENAI GATEWAY] stream error:", error);
|
|
20154
20865
|
}
|
|
@@ -20181,13 +20892,14 @@ ${project.description}` : ""}` : "",
|
|
|
20181
20892
|
const usage = await result.usage;
|
|
20182
20893
|
await writeStatistics(agent, project, user, usage.inputTokens ?? 0, usage.outputTokens ?? 0);
|
|
20183
20894
|
} else {
|
|
20184
|
-
const { text, usage } = await (0,
|
|
20895
|
+
const { text, usage } = await (0, import_ai14.generateText)({
|
|
20185
20896
|
model: languageModel,
|
|
20186
20897
|
system: systemPrompt || void 0,
|
|
20187
20898
|
messages: coreMessages,
|
|
20188
20899
|
tools: hasTools ? activeTools : void 0,
|
|
20189
20900
|
maxRetries: 2,
|
|
20190
|
-
|
|
20901
|
+
prepareStep: clientTools.length > 0 ? void 0 : composePrepareSteps(contextGuard(contextWindow), gatewayRetrievalGuard, finalAnswerGuard(turnBudget)),
|
|
20902
|
+
stopWhen: clientTools.length > 0 ? void 0 : [(0, import_ai14.stepCountIs)(turnBudget)]
|
|
20191
20903
|
});
|
|
20192
20904
|
res.json(transformCompletion(text, usage.inputTokens ?? 0, usage.outputTokens ?? 0, ctx));
|
|
20193
20905
|
await writeStatistics(agent, project, user, usage.inputTokens ?? 0, usage.outputTokens ?? 0);
|
|
@@ -20306,7 +21018,7 @@ init_flow();
|
|
|
20306
21018
|
|
|
20307
21019
|
// src/exulu/recall/verify.ts
|
|
20308
21020
|
init_cjs_shims();
|
|
20309
|
-
var
|
|
21021
|
+
var import_node_crypto8 = require("crypto");
|
|
20310
21022
|
var TOLERANCE_SECONDS = 5 * 60;
|
|
20311
21023
|
var header = (headers, ...names) => {
|
|
20312
21024
|
for (const name of names) {
|
|
@@ -20324,7 +21036,7 @@ var safeEqual = (a, b) => {
|
|
|
20324
21036
|
const bufA = Buffer.from(a);
|
|
20325
21037
|
const bufB = Buffer.from(b);
|
|
20326
21038
|
if (bufA.length !== bufB.length) return false;
|
|
20327
|
-
return (0,
|
|
21039
|
+
return (0, import_node_crypto8.timingSafeEqual)(bufA, bufB);
|
|
20328
21040
|
};
|
|
20329
21041
|
var verifyRecallRequest = (headers, rawBody, secret = recallVerificationSecret()) => {
|
|
20330
21042
|
if (!secret) {
|
|
@@ -20346,7 +21058,7 @@ var verifyRecallRequest = (headers, rawBody, secret = recallVerificationSecret()
|
|
|
20346
21058
|
}
|
|
20347
21059
|
const body = typeof rawBody === "string" ? rawBody : rawBody.toString("utf8");
|
|
20348
21060
|
const signedContent = `${id}.${timestamp}.${body}`;
|
|
20349
|
-
const expected = (0,
|
|
21061
|
+
const expected = (0, import_node_crypto8.createHmac)("sha256", secretKey(secret)).update(signedContent).digest("base64");
|
|
20350
21062
|
const passed = signatureHeader.split(" ").some((entry) => {
|
|
20351
21063
|
const [, sig] = entry.split(",");
|
|
20352
21064
|
return !!sig && safeEqual(sig, expected);
|
|
@@ -20699,7 +21411,7 @@ Mood: friendly and intelligent.
|
|
|
20699
21411
|
});
|
|
20700
21412
|
return;
|
|
20701
21413
|
}
|
|
20702
|
-
const uuid = (0,
|
|
21414
|
+
const uuid = (0, import_node_crypto9.randomUUID)();
|
|
20703
21415
|
const image_url = await uploadFile(Buffer.from(image_base64, "base64"), `${uuid}.png`, config, {
|
|
20704
21416
|
contentType: "image/png"
|
|
20705
21417
|
}, authenticationResult.user?.id, void 0, true);
|
|
@@ -20903,6 +21615,10 @@ Mood: friendly and intelligent.
|
|
|
20903
21615
|
const providerapikey = resolved.apiKey;
|
|
20904
21616
|
const resolvedLanguageModel = resolved.languageModel;
|
|
20905
21617
|
const resolvedModelId = resolved.model.id;
|
|
21618
|
+
const contextWindow = await resolveContextWindow({
|
|
21619
|
+
modelId: resolved.model.id,
|
|
21620
|
+
exuluProvider: isLiteLLMEnabled() ? void 0 : resolved.exuluProvider
|
|
21621
|
+
});
|
|
20906
21622
|
if (!!headers.stream) {
|
|
20907
21623
|
const statistics = {
|
|
20908
21624
|
label: agent.name,
|
|
@@ -20921,27 +21637,46 @@ Mood: friendly and intelligent.
|
|
|
20921
21637
|
const instructions = customInstructions ? `${agent.instructions}
|
|
20922
21638
|
|
|
20923
21639
|
${customInstructions}` : agent.instructions;
|
|
20924
|
-
|
|
20925
|
-
|
|
20926
|
-
|
|
20927
|
-
|
|
20928
|
-
|
|
20929
|
-
|
|
20930
|
-
|
|
20931
|
-
|
|
20932
|
-
|
|
20933
|
-
|
|
20934
|
-
|
|
20935
|
-
|
|
20936
|
-
|
|
20937
|
-
|
|
20938
|
-
|
|
20939
|
-
|
|
20940
|
-
|
|
20941
|
-
|
|
21640
|
+
if (headers.session) markStreamActive(headers.session);
|
|
21641
|
+
let result;
|
|
21642
|
+
try {
|
|
21643
|
+
result = await provider.generateStream({
|
|
21644
|
+
contexts,
|
|
21645
|
+
agent,
|
|
21646
|
+
user,
|
|
21647
|
+
instructions,
|
|
21648
|
+
session: headers.session,
|
|
21649
|
+
message,
|
|
21650
|
+
previousMessages,
|
|
21651
|
+
currentTools: enabledTools,
|
|
21652
|
+
currentSkills: enabledSkills,
|
|
21653
|
+
approvedTools,
|
|
21654
|
+
allExuluTools: tools,
|
|
21655
|
+
languageModel: resolvedLanguageModel,
|
|
21656
|
+
providerapikey,
|
|
21657
|
+
toolConfigs: agent.tools,
|
|
21658
|
+
exuluConfig: config,
|
|
21659
|
+
req,
|
|
21660
|
+
contextWindow,
|
|
21661
|
+
disabledTools
|
|
21662
|
+
});
|
|
21663
|
+
} catch (err) {
|
|
21664
|
+
if (headers.session) clearStreamActive(headers.session);
|
|
21665
|
+
if (err instanceof ContextCompactionRequiredError) {
|
|
21666
|
+
res.status(413).send(err.message);
|
|
21667
|
+
return;
|
|
21668
|
+
}
|
|
21669
|
+
throw err;
|
|
21670
|
+
}
|
|
20942
21671
|
result.stream.consumeStream();
|
|
20943
21672
|
result.stream.pipeUIMessageStreamToResponse(res, {
|
|
20944
21673
|
messageMetadata: ({ part }) => {
|
|
21674
|
+
if (part.type === "finish-step") {
|
|
21675
|
+
return {
|
|
21676
|
+
lastStepInputTokens: part.usage.inputTokens,
|
|
21677
|
+
lastStepOutputTokens: part.usage.outputTokens
|
|
21678
|
+
};
|
|
21679
|
+
}
|
|
20945
21680
|
if (part.type === "finish") {
|
|
20946
21681
|
return {
|
|
20947
21682
|
totalTokens: part.totalUsage.totalTokens,
|
|
@@ -20958,22 +21693,20 @@ ${customInstructions}` : agent.instructions;
|
|
|
20958
21693
|
sendSources: true,
|
|
20959
21694
|
onError: (error) => {
|
|
20960
21695
|
console.error("[EXULU] chat response error.", error);
|
|
20961
|
-
if (
|
|
20962
|
-
|
|
20963
|
-
|
|
20964
|
-
if (typeof error === "string")
|
|
20965
|
-
|
|
20966
|
-
|
|
20967
|
-
|
|
20968
|
-
return error.message;
|
|
20969
|
-
}
|
|
20970
|
-
return JSON.stringify(error);
|
|
21696
|
+
if (headers.session) clearStreamActive(headers.session);
|
|
21697
|
+
let message2;
|
|
21698
|
+
if (error == null) message2 = "unknown error";
|
|
21699
|
+
else if (typeof error === "string") message2 = error;
|
|
21700
|
+
else if (error instanceof Error) message2 = error.message;
|
|
21701
|
+
else message2 = JSON.stringify(error);
|
|
21702
|
+
return mapStreamErrorMessage(message2);
|
|
20971
21703
|
},
|
|
20972
|
-
generateMessageId: (0,
|
|
21704
|
+
generateMessageId: (0, import_ai15.createIdGenerator)({
|
|
20973
21705
|
prefix: "msg_",
|
|
20974
21706
|
size: 16
|
|
20975
21707
|
}),
|
|
20976
21708
|
onFinish: async ({ messages, isContinuation, isAborted, responseMessage }) => {
|
|
21709
|
+
if (headers.session) clearStreamActive(headers.session);
|
|
20977
21710
|
console.log(
|
|
20978
21711
|
"[EXULU] onFinish",
|
|
20979
21712
|
messages?.map((msg) => msg.parts?.map((part) => part.type === "text" ? part.text : null)).join("\n")
|
|
@@ -21035,33 +21768,129 @@ ${customInstructions}` : agent.instructions;
|
|
|
21035
21768
|
const instructions = customInstructions ? `${agent.instructions}
|
|
21036
21769
|
|
|
21037
21770
|
${customInstructions}` : agent.instructions;
|
|
21038
|
-
|
|
21039
|
-
|
|
21040
|
-
|
|
21041
|
-
|
|
21042
|
-
|
|
21043
|
-
|
|
21044
|
-
|
|
21045
|
-
|
|
21046
|
-
|
|
21047
|
-
|
|
21048
|
-
|
|
21049
|
-
|
|
21050
|
-
|
|
21051
|
-
|
|
21052
|
-
|
|
21053
|
-
|
|
21054
|
-
|
|
21055
|
-
|
|
21056
|
-
|
|
21057
|
-
|
|
21771
|
+
let response;
|
|
21772
|
+
try {
|
|
21773
|
+
response = await provider.generateSync({
|
|
21774
|
+
contexts,
|
|
21775
|
+
agent,
|
|
21776
|
+
user,
|
|
21777
|
+
req,
|
|
21778
|
+
instructions,
|
|
21779
|
+
session: headers.session,
|
|
21780
|
+
inputMessages: [req.body.message],
|
|
21781
|
+
currentTools: enabledTools,
|
|
21782
|
+
currentSkills: enabledSkills,
|
|
21783
|
+
allExuluTools: tools,
|
|
21784
|
+
languageModel: resolvedLanguageModel,
|
|
21785
|
+
providerapikey,
|
|
21786
|
+
exuluConfig: config,
|
|
21787
|
+
toolConfigs: agent.tools,
|
|
21788
|
+
contextWindow,
|
|
21789
|
+
disabledTools,
|
|
21790
|
+
statistics: {
|
|
21791
|
+
label: agent.name,
|
|
21792
|
+
trigger: "agent"
|
|
21793
|
+
},
|
|
21794
|
+
onTokenUsage: async ({ inputTokens, outputTokens }) => {
|
|
21795
|
+
}
|
|
21796
|
+
});
|
|
21797
|
+
} catch (err) {
|
|
21798
|
+
if (err instanceof ContextCompactionRequiredError) {
|
|
21799
|
+
res.status(413).send(err.message);
|
|
21800
|
+
return;
|
|
21058
21801
|
}
|
|
21059
|
-
|
|
21802
|
+
throw err;
|
|
21803
|
+
}
|
|
21060
21804
|
res.status(200).json(response);
|
|
21061
21805
|
return;
|
|
21062
21806
|
}
|
|
21063
21807
|
});
|
|
21064
21808
|
};
|
|
21809
|
+
const registerAgentCompactRoute = (slug) => {
|
|
21810
|
+
app.post(slug + "/:instance", async (req, res) => {
|
|
21811
|
+
const instance2 = req.params.instance;
|
|
21812
|
+
if (!instance2) {
|
|
21813
|
+
res.status(400).json({ message: "Missing instance in request." });
|
|
21814
|
+
return;
|
|
21815
|
+
}
|
|
21816
|
+
const sessionID = req.headers["session"] || null;
|
|
21817
|
+
if (!sessionID) {
|
|
21818
|
+
res.status(400).json({ message: "Missing session header." });
|
|
21819
|
+
return;
|
|
21820
|
+
}
|
|
21821
|
+
const agent = await exuluApp.get().agent(instance2);
|
|
21822
|
+
if (!agent) {
|
|
21823
|
+
res.status(404).json({ message: "Agent with id " + instance2 + " not found." });
|
|
21824
|
+
return;
|
|
21825
|
+
}
|
|
21826
|
+
const authenticationResult = await requestValidators.authenticate(req);
|
|
21827
|
+
if (!authenticationResult.user?.id) {
|
|
21828
|
+
res.status(authenticationResult.code || 401).json({ detail: `${authenticationResult.message}` });
|
|
21829
|
+
return;
|
|
21830
|
+
}
|
|
21831
|
+
const user = authenticationResult.user;
|
|
21832
|
+
const hasAccessToAgent = await checkRecordAccess(agent, "read", user);
|
|
21833
|
+
if (!hasAccessToAgent) {
|
|
21834
|
+
res.status(401).json({ message: "You don't have access to this agent." });
|
|
21835
|
+
return;
|
|
21836
|
+
}
|
|
21837
|
+
const { db: db2 } = await postgresClient();
|
|
21838
|
+
const sessionRow = await db2.from("agent_sessions").where({ id: sessionID }).first();
|
|
21839
|
+
if (!sessionRow) {
|
|
21840
|
+
res.status(404).json({ message: "Session not found for session ID: " + sessionID });
|
|
21841
|
+
return;
|
|
21842
|
+
}
|
|
21843
|
+
const hasAccessToSession = await checkRecordAccess(sessionRow, "write", user);
|
|
21844
|
+
if (!hasAccessToSession) {
|
|
21845
|
+
res.status(401).json({ message: "You don't have access to this session." });
|
|
21846
|
+
return;
|
|
21847
|
+
}
|
|
21848
|
+
if (isStreamActive(sessionID)) {
|
|
21849
|
+
res.status(409).json({ message: "A response is still streaming for this session \u2014 try again when it finishes." });
|
|
21850
|
+
return;
|
|
21851
|
+
}
|
|
21852
|
+
const overrideModelId = req.headers["x-exulu-model-override"];
|
|
21853
|
+
const modelId = overrideModelId ?? agent.model;
|
|
21854
|
+
if (!modelId) {
|
|
21855
|
+
res.status(400).json({ message: `Agent ${agent.name} (${agent.id}) has no model configured.` });
|
|
21856
|
+
return;
|
|
21857
|
+
}
|
|
21858
|
+
let resolved;
|
|
21859
|
+
try {
|
|
21860
|
+
resolved = await resolveModel({ modelId, user, providers, agent });
|
|
21861
|
+
} catch (err) {
|
|
21862
|
+
if (err instanceof ResolveModelError) {
|
|
21863
|
+
const status = err.code === "MODEL_FORBIDDEN" ? 403 : 400;
|
|
21864
|
+
res.status(status).json({ message: err.message, code: err.code });
|
|
21865
|
+
return;
|
|
21866
|
+
}
|
|
21867
|
+
throw err;
|
|
21868
|
+
}
|
|
21869
|
+
const contextWindow = await resolveContextWindow({
|
|
21870
|
+
modelId: resolved.model.id,
|
|
21871
|
+
exuluProvider: isLiteLLMEnabled() ? void 0 : resolved.exuluProvider
|
|
21872
|
+
});
|
|
21873
|
+
const steer = typeof req.body?.steer === "string" ? req.body.steer : void 0;
|
|
21874
|
+
try {
|
|
21875
|
+
const result = await compactSession({
|
|
21876
|
+
sessionID,
|
|
21877
|
+
user,
|
|
21878
|
+
languageModel: resolved.languageModel,
|
|
21879
|
+
contextWindow,
|
|
21880
|
+
steer,
|
|
21881
|
+
modelId: resolved.model.id
|
|
21882
|
+
});
|
|
21883
|
+
res.json(result);
|
|
21884
|
+
} catch (err) {
|
|
21885
|
+
if (err instanceof CompactionInsufficientError) {
|
|
21886
|
+
res.status(422).send(err.message);
|
|
21887
|
+
return;
|
|
21888
|
+
}
|
|
21889
|
+
console.error("[EXULU] compactSession failed.", err);
|
|
21890
|
+
res.status(500).json({ message: err instanceof Error ? err.message : "Compaction failed." });
|
|
21891
|
+
}
|
|
21892
|
+
});
|
|
21893
|
+
};
|
|
21065
21894
|
providers.forEach((provider) => {
|
|
21066
21895
|
const slug = provider.slug;
|
|
21067
21896
|
if (!slug) return;
|
|
@@ -21070,6 +21899,14 @@ ${customInstructions}` : agent.instructions;
|
|
|
21070
21899
|
if (isLiteLLMEnabled() && providers.length > 0) {
|
|
21071
21900
|
registerAgentRunRoute("/agents/litellm/run", providers[0]);
|
|
21072
21901
|
}
|
|
21902
|
+
providers.forEach((provider) => {
|
|
21903
|
+
const slug = provider.slug;
|
|
21904
|
+
if (!slug) return;
|
|
21905
|
+
registerAgentCompactRoute(slug.replace(/\/run$/, "/compact"));
|
|
21906
|
+
});
|
|
21907
|
+
if (isLiteLLMEnabled() && providers.length > 0) {
|
|
21908
|
+
registerAgentCompactRoute("/agents/litellm/compact");
|
|
21909
|
+
}
|
|
21073
21910
|
app.post("/agents/suggestions/:agentId", async (req, res) => {
|
|
21074
21911
|
const agentId = req.params.agentId;
|
|
21075
21912
|
if (!agentId) {
|
|
@@ -21421,7 +22258,7 @@ ${customInstructions}` : agent.instructions;
|
|
|
21421
22258
|
const keys = [];
|
|
21422
22259
|
const revisedPrompts = [];
|
|
21423
22260
|
for (const img of images) {
|
|
21424
|
-
const filename = `${(0,
|
|
22261
|
+
const filename = `${(0, import_node_crypto9.randomUUID)()}.${img.extension}`;
|
|
21425
22262
|
const key = `sessions/${sessionId}/images/${toolCallId}/${filename}`;
|
|
21426
22263
|
const fullKey = await uploadFile(
|
|
21427
22264
|
img.buffer,
|
|
@@ -21709,7 +22546,7 @@ ${style.markdown}` : params.prompt;
|
|
|
21709
22546
|
(d) => `- ${d.presignedUrl} (prompt: "${d.prompt}", model: ${d.model}${d.styleName ? `, style: ${d.styleName}` : ""})`
|
|
21710
22547
|
);
|
|
21711
22548
|
const messageText = "The user generated and selected the following image(s) in this chat:\n" + lines.join("\n");
|
|
21712
|
-
const messageId = (0,
|
|
22549
|
+
const messageId = (0, import_node_crypto9.randomUUID)();
|
|
21713
22550
|
const uiMessage = {
|
|
21714
22551
|
id: messageId,
|
|
21715
22552
|
role: "system",
|
|
@@ -22637,7 +23474,7 @@ ${style.markdown}` : params.prompt;
|
|
|
22637
23474
|
res.status(404).json({ detail: "Skill not found." });
|
|
22638
23475
|
return;
|
|
22639
23476
|
}
|
|
22640
|
-
const stagingKey = `user_${authResult.user.id}/skills/_staging/${(0,
|
|
23477
|
+
const stagingKey = `user_${authResult.user.id}/skills/_staging/${(0, import_node_crypto9.randomUUID)()}${extension}`;
|
|
22641
23478
|
const fullKey = config.fileUploads?.s3prefix ? `${config.fileUploads.s3prefix.replace(/\/$/, "")}/${stagingKey}` : stagingKey;
|
|
22642
23479
|
const uploadUrl = await getS3SignedUploadUrl(fullKey, contentType, config);
|
|
22643
23480
|
res.json({ uploadUrl, stagingKey });
|
|
@@ -23669,7 +24506,7 @@ function buildUnifiedDiff(fromLines, toLines, fromLabel, toLabel) {
|
|
|
23669
24506
|
// src/mcp/index.ts
|
|
23670
24507
|
init_cjs_shims();
|
|
23671
24508
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
23672
|
-
var
|
|
24509
|
+
var import_node_crypto10 = require("crypto");
|
|
23673
24510
|
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
23674
24511
|
var import_types3 = require("@modelcontextprotocol/sdk/types.js");
|
|
23675
24512
|
init_sanitize_tool_name();
|
|
@@ -23781,7 +24618,7 @@ var ExuluMCP = class {
|
|
|
23781
24618
|
throw new Error("Tool not found in converted tools array.");
|
|
23782
24619
|
}
|
|
23783
24620
|
const iterator = await convertedTool.execute(inputs, {
|
|
23784
|
-
toolCallId: tool4.id + "_" + (0,
|
|
24621
|
+
toolCallId: tool4.id + "_" + (0, import_node_crypto10.randomUUID)(),
|
|
23785
24622
|
messages: []
|
|
23786
24623
|
});
|
|
23787
24624
|
let result;
|
|
@@ -23973,7 +24810,7 @@ var ExuluMCP = class {
|
|
|
23973
24810
|
transport = this.transports[sessionId];
|
|
23974
24811
|
} else if (!sessionId && (0, import_types3.isInitializeRequest)(req.body)) {
|
|
23975
24812
|
transport = new import_streamableHttp.StreamableHTTPServerTransport({
|
|
23976
|
-
sessionIdGenerator: () => (0,
|
|
24813
|
+
sessionIdGenerator: () => (0, import_node_crypto10.randomUUID)(),
|
|
23977
24814
|
onsessioninitialized: (sessionId2) => {
|
|
23978
24815
|
this.transports[sessionId2] = transport;
|
|
23979
24816
|
}
|
|
@@ -24927,7 +25764,7 @@ init_cjs_shims();
|
|
|
24927
25764
|
|
|
24928
25765
|
// src/exulu/evals.ts
|
|
24929
25766
|
init_cjs_shims();
|
|
24930
|
-
var
|
|
25767
|
+
var import_ai16 = require("ai");
|
|
24931
25768
|
init_entitlements();
|
|
24932
25769
|
var ExuluEval = class {
|
|
24933
25770
|
id;
|
|
@@ -24968,7 +25805,7 @@ var ExuluEval = class {
|
|
|
24968
25805
|
init_resolve_model();
|
|
24969
25806
|
init_singleton();
|
|
24970
25807
|
var import_zod15 = require("zod");
|
|
24971
|
-
var
|
|
25808
|
+
var import_ai17 = require("ai");
|
|
24972
25809
|
var llmAsJudgeEval = () => {
|
|
24973
25810
|
if (process.env.REDIS_HOST?.length && process.env.REDIS_PORT?.length) {
|
|
24974
25811
|
return new ExuluEval({
|
|
@@ -25013,13 +25850,13 @@ var llmAsJudgeEval = () => {
|
|
|
25013
25850
|
rbacBypass: true
|
|
25014
25851
|
});
|
|
25015
25852
|
console.log("[EXULU] prompt", prompt);
|
|
25016
|
-
const { output } = await (0,
|
|
25853
|
+
const { output } = await (0, import_ai17.generateText)({
|
|
25017
25854
|
temperature: 0,
|
|
25018
25855
|
model: resolved.languageModel,
|
|
25019
25856
|
system: "",
|
|
25020
25857
|
prompt,
|
|
25021
25858
|
maxRetries: 2,
|
|
25022
|
-
output:
|
|
25859
|
+
output: import_ai17.Output.object({
|
|
25023
25860
|
schema: import_zod15.z.object({
|
|
25024
25861
|
score: import_zod15.z.number().min(0).max(100).describe("The score between 0 and 100.")
|
|
25025
25862
|
})
|
|
@@ -25453,7 +26290,7 @@ var import_zod17 = __toESM(require("zod"), 1);
|
|
|
25453
26290
|
init_tool();
|
|
25454
26291
|
init_check_record_access();
|
|
25455
26292
|
init_client();
|
|
25456
|
-
var
|
|
26293
|
+
var import_node_crypto11 = require("crypto");
|
|
25457
26294
|
var AnswerOptionSchema = import_zod17.default.object({
|
|
25458
26295
|
id: import_zod17.default.string().describe("Unique identifier for the answer option"),
|
|
25459
26296
|
text: import_zod17.default.string().describe("The text of the answer option")
|
|
@@ -25507,15 +26344,15 @@ var QuestionAskTool = new ExuluTool({
|
|
|
25507
26344
|
throw new Error("You don't have access to this session " + session.id + ".");
|
|
25508
26345
|
}
|
|
25509
26346
|
const answerOptionsWithIds = answerOptions.map((text) => ({
|
|
25510
|
-
id: (0,
|
|
26347
|
+
id: (0, import_node_crypto11.randomUUID)(),
|
|
25511
26348
|
text
|
|
25512
26349
|
}));
|
|
25513
26350
|
answerOptionsWithIds.push({
|
|
25514
|
-
id: (0,
|
|
26351
|
+
id: (0, import_node_crypto11.randomUUID)(),
|
|
25515
26352
|
text: "None of the above..."
|
|
25516
26353
|
});
|
|
25517
26354
|
const newQuestion = {
|
|
25518
|
-
id: (0,
|
|
26355
|
+
id: (0, import_node_crypto11.randomUUID)(),
|
|
25519
26356
|
question,
|
|
25520
26357
|
answerOptions: answerOptionsWithIds,
|
|
25521
26358
|
status: "pending"
|
|
@@ -28549,7 +29386,7 @@ var MarkdownChunker = class {
|
|
|
28549
29386
|
init_cjs_shims();
|
|
28550
29387
|
var fs4 = __toESM(require("fs"), 1);
|
|
28551
29388
|
var path = __toESM(require("path"), 1);
|
|
28552
|
-
var
|
|
29389
|
+
var import_ai18 = require("ai");
|
|
28553
29390
|
var import_zod22 = require("zod");
|
|
28554
29391
|
var import_p_limit = __toESM(require("p-limit"), 1);
|
|
28555
29392
|
var import_crypto2 = require("crypto");
|
|
@@ -29014,9 +29851,9 @@ If the page contains a flow-chart, schematic, technical drawing or control board
|
|
|
29014
29851
|
|
|
29015
29852
|
### 7. Only populate \`corrected_text\` when \`needs_correction\` is true. If the OCR output is accurate, return \`needs_correction: false\` and \`corrected_content: null\`.
|
|
29016
29853
|
`;
|
|
29017
|
-
const result = await (0,
|
|
29854
|
+
const result = await (0, import_ai18.generateText)({
|
|
29018
29855
|
model,
|
|
29019
|
-
output:
|
|
29856
|
+
output: import_ai18.Output.object({
|
|
29020
29857
|
schema: import_zod22.z.object({
|
|
29021
29858
|
needs_correction: import_zod22.z.boolean(),
|
|
29022
29859
|
corrected_text: import_zod22.z.string().nullable(),
|