@exulu/backend 2.0.1 → 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 +1206 -438
- package/dist/index.d.cts +37 -2
- package/dist/index.d.ts +37 -2
- package/dist/index.js +653 -212
- 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);
|
|
@@ -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,15 +18618,26 @@ 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
|
+
}
|
|
18284
18632
|
function flattenPart(part) {
|
|
18285
18633
|
const p = part;
|
|
18286
18634
|
if (p?.type === "text") return p.text ?? "";
|
|
18287
18635
|
if (p?.type === "tool-call") {
|
|
18288
|
-
return `
|
|
18636
|
+
return `Earlier, the assistant ran the "${p.toolName}" tool with input: ${JSON.stringify(p.input ?? {}).slice(0, 300)}`;
|
|
18289
18637
|
}
|
|
18290
18638
|
if (p?.type === "tool-result") {
|
|
18291
18639
|
const out = p.output?.value ?? p.output;
|
|
18292
|
-
return `
|
|
18640
|
+
return `The "${p.toolName}" tool returned: ${typeof out === "string" ? out : JSON.stringify(out ?? "")}`;
|
|
18293
18641
|
}
|
|
18294
18642
|
return "";
|
|
18295
18643
|
}
|
|
@@ -18307,7 +18655,7 @@ function flattenToolHistory(messages) {
|
|
|
18307
18655
|
return m;
|
|
18308
18656
|
});
|
|
18309
18657
|
}
|
|
18310
|
-
var FINAL_ANSWER_INSTRUCTION =
|
|
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.`;
|
|
18311
18659
|
function finalAnswerGuard(maxSteps) {
|
|
18312
18660
|
return ({ stepNumber, messages }) => stepNumber >= maxSteps - 1 ? {
|
|
18313
18661
|
toolChoice: "none",
|
|
@@ -18320,6 +18668,73 @@ function finalAnswerGuard(maxSteps) {
|
|
|
18320
18668
|
} : {}
|
|
18321
18669
|
} : void 0;
|
|
18322
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
|
+
};
|
|
18734
|
+
}
|
|
18735
|
+
|
|
18736
|
+
// src/exulu/provider.ts
|
|
18737
|
+
init_sanitize_tool_name();
|
|
18323
18738
|
|
|
18324
18739
|
// src/exulu/auto-decline-stale-approvals.ts
|
|
18325
18740
|
init_cjs_shims();
|
|
@@ -18419,6 +18834,8 @@ async function clearSessionCurrentTask(session) {
|
|
|
18419
18834
|
}
|
|
18420
18835
|
|
|
18421
18836
|
// src/exulu/provider.ts
|
|
18837
|
+
init_context_budget();
|
|
18838
|
+
init_tool_output_offload();
|
|
18422
18839
|
var ExuluProvider = class {
|
|
18423
18840
|
// Must begin with a letter (a-z) or underscore (_). Subsequent characters in a name can be letters, digits (0-9), or
|
|
18424
18841
|
// underscores and be a max length of 80 characters and at least 5 characters long.
|
|
@@ -18589,7 +19006,9 @@ var ExuluProvider = class {
|
|
|
18589
19006
|
agent,
|
|
18590
19007
|
instructions,
|
|
18591
19008
|
maxStepCount,
|
|
18592
|
-
onTokenUsage
|
|
19009
|
+
onTokenUsage,
|
|
19010
|
+
contextWindow,
|
|
19011
|
+
disabledTools
|
|
18593
19012
|
}) => {
|
|
18594
19013
|
console.log(
|
|
18595
19014
|
"[EXULU] Called generate sync for agent: " + this.name,
|
|
@@ -18617,9 +19036,7 @@ var ExuluProvider = class {
|
|
|
18617
19036
|
if (messages && session && user) {
|
|
18618
19037
|
const previousMessages = await getAgentMessages({
|
|
18619
19038
|
session,
|
|
18620
|
-
user: user.id
|
|
18621
|
-
limit: 50,
|
|
18622
|
-
page: 1
|
|
19039
|
+
user: user.id
|
|
18623
19040
|
});
|
|
18624
19041
|
const previousMessagesContent = previousMessages.map(
|
|
18625
19042
|
(message) => JSON.parse(message.content)
|
|
@@ -18628,6 +19045,12 @@ var ExuluProvider = class {
|
|
|
18628
19045
|
// append the new message to the previous messages:
|
|
18629
19046
|
messages: [...previousMessagesContent, ...messages]
|
|
18630
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);
|
|
18631
19054
|
}
|
|
18632
19055
|
console.log(
|
|
18633
19056
|
"[EXULU] Message count for agent: " + this.name,
|
|
@@ -18692,6 +19115,34 @@ var ExuluProvider = class {
|
|
|
18692
19115
|
if (memoryContext) {
|
|
18693
19116
|
system += "\n\n" + memoryContext;
|
|
18694
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);
|
|
18695
19146
|
const includesContextSearchTool = currentTools?.some(
|
|
18696
19147
|
(tool4) => tool4.name.toLowerCase().includes("context_search") || tool4.id.includes("context_search") || tool4.type === "context"
|
|
18697
19148
|
);
|
|
@@ -18704,12 +19155,12 @@ var ExuluProvider = class {
|
|
|
18704
19155
|
system += `
|
|
18705
19156
|
|
|
18706
19157
|
|
|
18707
|
-
|
|
19158
|
+
|
|
18708
19159
|
When you use a context search tool, you will include references to the items
|
|
18709
19160
|
retrieved from the tool call result inline in the response using this exact JSON format
|
|
18710
19161
|
(all on one line, no line breaks):
|
|
18711
19162
|
{item_name: <item_name>, item_id: <item_id>, context: <context_id>, chunk_id: <chunk_id>, chunk_index: <chunk_index>}
|
|
18712
|
-
|
|
19163
|
+
|
|
18713
19164
|
IMPORTANT formatting rules:
|
|
18714
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.
|
|
18715
19166
|
- Use the exact format shown above, all on ONE line
|
|
@@ -18717,9 +19168,9 @@ var ExuluProvider = class {
|
|
|
18717
19168
|
- Use the context ID from the tool result
|
|
18718
19169
|
- Include the file/item name, not the full path
|
|
18719
19170
|
- Separate multiple citations with spaces
|
|
18720
|
-
|
|
19171
|
+
|
|
18721
19172
|
Example: {item_name: document.pdf, item_id: abc123, context: my-context-id, chunk_id: chunk_456, chunk_index: 0}
|
|
18722
|
-
|
|
19173
|
+
|
|
18723
19174
|
The citations will be rendered as interactive badges in the UI.
|
|
18724
19175
|
`;
|
|
18725
19176
|
}
|
|
@@ -18730,12 +19181,12 @@ var ExuluProvider = class {
|
|
|
18730
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
|
|
18731
19182
|
(all on one line, no line breaks):
|
|
18732
19183
|
{url: <url>, title: <title>, snippet: <snippet>}
|
|
18733
|
-
|
|
19184
|
+
|
|
18734
19185
|
IMPORTANT formatting rules:
|
|
18735
19186
|
- Use the exact format shown above, all on ONE line
|
|
18736
19187
|
- Do NOT use quotes around field names or values
|
|
18737
19188
|
- Separate multiple results with spaces
|
|
18738
|
-
|
|
19189
|
+
|
|
18739
19190
|
Example: {url: https://www.google.com, title: Google, snippet: The result of the web search.}
|
|
18740
19191
|
`;
|
|
18741
19192
|
}
|
|
@@ -18768,29 +19219,12 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
18768
19219
|
system,
|
|
18769
19220
|
prompt,
|
|
18770
19221
|
maxRetries: 2,
|
|
18771
|
-
tools
|
|
18772
|
-
currentTools,
|
|
18773
|
-
currentSkills,
|
|
18774
|
-
approvedTools,
|
|
18775
|
-
allExuluTools,
|
|
18776
|
-
toolConfigs,
|
|
18777
|
-
providerapikey,
|
|
18778
|
-
contexts,
|
|
18779
|
-
user,
|
|
18780
|
-
exuluConfig,
|
|
18781
|
-
session,
|
|
18782
|
-
req,
|
|
18783
|
-
project,
|
|
18784
|
-
sessionItems,
|
|
18785
|
-
model,
|
|
18786
|
-
agent,
|
|
18787
|
-
memoryItems
|
|
18788
|
-
),
|
|
19222
|
+
tools,
|
|
18789
19223
|
// Stop after the image_generation tool fires — the widget IS the
|
|
18790
19224
|
// assistant's response, no follow-up text turn is wanted (same
|
|
18791
19225
|
// reasoning as question_ask: the UI artifact is the message).
|
|
18792
|
-
prepareStep:
|
|
18793
|
-
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")]
|
|
18794
19228
|
});
|
|
18795
19229
|
console.log("[EXULU] Output: " + JSON.stringify(output, null, 2));
|
|
18796
19230
|
const {
|
|
@@ -18851,26 +19285,9 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
18851
19285
|
ignoreIncompleteToolCalls: true
|
|
18852
19286
|
}),
|
|
18853
19287
|
maxRetries: 2,
|
|
18854
|
-
tools
|
|
18855
|
-
|
|
18856
|
-
|
|
18857
|
-
approvedTools,
|
|
18858
|
-
allExuluTools,
|
|
18859
|
-
toolConfigs,
|
|
18860
|
-
providerapikey,
|
|
18861
|
-
contexts,
|
|
18862
|
-
user,
|
|
18863
|
-
exuluConfig,
|
|
18864
|
-
session,
|
|
18865
|
-
req,
|
|
18866
|
-
project,
|
|
18867
|
-
sessionItems,
|
|
18868
|
-
model,
|
|
18869
|
-
agent,
|
|
18870
|
-
memoryItems
|
|
18871
|
-
),
|
|
18872
|
-
prepareStep: finalAnswerGuard(maxStepCount ?? resolveMaxStepsFromToolConfigs(toolConfigs) ?? 5),
|
|
18873
|
-
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")]
|
|
18874
19291
|
});
|
|
18875
19292
|
if (statistics) {
|
|
18876
19293
|
await Promise.all([
|
|
@@ -18922,7 +19339,7 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
18922
19339
|
* - Document files (PDF, DOCX, etc.) -> text parts with extracted content using officeparser
|
|
18923
19340
|
* - Image files -> image parts (which ARE supported by Responses API)
|
|
18924
19341
|
*/
|
|
18925
|
-
async processFilePartsInMessages(messages) {
|
|
19342
|
+
async processFilePartsInMessages(messages, offloadCtx) {
|
|
18926
19343
|
const processedMessages = await Promise.all(
|
|
18927
19344
|
messages.map(async (message) => {
|
|
18928
19345
|
if (message.role !== "user" || !Array.isArray(message.parts)) {
|
|
@@ -18967,10 +19384,11 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
18967
19384
|
outputErrorToConsole: false,
|
|
18968
19385
|
newlineDelimiter: "\n"
|
|
18969
19386
|
});
|
|
19387
|
+
const guardedText = await guardExtractedFileText(filename, String(extractedText), offloadCtx);
|
|
18970
19388
|
return {
|
|
18971
19389
|
type: "text",
|
|
18972
19390
|
text: `<file file name = "${filename}" >
|
|
18973
|
-
${
|
|
19391
|
+
${guardedText}
|
|
18974
19392
|
</file>`
|
|
18975
19393
|
};
|
|
18976
19394
|
} catch (error) {
|
|
@@ -18986,7 +19404,6 @@ ${extractedText}
|
|
|
18986
19404
|
...message,
|
|
18987
19405
|
parts: processedParts
|
|
18988
19406
|
};
|
|
18989
|
-
console.log("[EXULU] Result: " + JSON.stringify(result, null, 2));
|
|
18990
19407
|
return result;
|
|
18991
19408
|
})
|
|
18992
19409
|
);
|
|
@@ -19009,7 +19426,9 @@ ${extractedText}
|
|
|
19009
19426
|
exuluConfig,
|
|
19010
19427
|
instructions,
|
|
19011
19428
|
req,
|
|
19012
|
-
maxStepCount
|
|
19429
|
+
maxStepCount,
|
|
19430
|
+
contextWindow,
|
|
19431
|
+
disabledTools
|
|
19013
19432
|
}) => {
|
|
19014
19433
|
if (!this.config) {
|
|
19015
19434
|
console.error("[EXULU] Config is required for streaming.");
|
|
@@ -19030,9 +19449,7 @@ ${extractedText}
|
|
|
19030
19449
|
console.log("[EXULU] loading previous messages from session: " + session);
|
|
19031
19450
|
const previousMessages2 = await getAgentMessages({
|
|
19032
19451
|
session,
|
|
19033
|
-
user: user?.id
|
|
19034
|
-
limit: 50,
|
|
19035
|
-
page: 1
|
|
19452
|
+
user: user?.id
|
|
19036
19453
|
});
|
|
19037
19454
|
previousMessagesContent = previousMessages2.map((message2) => JSON.parse(message2.content));
|
|
19038
19455
|
}
|
|
@@ -19091,64 +19508,32 @@ ${extractedText}
|
|
|
19091
19508
|
(message2, index, self) => index === self.findLastIndex((t) => t.id === message2.id)
|
|
19092
19509
|
);
|
|
19093
19510
|
const { messages: reconciledMessages, declined } = autoDeclineStaleApprovals(messages);
|
|
19094
|
-
messages = reconciledMessages;
|
|
19095
|
-
if (declined.length && session && user) {
|
|
19096
|
-
await saveChat({ session, user: user.id, messages: declined });
|
|
19097
|
-
}
|
|
19098
|
-
messages = await this.processFilePartsInMessages(messages
|
|
19099
|
-
|
|
19100
|
-
|
|
19101
|
-
|
|
19102
|
-
|
|
19103
|
-
}
|
|
19104
|
-
|
|
19105
|
-
const
|
|
19106
|
-
|
|
19107
|
-
)
|
|
19108
|
-
|
|
19109
|
-
|
|
19110
|
-
|
|
19111
|
-
|
|
19112
|
-
|
|
19113
|
-
|
|
19114
|
-
|
|
19115
|
-
|
|
19116
|
-
|
|
19117
|
-
|
|
19118
|
-
|
|
19119
|
-
When you use a context search tool, you will include references to the items
|
|
19120
|
-
retrieved from the tool call result inline in the response using this exact JSON format
|
|
19121
|
-
(all on one line, no line breaks):
|
|
19122
|
-
{item_name: <item_name>, item_id: <item_id>, context: <context_id>, chunk_id: <chunk_id>, chunk_index: <chunk_index>}
|
|
19123
|
-
|
|
19124
|
-
IMPORTANT formatting rules:
|
|
19125
|
-
- Use the exact format shown above, all on ONE line
|
|
19126
|
-
- Do NOT use quotes around field names or values
|
|
19127
|
-
- Use the context ID from the tool result
|
|
19128
|
-
- Include the file/item name, not the full path
|
|
19129
|
-
- Separate multiple citations with spaces
|
|
19130
|
-
|
|
19131
|
-
Example: {item_name: document.pdf, item_id: abc123, context: my-context-id, chunk_id: chunk_456, chunk_index: 0}
|
|
19132
|
-
|
|
19133
|
-
The citations will be rendered as interactive badges in the UI.
|
|
19134
|
-
`;
|
|
19135
|
-
}
|
|
19136
|
-
if (includesWebSearchTool) {
|
|
19137
|
-
system += `
|
|
19138
|
-
|
|
19139
|
-
|
|
19140
|
-
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
|
|
19141
|
-
(all on one line, no line breaks):
|
|
19142
|
-
{url: <url>, title: <title>, snippet: <snippet>}
|
|
19143
|
-
|
|
19144
|
-
IMPORTANT formatting rules:
|
|
19145
|
-
- Use the exact format shown above, all on ONE line
|
|
19146
|
-
- Do NOT use quotes around field names or values
|
|
19147
|
-
- Separate multiple results with spaces
|
|
19148
|
-
|
|
19149
|
-
Example: {url: https://www.google.com, title: Google, snippet: The result of the web search.}
|
|
19150
|
-
`;
|
|
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();
|
|
19151
19535
|
}
|
|
19536
|
+
system += "\n\n" + genericContext;
|
|
19152
19537
|
if (currentSkills?.length) {
|
|
19153
19538
|
const skillsList = currentSkills.map((skill) => {
|
|
19154
19539
|
const description = (skill.description ?? "").trim();
|
|
@@ -19203,6 +19588,11 @@ ${skillsList}
|
|
|
19203
19588
|
read them with the readFile tool. Files you produce yourself (via writeFile or via shell
|
|
19204
19589
|
commands like \`node create_doc.js\`) live in the same place. These files are scoped to
|
|
19205
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.
|
|
19206
19596
|
`;
|
|
19207
19597
|
system += `
|
|
19208
19598
|
|
|
@@ -19226,9 +19616,66 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
19226
19616
|
sessionItems,
|
|
19227
19617
|
model,
|
|
19228
19618
|
agent,
|
|
19229
|
-
memoryItems
|
|
19619
|
+
memoryItems,
|
|
19620
|
+
contextWindow,
|
|
19621
|
+
disabledTools
|
|
19230
19622
|
);
|
|
19231
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);
|
|
19232
19679
|
const result = (0, import_ai11.streamText)({
|
|
19233
19680
|
temperature: 0,
|
|
19234
19681
|
// TODO Make this configurable
|
|
@@ -19253,10 +19700,9 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
19253
19700
|
`Chat stream error: ${error instanceof Error ? error.message : JSON.stringify(error)}`
|
|
19254
19701
|
);
|
|
19255
19702
|
},
|
|
19256
|
-
//
|
|
19257
|
-
|
|
19258
|
-
|
|
19259
|
-
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")]
|
|
19260
19706
|
});
|
|
19261
19707
|
return {
|
|
19262
19708
|
stream: result,
|
|
@@ -19267,19 +19713,14 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
19267
19713
|
};
|
|
19268
19714
|
var getAgentMessages = async ({
|
|
19269
19715
|
session,
|
|
19270
|
-
user
|
|
19271
|
-
limit,
|
|
19272
|
-
page
|
|
19716
|
+
user
|
|
19273
19717
|
}) => {
|
|
19274
19718
|
const { db: db2 } = await postgresClient();
|
|
19275
|
-
console.log(
|
|
19276
|
-
|
|
19277
|
-
|
|
19278
|
-
|
|
19279
|
-
|
|
19280
|
-
query.offset((page - 1) * limit);
|
|
19281
|
-
}
|
|
19282
|
-
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
|
+
]);
|
|
19283
19724
|
return messages;
|
|
19284
19725
|
};
|
|
19285
19726
|
var getSession = async ({ sessionID }) => {
|
|
@@ -19383,6 +19824,161 @@ ${SUGGESTIONS_SYSTEM_PROMPT}` : SUGGESTIONS_SYSTEM_PROMPT;
|
|
|
19383
19824
|
init_resolve_model();
|
|
19384
19825
|
init_supervisor();
|
|
19385
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
|
+
|
|
19386
19982
|
// src/exulu/transcribe.ts
|
|
19387
19983
|
init_cjs_shims();
|
|
19388
19984
|
var TranscriptionError = class extends Error {
|
|
@@ -19757,7 +20353,7 @@ function checkApiKeyScope(user, agentId) {
|
|
|
19757
20353
|
// src/exulu/openai-gateway.ts
|
|
19758
20354
|
init_cjs_shims();
|
|
19759
20355
|
var import_express2 = require("express");
|
|
19760
|
-
var
|
|
20356
|
+
var import_ai14 = require("ai");
|
|
19761
20357
|
|
|
19762
20358
|
// src/exulu/openai-transformer.ts
|
|
19763
20359
|
init_cjs_shims();
|
|
@@ -19845,7 +20441,7 @@ function transformCompletion(text, inputTokens, outputTokens, ctx) {
|
|
|
19845
20441
|
}
|
|
19846
20442
|
|
|
19847
20443
|
// src/exulu/openai-gateway.ts
|
|
19848
|
-
var
|
|
20444
|
+
var import_node_crypto7 = require("crypto");
|
|
19849
20445
|
var import_crypto_js8 = require("crypto-js");
|
|
19850
20446
|
var import_express3 = __toESM(require("express"), 1);
|
|
19851
20447
|
init_client();
|
|
@@ -19853,6 +20449,9 @@ init_convert_exulu_tools_to_ai_sdk_tools();
|
|
|
19853
20449
|
init_statistics2();
|
|
19854
20450
|
init_statistics();
|
|
19855
20451
|
init_resolve_model();
|
|
20452
|
+
init_sanitize_tool_name();
|
|
20453
|
+
init_context_budget();
|
|
20454
|
+
init_supervisor();
|
|
19856
20455
|
function convertOpenAIToolsToAiSdkTools(tools) {
|
|
19857
20456
|
return Object.fromEntries(
|
|
19858
20457
|
tools.map((t) => {
|
|
@@ -19861,7 +20460,7 @@ function convertOpenAIToolsToAiSdkTools(tools) {
|
|
|
19861
20460
|
t.function.name,
|
|
19862
20461
|
{
|
|
19863
20462
|
description: t.function.description ?? "",
|
|
19864
|
-
inputSchema: (0,
|
|
20463
|
+
inputSchema: (0, import_ai14.jsonSchema)({
|
|
19865
20464
|
type: "object",
|
|
19866
20465
|
properties: params.properties ?? {},
|
|
19867
20466
|
...params.required ? { required: params.required } : {}
|
|
@@ -20164,6 +20763,10 @@ var registerOpenAIGatewayRoutes = async (app, providers, tools, contexts, config
|
|
|
20164
20763
|
}
|
|
20165
20764
|
const providerapikey = resolved.apiKey;
|
|
20166
20765
|
const languageModel = resolved.languageModel;
|
|
20766
|
+
const contextWindow = await resolveContextWindow({
|
|
20767
|
+
modelId: resolved.model.id,
|
|
20768
|
+
exuluProvider: isLiteLLMEnabled() ? void 0 : resolved.exuluProvider
|
|
20769
|
+
});
|
|
20167
20770
|
const disabledTools = req.body.disabledTools ?? [];
|
|
20168
20771
|
const enabledTools = await getEnabledTools(
|
|
20169
20772
|
agent,
|
|
@@ -20188,12 +20791,50 @@ var registerOpenAIGatewayRoutes = async (app, providers, tools, contexts, config
|
|
|
20188
20791
|
project?.id,
|
|
20189
20792
|
void 0,
|
|
20190
20793
|
languageModel,
|
|
20191
|
-
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)
|
|
20192
20805
|
);
|
|
20806
|
+
const turnBudget = resolveTurnStepBudget(void 0, agent);
|
|
20193
20807
|
const clientTools = Array.isArray(req.body.tools) ? req.body.tools : [];
|
|
20194
20808
|
const activeTools = clientTools.length > 0 ? convertOpenAIToolsToAiSdkTools(clientTools) : convertedTools;
|
|
20195
20809
|
const openaiMessages = req.body.messages ?? [];
|
|
20196
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
|
+
}
|
|
20197
20838
|
const agentInstructions = agent.instructions ?? "";
|
|
20198
20839
|
const systemParts = [
|
|
20199
20840
|
agentInstructions ? `You are an agent named: ${agent.name}
|
|
@@ -20203,7 +20844,7 @@ ${project.description}` : ""}` : "",
|
|
|
20203
20844
|
requestSystemPrompt
|
|
20204
20845
|
].filter(Boolean);
|
|
20205
20846
|
const systemPrompt = systemParts.join("\n\n");
|
|
20206
|
-
const completionId = `chatcmpl-${(0,
|
|
20847
|
+
const completionId = `chatcmpl-${(0, import_node_crypto7.randomUUID)()}`;
|
|
20207
20848
|
const created = Math.floor(Date.now() / 1e3);
|
|
20208
20849
|
const hasTools = Object.keys(activeTools).length > 0;
|
|
20209
20850
|
const ctx = { completionId, created, modelId };
|
|
@@ -20211,13 +20852,14 @@ ${project.description}` : ""}` : "",
|
|
|
20211
20852
|
res.setHeader("Content-Type", "text/event-stream");
|
|
20212
20853
|
res.setHeader("Cache-Control", "no-cache");
|
|
20213
20854
|
res.setHeader("Connection", "keep-alive");
|
|
20214
|
-
const result = (0,
|
|
20855
|
+
const result = (0, import_ai14.streamText)({
|
|
20215
20856
|
model: languageModel,
|
|
20216
20857
|
system: systemPrompt || void 0,
|
|
20217
20858
|
messages: coreMessages,
|
|
20218
20859
|
tools: hasTools ? activeTools : void 0,
|
|
20219
20860
|
maxRetries: 2,
|
|
20220
|
-
|
|
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)],
|
|
20221
20863
|
onError: (error) => {
|
|
20222
20864
|
console.error("[OPENAI GATEWAY] stream error:", error);
|
|
20223
20865
|
}
|
|
@@ -20250,13 +20892,14 @@ ${project.description}` : ""}` : "",
|
|
|
20250
20892
|
const usage = await result.usage;
|
|
20251
20893
|
await writeStatistics(agent, project, user, usage.inputTokens ?? 0, usage.outputTokens ?? 0);
|
|
20252
20894
|
} else {
|
|
20253
|
-
const { text, usage } = await (0,
|
|
20895
|
+
const { text, usage } = await (0, import_ai14.generateText)({
|
|
20254
20896
|
model: languageModel,
|
|
20255
20897
|
system: systemPrompt || void 0,
|
|
20256
20898
|
messages: coreMessages,
|
|
20257
20899
|
tools: hasTools ? activeTools : void 0,
|
|
20258
20900
|
maxRetries: 2,
|
|
20259
|
-
|
|
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)]
|
|
20260
20903
|
});
|
|
20261
20904
|
res.json(transformCompletion(text, usage.inputTokens ?? 0, usage.outputTokens ?? 0, ctx));
|
|
20262
20905
|
await writeStatistics(agent, project, user, usage.inputTokens ?? 0, usage.outputTokens ?? 0);
|
|
@@ -20375,7 +21018,7 @@ init_flow();
|
|
|
20375
21018
|
|
|
20376
21019
|
// src/exulu/recall/verify.ts
|
|
20377
21020
|
init_cjs_shims();
|
|
20378
|
-
var
|
|
21021
|
+
var import_node_crypto8 = require("crypto");
|
|
20379
21022
|
var TOLERANCE_SECONDS = 5 * 60;
|
|
20380
21023
|
var header = (headers, ...names) => {
|
|
20381
21024
|
for (const name of names) {
|
|
@@ -20393,7 +21036,7 @@ var safeEqual = (a, b) => {
|
|
|
20393
21036
|
const bufA = Buffer.from(a);
|
|
20394
21037
|
const bufB = Buffer.from(b);
|
|
20395
21038
|
if (bufA.length !== bufB.length) return false;
|
|
20396
|
-
return (0,
|
|
21039
|
+
return (0, import_node_crypto8.timingSafeEqual)(bufA, bufB);
|
|
20397
21040
|
};
|
|
20398
21041
|
var verifyRecallRequest = (headers, rawBody, secret = recallVerificationSecret()) => {
|
|
20399
21042
|
if (!secret) {
|
|
@@ -20415,7 +21058,7 @@ var verifyRecallRequest = (headers, rawBody, secret = recallVerificationSecret()
|
|
|
20415
21058
|
}
|
|
20416
21059
|
const body = typeof rawBody === "string" ? rawBody : rawBody.toString("utf8");
|
|
20417
21060
|
const signedContent = `${id}.${timestamp}.${body}`;
|
|
20418
|
-
const expected = (0,
|
|
21061
|
+
const expected = (0, import_node_crypto8.createHmac)("sha256", secretKey(secret)).update(signedContent).digest("base64");
|
|
20419
21062
|
const passed = signatureHeader.split(" ").some((entry) => {
|
|
20420
21063
|
const [, sig] = entry.split(",");
|
|
20421
21064
|
return !!sig && safeEqual(sig, expected);
|
|
@@ -20768,7 +21411,7 @@ Mood: friendly and intelligent.
|
|
|
20768
21411
|
});
|
|
20769
21412
|
return;
|
|
20770
21413
|
}
|
|
20771
|
-
const uuid = (0,
|
|
21414
|
+
const uuid = (0, import_node_crypto9.randomUUID)();
|
|
20772
21415
|
const image_url = await uploadFile(Buffer.from(image_base64, "base64"), `${uuid}.png`, config, {
|
|
20773
21416
|
contentType: "image/png"
|
|
20774
21417
|
}, authenticationResult.user?.id, void 0, true);
|
|
@@ -20972,6 +21615,10 @@ Mood: friendly and intelligent.
|
|
|
20972
21615
|
const providerapikey = resolved.apiKey;
|
|
20973
21616
|
const resolvedLanguageModel = resolved.languageModel;
|
|
20974
21617
|
const resolvedModelId = resolved.model.id;
|
|
21618
|
+
const contextWindow = await resolveContextWindow({
|
|
21619
|
+
modelId: resolved.model.id,
|
|
21620
|
+
exuluProvider: isLiteLLMEnabled() ? void 0 : resolved.exuluProvider
|
|
21621
|
+
});
|
|
20975
21622
|
if (!!headers.stream) {
|
|
20976
21623
|
const statistics = {
|
|
20977
21624
|
label: agent.name,
|
|
@@ -20990,27 +21637,46 @@ Mood: friendly and intelligent.
|
|
|
20990
21637
|
const instructions = customInstructions ? `${agent.instructions}
|
|
20991
21638
|
|
|
20992
21639
|
${customInstructions}` : agent.instructions;
|
|
20993
|
-
|
|
20994
|
-
|
|
20995
|
-
|
|
20996
|
-
|
|
20997
|
-
|
|
20998
|
-
|
|
20999
|
-
|
|
21000
|
-
|
|
21001
|
-
|
|
21002
|
-
|
|
21003
|
-
|
|
21004
|
-
|
|
21005
|
-
|
|
21006
|
-
|
|
21007
|
-
|
|
21008
|
-
|
|
21009
|
-
|
|
21010
|
-
|
|
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
|
+
}
|
|
21011
21671
|
result.stream.consumeStream();
|
|
21012
21672
|
result.stream.pipeUIMessageStreamToResponse(res, {
|
|
21013
21673
|
messageMetadata: ({ part }) => {
|
|
21674
|
+
if (part.type === "finish-step") {
|
|
21675
|
+
return {
|
|
21676
|
+
lastStepInputTokens: part.usage.inputTokens,
|
|
21677
|
+
lastStepOutputTokens: part.usage.outputTokens
|
|
21678
|
+
};
|
|
21679
|
+
}
|
|
21014
21680
|
if (part.type === "finish") {
|
|
21015
21681
|
return {
|
|
21016
21682
|
totalTokens: part.totalUsage.totalTokens,
|
|
@@ -21027,22 +21693,20 @@ ${customInstructions}` : agent.instructions;
|
|
|
21027
21693
|
sendSources: true,
|
|
21028
21694
|
onError: (error) => {
|
|
21029
21695
|
console.error("[EXULU] chat response error.", error);
|
|
21030
|
-
if (
|
|
21031
|
-
|
|
21032
|
-
|
|
21033
|
-
if (typeof error === "string")
|
|
21034
|
-
|
|
21035
|
-
|
|
21036
|
-
|
|
21037
|
-
return error.message;
|
|
21038
|
-
}
|
|
21039
|
-
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);
|
|
21040
21703
|
},
|
|
21041
|
-
generateMessageId: (0,
|
|
21704
|
+
generateMessageId: (0, import_ai15.createIdGenerator)({
|
|
21042
21705
|
prefix: "msg_",
|
|
21043
21706
|
size: 16
|
|
21044
21707
|
}),
|
|
21045
21708
|
onFinish: async ({ messages, isContinuation, isAborted, responseMessage }) => {
|
|
21709
|
+
if (headers.session) clearStreamActive(headers.session);
|
|
21046
21710
|
console.log(
|
|
21047
21711
|
"[EXULU] onFinish",
|
|
21048
21712
|
messages?.map((msg) => msg.parts?.map((part) => part.type === "text" ? part.text : null)).join("\n")
|
|
@@ -21104,33 +21768,129 @@ ${customInstructions}` : agent.instructions;
|
|
|
21104
21768
|
const instructions = customInstructions ? `${agent.instructions}
|
|
21105
21769
|
|
|
21106
21770
|
${customInstructions}` : agent.instructions;
|
|
21107
|
-
|
|
21108
|
-
|
|
21109
|
-
|
|
21110
|
-
|
|
21111
|
-
|
|
21112
|
-
|
|
21113
|
-
|
|
21114
|
-
|
|
21115
|
-
|
|
21116
|
-
|
|
21117
|
-
|
|
21118
|
-
|
|
21119
|
-
|
|
21120
|
-
|
|
21121
|
-
|
|
21122
|
-
|
|
21123
|
-
|
|
21124
|
-
|
|
21125
|
-
|
|
21126
|
-
|
|
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;
|
|
21127
21801
|
}
|
|
21128
|
-
|
|
21802
|
+
throw err;
|
|
21803
|
+
}
|
|
21129
21804
|
res.status(200).json(response);
|
|
21130
21805
|
return;
|
|
21131
21806
|
}
|
|
21132
21807
|
});
|
|
21133
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
|
+
};
|
|
21134
21894
|
providers.forEach((provider) => {
|
|
21135
21895
|
const slug = provider.slug;
|
|
21136
21896
|
if (!slug) return;
|
|
@@ -21139,6 +21899,14 @@ ${customInstructions}` : agent.instructions;
|
|
|
21139
21899
|
if (isLiteLLMEnabled() && providers.length > 0) {
|
|
21140
21900
|
registerAgentRunRoute("/agents/litellm/run", providers[0]);
|
|
21141
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
|
+
}
|
|
21142
21910
|
app.post("/agents/suggestions/:agentId", async (req, res) => {
|
|
21143
21911
|
const agentId = req.params.agentId;
|
|
21144
21912
|
if (!agentId) {
|
|
@@ -21490,7 +22258,7 @@ ${customInstructions}` : agent.instructions;
|
|
|
21490
22258
|
const keys = [];
|
|
21491
22259
|
const revisedPrompts = [];
|
|
21492
22260
|
for (const img of images) {
|
|
21493
|
-
const filename = `${(0,
|
|
22261
|
+
const filename = `${(0, import_node_crypto9.randomUUID)()}.${img.extension}`;
|
|
21494
22262
|
const key = `sessions/${sessionId}/images/${toolCallId}/${filename}`;
|
|
21495
22263
|
const fullKey = await uploadFile(
|
|
21496
22264
|
img.buffer,
|
|
@@ -21778,7 +22546,7 @@ ${style.markdown}` : params.prompt;
|
|
|
21778
22546
|
(d) => `- ${d.presignedUrl} (prompt: "${d.prompt}", model: ${d.model}${d.styleName ? `, style: ${d.styleName}` : ""})`
|
|
21779
22547
|
);
|
|
21780
22548
|
const messageText = "The user generated and selected the following image(s) in this chat:\n" + lines.join("\n");
|
|
21781
|
-
const messageId = (0,
|
|
22549
|
+
const messageId = (0, import_node_crypto9.randomUUID)();
|
|
21782
22550
|
const uiMessage = {
|
|
21783
22551
|
id: messageId,
|
|
21784
22552
|
role: "system",
|
|
@@ -22706,7 +23474,7 @@ ${style.markdown}` : params.prompt;
|
|
|
22706
23474
|
res.status(404).json({ detail: "Skill not found." });
|
|
22707
23475
|
return;
|
|
22708
23476
|
}
|
|
22709
|
-
const stagingKey = `user_${authResult.user.id}/skills/_staging/${(0,
|
|
23477
|
+
const stagingKey = `user_${authResult.user.id}/skills/_staging/${(0, import_node_crypto9.randomUUID)()}${extension}`;
|
|
22710
23478
|
const fullKey = config.fileUploads?.s3prefix ? `${config.fileUploads.s3prefix.replace(/\/$/, "")}/${stagingKey}` : stagingKey;
|
|
22711
23479
|
const uploadUrl = await getS3SignedUploadUrl(fullKey, contentType, config);
|
|
22712
23480
|
res.json({ uploadUrl, stagingKey });
|
|
@@ -23738,7 +24506,7 @@ function buildUnifiedDiff(fromLines, toLines, fromLabel, toLabel) {
|
|
|
23738
24506
|
// src/mcp/index.ts
|
|
23739
24507
|
init_cjs_shims();
|
|
23740
24508
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
23741
|
-
var
|
|
24509
|
+
var import_node_crypto10 = require("crypto");
|
|
23742
24510
|
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
23743
24511
|
var import_types3 = require("@modelcontextprotocol/sdk/types.js");
|
|
23744
24512
|
init_sanitize_tool_name();
|
|
@@ -23850,7 +24618,7 @@ var ExuluMCP = class {
|
|
|
23850
24618
|
throw new Error("Tool not found in converted tools array.");
|
|
23851
24619
|
}
|
|
23852
24620
|
const iterator = await convertedTool.execute(inputs, {
|
|
23853
|
-
toolCallId: tool4.id + "_" + (0,
|
|
24621
|
+
toolCallId: tool4.id + "_" + (0, import_node_crypto10.randomUUID)(),
|
|
23854
24622
|
messages: []
|
|
23855
24623
|
});
|
|
23856
24624
|
let result;
|
|
@@ -24042,7 +24810,7 @@ var ExuluMCP = class {
|
|
|
24042
24810
|
transport = this.transports[sessionId];
|
|
24043
24811
|
} else if (!sessionId && (0, import_types3.isInitializeRequest)(req.body)) {
|
|
24044
24812
|
transport = new import_streamableHttp.StreamableHTTPServerTransport({
|
|
24045
|
-
sessionIdGenerator: () => (0,
|
|
24813
|
+
sessionIdGenerator: () => (0, import_node_crypto10.randomUUID)(),
|
|
24046
24814
|
onsessioninitialized: (sessionId2) => {
|
|
24047
24815
|
this.transports[sessionId2] = transport;
|
|
24048
24816
|
}
|
|
@@ -24996,7 +25764,7 @@ init_cjs_shims();
|
|
|
24996
25764
|
|
|
24997
25765
|
// src/exulu/evals.ts
|
|
24998
25766
|
init_cjs_shims();
|
|
24999
|
-
var
|
|
25767
|
+
var import_ai16 = require("ai");
|
|
25000
25768
|
init_entitlements();
|
|
25001
25769
|
var ExuluEval = class {
|
|
25002
25770
|
id;
|
|
@@ -25037,7 +25805,7 @@ var ExuluEval = class {
|
|
|
25037
25805
|
init_resolve_model();
|
|
25038
25806
|
init_singleton();
|
|
25039
25807
|
var import_zod15 = require("zod");
|
|
25040
|
-
var
|
|
25808
|
+
var import_ai17 = require("ai");
|
|
25041
25809
|
var llmAsJudgeEval = () => {
|
|
25042
25810
|
if (process.env.REDIS_HOST?.length && process.env.REDIS_PORT?.length) {
|
|
25043
25811
|
return new ExuluEval({
|
|
@@ -25082,13 +25850,13 @@ var llmAsJudgeEval = () => {
|
|
|
25082
25850
|
rbacBypass: true
|
|
25083
25851
|
});
|
|
25084
25852
|
console.log("[EXULU] prompt", prompt);
|
|
25085
|
-
const { output } = await (0,
|
|
25853
|
+
const { output } = await (0, import_ai17.generateText)({
|
|
25086
25854
|
temperature: 0,
|
|
25087
25855
|
model: resolved.languageModel,
|
|
25088
25856
|
system: "",
|
|
25089
25857
|
prompt,
|
|
25090
25858
|
maxRetries: 2,
|
|
25091
|
-
output:
|
|
25859
|
+
output: import_ai17.Output.object({
|
|
25092
25860
|
schema: import_zod15.z.object({
|
|
25093
25861
|
score: import_zod15.z.number().min(0).max(100).describe("The score between 0 and 100.")
|
|
25094
25862
|
})
|
|
@@ -25522,7 +26290,7 @@ var import_zod17 = __toESM(require("zod"), 1);
|
|
|
25522
26290
|
init_tool();
|
|
25523
26291
|
init_check_record_access();
|
|
25524
26292
|
init_client();
|
|
25525
|
-
var
|
|
26293
|
+
var import_node_crypto11 = require("crypto");
|
|
25526
26294
|
var AnswerOptionSchema = import_zod17.default.object({
|
|
25527
26295
|
id: import_zod17.default.string().describe("Unique identifier for the answer option"),
|
|
25528
26296
|
text: import_zod17.default.string().describe("The text of the answer option")
|
|
@@ -25576,15 +26344,15 @@ var QuestionAskTool = new ExuluTool({
|
|
|
25576
26344
|
throw new Error("You don't have access to this session " + session.id + ".");
|
|
25577
26345
|
}
|
|
25578
26346
|
const answerOptionsWithIds = answerOptions.map((text) => ({
|
|
25579
|
-
id: (0,
|
|
26347
|
+
id: (0, import_node_crypto11.randomUUID)(),
|
|
25580
26348
|
text
|
|
25581
26349
|
}));
|
|
25582
26350
|
answerOptionsWithIds.push({
|
|
25583
|
-
id: (0,
|
|
26351
|
+
id: (0, import_node_crypto11.randomUUID)(),
|
|
25584
26352
|
text: "None of the above..."
|
|
25585
26353
|
});
|
|
25586
26354
|
const newQuestion = {
|
|
25587
|
-
id: (0,
|
|
26355
|
+
id: (0, import_node_crypto11.randomUUID)(),
|
|
25588
26356
|
question,
|
|
25589
26357
|
answerOptions: answerOptionsWithIds,
|
|
25590
26358
|
status: "pending"
|
|
@@ -28618,7 +29386,7 @@ var MarkdownChunker = class {
|
|
|
28618
29386
|
init_cjs_shims();
|
|
28619
29387
|
var fs4 = __toESM(require("fs"), 1);
|
|
28620
29388
|
var path = __toESM(require("path"), 1);
|
|
28621
|
-
var
|
|
29389
|
+
var import_ai18 = require("ai");
|
|
28622
29390
|
var import_zod22 = require("zod");
|
|
28623
29391
|
var import_p_limit = __toESM(require("p-limit"), 1);
|
|
28624
29392
|
var import_crypto2 = require("crypto");
|
|
@@ -29083,9 +29851,9 @@ If the page contains a flow-chart, schematic, technical drawing or control board
|
|
|
29083
29851
|
|
|
29084
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\`.
|
|
29085
29853
|
`;
|
|
29086
|
-
const result = await (0,
|
|
29854
|
+
const result = await (0, import_ai18.generateText)({
|
|
29087
29855
|
model,
|
|
29088
|
-
output:
|
|
29856
|
+
output: import_ai18.Output.object({
|
|
29089
29857
|
schema: import_zod22.z.object({
|
|
29090
29858
|
needs_correction: import_zod22.z.boolean(),
|
|
29091
29859
|
corrected_text: import_zod22.z.string().nullable(),
|