@exulu/backend 3.0.0 → 3.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-KFL7HIID.js → chunk-ZDH5S2WF.js} +873 -277
- package/dist/{convert-exulu-tools-to-ai-sdk-tools-YY2WIMMJ.js → convert-exulu-tools-to-ai-sdk-tools-FN6WZSIQ.js} +1 -1
- package/dist/index.cjs +2306 -608
- package/dist/index.d.cts +41 -8
- package/dist/index.d.ts +41 -8
- package/dist/index.js +1203 -202
- package/ee/agentic-retrieval/pipeline/hyde.test.ts +1 -1
- package/ee/agentic-retrieval/pipeline/hyde.ts +6 -3
- package/ee/agentic-retrieval/pipeline/memory.test.ts +5 -1
- package/ee/agentic-retrieval/pipeline/memory.ts +71 -104
- package/ee/agentic-retrieval/pipeline/micro-call.test.ts +112 -0
- package/ee/agentic-retrieval/pipeline/micro-call.ts +98 -0
- package/ee/agentic-retrieval/pipeline/prefilter.test.ts +1 -0
- package/ee/agentic-retrieval/pipeline/prefilter.ts +11 -20
- package/ee/agentic-retrieval/pipeline/routing.test.ts +44 -1
- package/ee/agentic-retrieval/pipeline/routing.ts +31 -56
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1221,6 +1221,18 @@ var init_sanitize_name = __esm({
|
|
|
1221
1221
|
}
|
|
1222
1222
|
});
|
|
1223
1223
|
|
|
1224
|
+
// src/exulu/table-names.ts
|
|
1225
|
+
var getTableName, getChunksTableName;
|
|
1226
|
+
var init_table_names = __esm({
|
|
1227
|
+
"src/exulu/table-names.ts"() {
|
|
1228
|
+
"use strict";
|
|
1229
|
+
init_cjs_shims();
|
|
1230
|
+
init_sanitize_name();
|
|
1231
|
+
getTableName = (id) => sanitizeName(id) + "_items";
|
|
1232
|
+
getChunksTableName = (id) => sanitizeName(id) + "_chunks";
|
|
1233
|
+
}
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1224
1236
|
// src/exulu/litellm/supervisor.ts
|
|
1225
1237
|
var import_node_child_process, import_node_fs3, import_node_path2, LITELLM_UI_PATH, MAX_CRASHES, INITIAL_BACKOFF_MS, MAX_BACKOFF_MS, READY_TIMEOUT_MS, WAIT_TIMEOUT_MS, READY_POLL_INTERVAL_MS, SHUTDOWN_GRACE_MS, internal, isLiteLLMEnabled, resolveConfig, log2, pollHealth, spawnLiteLLM, supervise, _packageRoot, _clientMode, setLiteLLMPackageRoot, enableLiteLLMClientMode, startLiteLLMSupervisor, waitForLiteLLMReady, stopLiteLLM, shutdownHandlersRegistered, registerShutdownHandlers, getSupervisorState;
|
|
1226
1238
|
var init_supervisor = __esm({
|
|
@@ -1819,8 +1831,8 @@ async function getTagDailyActivity(params) {
|
|
|
1819
1831
|
}
|
|
1820
1832
|
return await res.json().catch(() => ({}));
|
|
1821
1833
|
}
|
|
1822
|
-
async function getTagSpendByWindow(
|
|
1823
|
-
const windowEntries = Object.entries(
|
|
1834
|
+
async function getTagSpendByWindow(windows2, endDate) {
|
|
1835
|
+
const windowEntries = Object.entries(windows2);
|
|
1824
1836
|
const out = {};
|
|
1825
1837
|
for (const [tag] of windowEntries) out[tag] = 0;
|
|
1826
1838
|
if (windowEntries.length === 0) return out;
|
|
@@ -1945,14 +1957,14 @@ function windowStartYmd(reset_at, duration) {
|
|
|
1945
1957
|
async function enrichSpendFromActivity(map) {
|
|
1946
1958
|
const names = Object.keys(map);
|
|
1947
1959
|
if (names.length === 0) return;
|
|
1948
|
-
const
|
|
1960
|
+
const windows2 = {};
|
|
1949
1961
|
for (const name of names) {
|
|
1950
1962
|
const ti = map[name];
|
|
1951
|
-
|
|
1963
|
+
windows2[name] = windowStartYmd(ti.budget_reset_at, ti.budget_duration);
|
|
1952
1964
|
}
|
|
1953
1965
|
try {
|
|
1954
1966
|
const spendByTag = await getTagSpendByWindow(
|
|
1955
|
-
|
|
1967
|
+
windows2,
|
|
1956
1968
|
ymd(new Date(Date.now() + DAY_MS))
|
|
1957
1969
|
);
|
|
1958
1970
|
for (const name of names) {
|
|
@@ -2524,89 +2536,152 @@ var init_singleton = __esm({
|
|
|
2524
2536
|
}
|
|
2525
2537
|
});
|
|
2526
2538
|
|
|
2527
|
-
// src/exulu/
|
|
2528
|
-
var
|
|
2539
|
+
// src/exulu/auth/validate.ts
|
|
2540
|
+
var OAUTH_REQUIRED_STRING_FIELDS, validateAuthConfig;
|
|
2529
2541
|
var init_validate = __esm({
|
|
2530
|
-
"src/exulu/
|
|
2542
|
+
"src/exulu/auth/validate.ts"() {
|
|
2531
2543
|
"use strict";
|
|
2532
2544
|
init_cjs_shims();
|
|
2533
|
-
|
|
2545
|
+
OAUTH_REQUIRED_STRING_FIELDS = [
|
|
2534
2546
|
"authorizationUrl",
|
|
2535
2547
|
"tokenUrl",
|
|
2536
2548
|
"clientId",
|
|
2537
2549
|
"clientSecret"
|
|
2538
2550
|
];
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2551
|
+
validateAuthConfig = (toolId, config) => {
|
|
2552
|
+
if (config.authType === "oauth") {
|
|
2553
|
+
for (const field of OAUTH_REQUIRED_STRING_FIELDS) {
|
|
2554
|
+
const value = config[field];
|
|
2555
|
+
if (!value || typeof value !== "string") {
|
|
2556
|
+
throw new Error(
|
|
2557
|
+
`ExuluTool "${toolId}": oauth.${field} is required and must be a non-empty string.`
|
|
2558
|
+
);
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
if (!Array.isArray(config.scopes)) {
|
|
2542
2562
|
throw new Error(
|
|
2543
|
-
`ExuluTool "${toolId}": oauth
|
|
2563
|
+
`ExuluTool "${toolId}": oauth.scopes must be an array of strings (use [] to request no scopes).`
|
|
2544
2564
|
);
|
|
2545
2565
|
}
|
|
2566
|
+
if (config.provider !== void 0) {
|
|
2567
|
+
if (typeof config.provider !== "string" || config.provider.length === 0 || config.provider.trim() !== config.provider) {
|
|
2568
|
+
throw new Error(
|
|
2569
|
+
`ExuluTool "${toolId}": oauth.provider must be a non-empty string with no leading or trailing whitespace when set.`
|
|
2570
|
+
);
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
if (!process.env.BACKEND) {
|
|
2574
|
+
throw new Error(
|
|
2575
|
+
`ExuluTool "${toolId}": oauth requires the BACKEND environment variable (the backend's public base URL) to build the redirect URI.`
|
|
2576
|
+
);
|
|
2577
|
+
}
|
|
2578
|
+
return;
|
|
2546
2579
|
}
|
|
2547
|
-
if (
|
|
2548
|
-
throw new Error(
|
|
2549
|
-
`ExuluTool "${toolId}": oauth.scopes must be an array of strings (use [] to request no scopes).`
|
|
2550
|
-
);
|
|
2551
|
-
}
|
|
2552
|
-
if (config.provider !== void 0) {
|
|
2580
|
+
if (config.authType === "user_credentials") {
|
|
2553
2581
|
if (typeof config.provider !== "string" || config.provider.length === 0 || config.provider.trim() !== config.provider) {
|
|
2554
2582
|
throw new Error(
|
|
2555
|
-
`ExuluTool "${toolId}":
|
|
2583
|
+
`ExuluTool "${toolId}": user_credentials.provider must be a non-empty string with no leading or trailing whitespace.`
|
|
2556
2584
|
);
|
|
2557
2585
|
}
|
|
2586
|
+
if (!Array.isArray(config.fields) || config.fields.length === 0) {
|
|
2587
|
+
throw new Error(
|
|
2588
|
+
`ExuluTool "${toolId}": user_credentials.fields must contain at least one field.`
|
|
2589
|
+
);
|
|
2590
|
+
}
|
|
2591
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
2592
|
+
for (let i = 0; i < config.fields.length; i++) {
|
|
2593
|
+
const field = config.fields[i];
|
|
2594
|
+
const name = field.name;
|
|
2595
|
+
if (typeof name !== "string" || name.length === 0 || name.trim() !== name) {
|
|
2596
|
+
throw new Error(
|
|
2597
|
+
`ExuluTool "${toolId}": user_credentials.fields[${i}].name must be a non-empty string with no leading or trailing whitespace.`
|
|
2598
|
+
);
|
|
2599
|
+
}
|
|
2600
|
+
if (seenNames.has(name)) {
|
|
2601
|
+
throw new Error(
|
|
2602
|
+
`ExuluTool "${toolId}": user_credentials.fields has duplicate field name '${name}'.`
|
|
2603
|
+
);
|
|
2604
|
+
}
|
|
2605
|
+
seenNames.add(name);
|
|
2606
|
+
const type = field.type;
|
|
2607
|
+
if (type !== "text" && type !== "password") {
|
|
2608
|
+
throw new Error(
|
|
2609
|
+
`ExuluTool "${toolId}": user_credentials.fields[${i}].type must be 'text' or 'password' (got '${type}').`
|
|
2610
|
+
);
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
if (!process.env.BACKEND) {
|
|
2614
|
+
throw new Error(
|
|
2615
|
+
`ExuluTool "${toolId}": user_credentials requires the BACKEND environment variable (the backend's public base URL) to build the credential submit URL.`
|
|
2616
|
+
);
|
|
2617
|
+
}
|
|
2618
|
+
return;
|
|
2558
2619
|
}
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
);
|
|
2563
|
-
}
|
|
2620
|
+
throw new Error(
|
|
2621
|
+
`ExuluTool "${toolId}": auth.authType '${config.authType}' is not supported.`
|
|
2622
|
+
);
|
|
2564
2623
|
};
|
|
2565
2624
|
}
|
|
2566
2625
|
});
|
|
2567
2626
|
|
|
2568
|
-
// src/exulu/
|
|
2627
|
+
// src/exulu/auth/provider-key.ts
|
|
2569
2628
|
var providerKeyFor;
|
|
2570
2629
|
var init_provider_key = __esm({
|
|
2571
|
-
"src/exulu/
|
|
2630
|
+
"src/exulu/auth/provider-key.ts"() {
|
|
2572
2631
|
"use strict";
|
|
2573
2632
|
init_cjs_shims();
|
|
2574
2633
|
providerKeyFor = (toolId, config) => config.provider && config.provider.length > 0 ? config.provider : toolId;
|
|
2575
2634
|
}
|
|
2576
2635
|
});
|
|
2577
2636
|
|
|
2578
|
-
// src/exulu/
|
|
2579
|
-
var byProvider, byTool,
|
|
2637
|
+
// src/exulu/auth/registry.ts
|
|
2638
|
+
var byProvider, byTool, STABLE_OAUTH_STRING_FIELDS, assertCompatible, authRegistry;
|
|
2580
2639
|
var init_registry = __esm({
|
|
2581
|
-
"src/exulu/
|
|
2640
|
+
"src/exulu/auth/registry.ts"() {
|
|
2582
2641
|
"use strict";
|
|
2583
2642
|
init_cjs_shims();
|
|
2584
2643
|
init_provider_key();
|
|
2585
2644
|
byProvider = /* @__PURE__ */ new Map();
|
|
2586
2645
|
byTool = /* @__PURE__ */ new Map();
|
|
2587
|
-
|
|
2646
|
+
STABLE_OAUTH_STRING_FIELDS = [
|
|
2588
2647
|
"authorizationUrl",
|
|
2589
2648
|
"tokenUrl",
|
|
2590
2649
|
"clientId",
|
|
2591
2650
|
"clientSecret"
|
|
2592
2651
|
];
|
|
2593
2652
|
assertCompatible = (providerKey, toolId, existing, next) => {
|
|
2594
|
-
|
|
2595
|
-
|
|
2653
|
+
if (existing.authType !== next.authType) {
|
|
2654
|
+
throw new Error(
|
|
2655
|
+
`ExuluTool "${toolId}": auth.authType '${next.authType}' disagrees with another tool that shares provider "${providerKey}" using authType '${existing.authType}'.`
|
|
2656
|
+
);
|
|
2657
|
+
}
|
|
2658
|
+
if (existing.authType === "oauth" && next.authType === "oauth") {
|
|
2659
|
+
for (const field of STABLE_OAUTH_STRING_FIELDS) {
|
|
2660
|
+
if (existing[field] !== next[field]) {
|
|
2661
|
+
throw new Error(
|
|
2662
|
+
`ExuluTool "${toolId}": oauth.${field} disagrees with another tool that shares provider "${providerKey}". Every tool on the same provider must use identical authorizationUrl/tokenUrl/clientId/clientSecret.`
|
|
2663
|
+
);
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
const a = new Set(existing.scopes);
|
|
2667
|
+
const b = new Set(next.scopes);
|
|
2668
|
+
if (a.size !== b.size || [...a].some((s) => !b.has(s))) {
|
|
2596
2669
|
throw new Error(
|
|
2597
|
-
`ExuluTool "${toolId}": oauth
|
|
2670
|
+
`ExuluTool "${toolId}": oauth.scopes disagrees with another tool that shares provider "${providerKey}". Every tool on the same provider must declare the same scope superset. Existing: [${[...a].sort().join(", ")}]. This tool: [${[...b].sort().join(", ")}].`
|
|
2598
2671
|
);
|
|
2599
2672
|
}
|
|
2673
|
+
return;
|
|
2600
2674
|
}
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2675
|
+
if (existing.authType === "user_credentials" && next.authType === "user_credentials") {
|
|
2676
|
+
if (JSON.stringify(existing.fields) !== JSON.stringify(next.fields)) {
|
|
2677
|
+
throw new Error(
|
|
2678
|
+
`ExuluTool "${toolId}": user_credentials.fields disagrees with another tool that shares provider "${providerKey}". Every tool on the same provider must declare structurally identical fields (same names, types, and order).`
|
|
2679
|
+
);
|
|
2680
|
+
}
|
|
2681
|
+
return;
|
|
2607
2682
|
}
|
|
2608
2683
|
};
|
|
2609
|
-
|
|
2684
|
+
authRegistry = {
|
|
2610
2685
|
register: (toolId, config) => {
|
|
2611
2686
|
const providerKey = providerKeyFor(toolId, config);
|
|
2612
2687
|
const existing = byProvider.get(providerKey);
|
|
@@ -2628,72 +2703,86 @@ var init_registry = __esm({
|
|
|
2628
2703
|
}
|
|
2629
2704
|
});
|
|
2630
2705
|
|
|
2631
|
-
// src/exulu/
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2706
|
+
// src/exulu/auth/credential-store.ts
|
|
2707
|
+
async function get(provider, userId) {
|
|
2708
|
+
const { db: db2 } = await postgresClient();
|
|
2709
|
+
const row = await db2.from(TABLE).where({ provider, user_id: String(userId) }).first();
|
|
2710
|
+
if (!row) {
|
|
2711
|
+
return null;
|
|
2712
|
+
}
|
|
2713
|
+
return {
|
|
2714
|
+
provider,
|
|
2715
|
+
userId,
|
|
2716
|
+
authType: row.auth_type,
|
|
2717
|
+
data: JSON.parse(decrypt(row.data))
|
|
2718
|
+
};
|
|
2719
|
+
}
|
|
2720
|
+
async function upsert(record) {
|
|
2721
|
+
const { db: db2 } = await postgresClient();
|
|
2722
|
+
const encrypted = encrypt(JSON.stringify(record.data));
|
|
2723
|
+
await db2.from(TABLE).insert({
|
|
2724
|
+
provider: record.provider,
|
|
2725
|
+
user_id: String(record.userId),
|
|
2726
|
+
auth_type: record.authType,
|
|
2727
|
+
data: encrypted,
|
|
2728
|
+
updated_at: /* @__PURE__ */ new Date()
|
|
2729
|
+
}).onConflict(["provider", "user_id"]).merge({ auth_type: record.authType, data: encrypted, updated_at: /* @__PURE__ */ new Date() });
|
|
2730
|
+
}
|
|
2731
|
+
async function listByUser(userId) {
|
|
2732
|
+
const { db: db2 } = await postgresClient();
|
|
2733
|
+
const list = await db2.from(TABLE).where({ user_id: String(userId) }).orderBy("provider");
|
|
2734
|
+
return list.map((row) => ({
|
|
2735
|
+
provider: row.provider,
|
|
2736
|
+
authType: row.auth_type,
|
|
2737
|
+
createdAt: row.created_at,
|
|
2738
|
+
updatedAt: row.updated_at
|
|
2739
|
+
}));
|
|
2740
|
+
}
|
|
2741
|
+
async function del(provider, userId) {
|
|
2742
|
+
const { db: db2 } = await postgresClient();
|
|
2743
|
+
await db2.from(TABLE).where({ provider, user_id: String(userId) }).del();
|
|
2744
|
+
}
|
|
2745
|
+
var import_crypto_js2, TABLE, encrypt, decrypt, credentialStore;
|
|
2746
|
+
var init_credential_store = __esm({
|
|
2747
|
+
"src/exulu/auth/credential-store.ts"() {
|
|
2635
2748
|
"use strict";
|
|
2636
2749
|
init_cjs_shims();
|
|
2637
2750
|
import_crypto_js2 = __toESM(require("crypto-js"), 1);
|
|
2638
2751
|
init_client();
|
|
2639
|
-
TABLE = "
|
|
2752
|
+
TABLE = "user_credentials";
|
|
2640
2753
|
encrypt = (value) => import_crypto_js2.default.AES.encrypt(value, process.env.NEXTAUTH_SECRET).toString();
|
|
2641
2754
|
decrypt = (value) => import_crypto_js2.default.AES.decrypt(value, process.env.NEXTAUTH_SECRET).toString(import_crypto_js2.default.enc.Utf8);
|
|
2642
|
-
|
|
2643
|
-
get: async (providerKey, userId) => {
|
|
2644
|
-
const { db: db2 } = await postgresClient();
|
|
2645
|
-
const row = await db2.from(TABLE).where({ provider: providerKey, user_id: userId }).first();
|
|
2646
|
-
if (!row) {
|
|
2647
|
-
return null;
|
|
2648
|
-
}
|
|
2649
|
-
return {
|
|
2650
|
-
accessToken: decrypt(row.access_token),
|
|
2651
|
-
refreshToken: row.refresh_token ? decrypt(row.refresh_token) : null,
|
|
2652
|
-
tokenType: row.token_type ?? null,
|
|
2653
|
-
scopes: row.scopes ?? null,
|
|
2654
|
-
expiresAt: row.expires_at ? new Date(row.expires_at) : null
|
|
2655
|
-
};
|
|
2656
|
-
},
|
|
2657
|
-
// toolId is stored on every written row as an audit trail — which tool
|
|
2658
|
-
// triggered the last grant/refresh — but does NOT participate in the key.
|
|
2659
|
-
upsert: async (providerKey, userId, toolId, record) => {
|
|
2660
|
-
const { db: db2 } = await postgresClient();
|
|
2661
|
-
const existing = await db2.from(TABLE).where({ provider: providerKey, user_id: userId }).first();
|
|
2662
|
-
const values = {
|
|
2663
|
-
provider: providerKey,
|
|
2664
|
-
tool_id: toolId,
|
|
2665
|
-
access_token: encrypt(record.accessToken),
|
|
2666
|
-
// Providers like Google only send a refresh_token on first consent;
|
|
2667
|
-
// never overwrite a stored one with nothing.
|
|
2668
|
-
refresh_token: record.refreshToken ? encrypt(record.refreshToken) : existing?.refresh_token ?? null,
|
|
2669
|
-
token_type: record.tokenType ?? null,
|
|
2670
|
-
scopes: record.scopes ?? null,
|
|
2671
|
-
expires_at: record.expiresAt ?? null,
|
|
2672
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
2673
|
-
};
|
|
2674
|
-
if (existing) {
|
|
2675
|
-
await db2.from(TABLE).where({ provider: providerKey, user_id: userId }).update(values);
|
|
2676
|
-
} else {
|
|
2677
|
-
await db2.from(TABLE).insert({ user_id: userId, ...values });
|
|
2678
|
-
}
|
|
2679
|
-
},
|
|
2680
|
-
delete: async (providerKey, userId) => {
|
|
2681
|
-
const { db: db2 } = await postgresClient();
|
|
2682
|
-
await db2.from(TABLE).where({ provider: providerKey, user_id: userId }).del();
|
|
2683
|
-
}
|
|
2684
|
-
};
|
|
2755
|
+
credentialStore = { get, upsert, listByUser, delete: del };
|
|
2685
2756
|
}
|
|
2686
2757
|
});
|
|
2687
2758
|
|
|
2688
|
-
// src/exulu/
|
|
2759
|
+
// src/exulu/auth/flow.ts
|
|
2760
|
+
function oauthRecordToBlob(r) {
|
|
2761
|
+
return {
|
|
2762
|
+
accessToken: r.accessToken,
|
|
2763
|
+
refreshToken: r.refreshToken ?? null,
|
|
2764
|
+
tokenType: r.tokenType ?? null,
|
|
2765
|
+
scopes: r.scopes ?? null,
|
|
2766
|
+
expiresAt: r.expiresAt ? r.expiresAt.toISOString() : null
|
|
2767
|
+
};
|
|
2768
|
+
}
|
|
2769
|
+
function oauthBlobToRecord(b) {
|
|
2770
|
+
return {
|
|
2771
|
+
accessToken: b.accessToken,
|
|
2772
|
+
refreshToken: b.refreshToken,
|
|
2773
|
+
tokenType: b.tokenType,
|
|
2774
|
+
scopes: b.scopes,
|
|
2775
|
+
expiresAt: b.expiresAt ? new Date(b.expiresAt) : null
|
|
2776
|
+
};
|
|
2777
|
+
}
|
|
2689
2778
|
var import_crypto_js3, import_node_crypto2, OAUTH_CALLBACK_PATH, STATE_TTL_MS, EXPIRY_SKEW_MS, getOauthRedirectUri, toBase64Url, fromBase64Url, encryptOauthState, decryptOauthState, buildAuthorizationUrl, tokenResponseToRecord, postTokenEndpoint, exchangeCodeForTokens, refreshAccessToken, getValidAccessToken;
|
|
2690
2779
|
var init_flow = __esm({
|
|
2691
|
-
"src/exulu/
|
|
2780
|
+
"src/exulu/auth/flow.ts"() {
|
|
2692
2781
|
"use strict";
|
|
2693
2782
|
init_cjs_shims();
|
|
2694
2783
|
import_crypto_js3 = __toESM(require("crypto-js"), 1);
|
|
2695
2784
|
import_node_crypto2 = require("crypto");
|
|
2696
|
-
|
|
2785
|
+
init_credential_store();
|
|
2697
2786
|
init_provider_key();
|
|
2698
2787
|
OAUTH_CALLBACK_PATH = "/oauth/callback";
|
|
2699
2788
|
STATE_TTL_MS = 10 * 60 * 1e3;
|
|
@@ -2837,7 +2926,8 @@ var init_flow = __esm({
|
|
|
2837
2926
|
toolId,
|
|
2838
2927
|
config
|
|
2839
2928
|
}) => {
|
|
2840
|
-
const
|
|
2929
|
+
const credRow = await credentialStore.get(providerKey, userId);
|
|
2930
|
+
const stored = credRow ? oauthBlobToRecord(credRow.data) : null;
|
|
2841
2931
|
if (!stored) {
|
|
2842
2932
|
return null;
|
|
2843
2933
|
}
|
|
@@ -2846,7 +2936,7 @@ var init_flow = __esm({
|
|
|
2846
2936
|
return stored;
|
|
2847
2937
|
}
|
|
2848
2938
|
if (!stored.refreshToken) {
|
|
2849
|
-
await
|
|
2939
|
+
await credentialStore.delete(providerKey, userId);
|
|
2850
2940
|
return null;
|
|
2851
2941
|
}
|
|
2852
2942
|
try {
|
|
@@ -2854,29 +2944,169 @@ var init_flow = __esm({
|
|
|
2854
2944
|
if (!refreshed.refreshToken) {
|
|
2855
2945
|
refreshed.refreshToken = stored.refreshToken;
|
|
2856
2946
|
}
|
|
2857
|
-
await
|
|
2947
|
+
await credentialStore.upsert({
|
|
2948
|
+
provider: providerKey,
|
|
2949
|
+
userId,
|
|
2950
|
+
authType: "oauth",
|
|
2951
|
+
data: oauthRecordToBlob(refreshed)
|
|
2952
|
+
});
|
|
2858
2953
|
return refreshed;
|
|
2859
2954
|
} catch (error) {
|
|
2860
2955
|
console.error(
|
|
2861
2956
|
`[EXULU] OAuth token refresh failed for provider "${providerKey}" tool "${toolId}" user ${userId}:`,
|
|
2862
2957
|
error
|
|
2863
2958
|
);
|
|
2864
|
-
await
|
|
2959
|
+
await credentialStore.delete(providerKey, userId);
|
|
2865
2960
|
return null;
|
|
2866
2961
|
}
|
|
2867
2962
|
};
|
|
2868
2963
|
}
|
|
2869
2964
|
});
|
|
2870
2965
|
|
|
2871
|
-
// src/exulu/
|
|
2872
|
-
|
|
2966
|
+
// src/exulu/auth/state.ts
|
|
2967
|
+
async function getValidUserCredentials(cfg, userId) {
|
|
2968
|
+
const row = await credentialStore.get(cfg.provider, userId);
|
|
2969
|
+
if (!row || row.authType !== "user_credentials") return null;
|
|
2970
|
+
const values = {};
|
|
2971
|
+
for (const [k, v] of Object.entries(row.data)) {
|
|
2972
|
+
if (typeof v !== "string") return null;
|
|
2973
|
+
values[k] = v;
|
|
2974
|
+
}
|
|
2975
|
+
return values;
|
|
2976
|
+
}
|
|
2977
|
+
var init_state = __esm({
|
|
2978
|
+
"src/exulu/auth/state.ts"() {
|
|
2979
|
+
"use strict";
|
|
2980
|
+
init_cjs_shims();
|
|
2981
|
+
init_credential_store();
|
|
2982
|
+
}
|
|
2983
|
+
});
|
|
2984
|
+
|
|
2985
|
+
// src/exulu/auth/credentials-request.ts
|
|
2986
|
+
function buildCredentialRequest(cfg, opts) {
|
|
2987
|
+
const ttl = opts.ttlSeconds ?? DEFAULT_TTL_SECONDS;
|
|
2988
|
+
const claims = {
|
|
2989
|
+
provider: cfg.provider,
|
|
2990
|
+
userId: opts.userId,
|
|
2991
|
+
expiresAt: Math.floor(Date.now() / 1e3) + ttl
|
|
2992
|
+
};
|
|
2993
|
+
const nonce = encrypt(JSON.stringify(claims));
|
|
2994
|
+
return {
|
|
2995
|
+
provider: cfg.provider,
|
|
2996
|
+
fields: cfg.fields,
|
|
2997
|
+
submitUrl: `${opts.baseUrl.replace(/\/+$/, "")}/credentials/submit`,
|
|
2998
|
+
nonce
|
|
2999
|
+
};
|
|
3000
|
+
}
|
|
3001
|
+
function verifyCredentialNonce(nonce) {
|
|
3002
|
+
let claims;
|
|
3003
|
+
try {
|
|
3004
|
+
claims = JSON.parse(decrypt(nonce));
|
|
3005
|
+
} catch {
|
|
3006
|
+
throw new Error("Invalid credential nonce");
|
|
3007
|
+
}
|
|
3008
|
+
if (typeof claims.provider !== "string" || typeof claims.userId !== "string" || typeof claims.expiresAt !== "number") {
|
|
3009
|
+
throw new Error("Malformed credential nonce claims");
|
|
3010
|
+
}
|
|
3011
|
+
if (claims.expiresAt < Math.floor(Date.now() / 1e3)) {
|
|
3012
|
+
throw new Error("Credential nonce expired");
|
|
3013
|
+
}
|
|
3014
|
+
return claims;
|
|
3015
|
+
}
|
|
3016
|
+
var DEFAULT_TTL_SECONDS;
|
|
3017
|
+
var init_credentials_request = __esm({
|
|
3018
|
+
"src/exulu/auth/credentials-request.ts"() {
|
|
3019
|
+
"use strict";
|
|
3020
|
+
init_cjs_shims();
|
|
3021
|
+
init_credential_store();
|
|
3022
|
+
DEFAULT_TTL_SECONDS = 15 * 60;
|
|
3023
|
+
}
|
|
3024
|
+
});
|
|
3025
|
+
|
|
3026
|
+
// src/exulu/auth/short-circuit.ts
|
|
3027
|
+
function credentialRequestResult(request3) {
|
|
3028
|
+
return { credentialRequest: request3, result: null };
|
|
3029
|
+
}
|
|
3030
|
+
var init_short_circuit = __esm({
|
|
3031
|
+
"src/exulu/auth/short-circuit.ts"() {
|
|
3032
|
+
"use strict";
|
|
3033
|
+
init_cjs_shims();
|
|
3034
|
+
}
|
|
3035
|
+
});
|
|
3036
|
+
|
|
3037
|
+
// src/exulu/auth/errors.ts
|
|
3038
|
+
var CredentialInvalidError;
|
|
3039
|
+
var init_errors = __esm({
|
|
3040
|
+
"src/exulu/auth/errors.ts"() {
|
|
3041
|
+
"use strict";
|
|
3042
|
+
init_cjs_shims();
|
|
3043
|
+
CredentialInvalidError = class extends Error {
|
|
3044
|
+
provider;
|
|
3045
|
+
reason;
|
|
3046
|
+
constructor(provider, reason) {
|
|
3047
|
+
super(reason ? `Credential invalid for provider '${provider}': ${reason}` : `Credential invalid for provider '${provider}'`);
|
|
3048
|
+
this.name = "CredentialInvalidError";
|
|
3049
|
+
this.provider = provider;
|
|
3050
|
+
this.reason = reason;
|
|
3051
|
+
}
|
|
3052
|
+
};
|
|
3053
|
+
}
|
|
3054
|
+
});
|
|
3055
|
+
|
|
3056
|
+
// src/exulu/auth/wrap-execute.ts
|
|
3057
|
+
var wrapExecuteWithAuth, wrapUserCredentials, wrapExecuteWithOauthInternal;
|
|
2873
3058
|
var init_wrap_execute = __esm({
|
|
2874
|
-
"src/exulu/
|
|
3059
|
+
"src/exulu/auth/wrap-execute.ts"() {
|
|
2875
3060
|
"use strict";
|
|
2876
3061
|
init_cjs_shims();
|
|
2877
3062
|
init_flow();
|
|
2878
3063
|
init_provider_key();
|
|
2879
|
-
|
|
3064
|
+
init_state();
|
|
3065
|
+
init_credentials_request();
|
|
3066
|
+
init_short_circuit();
|
|
3067
|
+
init_credential_store();
|
|
3068
|
+
init_errors();
|
|
3069
|
+
wrapExecuteWithAuth = (toolId, config, execute2) => {
|
|
3070
|
+
if (config.authType === "oauth") {
|
|
3071
|
+
return wrapExecuteWithOauthInternal(toolId, config, execute2);
|
|
3072
|
+
}
|
|
3073
|
+
if (config.authType === "user_credentials") {
|
|
3074
|
+
return wrapUserCredentials(toolId, config, execute2);
|
|
3075
|
+
}
|
|
3076
|
+
throw new Error(`ExuluTool "${toolId}": unknown authType`);
|
|
3077
|
+
};
|
|
3078
|
+
wrapUserCredentials = (toolId, config, execute2) => {
|
|
3079
|
+
return async (inputs, options) => {
|
|
3080
|
+
const userId = inputs?.user?.id;
|
|
3081
|
+
if (!userId) {
|
|
3082
|
+
return {
|
|
3083
|
+
result: `The "${toolId}" tool requires user-supplied credentials, which needs a signed-in user. No user identity is available for this run.`
|
|
3084
|
+
};
|
|
3085
|
+
}
|
|
3086
|
+
const baseUrl = (process.env.BACKEND ?? "").replace(/\/+$/, "");
|
|
3087
|
+
if (!baseUrl) {
|
|
3088
|
+
return {
|
|
3089
|
+
result: `The "${toolId}" tool requires the BACKEND env var to build the credential submit URL.`
|
|
3090
|
+
};
|
|
3091
|
+
}
|
|
3092
|
+
const values = await getValidUserCredentials(config, userId);
|
|
3093
|
+
if (!values) {
|
|
3094
|
+
const request3 = buildCredentialRequest(config, { baseUrl, userId: String(userId) });
|
|
3095
|
+
return credentialRequestResult(request3);
|
|
3096
|
+
}
|
|
3097
|
+
try {
|
|
3098
|
+
return await execute2({ ...inputs, credentials: values }, options);
|
|
3099
|
+
} catch (e) {
|
|
3100
|
+
if (e instanceof CredentialInvalidError && e.provider === config.provider) {
|
|
3101
|
+
await credentialStore.delete(config.provider, userId);
|
|
3102
|
+
const request3 = buildCredentialRequest(config, { baseUrl, userId: String(userId) });
|
|
3103
|
+
return credentialRequestResult(request3);
|
|
3104
|
+
}
|
|
3105
|
+
throw e;
|
|
3106
|
+
}
|
|
3107
|
+
};
|
|
3108
|
+
};
|
|
3109
|
+
wrapExecuteWithOauthInternal = (toolId, config, execute2) => {
|
|
2880
3110
|
return async (inputs, options) => {
|
|
2881
3111
|
const userId = inputs?.user?.id;
|
|
2882
3112
|
if (!userId) {
|
|
@@ -3233,6 +3463,409 @@ var init_memory_tool = __esm({
|
|
|
3233
3463
|
}
|
|
3234
3464
|
});
|
|
3235
3465
|
|
|
3466
|
+
// src/utils/check-item-write-access.ts
|
|
3467
|
+
var checkItemWriteAccess;
|
|
3468
|
+
var init_check_item_write_access = __esm({
|
|
3469
|
+
"src/utils/check-item-write-access.ts"() {
|
|
3470
|
+
"use strict";
|
|
3471
|
+
init_cjs_shims();
|
|
3472
|
+
init_client();
|
|
3473
|
+
init_table_names();
|
|
3474
|
+
checkItemWriteAccess = async (context, record, user) => {
|
|
3475
|
+
if (!user) {
|
|
3476
|
+
return false;
|
|
3477
|
+
}
|
|
3478
|
+
if (user.super_admin === true) {
|
|
3479
|
+
return true;
|
|
3480
|
+
}
|
|
3481
|
+
if (user.type === "api" && (!user.scope_mode || user.scope_mode === "admin")) {
|
|
3482
|
+
return true;
|
|
3483
|
+
}
|
|
3484
|
+
if (record.rights_mode === "public") {
|
|
3485
|
+
return true;
|
|
3486
|
+
}
|
|
3487
|
+
if (record.rights_mode === "private") {
|
|
3488
|
+
return record.created_by != null && String(record.created_by) === String(user.id);
|
|
3489
|
+
}
|
|
3490
|
+
const validRightsModes = ["users", "roles", "teams"];
|
|
3491
|
+
if (!validRightsModes.includes(record.rights_mode)) {
|
|
3492
|
+
return false;
|
|
3493
|
+
}
|
|
3494
|
+
const entity = getTableName(context.id);
|
|
3495
|
+
const { db: db2 } = await postgresClient();
|
|
3496
|
+
if (record.rights_mode === "users") {
|
|
3497
|
+
const grant = await db2.from("rbac").where({
|
|
3498
|
+
entity,
|
|
3499
|
+
target_resource_id: record.id,
|
|
3500
|
+
access_type: "User",
|
|
3501
|
+
user_id: user.id,
|
|
3502
|
+
rights: "write"
|
|
3503
|
+
}).first();
|
|
3504
|
+
return !!grant;
|
|
3505
|
+
}
|
|
3506
|
+
if (record.rights_mode === "roles") {
|
|
3507
|
+
const roleId = typeof user.role === "string" ? user.role : user.role?.id;
|
|
3508
|
+
if (!roleId) {
|
|
3509
|
+
return false;
|
|
3510
|
+
}
|
|
3511
|
+
const grant = await db2.from("rbac").where({
|
|
3512
|
+
entity,
|
|
3513
|
+
target_resource_id: record.id,
|
|
3514
|
+
access_type: "Role",
|
|
3515
|
+
role_id: roleId,
|
|
3516
|
+
rights: "write"
|
|
3517
|
+
}).first();
|
|
3518
|
+
return !!grant;
|
|
3519
|
+
}
|
|
3520
|
+
if (record.rights_mode === "teams") {
|
|
3521
|
+
const teamId = typeof user.team === "string" ? user.team : user.team?.id;
|
|
3522
|
+
if (!teamId) {
|
|
3523
|
+
return false;
|
|
3524
|
+
}
|
|
3525
|
+
const grant = await db2.from("rbac").where({
|
|
3526
|
+
entity,
|
|
3527
|
+
target_resource_id: record.id,
|
|
3528
|
+
access_type: "Team",
|
|
3529
|
+
team_id: teamId,
|
|
3530
|
+
rights: "write"
|
|
3531
|
+
}).first();
|
|
3532
|
+
return !!grant;
|
|
3533
|
+
}
|
|
3534
|
+
return false;
|
|
3535
|
+
};
|
|
3536
|
+
}
|
|
3537
|
+
});
|
|
3538
|
+
|
|
3539
|
+
// src/templates/tools/kb-editor-config.ts
|
|
3540
|
+
var import_zod4, KB_EDITOR_TOOL_ID, permissionsSchema, emptyConfig, parseKbEditorConfig;
|
|
3541
|
+
var init_kb_editor_config = __esm({
|
|
3542
|
+
"src/templates/tools/kb-editor-config.ts"() {
|
|
3543
|
+
"use strict";
|
|
3544
|
+
init_cjs_shims();
|
|
3545
|
+
import_zod4 = require("zod");
|
|
3546
|
+
KB_EDITOR_TOOL_ID = "knowledge_base_editor";
|
|
3547
|
+
permissionsSchema = import_zod4.z.object({
|
|
3548
|
+
create: import_zod4.z.boolean().catch(false).default(false),
|
|
3549
|
+
update: import_zod4.z.boolean().catch(false).default(false)
|
|
3550
|
+
});
|
|
3551
|
+
emptyConfig = () => ({
|
|
3552
|
+
enabled: false,
|
|
3553
|
+
knowledgeBases: {},
|
|
3554
|
+
skipApproval: false
|
|
3555
|
+
});
|
|
3556
|
+
parseKbEditorConfig = (tools) => {
|
|
3557
|
+
let entries = tools;
|
|
3558
|
+
if (typeof entries === "string") {
|
|
3559
|
+
try {
|
|
3560
|
+
entries = JSON.parse(entries);
|
|
3561
|
+
} catch {
|
|
3562
|
+
return emptyConfig();
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
if (!Array.isArray(entries)) {
|
|
3566
|
+
return emptyConfig();
|
|
3567
|
+
}
|
|
3568
|
+
const entry = entries.find((t) => t?.id === KB_EDITOR_TOOL_ID);
|
|
3569
|
+
if (!entry) {
|
|
3570
|
+
return emptyConfig();
|
|
3571
|
+
}
|
|
3572
|
+
const rawValue = (name) => {
|
|
3573
|
+
const row = Array.isArray(entry.config) ? entry.config.find((c) => c?.name === name) : void 0;
|
|
3574
|
+
return row?.value ?? row?.variable ?? row?.default;
|
|
3575
|
+
};
|
|
3576
|
+
let kbsRaw = rawValue("knowledge_bases");
|
|
3577
|
+
if (typeof kbsRaw === "string" && kbsRaw) {
|
|
3578
|
+
try {
|
|
3579
|
+
kbsRaw = JSON.parse(kbsRaw);
|
|
3580
|
+
} catch {
|
|
3581
|
+
kbsRaw = {};
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
const knowledgeBases = {};
|
|
3585
|
+
if (kbsRaw && typeof kbsRaw === "object" && !Array.isArray(kbsRaw)) {
|
|
3586
|
+
for (const [contextId, value] of Object.entries(kbsRaw)) {
|
|
3587
|
+
const parsed = permissionsSchema.safeParse(value);
|
|
3588
|
+
if (parsed.success && (parsed.data.create || parsed.data.update)) {
|
|
3589
|
+
knowledgeBases[contextId] = parsed.data;
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
const skipRaw = rawValue("skip_approval");
|
|
3594
|
+
const skipApproval = skipRaw === true || skipRaw === "true" || skipRaw === 1;
|
|
3595
|
+
return { enabled: true, knowledgeBases, skipApproval };
|
|
3596
|
+
};
|
|
3597
|
+
}
|
|
3598
|
+
});
|
|
3599
|
+
|
|
3600
|
+
// src/templates/tools/context-write-tools.ts
|
|
3601
|
+
var import_zod5, MAX_CONTEXT_SEGMENT, RESERVED_INPUT_KEYS, buildWriteSchema, canonicalizeEnumFields, pickContent, jobNote, createContextWriteTools, createKbEditorPickerTool, collectKbWriteTools;
|
|
3602
|
+
var init_context_write_tools = __esm({
|
|
3603
|
+
"src/templates/tools/context-write-tools.ts"() {
|
|
3604
|
+
"use strict";
|
|
3605
|
+
init_cjs_shims();
|
|
3606
|
+
init_tool();
|
|
3607
|
+
import_zod5 = require("zod");
|
|
3608
|
+
init_sanitize_name();
|
|
3609
|
+
init_check_item_write_access();
|
|
3610
|
+
init_kb_editor_config();
|
|
3611
|
+
MAX_CONTEXT_SEGMENT = 68;
|
|
3612
|
+
RESERVED_INPUT_KEYS = /* @__PURE__ */ new Set([
|
|
3613
|
+
"model",
|
|
3614
|
+
"user",
|
|
3615
|
+
"contexts",
|
|
3616
|
+
"memory",
|
|
3617
|
+
"req",
|
|
3618
|
+
"upload",
|
|
3619
|
+
"sessionID",
|
|
3620
|
+
"sessionItems",
|
|
3621
|
+
"providerapikey",
|
|
3622
|
+
"allExuluTools",
|
|
3623
|
+
"currentTools",
|
|
3624
|
+
"exuluConfig",
|
|
3625
|
+
"toolVariablesConfig",
|
|
3626
|
+
"oauth"
|
|
3627
|
+
]);
|
|
3628
|
+
buildWriteSchema = (context, mode) => {
|
|
3629
|
+
const shape = {};
|
|
3630
|
+
const contentKeys = [];
|
|
3631
|
+
const addContent = (key, schema, required) => {
|
|
3632
|
+
shape[key] = required && mode === "create" ? schema : schema.optional();
|
|
3633
|
+
contentKeys.push(key);
|
|
3634
|
+
};
|
|
3635
|
+
if (mode === "update") {
|
|
3636
|
+
shape["id"] = import_zod5.z.string().optional().describe("The id of the item to update.");
|
|
3637
|
+
shape["external_id"] = import_zod5.z.string().optional().describe("The external_id of the item to update, if the id is unknown. Lookup only \u2014 it is never changed.");
|
|
3638
|
+
}
|
|
3639
|
+
addContent("name", import_zod5.z.string().describe("The name of the item."), true);
|
|
3640
|
+
addContent("description", import_zod5.z.string().describe("A description of the item."), false);
|
|
3641
|
+
addContent("tags", import_zod5.z.array(import_zod5.z.string()).describe("Tags for the item."), false);
|
|
3642
|
+
if (mode === "create") {
|
|
3643
|
+
addContent(
|
|
3644
|
+
"external_id",
|
|
3645
|
+
import_zod5.z.string().describe("An optional external identifier for the item, e.g. an id from a source system."),
|
|
3646
|
+
false
|
|
3647
|
+
);
|
|
3648
|
+
}
|
|
3649
|
+
for (const field of context.fields ?? []) {
|
|
3650
|
+
if (field.type === "file" || field.type === "uuid") continue;
|
|
3651
|
+
if (field.calculated === true || field.editable === false) continue;
|
|
3652
|
+
if (field.hidden === true) continue;
|
|
3653
|
+
if (RESERVED_INPUT_KEYS.has(field.name)) continue;
|
|
3654
|
+
let schema;
|
|
3655
|
+
switch (field.type) {
|
|
3656
|
+
case "enum":
|
|
3657
|
+
schema = import_zod5.z.string().describe(
|
|
3658
|
+
`The ${field.name} of the item. Must be one of: ${(field.enumValues ?? []).join(", ")}`
|
|
3659
|
+
);
|
|
3660
|
+
break;
|
|
3661
|
+
case "json":
|
|
3662
|
+
schema = import_zod5.z.string().describe(`The ${field.name} of the item, as a valid JSON string.`);
|
|
3663
|
+
break;
|
|
3664
|
+
case "markdown":
|
|
3665
|
+
schema = import_zod5.z.string().describe(`The ${field.name} of the item, as a valid Markdown string.`);
|
|
3666
|
+
break;
|
|
3667
|
+
case "date":
|
|
3668
|
+
schema = import_zod5.z.string().describe(`The ${field.name} of the item, as an ISO-8601 date string.`);
|
|
3669
|
+
break;
|
|
3670
|
+
case "number":
|
|
3671
|
+
schema = import_zod5.z.number().describe(`The ${field.name} of the item.`);
|
|
3672
|
+
break;
|
|
3673
|
+
case "boolean":
|
|
3674
|
+
schema = import_zod5.z.boolean().describe(`The ${field.name} of the item.`);
|
|
3675
|
+
break;
|
|
3676
|
+
default:
|
|
3677
|
+
schema = import_zod5.z.string().describe(`The ${field.name} of the item.`);
|
|
3678
|
+
break;
|
|
3679
|
+
}
|
|
3680
|
+
addContent(field.name, schema, field.required === true);
|
|
3681
|
+
}
|
|
3682
|
+
return { shape, contentKeys };
|
|
3683
|
+
};
|
|
3684
|
+
canonicalizeEnumFields = (context, params) => {
|
|
3685
|
+
for (const field of context.fields ?? []) {
|
|
3686
|
+
if (field.type !== "enum" || !field.enumValues?.length) continue;
|
|
3687
|
+
const raw = params[field.name];
|
|
3688
|
+
if (raw === void 0 || raw === null || raw === "") continue;
|
|
3689
|
+
const rawStr = String(raw);
|
|
3690
|
+
const canonical = field.enumValues.find((v) => v.toUpperCase() === rawStr.toUpperCase());
|
|
3691
|
+
if (canonical === void 0) {
|
|
3692
|
+
return `Invalid value "${rawStr}" for field "${field.name}". Allowed values: ${field.enumValues.join(", ")}.`;
|
|
3693
|
+
}
|
|
3694
|
+
params[field.name] = canonical;
|
|
3695
|
+
}
|
|
3696
|
+
return void 0;
|
|
3697
|
+
};
|
|
3698
|
+
pickContent = (params, contentKeys) => {
|
|
3699
|
+
const item = {};
|
|
3700
|
+
for (const key of contentKeys) {
|
|
3701
|
+
if (params[key] !== void 0) {
|
|
3702
|
+
item[key] = params[key];
|
|
3703
|
+
}
|
|
3704
|
+
}
|
|
3705
|
+
return item;
|
|
3706
|
+
};
|
|
3707
|
+
jobNote = (job) => job ? ` Processing/embeddings queued (job: ${job}); changes become searchable when the job completes.` : "";
|
|
3708
|
+
createContextWriteTools = (context, perms, skipApproval) => {
|
|
3709
|
+
const tools = [];
|
|
3710
|
+
const segment = sanitizeName(context.id).slice(0, MAX_CONTEXT_SEGMENT);
|
|
3711
|
+
const contextLabel = context.description ? ` ${context.description}` : "";
|
|
3712
|
+
if (perms.create) {
|
|
3713
|
+
const { shape, contentKeys } = buildWriteSchema(context, "create");
|
|
3714
|
+
tools.push(
|
|
3715
|
+
new ExuluTool({
|
|
3716
|
+
id: `create_${segment}_item`,
|
|
3717
|
+
name: `Create ${context.name} item`,
|
|
3718
|
+
category: "knowledge_base_editing",
|
|
3719
|
+
description: `Create a new item in the "${context.name}" knowledge base.${contextLabel}`,
|
|
3720
|
+
type: "function",
|
|
3721
|
+
inputSchema: import_zod5.z.object(shape),
|
|
3722
|
+
config: [],
|
|
3723
|
+
needsApproval: !skipApproval,
|
|
3724
|
+
execute: async (params) => {
|
|
3725
|
+
const { user, exuluConfig } = params;
|
|
3726
|
+
if (!user?.id) {
|
|
3727
|
+
return { result: "Knowledge base writes require an authenticated user." };
|
|
3728
|
+
}
|
|
3729
|
+
try {
|
|
3730
|
+
const enumError = canonicalizeEnumFields(context, params);
|
|
3731
|
+
if (enumError) {
|
|
3732
|
+
return { result: enumError };
|
|
3733
|
+
}
|
|
3734
|
+
const item = pickContent(params, contentKeys);
|
|
3735
|
+
item.created_by = String(user.id);
|
|
3736
|
+
const { item: created, job } = await context.createItem(
|
|
3737
|
+
item,
|
|
3738
|
+
exuluConfig,
|
|
3739
|
+
user?.id,
|
|
3740
|
+
user?.role?.id,
|
|
3741
|
+
false
|
|
3742
|
+
);
|
|
3743
|
+
if (!created?.id) {
|
|
3744
|
+
return { result: `Failed to create item in "${context.name}".` };
|
|
3745
|
+
}
|
|
3746
|
+
return {
|
|
3747
|
+
result: `Created item ${created.id} in knowledge base "${context.name}".${jobNote(job)}`
|
|
3748
|
+
};
|
|
3749
|
+
} catch (error) {
|
|
3750
|
+
console.error(`[EXULU] Error creating item in context ${context.id}`, error);
|
|
3751
|
+
return {
|
|
3752
|
+
result: `Failed to create item in "${context.name}": ${error instanceof Error ? error.message : String(error)}`
|
|
3753
|
+
};
|
|
3754
|
+
}
|
|
3755
|
+
}
|
|
3756
|
+
})
|
|
3757
|
+
);
|
|
3758
|
+
}
|
|
3759
|
+
if (perms.update) {
|
|
3760
|
+
const { shape, contentKeys } = buildWriteSchema(context, "update");
|
|
3761
|
+
const NOT_FOUND = `Item not found in "${context.name}" or you don't have write access to it.`;
|
|
3762
|
+
tools.push(
|
|
3763
|
+
new ExuluTool({
|
|
3764
|
+
id: `update_${segment}_item`,
|
|
3765
|
+
name: `Update ${context.name} item`,
|
|
3766
|
+
category: "knowledge_base_editing",
|
|
3767
|
+
description: `Update an existing item in the "${context.name}" knowledge base. Provide the item's id (or external_id) plus only the fields to change; omitted fields keep their values.`,
|
|
3768
|
+
type: "function",
|
|
3769
|
+
inputSchema: import_zod5.z.object(shape),
|
|
3770
|
+
config: [],
|
|
3771
|
+
needsApproval: !skipApproval,
|
|
3772
|
+
execute: async (params) => {
|
|
3773
|
+
const { user, exuluConfig } = params;
|
|
3774
|
+
if (!user?.id) {
|
|
3775
|
+
return { result: "Knowledge base writes require an authenticated user." };
|
|
3776
|
+
}
|
|
3777
|
+
try {
|
|
3778
|
+
if (!params.id && !params.external_id) {
|
|
3779
|
+
return { result: "Provide the id or external_id of the item to update." };
|
|
3780
|
+
}
|
|
3781
|
+
const existing = await context.getItem({
|
|
3782
|
+
item: { id: params.id, external_id: params.external_id }
|
|
3783
|
+
});
|
|
3784
|
+
if (!existing?.id) {
|
|
3785
|
+
return { result: NOT_FOUND };
|
|
3786
|
+
}
|
|
3787
|
+
const allowed = await checkItemWriteAccess(context, existing, user);
|
|
3788
|
+
if (!allowed) {
|
|
3789
|
+
return { result: NOT_FOUND };
|
|
3790
|
+
}
|
|
3791
|
+
const enumError = canonicalizeEnumFields(context, params);
|
|
3792
|
+
if (enumError) {
|
|
3793
|
+
return { result: enumError };
|
|
3794
|
+
}
|
|
3795
|
+
const patch = pickContent(params, contentKeys);
|
|
3796
|
+
if (Object.keys(patch).length === 0) {
|
|
3797
|
+
return { result: "No fields to update were provided." };
|
|
3798
|
+
}
|
|
3799
|
+
patch.id = existing.id;
|
|
3800
|
+
const { job } = await context.updateItem(patch, exuluConfig, user?.id, user?.role?.id);
|
|
3801
|
+
const fresh = await context.getItem({ item: { id: existing.id } });
|
|
3802
|
+
const summary = { id: existing.id };
|
|
3803
|
+
for (const key of contentKeys) {
|
|
3804
|
+
if (fresh?.[key] !== void 0 && fresh?.[key] !== null) {
|
|
3805
|
+
summary[key] = fresh[key];
|
|
3806
|
+
}
|
|
3807
|
+
}
|
|
3808
|
+
return {
|
|
3809
|
+
result: `Updated item ${existing.id} in knowledge base "${context.name}".${jobNote(job)}
|
|
3810
|
+
Current item: ${JSON.stringify(summary)}`
|
|
3811
|
+
};
|
|
3812
|
+
} catch (error) {
|
|
3813
|
+
console.error(`[EXULU] Error updating item in context ${context.id}`, error);
|
|
3814
|
+
return {
|
|
3815
|
+
result: `Failed to update item in "${context.name}": ${error instanceof Error ? error.message : String(error)}`
|
|
3816
|
+
};
|
|
3817
|
+
}
|
|
3818
|
+
}
|
|
3819
|
+
})
|
|
3820
|
+
);
|
|
3821
|
+
}
|
|
3822
|
+
return tools;
|
|
3823
|
+
};
|
|
3824
|
+
createKbEditorPickerTool = () => new ExuluTool({
|
|
3825
|
+
id: KB_EDITOR_TOOL_ID,
|
|
3826
|
+
name: "Knowledge base editor",
|
|
3827
|
+
category: "default",
|
|
3828
|
+
description: "Let this agent create or update items in selected knowledge bases during chat. Configure per knowledge base whether the agent may create and/or update items.",
|
|
3829
|
+
type: "function",
|
|
3830
|
+
inputSchema: import_zod5.z.object({}),
|
|
3831
|
+
config: [
|
|
3832
|
+
{
|
|
3833
|
+
name: "knowledge_bases",
|
|
3834
|
+
description: "JSON record of context id to { create: boolean, update: boolean }. Contexts absent here get no write access.",
|
|
3835
|
+
type: "json"
|
|
3836
|
+
},
|
|
3837
|
+
{
|
|
3838
|
+
name: "skip_approval",
|
|
3839
|
+
description: "Run knowledge base writes without asking for approval in the chat.",
|
|
3840
|
+
type: "boolean",
|
|
3841
|
+
default: false
|
|
3842
|
+
}
|
|
3843
|
+
],
|
|
3844
|
+
execute: async () => ({
|
|
3845
|
+
result: "This entry is configuration-only; Exulu expands it into per-context create/update tools at runtime."
|
|
3846
|
+
})
|
|
3847
|
+
});
|
|
3848
|
+
collectKbWriteTools = (agent, contexts) => {
|
|
3849
|
+
if (!agent?.tools || !contexts?.length) {
|
|
3850
|
+
return [];
|
|
3851
|
+
}
|
|
3852
|
+
const config = parseKbEditorConfig(agent.tools);
|
|
3853
|
+
if (!config.enabled) {
|
|
3854
|
+
return [];
|
|
3855
|
+
}
|
|
3856
|
+
const tools = [];
|
|
3857
|
+
for (const [contextId, perms] of Object.entries(config.knowledgeBases)) {
|
|
3858
|
+
const context = contexts.find((c) => c.id === contextId);
|
|
3859
|
+
if (!context) {
|
|
3860
|
+
continue;
|
|
3861
|
+
}
|
|
3862
|
+
tools.push(...createContextWriteTools(context, perms, config.skipApproval));
|
|
3863
|
+
}
|
|
3864
|
+
return tools;
|
|
3865
|
+
};
|
|
3866
|
+
}
|
|
3867
|
+
});
|
|
3868
|
+
|
|
3236
3869
|
// src/exulu/system-dependencies.ts
|
|
3237
3870
|
async function getNpmGlobalRoot() {
|
|
3238
3871
|
if (cachedNpmGlobalRoot !== void 0) return cachedNpmGlobalRoot;
|
|
@@ -3747,9 +4380,9 @@ Probe error: ${probe.reason ?? "(no detail)"}`
|
|
|
3747
4380
|
});
|
|
3748
4381
|
const writeFileTool = (0, import_ai2.tool)({
|
|
3749
4382
|
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:
|
|
4383
|
+
inputSchema: import_zod6.z.object({
|
|
4384
|
+
path: import_zod6.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."),
|
|
4385
|
+
content: import_zod6.z.string().describe("The content to write to the file")
|
|
3753
4386
|
}),
|
|
3754
4387
|
execute: async ({ path: path2, content }) => {
|
|
3755
4388
|
const resolvedPath = resolveSessionPath(path2, sessionDir);
|
|
@@ -3768,8 +4401,8 @@ Probe error: ${probe.reason ?? "(no detail)"}`
|
|
|
3768
4401
|
});
|
|
3769
4402
|
const readFileTool = (0, import_ai2.tool)({
|
|
3770
4403
|
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:
|
|
4404
|
+
inputSchema: import_zod6.z.object({
|
|
4405
|
+
path: import_zod6.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
4406
|
}),
|
|
3774
4407
|
execute: async ({ path: path2 }) => {
|
|
3775
4408
|
const resolvedPath = resolveSessionPath(path2, sessionDir);
|
|
@@ -3780,8 +4413,8 @@ Probe error: ${probe.reason ?? "(no detail)"}`
|
|
|
3780
4413
|
const originalBashTool = tools.bash;
|
|
3781
4414
|
const bashTool = (0, import_ai2.tool)({
|
|
3782
4415
|
description: originalBashTool.description ?? "",
|
|
3783
|
-
inputSchema:
|
|
3784
|
-
command:
|
|
4416
|
+
inputSchema: import_zod6.z.object({
|
|
4417
|
+
command: import_zod6.z.string().describe("The bash command to execute.")
|
|
3785
4418
|
}),
|
|
3786
4419
|
execute: async (args, opts) => {
|
|
3787
4420
|
const before = persistenceEnabled ? await snapshotSessionArtifacts() : null;
|
|
@@ -3851,7 +4484,7 @@ ${lines.join("\n")}`;
|
|
|
3851
4484
|
sandboxCache.set(sessionId, { handle, installedSkills });
|
|
3852
4485
|
return handle;
|
|
3853
4486
|
}
|
|
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,
|
|
4487
|
+
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_zod6, import_crypto_js4, getAllExuluVariables, execAsync2, EXEC_MAX_BUFFER, sandboxProbePromise, SANDBOX_FALLBACK_INSTRUCTIONS, degradedModeLogged, sandboxCache;
|
|
3855
4488
|
var init_create_sandbox = __esm({
|
|
3856
4489
|
"ee/invoke-skills/create-sandbox.ts"() {
|
|
3857
4490
|
"use strict";
|
|
@@ -3866,7 +4499,7 @@ var init_create_sandbox = __esm({
|
|
|
3866
4499
|
init_system_dependencies();
|
|
3867
4500
|
import_bash_tool = require("bash-tool");
|
|
3868
4501
|
import_ai2 = require("ai");
|
|
3869
|
-
|
|
4502
|
+
import_zod6 = require("zod");
|
|
3870
4503
|
init_variable();
|
|
3871
4504
|
import_crypto_js4 = __toESM(require("crypto-js"), 1);
|
|
3872
4505
|
init_client();
|
|
@@ -4101,13 +4734,47 @@ ${notice}`;
|
|
|
4101
4734
|
}
|
|
4102
4735
|
});
|
|
4103
4736
|
|
|
4737
|
+
// src/exulu/auth/scrub-text.ts
|
|
4738
|
+
var credentialScrubText, SCRUBBED_CREDENTIAL_TEXT, SCRUBBED_OAUTH_TEXT;
|
|
4739
|
+
var init_scrub_text = __esm({
|
|
4740
|
+
"src/exulu/auth/scrub-text.ts"() {
|
|
4741
|
+
"use strict";
|
|
4742
|
+
init_cjs_shims();
|
|
4743
|
+
credentialScrubText = (provider, fieldLabels) => `A secure credential form for provider "${provider}"` + (fieldLabels.length ? ` (fields: ${fieldLabels.join(", ")})` : "") + ` is shown to the user in the chat UI. Never ask for these values in chat. After the user confirms saving, call the tool again.`;
|
|
4744
|
+
SCRUBBED_CREDENTIAL_TEXT = "A secure credential form was shown to the user in the chat UI. Never ask for credential values in chat. After the user confirms saving, call the tool again.";
|
|
4745
|
+
SCRUBBED_OAUTH_TEXT = "Authorization is required. A Connect button was shown to the user in the chat UI. Do not relay any URL in chat. After the user confirms connecting, call the tool again.";
|
|
4746
|
+
}
|
|
4747
|
+
});
|
|
4748
|
+
|
|
4749
|
+
// src/templates/tools/auth-tool-model-output.ts
|
|
4750
|
+
var buildAuthToolModelOutput;
|
|
4751
|
+
var init_auth_tool_model_output = __esm({
|
|
4752
|
+
"src/templates/tools/auth-tool-model-output.ts"() {
|
|
4753
|
+
"use strict";
|
|
4754
|
+
init_cjs_shims();
|
|
4755
|
+
init_scrub_text();
|
|
4756
|
+
buildAuthToolModelOutput = (tool4) => ({ output }) => {
|
|
4757
|
+
if (output && typeof output === "object" && output.credentialRequest) {
|
|
4758
|
+
const auth = tool4.authentication;
|
|
4759
|
+
const labels = auth?.authType === "user_credentials" ? auth.fields.map((f) => f.label) : [];
|
|
4760
|
+
const provider = output.credentialRequest.provider ?? (auth && "provider" in auth ? auth.provider : "unknown");
|
|
4761
|
+
return { type: "text", value: credentialScrubText(provider, labels) };
|
|
4762
|
+
}
|
|
4763
|
+
if (output && typeof output === "object" && output.oauth?.authorizationUrl) {
|
|
4764
|
+
return { type: "text", value: SCRUBBED_OAUTH_TEXT };
|
|
4765
|
+
}
|
|
4766
|
+
return { type: "json", value: output ?? null };
|
|
4767
|
+
};
|
|
4768
|
+
}
|
|
4769
|
+
});
|
|
4770
|
+
|
|
4104
4771
|
// src/templates/tools/session-file-read-tool.ts
|
|
4105
|
-
var
|
|
4772
|
+
var import_zod7, DEFAULT_LIMIT, MAX_CONTENT_CHARS, createSessionFileReadTool;
|
|
4106
4773
|
var init_session_file_read_tool = __esm({
|
|
4107
4774
|
"src/templates/tools/session-file-read-tool.ts"() {
|
|
4108
4775
|
"use strict";
|
|
4109
4776
|
init_cjs_shims();
|
|
4110
|
-
|
|
4777
|
+
import_zod7 = require("zod");
|
|
4111
4778
|
init_tool();
|
|
4112
4779
|
init_uppy();
|
|
4113
4780
|
DEFAULT_LIMIT = 250;
|
|
@@ -4161,10 +4828,10 @@ var init_session_file_read_tool = __esm({
|
|
|
4161
4828
|
name: "read_session_file",
|
|
4162
4829
|
needsApproval: false,
|
|
4163
4830
|
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.",
|
|
4164
|
-
inputSchema:
|
|
4165
|
-
filename:
|
|
4166
|
-
offset:
|
|
4167
|
-
limit:
|
|
4831
|
+
inputSchema: import_zod7.z.object({
|
|
4832
|
+
filename: import_zod7.z.string().describe('Exact session file name as referenced in a truncation notice, e.g. "tool-output-web_search-a1b2c3d4.txt"'),
|
|
4833
|
+
offset: import_zod7.z.number().int().min(1).optional().describe("1-based first line to read (default 1)"),
|
|
4834
|
+
limit: import_zod7.z.number().int().min(1).max(1e3).optional().describe(`Number of lines to read (default ${DEFAULT_LIMIT})`)
|
|
4168
4835
|
}),
|
|
4169
4836
|
type: "function",
|
|
4170
4837
|
category: "session",
|
|
@@ -4231,12 +4898,12 @@ var init_document_render_helpers = __esm({
|
|
|
4231
4898
|
});
|
|
4232
4899
|
|
|
4233
4900
|
// src/templates/tools/parse-document-tool.ts
|
|
4234
|
-
var
|
|
4901
|
+
var import_zod8, import_node_path6, import_officeparser, DEFAULT_LIMIT2, MAX_CONTENT_CHARS2, MIN_CHARS_PER_PAGE, OFFICE_EXTENSIONS, pagesPattern, createParseDocumentTool;
|
|
4235
4902
|
var init_parse_document_tool = __esm({
|
|
4236
4903
|
"src/templates/tools/parse-document-tool.ts"() {
|
|
4237
4904
|
"use strict";
|
|
4238
4905
|
init_cjs_shims();
|
|
4239
|
-
|
|
4906
|
+
import_zod8 = require("zod");
|
|
4240
4907
|
import_node_path6 = require("path");
|
|
4241
4908
|
import_officeparser = require("officeparser");
|
|
4242
4909
|
init_tool();
|
|
@@ -4355,11 +5022,11 @@ ${text.trim()}`).join("\n");
|
|
|
4355
5022
|
name: "parse_document",
|
|
4356
5023
|
needsApproval: false,
|
|
4357
5024
|
description: `Extract the text of an uploaded PDF or Office document from this session's files, with "--- page N ---" markers for PDFs so you can locate content by page. Free and fast (no OCR): works only on documents with a real text layer. To SEE a page or an image inside a document, use view_document_page.`,
|
|
4358
|
-
inputSchema:
|
|
4359
|
-
filename:
|
|
4360
|
-
pages:
|
|
4361
|
-
offset:
|
|
4362
|
-
limit:
|
|
5025
|
+
inputSchema: import_zod8.z.object({
|
|
5026
|
+
filename: import_zod8.z.string().describe('Exact session file name, e.g. "report.pdf"'),
|
|
5027
|
+
pages: import_zod8.z.string().optional().describe('PDF page or range to extract, e.g. "2" or "1-5" (default: all pages) (PDF only)'),
|
|
5028
|
+
offset: import_zod8.z.number().int().min(1).optional().describe("1-based first output line to read (default 1)"),
|
|
5029
|
+
limit: import_zod8.z.number().int().min(1).max(1e3).optional().describe(`Number of lines to read (default ${DEFAULT_LIMIT2})`)
|
|
4363
5030
|
}),
|
|
4364
5031
|
type: "function",
|
|
4365
5032
|
category: "session",
|
|
@@ -4622,12 +5289,12 @@ var init_tool_image_attachments = __esm({
|
|
|
4622
5289
|
});
|
|
4623
5290
|
|
|
4624
5291
|
// src/templates/tools/view-document-page-tool.ts
|
|
4625
|
-
var
|
|
5292
|
+
var import_zod9, import_node_path8, MAX_IMAGE_BYTES, SCALE_PRIMARY, SCALE_FALLBACK, IMAGE_MEDIA_TYPES, OFFICE_EXTENSIONS2, createViewDocumentPageTool;
|
|
4626
5293
|
var init_view_document_page_tool = __esm({
|
|
4627
5294
|
"src/templates/tools/view-document-page-tool.ts"() {
|
|
4628
5295
|
"use strict";
|
|
4629
5296
|
init_cjs_shims();
|
|
4630
|
-
|
|
5297
|
+
import_zod9 = require("zod");
|
|
4631
5298
|
import_node_path8 = require("path");
|
|
4632
5299
|
init_tool();
|
|
4633
5300
|
init_uppy();
|
|
@@ -4753,9 +5420,9 @@ var init_view_document_page_tool = __esm({
|
|
|
4753
5420
|
name: "view_document_page",
|
|
4754
5421
|
needsApproval: false,
|
|
4755
5422
|
description: "LOOK at a page of an uploaded PDF/Office document, or at an uploaded image, from this session's files. The rendered image is attached as a user message directly after this tool result so you can visually analyze photos, charts, scans, and layouts. Use parse_document first to find which page you need. Requires a vision-capable model.",
|
|
4756
|
-
inputSchema:
|
|
4757
|
-
filename:
|
|
4758
|
-
page:
|
|
5423
|
+
inputSchema: import_zod9.z.object({
|
|
5424
|
+
filename: import_zod9.z.string().describe('Exact session file name, e.g. "report.pdf" or "screenshot.png"'),
|
|
5425
|
+
page: import_zod9.z.number().int().min(1).optional().describe("Page number to render (default 1; ignored for image files)")
|
|
4759
5426
|
}),
|
|
4760
5427
|
type: "function",
|
|
4761
5428
|
category: "session",
|
|
@@ -4790,10 +5457,12 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
|
|
|
4790
5457
|
import_node_crypto4 = require("crypto");
|
|
4791
5458
|
init_statistics();
|
|
4792
5459
|
init_memory_tool();
|
|
5460
|
+
init_context_write_tools();
|
|
4793
5461
|
init_create_sandbox();
|
|
4794
5462
|
init_uppy();
|
|
4795
5463
|
init_truncate_tool_output();
|
|
4796
5464
|
init_tool_output_offload();
|
|
5465
|
+
init_auth_tool_model_output();
|
|
4797
5466
|
init_session_file_read_tool();
|
|
4798
5467
|
init_parse_document_tool();
|
|
4799
5468
|
init_view_document_page_tool();
|
|
@@ -4961,6 +5630,11 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
|
|
|
4961
5630
|
currentTools.push(createNewMemoryTool);
|
|
4962
5631
|
}
|
|
4963
5632
|
}
|
|
5633
|
+
for (const kbWriteTool of collectKbWriteTools(agent, contexts)) {
|
|
5634
|
+
if (!disabled.has(kbWriteTool.id)) {
|
|
5635
|
+
currentTools.push(kbWriteTool);
|
|
5636
|
+
}
|
|
5637
|
+
}
|
|
4964
5638
|
console.log("[EXULU] Convert tools array to object, session items", sessionItems);
|
|
4965
5639
|
if (sessionItems) {
|
|
4966
5640
|
const sessionItemsRetrievalTool = await createSessionItemsRetrievalTool({
|
|
@@ -5113,6 +5787,10 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
|
|
|
5113
5787
|
// Vercel AI SDK uses the sanitized tool name as the key, so this matches.
|
|
5114
5788
|
needsApproval: approvedTools?.includes("tool-" + cur.name) || !cur.needsApproval ? false : true,
|
|
5115
5789
|
// todo make configurable
|
|
5790
|
+
// Auth-wrapped tools: the model sees scrub text instead of the
|
|
5791
|
+
// credentialRequest/oauth payload; the UI stream keeps the raw
|
|
5792
|
+
// output (spec 2026-07-22 §1.2).
|
|
5793
|
+
...cur.authentication ? { toModelOutput: buildAuthToolModelOutput(cur) } : {},
|
|
5116
5794
|
async *execute(inputs, options) {
|
|
5117
5795
|
console.log(
|
|
5118
5796
|
"[EXULU] Executing tool",
|
|
@@ -5252,13 +5930,13 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
|
|
|
5252
5930
|
});
|
|
5253
5931
|
|
|
5254
5932
|
// src/exulu/tool.ts
|
|
5255
|
-
var import_ai3,
|
|
5933
|
+
var import_ai3, import_zod10, import_node_crypto5, PUBLIC_TOOL_TYPES, ExuluTool;
|
|
5256
5934
|
var init_tool = __esm({
|
|
5257
5935
|
"src/exulu/tool.ts"() {
|
|
5258
5936
|
"use strict";
|
|
5259
5937
|
init_cjs_shims();
|
|
5260
5938
|
import_ai3 = require("ai");
|
|
5261
|
-
|
|
5939
|
+
import_zod10 = require("zod");
|
|
5262
5940
|
init_sanitize_name();
|
|
5263
5941
|
import_node_crypto5 = require("crypto");
|
|
5264
5942
|
init_singleton();
|
|
@@ -5279,7 +5957,7 @@ var init_tool = __esm({
|
|
|
5279
5957
|
type;
|
|
5280
5958
|
tool;
|
|
5281
5959
|
needsApproval;
|
|
5282
|
-
|
|
5960
|
+
authentication;
|
|
5283
5961
|
config;
|
|
5284
5962
|
constructor({
|
|
5285
5963
|
id,
|
|
@@ -5291,7 +5969,7 @@ var init_tool = __esm({
|
|
|
5291
5969
|
execute: execute2,
|
|
5292
5970
|
config,
|
|
5293
5971
|
needsApproval,
|
|
5294
|
-
|
|
5972
|
+
authentication: authentication2
|
|
5295
5973
|
}) {
|
|
5296
5974
|
if (!PUBLIC_TOOL_TYPES.includes(type)) {
|
|
5297
5975
|
throw new Error(
|
|
@@ -5300,11 +5978,11 @@ var init_tool = __esm({
|
|
|
5300
5978
|
)}. The "agent" and "context" types are managed by Exulu internally and cannot be set on a tool.`
|
|
5301
5979
|
);
|
|
5302
5980
|
}
|
|
5303
|
-
if (
|
|
5304
|
-
|
|
5305
|
-
|
|
5981
|
+
if (authentication2) {
|
|
5982
|
+
validateAuthConfig(id, authentication2);
|
|
5983
|
+
authRegistry.register(id, authentication2);
|
|
5306
5984
|
}
|
|
5307
|
-
this.
|
|
5985
|
+
this.authentication = authentication2;
|
|
5308
5986
|
this.id = id;
|
|
5309
5987
|
this.config = config;
|
|
5310
5988
|
this.needsApproval = needsApproval ?? true;
|
|
@@ -5315,8 +5993,8 @@ var init_tool = __esm({
|
|
|
5315
5993
|
this.type = type;
|
|
5316
5994
|
this.tool = (0, import_ai3.tool)({
|
|
5317
5995
|
description,
|
|
5318
|
-
inputSchema: inputSchema ||
|
|
5319
|
-
execute:
|
|
5996
|
+
inputSchema: inputSchema || import_zod10.z.object({}),
|
|
5997
|
+
execute: authentication2 ? wrapExecuteWithAuth(id, authentication2, execute2) : execute2
|
|
5320
5998
|
});
|
|
5321
5999
|
}
|
|
5322
6000
|
/**
|
|
@@ -5608,62 +6286,62 @@ function effectiveKbSettings(profile, ctx) {
|
|
|
5608
6286
|
keywordPrefilter: preset.keywordPrefilter
|
|
5609
6287
|
};
|
|
5610
6288
|
}
|
|
5611
|
-
var
|
|
6289
|
+
var import_zod11, KB_KINDS, DEFAULT_PREFILTER_CUTOFF, RRF_K, CHUNK_GROUP_MAX, kbProfileSchema, knowledgeBasesSchema, routingRuleSchema, routingSchema, identifierSetSchema, vocabularySchema, memorySchema, tuningSchema, boolVal, strVal, KIND_PRESETS;
|
|
5612
6290
|
var init_config = __esm({
|
|
5613
6291
|
"ee/agentic-retrieval/pipeline/config.ts"() {
|
|
5614
6292
|
"use strict";
|
|
5615
6293
|
init_cjs_shims();
|
|
5616
|
-
|
|
6294
|
+
import_zod11 = require("zod");
|
|
5617
6295
|
KB_KINDS = ["documents", "conversations", "records"];
|
|
5618
6296
|
DEFAULT_PREFILTER_CUTOFF = 2.5;
|
|
5619
6297
|
RRF_K = 60;
|
|
5620
6298
|
CHUNK_GROUP_MAX = 10;
|
|
5621
|
-
kbProfileSchema =
|
|
5622
|
-
enabled:
|
|
5623
|
-
kind:
|
|
5624
|
-
instructions:
|
|
5625
|
-
overrides:
|
|
5626
|
-
limit:
|
|
5627
|
-
expand:
|
|
5628
|
-
multiQuery:
|
|
5629
|
-
hyde:
|
|
6299
|
+
kbProfileSchema = import_zod11.z.object({
|
|
6300
|
+
enabled: import_zod11.z.boolean().default(true),
|
|
6301
|
+
kind: import_zod11.z.enum(KB_KINDS).default("documents"),
|
|
6302
|
+
instructions: import_zod11.z.string().default(""),
|
|
6303
|
+
overrides: import_zod11.z.object({
|
|
6304
|
+
limit: import_zod11.z.number().int().positive().optional(),
|
|
6305
|
+
expand: import_zod11.z.number().int().min(0).optional(),
|
|
6306
|
+
multiQuery: import_zod11.z.boolean().optional(),
|
|
6307
|
+
hyde: import_zod11.z.boolean().optional()
|
|
5630
6308
|
}).default({})
|
|
5631
6309
|
});
|
|
5632
|
-
knowledgeBasesSchema =
|
|
5633
|
-
routingRuleSchema =
|
|
5634
|
-
id:
|
|
5635
|
-
label:
|
|
5636
|
-
description:
|
|
5637
|
-
main:
|
|
5638
|
-
fallback:
|
|
6310
|
+
knowledgeBasesSchema = import_zod11.z.record(import_zod11.z.string(), kbProfileSchema);
|
|
6311
|
+
routingRuleSchema = import_zod11.z.object({
|
|
6312
|
+
id: import_zod11.z.string(),
|
|
6313
|
+
label: import_zod11.z.string(),
|
|
6314
|
+
description: import_zod11.z.string(),
|
|
6315
|
+
main: import_zod11.z.array(import_zod11.z.string()),
|
|
6316
|
+
fallback: import_zod11.z.array(import_zod11.z.string()).default([])
|
|
5639
6317
|
});
|
|
5640
|
-
routingSchema =
|
|
5641
|
-
identifierSetSchema =
|
|
5642
|
-
name:
|
|
5643
|
-
description:
|
|
5644
|
-
examples:
|
|
5645
|
-
strategy:
|
|
5646
|
-
contexts:
|
|
6318
|
+
routingSchema = import_zod11.z.object({ rules: import_zod11.z.array(routingRuleSchema).default([]) });
|
|
6319
|
+
identifierSetSchema = import_zod11.z.object({
|
|
6320
|
+
name: import_zod11.z.string(),
|
|
6321
|
+
description: import_zod11.z.string().default(""),
|
|
6322
|
+
examples: import_zod11.z.array(import_zod11.z.string()).default([]),
|
|
6323
|
+
strategy: import_zod11.z.enum(["fuzzy", "exact"]),
|
|
6324
|
+
contexts: import_zod11.z.array(import_zod11.z.string()).default([])
|
|
5647
6325
|
});
|
|
5648
|
-
vocabularySchema =
|
|
5649
|
-
glossary:
|
|
5650
|
-
identifiers:
|
|
5651
|
-
rewrites:
|
|
5652
|
-
styleHint:
|
|
6326
|
+
vocabularySchema = import_zod11.z.object({
|
|
6327
|
+
glossary: import_zod11.z.array(import_zod11.z.object({ term: import_zod11.z.string(), meaning: import_zod11.z.string() })).default([]),
|
|
6328
|
+
identifiers: import_zod11.z.array(identifierSetSchema).default([]),
|
|
6329
|
+
rewrites: import_zod11.z.array(import_zod11.z.object({ find: import_zod11.z.string(), replace: import_zod11.z.string() })).default([]),
|
|
6330
|
+
styleHint: import_zod11.z.string().default("")
|
|
5653
6331
|
});
|
|
5654
|
-
memorySchema =
|
|
5655
|
-
enabled:
|
|
5656
|
-
override:
|
|
5657
|
-
filePrioritization:
|
|
5658
|
-
queryAugmentation:
|
|
6332
|
+
memorySchema = import_zod11.z.object({
|
|
6333
|
+
enabled: import_zod11.z.boolean().default(true),
|
|
6334
|
+
override: import_zod11.z.boolean().default(false),
|
|
6335
|
+
filePrioritization: import_zod11.z.boolean().default(false),
|
|
6336
|
+
queryAugmentation: import_zod11.z.boolean().default(true)
|
|
5659
6337
|
});
|
|
5660
|
-
tuningSchema =
|
|
5661
|
-
topK:
|
|
5662
|
-
fallbackThreshold:
|
|
5663
|
-
pinBoost:
|
|
5664
|
-
identifierBoost:
|
|
5665
|
-
pageWindow:
|
|
5666
|
-
maxQueriesPerContext:
|
|
6338
|
+
tuningSchema = import_zod11.z.object({
|
|
6339
|
+
topK: import_zod11.z.number().int().positive().default(5),
|
|
6340
|
+
fallbackThreshold: import_zod11.z.number().min(0).max(1).default(0.95),
|
|
6341
|
+
pinBoost: import_zod11.z.number().min(0).max(1).default(0.15),
|
|
6342
|
+
identifierBoost: import_zod11.z.number().min(0).max(1).default(0.15),
|
|
6343
|
+
pageWindow: import_zod11.z.number().int().min(0).default(1),
|
|
6344
|
+
maxQueriesPerContext: import_zod11.z.number().int().positive().default(5)
|
|
5667
6345
|
});
|
|
5668
6346
|
boolVal = (v) => v === true || v === "true" || v === 1;
|
|
5669
6347
|
strVal = (v, fallback) => typeof v === "string" && v.length > 0 ? v : fallback;
|
|
@@ -5676,7 +6354,8 @@ var init_config = __esm({
|
|
|
5676
6354
|
});
|
|
5677
6355
|
|
|
5678
6356
|
// src/utils/with-retry.ts
|
|
5679
|
-
async function withRetry(generateFn, maxRetries = 3) {
|
|
6357
|
+
async function withRetry(generateFn, maxRetries = 3, opts = {}) {
|
|
6358
|
+
const { shouldRetry, baseDelayMs = 1e3 } = opts;
|
|
5680
6359
|
let lastError;
|
|
5681
6360
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
5682
6361
|
try {
|
|
@@ -5684,10 +6363,10 @@ async function withRetry(generateFn, maxRetries = 3) {
|
|
|
5684
6363
|
} catch (error) {
|
|
5685
6364
|
lastError = error;
|
|
5686
6365
|
console.error(`[EXULU] generateText attempt ${attempt} failed:`, error);
|
|
5687
|
-
if (attempt === maxRetries) {
|
|
6366
|
+
if (attempt === maxRetries || shouldRetry && !shouldRetry(error)) {
|
|
5688
6367
|
throw error;
|
|
5689
6368
|
}
|
|
5690
|
-
await new Promise((resolve8) => setTimeout(resolve8, Math.pow(2, attempt) *
|
|
6369
|
+
await new Promise((resolve8) => setTimeout(resolve8, Math.pow(2, attempt) * baseDelayMs));
|
|
5691
6370
|
}
|
|
5692
6371
|
}
|
|
5693
6372
|
throw lastError;
|
|
@@ -5699,6 +6378,64 @@ var init_with_retry = __esm({
|
|
|
5699
6378
|
}
|
|
5700
6379
|
});
|
|
5701
6380
|
|
|
6381
|
+
// ee/agentic-retrieval/pipeline/micro-call.ts
|
|
6382
|
+
function microCallProviderOptions(model) {
|
|
6383
|
+
const modelId = typeof model === "string" ? model : model?.modelId;
|
|
6384
|
+
return typeof modelId === "string" && /gemini/i.test(modelId) ? { litellm: { reasoningEffort: "disable" } } : void 0;
|
|
6385
|
+
}
|
|
6386
|
+
async function microCall(args) {
|
|
6387
|
+
const {
|
|
6388
|
+
model,
|
|
6389
|
+
system,
|
|
6390
|
+
prompt,
|
|
6391
|
+
messages,
|
|
6392
|
+
schema,
|
|
6393
|
+
temperature = 0,
|
|
6394
|
+
maxOutputTokens = MICRO_CALL_MAX_OUTPUT_TOKENS,
|
|
6395
|
+
maxAttempts = 3,
|
|
6396
|
+
retryBaseDelayMs
|
|
6397
|
+
} = args;
|
|
6398
|
+
return withRetry(
|
|
6399
|
+
async () => {
|
|
6400
|
+
const result = await (0, import_ai4.generateText)({
|
|
6401
|
+
model,
|
|
6402
|
+
temperature,
|
|
6403
|
+
system,
|
|
6404
|
+
prompt,
|
|
6405
|
+
messages,
|
|
6406
|
+
...schema ? { output: import_ai4.Output.object({ schema }) } : {},
|
|
6407
|
+
maxOutputTokens,
|
|
6408
|
+
// withRetry owns retries. The SDK's internal retries on top of it
|
|
6409
|
+
// tripled request volume per attempt while the provider was already
|
|
6410
|
+
// rate-limiting.
|
|
6411
|
+
maxRetries: 0,
|
|
6412
|
+
providerOptions: microCallProviderOptions(model)
|
|
6413
|
+
});
|
|
6414
|
+
return {
|
|
6415
|
+
output: schema ? result.output : void 0,
|
|
6416
|
+
text: result.text
|
|
6417
|
+
};
|
|
6418
|
+
},
|
|
6419
|
+
maxAttempts,
|
|
6420
|
+
{
|
|
6421
|
+
// Empty output is deterministic for identical params — retrying only
|
|
6422
|
+
// added latency before the degraded path. Fail fast instead.
|
|
6423
|
+
shouldRetry: (error) => !import_ai4.NoOutputGeneratedError.isInstance(error),
|
|
6424
|
+
...retryBaseDelayMs !== void 0 ? { baseDelayMs: retryBaseDelayMs } : {}
|
|
6425
|
+
}
|
|
6426
|
+
);
|
|
6427
|
+
}
|
|
6428
|
+
var import_ai4, MICRO_CALL_MAX_OUTPUT_TOKENS;
|
|
6429
|
+
var init_micro_call = __esm({
|
|
6430
|
+
"ee/agentic-retrieval/pipeline/micro-call.ts"() {
|
|
6431
|
+
"use strict";
|
|
6432
|
+
init_cjs_shims();
|
|
6433
|
+
import_ai4 = require("ai");
|
|
6434
|
+
init_with_retry();
|
|
6435
|
+
MICRO_CALL_MAX_OUTPUT_TOKENS = 2e3;
|
|
6436
|
+
}
|
|
6437
|
+
});
|
|
6438
|
+
|
|
5702
6439
|
// ee/agentic-retrieval/pipeline/text-utils.ts
|
|
5703
6440
|
function extractIdentifierTokens(parts) {
|
|
5704
6441
|
const tokens = /* @__PURE__ */ new Set();
|
|
@@ -5903,22 +6640,15 @@ async function resolveIdentifierPins({
|
|
|
5903
6640
|
identifierSets.map(async (set) => {
|
|
5904
6641
|
if (!set.contexts.length) return;
|
|
5905
6642
|
try {
|
|
5906
|
-
const { output } = await
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
matches: import_zod10.z.array(import_zod10.z.string()).optional()
|
|
5916
|
-
})
|
|
5917
|
-
}),
|
|
5918
|
-
maxOutputTokens: 300
|
|
5919
|
-
}),
|
|
5920
|
-
3
|
|
5921
|
-
);
|
|
6643
|
+
const { output } = await microCall({
|
|
6644
|
+
model,
|
|
6645
|
+
system: set.strategy === "exact" ? EXACT_EXTRACTION_PROMPT(set) : FUZZY_EXTRACTION_PROMPT(set),
|
|
6646
|
+
messages: [{ role: "user", content: question }],
|
|
6647
|
+
schema: import_zod12.z.object({
|
|
6648
|
+
hasMatches: import_zod12.z.boolean(),
|
|
6649
|
+
matches: import_zod12.z.array(import_zod12.z.string()).optional()
|
|
6650
|
+
})
|
|
6651
|
+
});
|
|
5922
6652
|
if (!output?.hasMatches || !output.matches?.length) return;
|
|
5923
6653
|
steps.push({ text: `Detected ${set.name} in the question: ${output.matches.join(", ")}` });
|
|
5924
6654
|
await Promise.all(
|
|
@@ -5960,15 +6690,14 @@ async function resolveIdentifierPins({
|
|
|
5960
6690
|
);
|
|
5961
6691
|
return { pinsByContext, exactPinsByContext, steps };
|
|
5962
6692
|
}
|
|
5963
|
-
var import_fuse,
|
|
6693
|
+
var import_fuse, import_zod12, itemCaches, ensureItemsCache, FUZZY_EXTRACTION_PROMPT, EXACT_EXTRACTION_PROMPT;
|
|
5964
6694
|
var init_prefilter = __esm({
|
|
5965
6695
|
"ee/agentic-retrieval/pipeline/prefilter.ts"() {
|
|
5966
6696
|
"use strict";
|
|
5967
6697
|
init_cjs_shims();
|
|
5968
6698
|
import_fuse = __toESM(require("fuse.js"), 1);
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
init_with_retry();
|
|
6699
|
+
import_zod12 = require("zod");
|
|
6700
|
+
init_micro_call();
|
|
5972
6701
|
init_text_utils();
|
|
5973
6702
|
init_config();
|
|
5974
6703
|
itemCaches = {};
|
|
@@ -6045,24 +6774,17 @@ If explicit, return the knowledge base ids. If not, return an empty array.`;
|
|
|
6045
6774
|
const [docPageRaw, explicitKBRaw] = await Promise.all([
|
|
6046
6775
|
(async () => {
|
|
6047
6776
|
try {
|
|
6048
|
-
return await
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
pageNumber: import_zod11.z.number().int().nullable().optional()
|
|
6060
|
-
})
|
|
6061
|
-
}),
|
|
6062
|
-
maxOutputTokens: 300
|
|
6063
|
-
}),
|
|
6064
|
-
3
|
|
6065
|
-
);
|
|
6777
|
+
return await microCall({
|
|
6778
|
+
model,
|
|
6779
|
+
system: buildDocPagePrompt(knownIdentifiers),
|
|
6780
|
+
messages: [{ role: "user", content: question }],
|
|
6781
|
+
schema: import_zod13.z.object({
|
|
6782
|
+
hasFilenameHint: import_zod13.z.boolean(),
|
|
6783
|
+
filenameHints: import_zod13.z.array(import_zod13.z.string()).optional(),
|
|
6784
|
+
hasPageHint: import_zod13.z.boolean(),
|
|
6785
|
+
pageNumber: import_zod13.z.number().int().nullable().optional()
|
|
6786
|
+
})
|
|
6787
|
+
});
|
|
6066
6788
|
} catch (err) {
|
|
6067
6789
|
steps.push({ text: "Doc/page detection failed \u2014 skipping filename and page hints." });
|
|
6068
6790
|
return {
|
|
@@ -6077,23 +6799,16 @@ If explicit, return the knowledge base ids. If not, return an empty array.`;
|
|
|
6077
6799
|
})(),
|
|
6078
6800
|
(async () => {
|
|
6079
6801
|
try {
|
|
6080
|
-
return await
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
explicitlyRequestedKnowledgeBases: import_zod11.z.array(
|
|
6088
|
-
import_zod11.z.enum(enabledContexts.map((c) => c.id))
|
|
6089
|
-
)
|
|
6090
|
-
})
|
|
6091
|
-
}),
|
|
6092
|
-
messages: [{ role: "user", content: question }],
|
|
6093
|
-
maxOutputTokens: 200
|
|
6802
|
+
return await microCall({
|
|
6803
|
+
model,
|
|
6804
|
+
system: kbSystemPrompt,
|
|
6805
|
+
schema: import_zod13.z.object({
|
|
6806
|
+
explicitlyRequestedKnowledgeBases: import_zod13.z.array(
|
|
6807
|
+
import_zod13.z.enum(enabledContexts.map((c) => c.id))
|
|
6808
|
+
)
|
|
6094
6809
|
}),
|
|
6095
|
-
|
|
6096
|
-
);
|
|
6810
|
+
messages: [{ role: "user", content: question }]
|
|
6811
|
+
});
|
|
6097
6812
|
} catch (err) {
|
|
6098
6813
|
return { output: { explicitlyRequestedKnowledgeBases: [] } };
|
|
6099
6814
|
}
|
|
@@ -6176,22 +6891,15 @@ ${extraInstructions}
|
|
|
6176
6891
|
</instructions>`;
|
|
6177
6892
|
}
|
|
6178
6893
|
try {
|
|
6179
|
-
const { output: classified } = await
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
reason: import_zod11.z.string()
|
|
6189
|
-
})
|
|
6190
|
-
}),
|
|
6191
|
-
maxOutputTokens: 200
|
|
6192
|
-
}),
|
|
6193
|
-
3
|
|
6194
|
-
);
|
|
6894
|
+
const { output: classified } = await microCall({
|
|
6895
|
+
model,
|
|
6896
|
+
system: classifyPrompt,
|
|
6897
|
+
messages: [{ role: "user", content: question }],
|
|
6898
|
+
schema: import_zod13.z.object({
|
|
6899
|
+
ruleId: import_zod13.z.enum(ruleIds),
|
|
6900
|
+
reason: import_zod13.z.string()
|
|
6901
|
+
})
|
|
6902
|
+
});
|
|
6195
6903
|
const matchedRule = routingRules.find((r) => r.id === classified.ruleId);
|
|
6196
6904
|
if (matchedRule) {
|
|
6197
6905
|
const main = matchedRule.main.filter((id) => enabledIds.has(id));
|
|
@@ -6244,14 +6952,13 @@ ${extraInstructions}
|
|
|
6244
6952
|
};
|
|
6245
6953
|
}
|
|
6246
6954
|
}
|
|
6247
|
-
var
|
|
6955
|
+
var import_zod13, MAX_USER_PIN_MATCHES, buildDocPagePrompt;
|
|
6248
6956
|
var init_routing = __esm({
|
|
6249
6957
|
"ee/agentic-retrieval/pipeline/routing.ts"() {
|
|
6250
6958
|
"use strict";
|
|
6251
6959
|
init_cjs_shims();
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
init_with_retry();
|
|
6960
|
+
import_zod13 = require("zod");
|
|
6961
|
+
init_micro_call();
|
|
6255
6962
|
init_prefilter();
|
|
6256
6963
|
init_text_utils();
|
|
6257
6964
|
MAX_USER_PIN_MATCHES = 8;
|
|
@@ -6506,32 +7213,25 @@ async function runMemoryPhase({
|
|
|
6506
7213
|
`;
|
|
6507
7214
|
let relevantMemoryChunks = [];
|
|
6508
7215
|
try {
|
|
6509
|
-
const { output: output_relevant_memory } = await
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
role: "user",
|
|
6517
|
-
content: `
|
|
7216
|
+
const { output: output_relevant_memory } = await microCall({
|
|
7217
|
+
model,
|
|
7218
|
+
system: CHECK_MEMORIES_FOR_RELEVANT_INFORMATION,
|
|
7219
|
+
messages: [
|
|
7220
|
+
{
|
|
7221
|
+
role: "user",
|
|
7222
|
+
content: `
|
|
6518
7223
|
<user_question>${question}</user_question>
|
|
6519
7224
|
<relevant_keywords>${keywords.join(", ")}</relevant_keywords>
|
|
6520
7225
|
<important_keyword>${importantKeyword}</important_keyword>
|
|
6521
7226
|
`
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
}),
|
|
6531
|
-
maxOutputTokens: 400
|
|
6532
|
-
}),
|
|
6533
|
-
3
|
|
6534
|
-
);
|
|
7227
|
+
}
|
|
7228
|
+
],
|
|
7229
|
+
schema: import_zod14.z.object({
|
|
7230
|
+
relevantChunkIds: import_zod14.z.array(import_zod14.z.string()).describe(
|
|
7231
|
+
"The chunk_ids (UUIDs at the start of each bullet) of chunks containing information relevant to the user's question. Empty array if none are relevant."
|
|
7232
|
+
)
|
|
7233
|
+
})
|
|
7234
|
+
});
|
|
6535
7235
|
const ids = new Set(output_relevant_memory?.relevantChunkIds ?? []);
|
|
6536
7236
|
relevantMemoryChunks = ids.size === 0 ? [] : retrieved_memory.filter((c) => ids.has(c.chunk_id));
|
|
6537
7237
|
} catch (e) {
|
|
@@ -6623,41 +7323,34 @@ ${glossary.map((g) => `${g.term} : ${g.meaning}`).join("\n")}` : "";
|
|
|
6623
7323
|
`;
|
|
6624
7324
|
const [overrideResult, fileResult, queryResult] = await Promise.all([
|
|
6625
7325
|
// Override check: strict gate to decide if memory should be authoritative
|
|
6626
|
-
memoryConfig.override ?
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
role: "user",
|
|
6634
|
-
content: `
|
|
7326
|
+
memoryConfig.override ? microCall({
|
|
7327
|
+
model,
|
|
7328
|
+
system: CHECK_MEMORY_OVERRIDE,
|
|
7329
|
+
messages: [
|
|
7330
|
+
{
|
|
7331
|
+
role: "user",
|
|
7332
|
+
content: `
|
|
6635
7333
|
<user_question>${question}</user_question>
|
|
6636
7334
|
<relevant_keywords>${keywords.join(", ")}</relevant_keywords>
|
|
6637
7335
|
<important_keyword>${importantKeyword}</important_keyword>
|
|
6638
7336
|
`
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
}),
|
|
6657
|
-
maxOutputTokens: 300
|
|
6658
|
-
}),
|
|
6659
|
-
3
|
|
6660
|
-
).catch(() => ({
|
|
7337
|
+
}
|
|
7338
|
+
],
|
|
7339
|
+
schema: import_zod14.z.object({
|
|
7340
|
+
overrides: import_zod14.z.boolean().describe(
|
|
7341
|
+
"True ONLY if a memory chunk directly and sufficiently answers the user's question and should be authoritative over the documents. Be strict; when unsure, false."
|
|
7342
|
+
),
|
|
7343
|
+
confidence: import_zod14.z.enum(["high", "medium", "low"]).describe(
|
|
7344
|
+
"Confidence that the selected memory chunk(s) fully and directly answer the question."
|
|
7345
|
+
),
|
|
7346
|
+
authoritativeChunkIds: import_zod14.z.array(import_zod14.z.string()).describe(
|
|
7347
|
+
"The chunk_ids of the memory chunk(s) that directly answer the question. Empty if overrides is false."
|
|
7348
|
+
),
|
|
7349
|
+
reason: import_zod14.z.string().describe(
|
|
7350
|
+
"One short sentence: why this memory does or does not directly answer the question."
|
|
7351
|
+
)
|
|
7352
|
+
})
|
|
7353
|
+
}).catch(() => ({
|
|
6661
7354
|
output: {
|
|
6662
7355
|
overrides: false,
|
|
6663
7356
|
confidence: "low",
|
|
@@ -6673,44 +7366,30 @@ ${glossary.map((g) => `${g.term} : ${g.meaning}`).join("\n")}` : "";
|
|
|
6673
7366
|
}
|
|
6674
7367
|
}),
|
|
6675
7368
|
// File prioritization: detect explicit document-pinning instructions in memory
|
|
6676
|
-
memoryConfig.filePrioritization ?
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
fileNameHints: import_zod12.z.array(import_zod12.z.string()).optional()
|
|
6686
|
-
})
|
|
6687
|
-
}),
|
|
6688
|
-
maxOutputTokens: 300
|
|
6689
|
-
}),
|
|
6690
|
-
3
|
|
6691
|
-
).catch(() => ({
|
|
7369
|
+
memoryConfig.filePrioritization ? microCall({
|
|
7370
|
+
model,
|
|
7371
|
+
system: "You are a helpful assistant that will strictly follow the user's instructions.",
|
|
7372
|
+
messages: [{ role: "user", content: PROMPT_EXTRACT_PRIORITIZED_FILES }],
|
|
7373
|
+
schema: import_zod14.z.object({
|
|
7374
|
+
shouldPrioritizeFiles: import_zod14.z.boolean(),
|
|
7375
|
+
fileNameHints: import_zod14.z.array(import_zod14.z.string()).optional()
|
|
7376
|
+
})
|
|
7377
|
+
}).catch(() => ({
|
|
6692
7378
|
output: { shouldPrioritizeFiles: false, fileNameHints: [] }
|
|
6693
7379
|
})) : Promise.resolve({
|
|
6694
7380
|
output: { shouldPrioritizeFiles: false, fileNameHints: [] }
|
|
6695
7381
|
}),
|
|
6696
7382
|
// Query augmentation: expand keywords with synonyms/abbreviations from memory
|
|
6697
|
-
memoryConfig.queryAugmentation && hasAugmentationContent ?
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
updatedImportantKeyword: import_zod12.z.string()
|
|
6708
|
-
})
|
|
6709
|
-
}),
|
|
6710
|
-
maxOutputTokens: 600
|
|
6711
|
-
}),
|
|
6712
|
-
3
|
|
6713
|
-
).catch(() => ({
|
|
7383
|
+
memoryConfig.queryAugmentation && hasAugmentationContent ? microCall({
|
|
7384
|
+
model,
|
|
7385
|
+
system: "You are a helpful assistant that will strictly follow the user's instructions.",
|
|
7386
|
+
messages: [{ role: "user", content: QUERY_AUGMENTATION_PROMPT }],
|
|
7387
|
+
schema: import_zod14.z.object({
|
|
7388
|
+
updatedUserQuestion: import_zod14.z.string(),
|
|
7389
|
+
updatedRelevantKeywords: import_zod14.z.array(import_zod14.z.string()),
|
|
7390
|
+
updatedImportantKeyword: import_zod14.z.string()
|
|
7391
|
+
})
|
|
7392
|
+
}).catch(() => ({
|
|
6714
7393
|
output: {
|
|
6715
7394
|
updatedUserQuestion: question,
|
|
6716
7395
|
updatedRelevantKeywords: [],
|
|
@@ -6790,14 +7469,13 @@ ${glossary.map((g) => `${g.term} : ${g.meaning}`).join("\n")}` : "";
|
|
|
6790
7469
|
return neutralResult(question, keywords, importantKeyword);
|
|
6791
7470
|
}
|
|
6792
7471
|
}
|
|
6793
|
-
var
|
|
7472
|
+
var import_zod14, MEMORY_OVERRIDE_MIN_CONFIDENCE, MEMORY_SYNTHETIC_RERANK_SCORE, ITEM_CACHE_TTL_MS, memoryItemCache;
|
|
6794
7473
|
var init_memory = __esm({
|
|
6795
7474
|
"ee/agentic-retrieval/pipeline/memory.ts"() {
|
|
6796
7475
|
"use strict";
|
|
6797
7476
|
init_cjs_shims();
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
init_with_retry();
|
|
7477
|
+
import_zod14 = require("zod");
|
|
7478
|
+
init_micro_call();
|
|
6801
7479
|
init_multi_query();
|
|
6802
7480
|
init_prefilter();
|
|
6803
7481
|
init_text_utils();
|
|
@@ -6875,21 +7553,21 @@ IMPORTANT:
|
|
|
6875
7553
|
prompt += `
|
|
6876
7554
|
Question: "${originalQuestion}"
|
|
6877
7555
|
Relevant keywords: ${relevantKeywords.join(", ")}`;
|
|
6878
|
-
const { text } = await (
|
|
7556
|
+
const { text } = await microCall({
|
|
6879
7557
|
model,
|
|
6880
7558
|
prompt,
|
|
6881
7559
|
temperature: 0.3,
|
|
6882
|
-
|
|
7560
|
+
maxAttempts: 1
|
|
6883
7561
|
});
|
|
6884
7562
|
const passage = (text || "").trim();
|
|
6885
7563
|
return passage.length > 0 ? passage : null;
|
|
6886
7564
|
}
|
|
6887
|
-
var
|
|
7565
|
+
var hydeCache, HYDE_CACHE_MAX;
|
|
6888
7566
|
var init_hyde = __esm({
|
|
6889
7567
|
"ee/agentic-retrieval/pipeline/hyde.ts"() {
|
|
6890
7568
|
"use strict";
|
|
6891
7569
|
init_cjs_shims();
|
|
6892
|
-
|
|
7570
|
+
init_micro_call();
|
|
6893
7571
|
init_text_utils();
|
|
6894
7572
|
hydeCache = /* @__PURE__ */ new Map();
|
|
6895
7573
|
HYDE_CACHE_MAX = 200;
|
|
@@ -7292,11 +7970,11 @@ function createAgenticRetrievalTool(opts) {
|
|
|
7292
7970
|
default: '{"topK":5,"fallbackThreshold":0.95,"pinBoost":0.15,"identifierBoost":0.15,"pageWindow":1,"maxQueriesPerContext":5}'
|
|
7293
7971
|
}
|
|
7294
7972
|
],
|
|
7295
|
-
inputSchema:
|
|
7296
|
-
userQuery:
|
|
7297
|
-
relevantKeywords:
|
|
7298
|
-
importantKeyword:
|
|
7299
|
-
confirmedContextIds:
|
|
7973
|
+
inputSchema: import_zod15.z.object({
|
|
7974
|
+
userQuery: import_zod15.z.string().describe("The original unaltered question from the user"),
|
|
7975
|
+
relevantKeywords: import_zod15.z.array(import_zod15.z.string()).describe("Keywords extracted from the user's question relevant to the search"),
|
|
7976
|
+
importantKeyword: import_zod15.z.string().describe("The single most important keyword from the user's question"),
|
|
7977
|
+
confirmedContextIds: import_zod15.z.array(import_zod15.z.string()).optional().describe(
|
|
7300
7978
|
"Knowledge base IDs explicitly confirmed by the user to be used in the retrieval. When present, only searches these contexts."
|
|
7301
7979
|
)
|
|
7302
7980
|
}),
|
|
@@ -7687,12 +8365,12 @@ Verified answer:
|
|
|
7687
8365
|
}
|
|
7688
8366
|
});
|
|
7689
8367
|
}
|
|
7690
|
-
var
|
|
8368
|
+
var import_zod15;
|
|
7691
8369
|
var init_pipeline = __esm({
|
|
7692
8370
|
"ee/agentic-retrieval/pipeline/index.ts"() {
|
|
7693
8371
|
"use strict";
|
|
7694
8372
|
init_cjs_shims();
|
|
7695
|
-
|
|
8373
|
+
import_zod15 = require("zod");
|
|
7696
8374
|
init_tool();
|
|
7697
8375
|
init_entitlements();
|
|
7698
8376
|
init_resolve_reranker();
|
|
@@ -7713,6 +8391,7 @@ var init_pipeline = __esm({
|
|
|
7713
8391
|
// src/index.ts
|
|
7714
8392
|
var index_exports = {};
|
|
7715
8393
|
__export(index_exports, {
|
|
8394
|
+
CredentialInvalidError: () => CredentialInvalidError,
|
|
7716
8395
|
EXULU_JOB_STATUS_ENUM: () => JOB_STATUS_ENUM,
|
|
7717
8396
|
EXULU_STATISTICS_TYPE_ENUM: () => STATISTICS_TYPE_ENUM,
|
|
7718
8397
|
ExuluApp: () => ExuluApp,
|
|
@@ -7797,9 +8476,9 @@ async function guardRedisStartup(label, run, source) {
|
|
|
7797
8476
|
);
|
|
7798
8477
|
}, WATCHDOG_INTERVAL_MS);
|
|
7799
8478
|
watchdog.unref?.();
|
|
7800
|
-
let
|
|
8479
|
+
let timer3;
|
|
7801
8480
|
const timeout = new Promise((_resolve, reject) => {
|
|
7802
|
-
|
|
8481
|
+
timer3 = setTimeout(() => {
|
|
7803
8482
|
reject(
|
|
7804
8483
|
new Error(
|
|
7805
8484
|
`[EXULU-REDIS] Redis unreachable at ${addr} after ${REDIS_STARTUP_TIMEOUT_MS / 1e3}s \u2014 aborting ${label} startup. Last error: ${lastError ? describeError(lastError) : "none surfaced"}. Check REDIS_HOST/REDIS_PORT and that a Redis server is reachable at ${addr}.`
|
|
@@ -7816,7 +8495,7 @@ async function guardRedisStartup(label, run, source) {
|
|
|
7816
8495
|
return result;
|
|
7817
8496
|
} finally {
|
|
7818
8497
|
clearInterval(watchdog);
|
|
7819
|
-
if (
|
|
8498
|
+
if (timer3) clearTimeout(timer3);
|
|
7820
8499
|
source?.off?.("error", onError);
|
|
7821
8500
|
}
|
|
7822
8501
|
}
|
|
@@ -7873,7 +8552,13 @@ var requestValidators = {
|
|
|
7873
8552
|
const { db: db2 } = await postgresClient();
|
|
7874
8553
|
let authtoken = null;
|
|
7875
8554
|
if (!apikey) {
|
|
7876
|
-
|
|
8555
|
+
try {
|
|
8556
|
+
authtoken = await getToken(
|
|
8557
|
+
(req.headers["authorization"] || req.headers["x-api-key"]) ?? ""
|
|
8558
|
+
);
|
|
8559
|
+
} catch {
|
|
8560
|
+
authtoken = null;
|
|
8561
|
+
}
|
|
7877
8562
|
}
|
|
7878
8563
|
return await authentication({
|
|
7879
8564
|
authtoken,
|
|
@@ -8202,14 +8887,7 @@ var ExuluStorage = class {
|
|
|
8202
8887
|
|
|
8203
8888
|
// src/exulu/context.ts
|
|
8204
8889
|
init_sanitize_name();
|
|
8205
|
-
|
|
8206
|
-
// src/exulu/table-names.ts
|
|
8207
|
-
init_cjs_shims();
|
|
8208
|
-
init_sanitize_name();
|
|
8209
|
-
var getTableName = (id) => sanitizeName(id) + "_items";
|
|
8210
|
-
var getChunksTableName = (id) => sanitizeName(id) + "_chunks";
|
|
8211
|
-
|
|
8212
|
-
// src/exulu/context.ts
|
|
8890
|
+
init_table_names();
|
|
8213
8891
|
var import_knex5 = __toESM(require("pgvector/knex"), 1);
|
|
8214
8892
|
|
|
8215
8893
|
// src/exulu/chunker.ts
|
|
@@ -9097,9 +9775,33 @@ function preprocessQuery(query, options = {}) {
|
|
|
9097
9775
|
|
|
9098
9776
|
// src/graphql/resolvers/apply-sorting.ts
|
|
9099
9777
|
init_cjs_shims();
|
|
9100
|
-
|
|
9778
|
+
|
|
9779
|
+
// src/graphql/resolvers/field-allow-list.ts
|
|
9780
|
+
init_cjs_shims();
|
|
9781
|
+
var ALWAYS_ALLOWED = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt"]);
|
|
9782
|
+
function groupableFields(table) {
|
|
9783
|
+
const allowed = new Set(ALWAYS_ALLOWED);
|
|
9784
|
+
for (const field of table.fields) {
|
|
9785
|
+
if (field.hidden !== true) {
|
|
9786
|
+
allowed.add(field.name);
|
|
9787
|
+
}
|
|
9788
|
+
}
|
|
9789
|
+
return allowed;
|
|
9790
|
+
}
|
|
9791
|
+
function assertAllowedField(table, fieldName, label) {
|
|
9792
|
+
const allowed = groupableFields(table);
|
|
9793
|
+
if (!allowed.has(fieldName)) {
|
|
9794
|
+
throw new Error(`Cannot ${label} by "${fieldName}".`);
|
|
9795
|
+
}
|
|
9796
|
+
}
|
|
9797
|
+
|
|
9798
|
+
// src/graphql/resolvers/apply-sorting.ts
|
|
9799
|
+
var applySorting = (query, sort, field_prefix, table) => {
|
|
9101
9800
|
const prefix = field_prefix ? field_prefix + "." : "";
|
|
9102
9801
|
if (sort) {
|
|
9802
|
+
if (table) {
|
|
9803
|
+
assertAllowedField(table, sort.field, "sort");
|
|
9804
|
+
}
|
|
9103
9805
|
sort.field = prefix + sort.field;
|
|
9104
9806
|
query = query.orderBy(sort.field, sort.direction.toLowerCase());
|
|
9105
9807
|
}
|
|
@@ -10050,6 +10752,31 @@ var agentsSchema = {
|
|
|
10050
10752
|
// (DEFAULT_MAX_STEPS in resolve-max-steps.ts). Auto-ALTERed on boot.
|
|
10051
10753
|
name: "max_tool_steps",
|
|
10052
10754
|
type: "number"
|
|
10755
|
+
},
|
|
10756
|
+
{
|
|
10757
|
+
name: "guest_access",
|
|
10758
|
+
type: "boolean",
|
|
10759
|
+
default: false
|
|
10760
|
+
},
|
|
10761
|
+
{
|
|
10762
|
+
name: "guest_auth_mode",
|
|
10763
|
+
type: "text",
|
|
10764
|
+
default: "regular"
|
|
10765
|
+
// 'public' | 'password' | 'regular' (= login)
|
|
10766
|
+
},
|
|
10767
|
+
{
|
|
10768
|
+
// bcrypt hash (hashSharePassword); NEVER exposed via GraphQL/REST —
|
|
10769
|
+
// see sanitizeRequestedFields + createExuluContextsTypeDefs filtering.
|
|
10770
|
+
name: "guest_password_hash",
|
|
10771
|
+
type: "text",
|
|
10772
|
+
required: false,
|
|
10773
|
+
hidden: true
|
|
10774
|
+
},
|
|
10775
|
+
{
|
|
10776
|
+
// S3 key of the custom login-page image shown on the public auth page.
|
|
10777
|
+
name: "guest_cover_image",
|
|
10778
|
+
type: "text",
|
|
10779
|
+
required: false
|
|
10053
10780
|
}
|
|
10054
10781
|
]
|
|
10055
10782
|
};
|
|
@@ -10166,7 +10893,8 @@ var usersSchema = {
|
|
|
10166
10893
|
},
|
|
10167
10894
|
{
|
|
10168
10895
|
name: "temporary_token",
|
|
10169
|
-
type: "text"
|
|
10896
|
+
type: "text",
|
|
10897
|
+
hidden: true
|
|
10170
10898
|
},
|
|
10171
10899
|
{
|
|
10172
10900
|
name: "type",
|
|
@@ -10192,7 +10920,8 @@ var usersSchema = {
|
|
|
10192
10920
|
},
|
|
10193
10921
|
{
|
|
10194
10922
|
name: "apikey",
|
|
10195
|
-
type: "text"
|
|
10923
|
+
type: "text",
|
|
10924
|
+
hidden: true
|
|
10196
10925
|
},
|
|
10197
10926
|
{
|
|
10198
10927
|
name: "scope_mode",
|
|
@@ -10209,11 +10938,13 @@ var usersSchema = {
|
|
|
10209
10938
|
},
|
|
10210
10939
|
{
|
|
10211
10940
|
name: "password",
|
|
10212
|
-
type: "text"
|
|
10941
|
+
type: "text",
|
|
10942
|
+
hidden: true
|
|
10213
10943
|
},
|
|
10214
10944
|
{
|
|
10215
10945
|
name: "anthropic_token",
|
|
10216
|
-
type: "text"
|
|
10946
|
+
type: "text",
|
|
10947
|
+
hidden: true
|
|
10217
10948
|
},
|
|
10218
10949
|
{
|
|
10219
10950
|
name: "personal_system_prompt",
|
|
@@ -10433,29 +11164,20 @@ var imageGenerationsSchema = {
|
|
|
10433
11164
|
{ name: "error", type: "text", required: false }
|
|
10434
11165
|
]
|
|
10435
11166
|
};
|
|
10436
|
-
|
|
10437
|
-
|
|
10438
|
-
|
|
10439
|
-
|
|
10440
|
-
|
|
10441
|
-
|
|
10442
|
-
|
|
10443
|
-
|
|
10444
|
-
|
|
10445
|
-
|
|
10446
|
-
|
|
10447
|
-
|
|
10448
|
-
|
|
10449
|
-
|
|
10450
|
-
// AES-encrypted
|
|
10451
|
-
{ name: "refresh_token", type: "longText", required: false },
|
|
10452
|
-
// AES-encrypted
|
|
10453
|
-
{ name: "token_type", type: "text", required: false },
|
|
10454
|
-
{ name: "scopes", type: "text", required: false },
|
|
10455
|
-
{ name: "expires_at", type: "date", required: false }
|
|
10456
|
-
// null = non-expiring
|
|
10457
|
-
]
|
|
10458
|
-
};
|
|
11167
|
+
function userCredentialsSchema() {
|
|
11168
|
+
return `
|
|
11169
|
+
CREATE TABLE IF NOT EXISTS user_credentials (
|
|
11170
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
11171
|
+
provider text NOT NULL,
|
|
11172
|
+
user_id text NOT NULL,
|
|
11173
|
+
auth_type text NOT NULL CHECK (auth_type IN ('oauth', 'user_credentials')),
|
|
11174
|
+
data text NOT NULL,
|
|
11175
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
11176
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
11177
|
+
UNIQUE (provider, user_id)
|
|
11178
|
+
);
|
|
11179
|
+
`;
|
|
11180
|
+
}
|
|
10459
11181
|
var sharedArtifactsSchema = {
|
|
10460
11182
|
type: "shared_artifacts",
|
|
10461
11183
|
name: {
|
|
@@ -10469,7 +11191,7 @@ var sharedArtifactsSchema = {
|
|
|
10469
11191
|
{ name: "name", type: "text", index: true, unique: true, required: true },
|
|
10470
11192
|
{ name: "s3key", type: "text", required: true },
|
|
10471
11193
|
{ name: "auth_mode", type: "text", default: "regular" },
|
|
10472
|
-
{ name: "password_hash", type: "text", required: false },
|
|
11194
|
+
{ name: "password_hash", type: "text", required: false, hidden: true },
|
|
10473
11195
|
// bcrypt; password mode only
|
|
10474
11196
|
{ name: "expires_at", type: "date", required: false },
|
|
10475
11197
|
// null = no expiry
|
|
@@ -10567,7 +11289,6 @@ var coreSchemas = {
|
|
|
10567
11289
|
entityTypeSettingsSchema: () => addCoreFields(entityTypeSettingsSchema),
|
|
10568
11290
|
promptFavoritesSchema: () => addCoreFields(promptFavoritesSchema),
|
|
10569
11291
|
contextPresetsSchema: () => addCoreFields(contextPresetsSchema),
|
|
10570
|
-
oauthTokensSchema: () => addCoreFields(oauthTokensSchema),
|
|
10571
11292
|
sharedArtifactsSchema: () => addCoreFields(sharedArtifactsSchema),
|
|
10572
11293
|
transcriptionJobsSchema: () => addCoreFields(transcriptionJobsSchema),
|
|
10573
11294
|
imageGenerationsSchema: () => addCoreFields(imageGenerationsSchema)
|
|
@@ -11435,7 +12156,7 @@ var vectorSearch = async ({
|
|
|
11435
12156
|
chunksQuery = applyFilters(chunksQuery, itemFilters, table, "items");
|
|
11436
12157
|
chunksQuery = applyFilters(chunksQuery, chunkFilters, table, "chunks");
|
|
11437
12158
|
chunksQuery = applyAccessControl(table, chunksQuery, user, "items");
|
|
11438
|
-
chunksQuery = applySorting(chunksQuery, sort, "items");
|
|
12159
|
+
chunksQuery = applySorting(chunksQuery, sort, "items", table);
|
|
11439
12160
|
if (filterEntityIds) {
|
|
11440
12161
|
applyEntityFilter(chunksQuery, "chunks", context, filterEntityIds, entityFilter?.mode || "any");
|
|
11441
12162
|
}
|
|
@@ -12398,9 +13119,9 @@ var ExuluContext2 = class {
|
|
|
12398
13119
|
job
|
|
12399
13120
|
};
|
|
12400
13121
|
};
|
|
12401
|
-
createItem = async (item, config, user, role,
|
|
12402
|
-
console.log("[EXULU] creating item", item,
|
|
12403
|
-
if (
|
|
13122
|
+
createItem = async (item, config, user, role, upsert2, generateEmbeddingsOverwrite) => {
|
|
13123
|
+
console.log("[EXULU] creating item", item, upsert2);
|
|
13124
|
+
if (upsert2 && !item.id && !item.external_id) {
|
|
12404
13125
|
throw new Error("Item id or external id is required for upsert.");
|
|
12405
13126
|
}
|
|
12406
13127
|
const { db: db2 } = await postgresClient();
|
|
@@ -12416,8 +13137,8 @@ var ExuluContext2 = class {
|
|
|
12416
13137
|
...item,
|
|
12417
13138
|
tags: item.tags ? Array.isArray(item.tags) ? item.tags.join(",") : item.tags : void 0
|
|
12418
13139
|
}).returning("id");
|
|
12419
|
-
console.log("[EXULU] Upsert",
|
|
12420
|
-
if (
|
|
13140
|
+
console.log("[EXULU] Upsert", upsert2);
|
|
13141
|
+
if (upsert2) {
|
|
12421
13142
|
if (item.external_id) {
|
|
12422
13143
|
mutation.onConflict("external_id").merge();
|
|
12423
13144
|
} else if (item.id) {
|
|
@@ -12999,6 +13720,7 @@ var queues = new ExuluQueues();
|
|
|
12999
13720
|
// src/graphql/schemas/index.ts
|
|
13000
13721
|
var import_uuid4 = require("uuid");
|
|
13001
13722
|
init_pipeline();
|
|
13723
|
+
init_context_write_tools();
|
|
13002
13724
|
|
|
13003
13725
|
// src/graphql/types/index.ts
|
|
13004
13726
|
init_cjs_shims();
|
|
@@ -13040,6 +13762,8 @@ var GraphQLDate = new import_graphql.GraphQLScalarType({
|
|
|
13040
13762
|
|
|
13041
13763
|
// src/graphql/resolvers/utils.ts
|
|
13042
13764
|
init_cjs_shims();
|
|
13765
|
+
var NON_COLUMN_SELECTIONS = /* @__PURE__ */ new Set(["pageInfo", "items", "RBAC"]);
|
|
13766
|
+
var isSelectableColumn = (field) => !NON_COLUMN_SELECTIONS.has(field) && !field.startsWith("__");
|
|
13043
13767
|
var getRequestedFields = (info) => {
|
|
13044
13768
|
const selections = info.operation.selectionSet.selections[0].selectionSet.selections;
|
|
13045
13769
|
const itemSelection = selections.find((s) => s.name.value === "item");
|
|
@@ -13052,7 +13776,7 @@ var getRequestedFields = (info) => {
|
|
|
13052
13776
|
return acc;
|
|
13053
13777
|
}, {})
|
|
13054
13778
|
);
|
|
13055
|
-
return fields.filter(
|
|
13779
|
+
return fields.filter(isSelectableColumn);
|
|
13056
13780
|
}
|
|
13057
13781
|
if (itemsSelection) {
|
|
13058
13782
|
fields = Object.keys(
|
|
@@ -13061,7 +13785,7 @@ var getRequestedFields = (info) => {
|
|
|
13061
13785
|
return acc;
|
|
13062
13786
|
}, {})
|
|
13063
13787
|
);
|
|
13064
|
-
return fields.filter(
|
|
13788
|
+
return fields.filter(isSelectableColumn);
|
|
13065
13789
|
}
|
|
13066
13790
|
fields = Object.keys(
|
|
13067
13791
|
selections.reduce((acc, field) => {
|
|
@@ -13069,7 +13793,7 @@ var getRequestedFields = (info) => {
|
|
|
13069
13793
|
return acc;
|
|
13070
13794
|
}, {})
|
|
13071
13795
|
);
|
|
13072
|
-
return fields.filter(
|
|
13796
|
+
return fields.filter(isSelectableColumn);
|
|
13073
13797
|
};
|
|
13074
13798
|
var contextItemsProcessorHandler = async (context, config, items, user, role) => {
|
|
13075
13799
|
let jobs = [];
|
|
@@ -13150,8 +13874,11 @@ init_client();
|
|
|
13150
13874
|
init_singleton();
|
|
13151
13875
|
init_supervisor();
|
|
13152
13876
|
init_catalog();
|
|
13153
|
-
|
|
13877
|
+
|
|
13878
|
+
// src/graphql/utilities/budget-field.ts
|
|
13879
|
+
init_cjs_shims();
|
|
13154
13880
|
init_budget_service();
|
|
13881
|
+
init_tags();
|
|
13155
13882
|
var BUDGET_ENTITY_SINGULARS = /* @__PURE__ */ new Set([
|
|
13156
13883
|
"user",
|
|
13157
13884
|
"role",
|
|
@@ -13174,22 +13901,37 @@ var BUDGET_ENTITY_TYPE_BY_SINGULAR = {
|
|
|
13174
13901
|
};
|
|
13175
13902
|
var addBudgetField = async (requestedFields, result, tableSingular, user) => {
|
|
13176
13903
|
if (!requestedFields.includes("budget")) return result;
|
|
13904
|
+
const entityType = BUDGET_ENTITY_TYPE_BY_SINGULAR[tableSingular];
|
|
13177
13905
|
const scope = user?.role?.budget_management;
|
|
13178
|
-
const
|
|
13179
|
-
|
|
13906
|
+
const canReadAll = !!user?.super_admin || scope === "read" || scope === "write";
|
|
13907
|
+
const memberView = !canReadAll && entityType === "project";
|
|
13908
|
+
if (!canReadAll && !memberView || result?.id == null || !entityType) {
|
|
13180
13909
|
result.budget = null;
|
|
13181
13910
|
return result;
|
|
13182
13911
|
}
|
|
13183
|
-
const
|
|
13184
|
-
|
|
13912
|
+
const map = await getTagBudgetMap();
|
|
13913
|
+
const tag = budgetTagFor(entityType, result.id);
|
|
13914
|
+
const info = tag ? map[tag] ?? null : null;
|
|
13915
|
+
if (!memberView) {
|
|
13916
|
+
result.budget = info;
|
|
13917
|
+
return result;
|
|
13918
|
+
}
|
|
13919
|
+
if (!info) {
|
|
13185
13920
|
result.budget = null;
|
|
13186
13921
|
return result;
|
|
13187
13922
|
}
|
|
13188
|
-
const
|
|
13189
|
-
|
|
13190
|
-
|
|
13923
|
+
const settings = await getBudgetSettings();
|
|
13924
|
+
result.budget = {
|
|
13925
|
+
spend: info.spend,
|
|
13926
|
+
max_budget: info.max_budget,
|
|
13927
|
+
budget_duration: info.budget_duration,
|
|
13928
|
+
budget_reset_at: info.budget_reset_at,
|
|
13929
|
+
display: settings.user_budget_display
|
|
13930
|
+
};
|
|
13191
13931
|
return result;
|
|
13192
13932
|
};
|
|
13933
|
+
|
|
13934
|
+
// src/graphql/utilities/sanitize-and-hydrate-fields.ts
|
|
13193
13935
|
var addProviderFields = async (args, requestedFields, providers, result, tools, user, contexts) => {
|
|
13194
13936
|
let provider;
|
|
13195
13937
|
let modelRow;
|
|
@@ -13439,6 +14181,14 @@ var finalizeRequestedFields = async ({
|
|
|
13439
14181
|
if (!requestedFields.includes("provider")) {
|
|
13440
14182
|
delete result.provider;
|
|
13441
14183
|
}
|
|
14184
|
+
if (requestedFields.includes("guest_has_password")) {
|
|
14185
|
+
result.guest_has_password = !!result.guest_password_hash;
|
|
14186
|
+
}
|
|
14187
|
+
}
|
|
14188
|
+
for (const field of table.fields) {
|
|
14189
|
+
if (field.hidden === true) {
|
|
14190
|
+
delete result[field.name];
|
|
14191
|
+
}
|
|
13442
14192
|
}
|
|
13443
14193
|
if (BUDGET_ENTITY_SINGULARS.has(table.name.singular)) {
|
|
13444
14194
|
result = await addBudgetField(requestedFields, result, table.name.singular, user);
|
|
@@ -13510,7 +14260,7 @@ var itemsPaginationRequest = async ({
|
|
|
13510
14260
|
let dataQuery = db2(tableName);
|
|
13511
14261
|
dataQuery = applyFilters(dataQuery, filters, table);
|
|
13512
14262
|
dataQuery = applyAccessControl(table, dataQuery, user);
|
|
13513
|
-
dataQuery = applySorting(dataQuery, sort);
|
|
14263
|
+
dataQuery = applySorting(dataQuery, sort, void 0, table);
|
|
13514
14264
|
if (page > 1) {
|
|
13515
14265
|
dataQuery = dataQuery.offset((page - 1) * limit);
|
|
13516
14266
|
}
|
|
@@ -13533,8 +14283,20 @@ var removeProviderFields = (requestedFields) => {
|
|
|
13533
14283
|
return filtered;
|
|
13534
14284
|
};
|
|
13535
14285
|
var sanitizeRequestedFields = (table, requestedFields) => {
|
|
14286
|
+
const hiddenNames = new Set(
|
|
14287
|
+
table.fields.filter((f) => f.hidden === true).map((f) => f.name)
|
|
14288
|
+
);
|
|
14289
|
+
if (hiddenNames.size > 0) {
|
|
14290
|
+
requestedFields = requestedFields.filter((f) => !hiddenNames.has(f));
|
|
14291
|
+
}
|
|
13536
14292
|
if (table.name.singular === "agent") {
|
|
13537
14293
|
requestedFields = removeProviderFields(requestedFields);
|
|
14294
|
+
if (requestedFields.includes("guest_has_password")) {
|
|
14295
|
+
requestedFields = requestedFields.filter(
|
|
14296
|
+
(field) => field !== "guest_has_password"
|
|
14297
|
+
);
|
|
14298
|
+
requestedFields.push("guest_password_hash");
|
|
14299
|
+
}
|
|
13538
14300
|
}
|
|
13539
14301
|
if (["user", "role", "team", "project", "agent"].includes(table.name.singular)) {
|
|
13540
14302
|
requestedFields = requestedFields.filter((field) => field !== "budget");
|
|
@@ -13632,7 +14394,7 @@ function createQueries(table, providers, tools, contexts) {
|
|
|
13632
14394
|
let query = db2.from(tableNamePlural).select(sanitizedFields);
|
|
13633
14395
|
query = applyFilters(query, filters, table);
|
|
13634
14396
|
query = applyAccessControl(table, query, context.user);
|
|
13635
|
-
query = applySorting(query, sort);
|
|
14397
|
+
query = applySorting(query, sort, void 0, table);
|
|
13636
14398
|
let result = await query.first();
|
|
13637
14399
|
return finalizeRequestedFields({
|
|
13638
14400
|
args,
|
|
@@ -13683,6 +14445,7 @@ function createQueries(table, providers, tools, contexts) {
|
|
|
13683
14445
|
query = applyAccessControl(table, query, context.user);
|
|
13684
14446
|
query = query.limit(limit);
|
|
13685
14447
|
if (groupBy) {
|
|
14448
|
+
assertAllowedField(table, groupBy, "group");
|
|
13686
14449
|
query = query.select(groupBy).groupBy(groupBy);
|
|
13687
14450
|
if (tableNamePlural === "tracking") {
|
|
13688
14451
|
query = query.sum("total as count");
|
|
@@ -13919,7 +14682,7 @@ var encryptSensitiveFields = (input) => {
|
|
|
13919
14682
|
};
|
|
13920
14683
|
|
|
13921
14684
|
// src/graphql/mutations/index.ts
|
|
13922
|
-
var
|
|
14685
|
+
var import_bcryptjs4 = __toESM(require("bcryptjs"), 1);
|
|
13923
14686
|
init_statistics();
|
|
13924
14687
|
|
|
13925
14688
|
// src/exulu/routines/run-state.ts
|
|
@@ -14207,7 +14970,77 @@ var handleRBACUpdate = async (db2, entityName, resourceId, rbacData, existingRba
|
|
|
14207
14970
|
}
|
|
14208
14971
|
};
|
|
14209
14972
|
|
|
14973
|
+
// src/graphql/utilities/agent-guest-fields.ts
|
|
14974
|
+
init_cjs_shims();
|
|
14975
|
+
|
|
14976
|
+
// src/exulu/shared-artifacts.ts
|
|
14977
|
+
init_cjs_shims();
|
|
14978
|
+
var import_bcryptjs3 = __toESM(require("bcryptjs"), 1);
|
|
14979
|
+
var normalizeS3Key = (key, bucket) => {
|
|
14980
|
+
const segments = key.split("/").filter((s, i) => !(i === 0 && s === "")).map((s) => decodeURIComponent(s));
|
|
14981
|
+
if (segments[0] === bucket) segments.shift();
|
|
14982
|
+
return segments.join("/");
|
|
14983
|
+
};
|
|
14984
|
+
var isHtmlKey = (key) => /\.html?$/i.test(key);
|
|
14985
|
+
var deriveFilename = (key) => {
|
|
14986
|
+
const base = key.split("/").pop() ?? key;
|
|
14987
|
+
return base.split("_EXULU_").pop() ?? base;
|
|
14988
|
+
};
|
|
14989
|
+
var slugifyShareName = (input) => deriveFilename(input).toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
14990
|
+
var isExpired = (expiresAt, now) => {
|
|
14991
|
+
if (!expiresAt) return false;
|
|
14992
|
+
return new Date(expiresAt).getTime() <= now.getTime();
|
|
14993
|
+
};
|
|
14994
|
+
var validateCreateInput = (input, now) => {
|
|
14995
|
+
if (!input.s3key) return { ok: false, message: "s3key is required." };
|
|
14996
|
+
if (!input.name) return { ok: false, message: "name is required." };
|
|
14997
|
+
const mode = input.auth_mode;
|
|
14998
|
+
if (mode !== "public" && mode !== "password" && mode !== "regular") {
|
|
14999
|
+
return { ok: false, message: "auth_mode must be public, password, or regular." };
|
|
15000
|
+
}
|
|
15001
|
+
if (mode === "password" && !input.password) {
|
|
15002
|
+
return { ok: false, message: "A password is required for password mode." };
|
|
15003
|
+
}
|
|
15004
|
+
if (input.expires_at && Number.isNaN(new Date(input.expires_at).getTime())) {
|
|
15005
|
+
return { ok: false, message: "expires_at is not a valid date." };
|
|
15006
|
+
}
|
|
15007
|
+
if (input.expires_at && isExpired(input.expires_at, now)) {
|
|
15008
|
+
return { ok: false, message: "expires_at must be in the future." };
|
|
15009
|
+
}
|
|
15010
|
+
return { ok: true };
|
|
15011
|
+
};
|
|
15012
|
+
var hashSharePassword = (password) => import_bcryptjs3.default.hash(password, 10);
|
|
15013
|
+
var verifySharePassword = (password, hash) => import_bcryptjs3.default.compare(password, hash);
|
|
15014
|
+
var contentHeadersFor = (key, contentType, filename) => {
|
|
15015
|
+
if (isHtmlKey(key)) return { contentType: "text/html; charset=utf-8" };
|
|
15016
|
+
return {
|
|
15017
|
+
contentType: contentType || "application/octet-stream",
|
|
15018
|
+
disposition: `attachment; filename="${filename.replace(/"/g, "")}"`
|
|
15019
|
+
};
|
|
15020
|
+
};
|
|
15021
|
+
var getSharedArtifactByName = (db2, name) => db2("shared_artifacts").where({ name }).first();
|
|
15022
|
+
|
|
15023
|
+
// src/graphql/utilities/agent-guest-fields.ts
|
|
15024
|
+
var VALID_GUEST_AUTH_MODES = /* @__PURE__ */ new Set(["public", "password", "regular"]);
|
|
15025
|
+
var applyAgentGuestFieldTransforms = async (input) => {
|
|
15026
|
+
if (input.guest_auth_mode !== void 0 && !VALID_GUEST_AUTH_MODES.has(input.guest_auth_mode)) {
|
|
15027
|
+
throw new Error(
|
|
15028
|
+
'guest_auth_mode must be "public", "password", or "regular".'
|
|
15029
|
+
);
|
|
15030
|
+
}
|
|
15031
|
+
delete input.guest_password_hash;
|
|
15032
|
+
if (typeof input.guest_password === "string" && input.guest_password.length > 0) {
|
|
15033
|
+
input.guest_password_hash = await hashSharePassword(input.guest_password);
|
|
15034
|
+
}
|
|
15035
|
+
delete input.guest_password;
|
|
15036
|
+
if (input.guest_auth_mode !== void 0 && input.guest_auth_mode !== "password") {
|
|
15037
|
+
input.guest_password_hash = null;
|
|
15038
|
+
}
|
|
15039
|
+
return input;
|
|
15040
|
+
};
|
|
15041
|
+
|
|
14210
15042
|
// src/graphql/mutations/index.ts
|
|
15043
|
+
var VALID_RIGHTS_MODES = ["private", "users", "roles", "teams", "public"];
|
|
14211
15044
|
var postprocessDeletion = async ({
|
|
14212
15045
|
table,
|
|
14213
15046
|
requestedFields,
|
|
@@ -14401,7 +15234,9 @@ function createMutations(table, providers, contexts, tools, config) {
|
|
|
14401
15234
|
entity: table.name.singular,
|
|
14402
15235
|
target_resource_id: id,
|
|
14403
15236
|
access_type: "Role",
|
|
14404
|
-
|
|
15237
|
+
// auth.ts hydrates user.role into the full roles row when it
|
|
15238
|
+
// exists; unhydrated it is still the uuid string.
|
|
15239
|
+
role_id: user.role?.id ?? user.role,
|
|
14405
15240
|
rights: "write"
|
|
14406
15241
|
}).first();
|
|
14407
15242
|
if (rbacRecord) {
|
|
@@ -14414,7 +15249,8 @@ function createMutations(table, providers, contexts, tools, config) {
|
|
|
14414
15249
|
entity: table.name.singular,
|
|
14415
15250
|
target_resource_id: id,
|
|
14416
15251
|
access_type: "Team",
|
|
14417
|
-
|
|
15252
|
+
// Same best-effort hydration as user.role above.
|
|
15253
|
+
team_id: user.team?.id ?? user.team,
|
|
14418
15254
|
rights: "write"
|
|
14419
15255
|
}).first();
|
|
14420
15256
|
if (rbacRecord) {
|
|
@@ -14444,6 +15280,9 @@ function createMutations(table, providers, contexts, tools, config) {
|
|
|
14444
15280
|
if (item.rights_mode) {
|
|
14445
15281
|
item.rights_mode = "private";
|
|
14446
15282
|
}
|
|
15283
|
+
if (tableNamePlural === "agents" && "guest_access" in item) {
|
|
15284
|
+
item.guest_access = false;
|
|
15285
|
+
}
|
|
14447
15286
|
if (item.created_at) {
|
|
14448
15287
|
item.created_at = /* @__PURE__ */ new Date();
|
|
14449
15288
|
}
|
|
@@ -14512,9 +15351,12 @@ function createMutations(table, providers, contexts, tools, config) {
|
|
|
14512
15351
|
}
|
|
14513
15352
|
if (table.name.singular === "user" && input.password) {
|
|
14514
15353
|
console.log("[EXULU] Hashing password", input.password);
|
|
14515
|
-
input.password = await
|
|
15354
|
+
input.password = await import_bcryptjs4.default.hash(input.password, SALT_ROUNDS);
|
|
14516
15355
|
console.log("[EXULU] Hashed password", input.password);
|
|
14517
15356
|
}
|
|
15357
|
+
if (table.name.singular === "agent") {
|
|
15358
|
+
input = await applyAgentGuestFieldTransforms(input);
|
|
15359
|
+
}
|
|
14518
15360
|
Object.keys(input).forEach((key) => {
|
|
14519
15361
|
if (table.fields.find((field) => field.name === key)?.type === "json") {
|
|
14520
15362
|
if (typeof input[key] === "object" || Array.isArray(input[key])) {
|
|
@@ -14528,10 +15370,15 @@ function createMutations(table, providers, contexts, tools, config) {
|
|
|
14528
15370
|
input.id = db2.fn.uuid();
|
|
14529
15371
|
}
|
|
14530
15372
|
}
|
|
15373
|
+
if (table.RBAC && input.rights_mode != null && !VALID_RIGHTS_MODES.includes(input.rights_mode)) {
|
|
15374
|
+
throw new Error(
|
|
15375
|
+
`Invalid rights_mode "${input.rights_mode}" \u2014 expected one of: ${VALID_RIGHTS_MODES.join(", ")}`
|
|
15376
|
+
);
|
|
15377
|
+
}
|
|
14531
15378
|
const columns = await db2(tableNamePlural).columnInfo();
|
|
14532
15379
|
const insert = db2(tableNamePlural).insert({
|
|
14533
15380
|
...input,
|
|
14534
|
-
...table.RBAC ? { rights_mode: "private" } : {}
|
|
15381
|
+
...table.RBAC ? { rights_mode: input.rights_mode ?? "private" } : {}
|
|
14535
15382
|
}).returning(Object.keys(columns));
|
|
14536
15383
|
if (args.upsert) {
|
|
14537
15384
|
insert.onConflict().merge();
|
|
@@ -14579,9 +15426,12 @@ function createMutations(table, providers, contexts, tools, config) {
|
|
|
14579
15426
|
}
|
|
14580
15427
|
if (table.name.singular === "user" && input.password) {
|
|
14581
15428
|
console.log("[EXULU] Hashing password", input.password);
|
|
14582
|
-
input.password = await
|
|
15429
|
+
input.password = await import_bcryptjs4.default.hash(input.password, SALT_ROUNDS);
|
|
14583
15430
|
console.log("[EXULU] Hashed password", input.password);
|
|
14584
15431
|
}
|
|
15432
|
+
if (table.name.singular === "agent") {
|
|
15433
|
+
input = await applyAgentGuestFieldTransforms(input);
|
|
15434
|
+
}
|
|
14585
15435
|
Object.keys(input).forEach((key) => {
|
|
14586
15436
|
if (table.fields.find((field) => field.name === key)?.type === "json") {
|
|
14587
15437
|
if (typeof input[key] === "object" || Array.isArray(input[key])) {
|
|
@@ -14652,9 +15502,12 @@ function createMutations(table, providers, contexts, tools, config) {
|
|
|
14652
15502
|
}
|
|
14653
15503
|
if (table.name.singular === "user" && input.password) {
|
|
14654
15504
|
console.log("[EXULU] Hashing password", input.password);
|
|
14655
|
-
input.password = await
|
|
15505
|
+
input.password = await import_bcryptjs4.default.hash(input.password, SALT_ROUNDS);
|
|
14656
15506
|
console.log("[EXULU] Hashed password", input.password);
|
|
14657
15507
|
}
|
|
15508
|
+
if (table.name.singular === "agent") {
|
|
15509
|
+
input = await applyAgentGuestFieldTransforms(input);
|
|
15510
|
+
}
|
|
14658
15511
|
Object.keys(input).forEach((key) => {
|
|
14659
15512
|
if (table.fields.find((field) => field.name === key)?.type === "json") {
|
|
14660
15513
|
if (typeof input[key] === "object" || Array.isArray(input[key])) {
|
|
@@ -15138,6 +15991,7 @@ init_cjs_shims();
|
|
|
15138
15991
|
init_pipeline();
|
|
15139
15992
|
init_check_record_access();
|
|
15140
15993
|
init_singleton();
|
|
15994
|
+
init_kb_editor_config();
|
|
15141
15995
|
var getEnabledTools = async (agent, allExuluTools, allContexts, disabledTools = [], providers, user) => {
|
|
15142
15996
|
let enabledTools = [];
|
|
15143
15997
|
if (agent.tools) {
|
|
@@ -15155,6 +16009,9 @@ var getEnabledTools = async (agent, allExuluTools, allContexts, disabledTools =
|
|
|
15155
16009
|
model: void 0
|
|
15156
16010
|
});
|
|
15157
16011
|
}
|
|
16012
|
+
if (id === KB_EDITOR_TOOL_ID) {
|
|
16013
|
+
return null;
|
|
16014
|
+
}
|
|
15158
16015
|
if (type === "agent") {
|
|
15159
16016
|
if (id === agent.id) {
|
|
15160
16017
|
return null;
|
|
@@ -15195,7 +16052,7 @@ init_resolve_model();
|
|
|
15195
16052
|
init_client();
|
|
15196
16053
|
var import_api = require("@opentelemetry/api");
|
|
15197
16054
|
var import_uuid3 = require("uuid");
|
|
15198
|
-
var
|
|
16055
|
+
var import_ai7 = require("ai");
|
|
15199
16056
|
var import_crypto_js7 = require("crypto-js");
|
|
15200
16057
|
init_statistics();
|
|
15201
16058
|
init_sanitize_tool_name();
|
|
@@ -15364,11 +16221,11 @@ var autoDeclineStaleApprovals = (messages) => {
|
|
|
15364
16221
|
};
|
|
15365
16222
|
|
|
15366
16223
|
// src/exulu/provider.ts
|
|
15367
|
-
var
|
|
16224
|
+
var import_zod16 = require("zod");
|
|
15368
16225
|
init_tool();
|
|
15369
16226
|
init_resolve_model();
|
|
15370
16227
|
init_statistics2();
|
|
15371
|
-
var
|
|
16228
|
+
var import_ai6 = require("ai");
|
|
15372
16229
|
|
|
15373
16230
|
// src/utils/generate-slug.ts
|
|
15374
16231
|
init_cjs_shims();
|
|
@@ -15390,7 +16247,7 @@ init_entitlements();
|
|
|
15390
16247
|
|
|
15391
16248
|
// src/exulu/task-description.ts
|
|
15392
16249
|
init_cjs_shims();
|
|
15393
|
-
var
|
|
16250
|
+
var import_ai5 = require("ai");
|
|
15394
16251
|
init_client();
|
|
15395
16252
|
var AGENT_VISUALIZATION_ENABLED = process.env.NEXT_PUBLIC_AGENT_VISUALIZATION === "true";
|
|
15396
16253
|
async function setSessionCurrentTask({
|
|
@@ -15405,7 +16262,7 @@ async function setSessionCurrentTask({
|
|
|
15405
16262
|
}
|
|
15406
16263
|
const truncated = userMessage.slice(0, 500);
|
|
15407
16264
|
console.log("[EXULU] Generating text for session current task: " + truncated);
|
|
15408
|
-
const { text } = await (0,
|
|
16265
|
+
const { text } = await (0, import_ai5.generateText)({
|
|
15409
16266
|
model,
|
|
15410
16267
|
prompt: `You are a task labeler for an agent monitoring dashboard visible to all employees.
|
|
15411
16268
|
Given the user message below, write a 4\u20138 word present-tense description of what the AI assistant is working on.
|
|
@@ -15489,6 +16346,38 @@ var isRunSessionMetadata = (metadata) => {
|
|
|
15489
16346
|
return typeof parsed === "object" && parsed !== null && !!parsed.job_result_id;
|
|
15490
16347
|
};
|
|
15491
16348
|
|
|
16349
|
+
// src/exulu/auth/sanitize-ui-messages.ts
|
|
16350
|
+
init_cjs_shims();
|
|
16351
|
+
init_scrub_text();
|
|
16352
|
+
var sanitizeAuthPayloadsInUiMessages = (messages) => messages.map((message) => {
|
|
16353
|
+
if (message.role !== "assistant" || !Array.isArray(message.parts)) {
|
|
16354
|
+
return message;
|
|
16355
|
+
}
|
|
16356
|
+
let changed = false;
|
|
16357
|
+
const parts = message.parts.map((part) => {
|
|
16358
|
+
const output = part?.output;
|
|
16359
|
+
if (output && typeof output === "object" && output.credentialRequest) {
|
|
16360
|
+
changed = true;
|
|
16361
|
+
return { ...part, output: { result: SCRUBBED_CREDENTIAL_TEXT } };
|
|
16362
|
+
}
|
|
16363
|
+
if (output && typeof output === "object" && output.oauth?.authorizationUrl) {
|
|
16364
|
+
changed = true;
|
|
16365
|
+
return { ...part, output: { result: SCRUBBED_OAUTH_TEXT } };
|
|
16366
|
+
}
|
|
16367
|
+
return part;
|
|
16368
|
+
});
|
|
16369
|
+
return changed ? { ...message, parts } : message;
|
|
16370
|
+
});
|
|
16371
|
+
|
|
16372
|
+
// src/exulu/auth/guardrail.ts
|
|
16373
|
+
init_cjs_shims();
|
|
16374
|
+
var CREDENTIAL_GUARDRAIL = `Credential safety:
|
|
16375
|
+
Some tools collect credentials (API keys, passwords, tokens) through a secure form shown directly to the user in the chat UI. Credentials are never entered in the conversation itself.
|
|
16376
|
+
- Never ask the user to type credential values into the chat.
|
|
16377
|
+
- If the user pastes a credential value into the chat anyway, do not repeat it, do not store it, and do not pass it to any tool. Tell them to use the secure form instead (calling the tool again shows the form if it is no longer visible).
|
|
16378
|
+
- After the user confirms they submitted the form, call the tool again.`;
|
|
16379
|
+
var credentialGuardrailBlock = (currentTools) => currentTools?.some((t) => t.authentication?.authType === "user_credentials") ? CREDENTIAL_GUARDRAIL : null;
|
|
16380
|
+
|
|
15492
16381
|
// src/exulu/provider.ts
|
|
15493
16382
|
var ExuluProvider = class {
|
|
15494
16383
|
// Must begin with a letter (a-z) or underscore (_). Subsequent characters in a name can be letters, digits (0-9), or
|
|
@@ -15568,9 +16457,9 @@ var ExuluProvider = class {
|
|
|
15568
16457
|
name: `${agent.name}`,
|
|
15569
16458
|
type: "agent",
|
|
15570
16459
|
category: "agents",
|
|
15571
|
-
inputSchema:
|
|
15572
|
-
prompt:
|
|
15573
|
-
information:
|
|
16460
|
+
inputSchema: import_zod16.z.object({
|
|
16461
|
+
prompt: import_zod16.z.string().describe("The prompt (usually a question for the agent) to send to the agent."),
|
|
16462
|
+
information: import_zod16.z.string().describe("A summary of relevant context / information from the current session")
|
|
15574
16463
|
}),
|
|
15575
16464
|
description: `This tool calls an agent named: ${agent.name}. The agent does the following: ${agent.description}.`,
|
|
15576
16465
|
config: [],
|
|
@@ -15695,7 +16584,7 @@ var ExuluProvider = class {
|
|
|
15695
16584
|
const previousMessagesContent = previousMessages.map(
|
|
15696
16585
|
(message) => JSON.parse(message.content)
|
|
15697
16586
|
);
|
|
15698
|
-
messages = await (0,
|
|
16587
|
+
messages = await (0, import_ai6.validateUIMessages)({
|
|
15699
16588
|
// append the new message to the previous messages:
|
|
15700
16589
|
messages: [...previousMessagesContent, ...messages]
|
|
15701
16590
|
});
|
|
@@ -15858,6 +16747,10 @@ var ExuluProvider = class {
|
|
|
15858
16747
|
|
|
15859
16748
|
When a tool execution is not approved by the user, do not retry it unless explicitly asked by the user. ' +
|
|
15860
16749
|
'Inform the user that the action was not performed.`;
|
|
16750
|
+
const credentialGuardrail = credentialGuardrailBlock(currentTools);
|
|
16751
|
+
if (credentialGuardrail) {
|
|
16752
|
+
system += "\n\n" + credentialGuardrail;
|
|
16753
|
+
}
|
|
15861
16754
|
if (prompt) {
|
|
15862
16755
|
let result = { object: null, text: "" };
|
|
15863
16756
|
let inputTokens = 0;
|
|
@@ -15866,7 +16759,7 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
15866
16759
|
"[EXULU] Generating text for agent: " + this.name,
|
|
15867
16760
|
"with prompt: " + prompt?.slice(0, 100) + "..."
|
|
15868
16761
|
);
|
|
15869
|
-
const output = await (0,
|
|
16762
|
+
const output = await (0, import_ai6.generateText)({
|
|
15870
16763
|
temperature: 0,
|
|
15871
16764
|
// TODO Make this configurable
|
|
15872
16765
|
model,
|
|
@@ -15878,7 +16771,7 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
15878
16771
|
// assistant's response, no follow-up text turn is wanted (same
|
|
15879
16772
|
// reasoning as question_ask: the UI artifact is the message).
|
|
15880
16773
|
prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget), imageAttachmentGuard()),
|
|
15881
|
-
stopWhen: [(0,
|
|
16774
|
+
stopWhen: [(0, import_ai6.stepCountIs)(turnBudget), (0, import_ai6.hasToolCall)("image_generation")]
|
|
15882
16775
|
});
|
|
15883
16776
|
console.log("[EXULU] Output: " + JSON.stringify(output, null, 2));
|
|
15884
16777
|
const {
|
|
@@ -15929,19 +16822,23 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
15929
16822
|
"[EXULU] Generating text for agent: " + this.name,
|
|
15930
16823
|
"with messages: " + messages.length
|
|
15931
16824
|
);
|
|
15932
|
-
const { text, totalUsage } = await (0,
|
|
16825
|
+
const { text, totalUsage } = await (0, import_ai6.generateText)({
|
|
15933
16826
|
temperature: 0,
|
|
15934
16827
|
// TODO Make this configurable
|
|
15935
16828
|
model,
|
|
15936
16829
|
// Should be a LanguageModelV1
|
|
15937
16830
|
system,
|
|
15938
|
-
|
|
15939
|
-
|
|
16831
|
+
// tools: applies each tool's toModelOutput to historical tool results;
|
|
16832
|
+
// sanitize: guarantees auth payloads never reach the model regardless
|
|
16833
|
+
// of part encoding (spec 2026-07-22 §1.2).
|
|
16834
|
+
messages: await (0, import_ai6.convertToModelMessages)(sanitizeAuthPayloadsInUiMessages(messages), {
|
|
16835
|
+
ignoreIncompleteToolCalls: true,
|
|
16836
|
+
tools
|
|
15940
16837
|
}),
|
|
15941
16838
|
maxRetries: 2,
|
|
15942
16839
|
tools,
|
|
15943
16840
|
prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget), imageAttachmentGuard()),
|
|
15944
|
-
stopWhen: [(0,
|
|
16841
|
+
stopWhen: [(0, import_ai6.stepCountIs)(turnBudget), (0, import_ai6.hasToolCall)("image_generation")]
|
|
15945
16842
|
});
|
|
15946
16843
|
if (statistics) {
|
|
15947
16844
|
await Promise.all([
|
|
@@ -16112,7 +17009,7 @@ ${guardedText}
|
|
|
16112
17009
|
previousMessagesContent = previousMessages2.map((message2) => JSON.parse(message2.content));
|
|
16113
17010
|
}
|
|
16114
17011
|
const model = languageModel;
|
|
16115
|
-
messages = await (0,
|
|
17012
|
+
messages = await (0, import_ai6.validateUIMessages)({
|
|
16116
17013
|
// append the new message to the previous messages:
|
|
16117
17014
|
messages: [...previousMessagesContent, message]
|
|
16118
17015
|
});
|
|
@@ -16256,6 +17153,10 @@ ${skillsList}
|
|
|
16256
17153
|
|
|
16257
17154
|
When a tool execution is not approved by the user, do not retry it unless explicitly asked by the user. ' +
|
|
16258
17155
|
'Inform the user that the action was not performed.`;
|
|
17156
|
+
const credentialGuardrail = credentialGuardrailBlock(currentTools);
|
|
17157
|
+
if (credentialGuardrail) {
|
|
17158
|
+
system += "\n\n" + credentialGuardrail;
|
|
17159
|
+
}
|
|
16259
17160
|
console.log("[EXULU] Tools", currentTools?.map((x) => x.name));
|
|
16260
17161
|
console.log("[EXULU] Skills", currentSkills?.map((x) => x.name));
|
|
16261
17162
|
const tools = await convertExuluToolsToAiSdkTools(
|
|
@@ -16334,13 +17235,17 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
16334
17235
|
Object.keys(tools)
|
|
16335
17236
|
);
|
|
16336
17237
|
const turnBudget = resolveTurnStepBudget(maxStepCount, agent);
|
|
16337
|
-
const result = (0,
|
|
17238
|
+
const result = (0, import_ai6.streamText)({
|
|
16338
17239
|
temperature: 0,
|
|
16339
17240
|
// TODO Make this configurable
|
|
16340
17241
|
model,
|
|
16341
17242
|
// Should be a LanguageModelV1
|
|
16342
|
-
|
|
16343
|
-
|
|
17243
|
+
// tools: applies each tool's toModelOutput to historical tool results;
|
|
17244
|
+
// sanitize: guarantees auth payloads never reach the model regardless
|
|
17245
|
+
// of part encoding (spec 2026-07-22 §1.2).
|
|
17246
|
+
messages: await (0, import_ai6.convertToModelMessages)(sanitizeAuthPayloadsInUiMessages(messages), {
|
|
17247
|
+
ignoreIncompleteToolCalls: true,
|
|
17248
|
+
tools
|
|
16344
17249
|
}),
|
|
16345
17250
|
// PrepareStep could be used here to set the model
|
|
16346
17251
|
// for the first step or change other parameters.
|
|
@@ -16360,7 +17265,7 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
16360
17265
|
},
|
|
16361
17266
|
// todo allow configuring the step budget per skill
|
|
16362
17267
|
prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget), imageAttachmentGuard()),
|
|
16363
|
-
stopWhen: [(0,
|
|
17268
|
+
stopWhen: [(0, import_ai6.stepCountIs)(turnBudget), (0, import_ai6.hasToolCall)("image_generation")]
|
|
16364
17269
|
});
|
|
16365
17270
|
return {
|
|
16366
17271
|
stream: result,
|
|
@@ -18614,7 +19519,7 @@ var transcriptionService = {
|
|
|
18614
19519
|
|
|
18615
19520
|
// src/exulu/recall/service.ts
|
|
18616
19521
|
init_cjs_shims();
|
|
18617
|
-
var
|
|
19522
|
+
var import_ai8 = require("ai");
|
|
18618
19523
|
init_singleton();
|
|
18619
19524
|
init_client();
|
|
18620
19525
|
init_resolve_model();
|
|
@@ -18688,10 +19593,25 @@ var RecallApiError = class extends Error {
|
|
|
18688
19593
|
}
|
|
18689
19594
|
};
|
|
18690
19595
|
var jitterMs = () => 1e3 * Math.ceil(Math.random() * 5);
|
|
19596
|
+
var API_TIMEOUT_MS = 6e4;
|
|
19597
|
+
var DOWNLOAD_TIMEOUT_MS = 3e5;
|
|
18691
19598
|
async function fetch_with_retry(args) {
|
|
18692
|
-
const { url, options, max_attempts = 6 } = args;
|
|
19599
|
+
const { url, options, max_attempts = 6, timeout_ms, retry_on_reject } = args;
|
|
18693
19600
|
for (let attempt = 1; attempt <= max_attempts; attempt++) {
|
|
18694
|
-
|
|
19601
|
+
let response;
|
|
19602
|
+
try {
|
|
19603
|
+
response = await fetch(
|
|
19604
|
+
url,
|
|
19605
|
+
timeout_ms ? { ...options, signal: AbortSignal.timeout(timeout_ms) } : options
|
|
19606
|
+
);
|
|
19607
|
+
} catch (err) {
|
|
19608
|
+
if (!retry_on_reject || attempt === max_attempts) throw err;
|
|
19609
|
+
console.log(
|
|
19610
|
+
`[EXULU-RECALL] fetch error from ${url} (${err.message}); retrying in ~5s (attempt ${attempt}/${max_attempts})`
|
|
19611
|
+
);
|
|
19612
|
+
await new Promise((resolve8) => setTimeout(resolve8, 5e3 + jitterMs()));
|
|
19613
|
+
continue;
|
|
19614
|
+
}
|
|
18695
19615
|
let wait_for = null;
|
|
18696
19616
|
switch (response.status) {
|
|
18697
19617
|
case 429:
|
|
@@ -18732,7 +19652,9 @@ var request2 = async (path2, init = {}) => {
|
|
|
18732
19652
|
...init.body ? { "content-type": "application/json" } : {},
|
|
18733
19653
|
...init.headers ?? {}
|
|
18734
19654
|
}
|
|
18735
|
-
}
|
|
19655
|
+
},
|
|
19656
|
+
timeout_ms: API_TIMEOUT_MS,
|
|
19657
|
+
retry_on_reject: (init.method ?? "GET").toUpperCase() === "GET"
|
|
18736
19658
|
});
|
|
18737
19659
|
if (!response.ok) {
|
|
18738
19660
|
const body = await response.text();
|
|
@@ -18804,7 +19726,9 @@ var recallClient = {
|
|
|
18804
19726
|
downloadTranscript: async (downloadUrl) => {
|
|
18805
19727
|
const response = await fetch_with_retry({
|
|
18806
19728
|
url: downloadUrl,
|
|
18807
|
-
options: { method: "GET" }
|
|
19729
|
+
options: { method: "GET" },
|
|
19730
|
+
timeout_ms: DOWNLOAD_TIMEOUT_MS,
|
|
19731
|
+
retry_on_reject: true
|
|
18808
19732
|
});
|
|
18809
19733
|
if (!response.ok) {
|
|
18810
19734
|
const body = await response.text();
|
|
@@ -18858,6 +19782,12 @@ var durationFromSegments = (segments) => {
|
|
|
18858
19782
|
// src/exulu/recall/service.ts
|
|
18859
19783
|
var TABLE3 = "transcription_jobs";
|
|
18860
19784
|
var DEFAULT_BOT_NAME = "Company Notetaker";
|
|
19785
|
+
var RECONCILE_STALE_MS = 10 * 60 * 1e3;
|
|
19786
|
+
var RECONCILE_PROBE_QUIET_MS = 60 * 60 * 1e3;
|
|
19787
|
+
var RECONCILE_GIVE_UP_MS = 24 * 60 * 60 * 1e3;
|
|
19788
|
+
var POST_PROCESSING_REDO_STALE_MS = 30 * 60 * 1e3;
|
|
19789
|
+
var POST_PROCESSING_PROMPT_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
19790
|
+
var BOT_ENDED_STATUSES = ["done", "call_ended"];
|
|
18861
19791
|
var log4 = (msg) => console.log(`[EXULU-RECALL] ${msg}`);
|
|
18862
19792
|
var parseJson = (v) => {
|
|
18863
19793
|
if (v == null) return null;
|
|
@@ -18934,7 +19864,9 @@ var recallService = {
|
|
|
18934
19864
|
target_rights_mode: input.target_rights_mode ?? "private",
|
|
18935
19865
|
target_rbac_users: input.target_rbac_users ? JSON.stringify(input.target_rbac_users) : null,
|
|
18936
19866
|
target_rbac_roles: input.target_rbac_roles ? JSON.stringify(input.target_rbac_roles) : null,
|
|
18937
|
-
|
|
19867
|
+
// Normalized to NULL when empty so the reconcile sweep's redo select
|
|
19868
|
+
// ("prompts configured but never ran") can never match a no-prompt row.
|
|
19869
|
+
post_processing_prompts: input.post_processing_prompts?.length ? JSON.stringify(input.post_processing_prompts) : null,
|
|
18938
19870
|
rights_mode: "private",
|
|
18939
19871
|
created_by: input.userId,
|
|
18940
19872
|
createdAt: now,
|
|
@@ -19003,7 +19935,13 @@ var recallService = {
|
|
|
19003
19935
|
return;
|
|
19004
19936
|
}
|
|
19005
19937
|
const { db: db2 } = await postgresClient();
|
|
19006
|
-
const claimed = await db2(TABLE3).where({ id: jobId }).whereNotIn("status", [
|
|
19938
|
+
const claimed = await db2(TABLE3).where({ id: jobId }).whereNotIn("status", [
|
|
19939
|
+
"transcribing",
|
|
19940
|
+
"awaiting_review",
|
|
19941
|
+
"saved",
|
|
19942
|
+
"failed",
|
|
19943
|
+
"cancelled"
|
|
19944
|
+
]).update({
|
|
19007
19945
|
recall_recording_id: recordingId,
|
|
19008
19946
|
status: "transcribing",
|
|
19009
19947
|
updatedAt: /* @__PURE__ */ new Date()
|
|
@@ -19095,14 +20033,55 @@ var recallService = {
|
|
|
19095
20033
|
log4(`post-processing for job ${jobId} skipped: no transcript.`);
|
|
19096
20034
|
return [];
|
|
19097
20035
|
}
|
|
20036
|
+
const claimed = await db2(TABLE3).where({ id: jobId }).whereRaw(
|
|
20037
|
+
`(post_processing_outputs IS NULL OR (post_processing_outputs::text = '[]' AND "updatedAt" < ?))`,
|
|
20038
|
+
[new Date(Date.now() - POST_PROCESSING_REDO_STALE_MS)]
|
|
20039
|
+
).update({ post_processing_outputs: "[]", updatedAt: /* @__PURE__ */ new Date() });
|
|
20040
|
+
if (!claimed) {
|
|
20041
|
+
log4(`post-processing for job ${jobId} already in flight; skipping.`);
|
|
20042
|
+
return [];
|
|
20043
|
+
}
|
|
19098
20044
|
const outputs = [];
|
|
19099
20045
|
for (const p of prompts) {
|
|
19100
20046
|
outputs.push(await this._runOnePrompt(job, p.prompt_id, p.agent_id));
|
|
20047
|
+
await this._update(jobId, {});
|
|
19101
20048
|
}
|
|
19102
|
-
|
|
19103
|
-
|
|
19104
|
-
|
|
19105
|
-
|
|
20049
|
+
return this._mergeOutputs(jobId, outputs);
|
|
20050
|
+
},
|
|
20051
|
+
/**
|
|
20052
|
+
* Upsert entries into post_processing_outputs without clobbering concurrent
|
|
20053
|
+
* writers: optimistic-concurrency loop — read, merge by {prompt_id,
|
|
20054
|
+
* agent_id}, write only while the column still matches the snapshot.
|
|
20055
|
+
*/
|
|
20056
|
+
async _mergeOutputs(jobId, entries) {
|
|
20057
|
+
const { db: db2 } = await postgresClient();
|
|
20058
|
+
for (let attempt = 1; attempt <= 5; attempt++) {
|
|
20059
|
+
const row = await db2(TABLE3).where({ id: jobId }).first();
|
|
20060
|
+
if (!row) return entries;
|
|
20061
|
+
const raw = row.post_processing_outputs ?? null;
|
|
20062
|
+
const snapshot = raw == null ? null : typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
20063
|
+
const current = parseJson(raw) ?? [];
|
|
20064
|
+
const merged = [
|
|
20065
|
+
...current.filter(
|
|
20066
|
+
(o) => !entries.some(
|
|
20067
|
+
(e) => e.prompt_id === o.prompt_id && e.agent_id === o.agent_id
|
|
20068
|
+
)
|
|
20069
|
+
),
|
|
20070
|
+
...entries
|
|
20071
|
+
];
|
|
20072
|
+
const updated = await db2(TABLE3).where({ id: jobId }).whereRaw(
|
|
20073
|
+
"post_processing_outputs::jsonb IS NOT DISTINCT FROM ?::jsonb",
|
|
20074
|
+
[snapshot]
|
|
20075
|
+
).update({
|
|
20076
|
+
post_processing_outputs: JSON.stringify(merged),
|
|
20077
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
20078
|
+
});
|
|
20079
|
+
if (updated) return merged;
|
|
20080
|
+
}
|
|
20081
|
+
log4(
|
|
20082
|
+
`post-processing outputs for job ${jobId} kept changing under the merge; leaving the concurrent writer's data in place.`
|
|
20083
|
+
);
|
|
20084
|
+
return entries;
|
|
19106
20085
|
},
|
|
19107
20086
|
/**
|
|
19108
20087
|
* Manual single-prompt run (re-run from the review sheet). Upserts the matching
|
|
@@ -19113,13 +20092,14 @@ var recallService = {
|
|
|
19113
20092
|
const dbRow = await db2(TABLE3).where({ id: jobId }).first();
|
|
19114
20093
|
if (!dbRow) throw new Error(`transcription_job ${jobId} not found`);
|
|
19115
20094
|
const job = this._row(dbRow);
|
|
20095
|
+
const claimInFlight = dbRow.post_processing_outputs != null && (job.post_processing_outputs?.length ?? 0) === 0 && Date.now() - new Date(job.updatedAt).getTime() < POST_PROCESSING_REDO_STALE_MS;
|
|
20096
|
+
if (claimInFlight) {
|
|
20097
|
+
throw new Error(
|
|
20098
|
+
"POST_PROCESSING_IN_FLIGHT: the automatic post-processing run is still in progress; its results will appear shortly."
|
|
20099
|
+
);
|
|
20100
|
+
}
|
|
19116
20101
|
const result = await this._runOnePrompt(job, promptId, agentId);
|
|
19117
|
-
|
|
19118
|
-
const next = existing.filter(
|
|
19119
|
-
(o) => !(o.prompt_id === promptId && o.agent_id === agentId)
|
|
19120
|
-
);
|
|
19121
|
-
next.push(result);
|
|
19122
|
-
await this._update(jobId, { post_processing_outputs: JSON.stringify(next) });
|
|
20102
|
+
await this._mergeOutputs(jobId, [result]);
|
|
19123
20103
|
return result;
|
|
19124
20104
|
},
|
|
19125
20105
|
async _runOnePrompt(job, promptId, agentId) {
|
|
@@ -19144,7 +20124,7 @@ var recallService = {
|
|
|
19144
20124
|
job.raw_segments ?? [],
|
|
19145
20125
|
job.speakers ?? {}
|
|
19146
20126
|
);
|
|
19147
|
-
const { text } = await (0,
|
|
20127
|
+
const { text } = await (0, import_ai8.generateText)({
|
|
19148
20128
|
model: resolved.languageModel,
|
|
19149
20129
|
system: agent.instructions || void 0,
|
|
19150
20130
|
prompt: `${prompt.content}
|
|
@@ -19153,7 +20133,8 @@ var recallService = {
|
|
|
19153
20133
|
Meeting transcript:
|
|
19154
20134
|
|
|
19155
20135
|
${transcriptText}`,
|
|
19156
|
-
maxRetries: 3
|
|
20136
|
+
maxRetries: 3,
|
|
20137
|
+
abortSignal: AbortSignal.timeout(POST_PROCESSING_PROMPT_TIMEOUT_MS)
|
|
19157
20138
|
});
|
|
19158
20139
|
return {
|
|
19159
20140
|
prompt_id: promptId,
|
|
@@ -19177,6 +20158,206 @@ ${transcriptText}`,
|
|
|
19177
20158
|
};
|
|
19178
20159
|
}
|
|
19179
20160
|
},
|
|
20161
|
+
/**
|
|
20162
|
+
* Reconciliation sweep: re-drive recall jobs whose webhook event was lost
|
|
20163
|
+
* (ACK-first delivery + crash/restart = no redelivery). Called periodically
|
|
20164
|
+
* from the reconcile loop. Returns the number of jobs it acted on.
|
|
20165
|
+
*
|
|
20166
|
+
* Idempotent by construction: every recovery path funnels into the same
|
|
20167
|
+
* guarded transitions the webhook handlers use (_onRecordingDone's atomic
|
|
20168
|
+
* claim, _onTranscriptDone's already-processed check, runPostProcessing's
|
|
20169
|
+
* skip-if-outputs-exist), so a webhook racing the sweep is harmless.
|
|
20170
|
+
*/
|
|
20171
|
+
async reconcileOnce(limit = 10) {
|
|
20172
|
+
if (!recallEnabled()) return 0;
|
|
20173
|
+
const { db: db2 } = await postgresClient();
|
|
20174
|
+
const now = Date.now();
|
|
20175
|
+
const stuck = await db2(TABLE3).where({ source: "recall" }).whereIn("status", ["queued", "transcribing"]).whereRaw(`("join_at" IS NULL OR "join_at" < ?)`, [
|
|
20176
|
+
new Date(now - RECONCILE_STALE_MS)
|
|
20177
|
+
]).where("updatedAt", "<", new Date(now - RECONCILE_STALE_MS)).orderBy("updatedAt", "asc").limit(limit);
|
|
20178
|
+
const redo = await db2(TABLE3).where({ source: "recall", status: "awaiting_review" }).whereNotNull("post_processing_prompts").whereRaw("post_processing_prompts::text <> '[]'").whereRaw("(raw_segments IS NOT NULL AND raw_segments::text <> '[]')").whereRaw(
|
|
20179
|
+
"(post_processing_outputs IS NULL OR post_processing_outputs::text = '[]')"
|
|
20180
|
+
).where("updatedAt", "<", new Date(now - POST_PROCESSING_REDO_STALE_MS)).orderBy("updatedAt", "asc").limit(limit);
|
|
20181
|
+
let acted = 0;
|
|
20182
|
+
for (const dbRow of stuck) {
|
|
20183
|
+
const job = this._row(dbRow);
|
|
20184
|
+
try {
|
|
20185
|
+
if (await this._reconcileStuckJob(job)) acted++;
|
|
20186
|
+
} catch (err) {
|
|
20187
|
+
if (await this._reconcileError(job, err)) acted++;
|
|
20188
|
+
}
|
|
20189
|
+
}
|
|
20190
|
+
for (const dbRow of redo) {
|
|
20191
|
+
try {
|
|
20192
|
+
log4(`reconcile: re-running lost post-processing for job ${dbRow.id}`);
|
|
20193
|
+
const outputs = await this.runPostProcessing(dbRow.id);
|
|
20194
|
+
if (outputs.length > 0) acted++;
|
|
20195
|
+
} catch (err) {
|
|
20196
|
+
log4(
|
|
20197
|
+
`post-processing redo failed for job ${dbRow.id}: ${err.message}`
|
|
20198
|
+
);
|
|
20199
|
+
}
|
|
20200
|
+
}
|
|
20201
|
+
return acted;
|
|
20202
|
+
},
|
|
20203
|
+
async _reconcileStuckJob(job) {
|
|
20204
|
+
if (!job.recall_bot_id) {
|
|
20205
|
+
await this._fail(job.id, "bot was never launched (lost during creation)");
|
|
20206
|
+
return true;
|
|
20207
|
+
}
|
|
20208
|
+
if (job.status === "queued") return this._reconcileQueued(job);
|
|
20209
|
+
if (job.status === "transcribing") return this._reconcileTranscribing(job);
|
|
20210
|
+
return false;
|
|
20211
|
+
},
|
|
20212
|
+
/**
|
|
20213
|
+
* A recovery step threw. 404 means the Recall object is gone — terminal.
|
|
20214
|
+
* Otherwise apply the 24h give-up (API errors must not defer it forever),
|
|
20215
|
+
* and touch the row so it rotates to the back of the sweep window.
|
|
20216
|
+
*/
|
|
20217
|
+
async _reconcileError(job, err) {
|
|
20218
|
+
const message = err.message ?? String(err);
|
|
20219
|
+
if (err.status === 404) {
|
|
20220
|
+
await this._fail(
|
|
20221
|
+
job.id,
|
|
20222
|
+
`Recall no longer knows this recording: ${message}`
|
|
20223
|
+
);
|
|
20224
|
+
return true;
|
|
20225
|
+
}
|
|
20226
|
+
if (this._pastGiveUp(job)) {
|
|
20227
|
+
await this._fail(
|
|
20228
|
+
job.id,
|
|
20229
|
+
`still stuck 24 hours after the meeting start (last error: ${message})`
|
|
20230
|
+
);
|
|
20231
|
+
return true;
|
|
20232
|
+
}
|
|
20233
|
+
log4(`reconcile failed for job ${job.id}: ${message}`);
|
|
20234
|
+
await this._update(job.id, {});
|
|
20235
|
+
return false;
|
|
20236
|
+
},
|
|
20237
|
+
/** A queued row: the recording.done event (or the whole bot lifecycle) was lost. */
|
|
20238
|
+
async _reconcileQueued(job) {
|
|
20239
|
+
const ended = !!job.bot_status && BOT_ENDED_STATUSES.includes(job.bot_status);
|
|
20240
|
+
const quietMs = Date.now() - new Date(job.updatedAt).getTime();
|
|
20241
|
+
if (!ended && quietMs < RECONCILE_PROBE_QUIET_MS) return false;
|
|
20242
|
+
const bot = await recallClient.retrieveBot(job.recall_bot_id);
|
|
20243
|
+
const code = bot?.status_changes?.at(-1)?.code ?? null;
|
|
20244
|
+
if (code === "fatal") {
|
|
20245
|
+
await this._fail(job.id, "bot fatal (recovered by reconciliation)");
|
|
20246
|
+
return true;
|
|
20247
|
+
}
|
|
20248
|
+
const recording = bot?.recordings?.[0] ?? null;
|
|
20249
|
+
if (recording?.id) {
|
|
20250
|
+
const readiness = await this._recordingReadiness(recording);
|
|
20251
|
+
if (readiness === "failed") {
|
|
20252
|
+
await this._fail(job.id, "recording failed (found by reconciliation)");
|
|
20253
|
+
return true;
|
|
20254
|
+
}
|
|
20255
|
+
if (readiness === "done") {
|
|
20256
|
+
log4(`reconcile: driving lost recording.done for job ${job.id}`);
|
|
20257
|
+
await this._onRecordingDone(job.id, recording.id);
|
|
20258
|
+
return true;
|
|
20259
|
+
}
|
|
20260
|
+
} else if (code === "done") {
|
|
20261
|
+
await this._fail(job.id, "bot finished without a recording");
|
|
20262
|
+
return true;
|
|
20263
|
+
}
|
|
20264
|
+
if (this._pastGiveUp(job)) {
|
|
20265
|
+
await this._fail(
|
|
20266
|
+
job.id,
|
|
20267
|
+
"no recording within 24 hours of the meeting start"
|
|
20268
|
+
);
|
|
20269
|
+
return true;
|
|
20270
|
+
}
|
|
20271
|
+
await this._update(
|
|
20272
|
+
job.id,
|
|
20273
|
+
code && code !== job.bot_status ? { bot_status: code } : {}
|
|
20274
|
+
);
|
|
20275
|
+
return false;
|
|
20276
|
+
},
|
|
20277
|
+
/** Whether a recording is safe to transcribe, checking the full recording
|
|
20278
|
+
* object when the bot payload carries no status. */
|
|
20279
|
+
async _recordingReadiness(recording) {
|
|
20280
|
+
const codeOf = (c) => c === "done" ? "done" : c === "failed" ? "failed" : c ? "processing" : null;
|
|
20281
|
+
const fromBot = codeOf(recording.status?.code);
|
|
20282
|
+
if (fromBot) return fromBot;
|
|
20283
|
+
if (recording.completed_at) return "done";
|
|
20284
|
+
const full = await recallClient.retrieveRecording(recording.id);
|
|
20285
|
+
const fromFull = codeOf(full?.status?.code);
|
|
20286
|
+
if (fromFull) return fromFull;
|
|
20287
|
+
return full?.completed_at || recordingDurationSeconds(full) != null ? "done" : "processing";
|
|
20288
|
+
},
|
|
20289
|
+
/**
|
|
20290
|
+
* A transcribing row: transcript.done was lost, or the original
|
|
20291
|
+
* recording.done handling crashed between the status claim and
|
|
20292
|
+
* createAsyncTranscript (in which case Recall was never asked to
|
|
20293
|
+
* transcribe and no transcript.* event will ever arrive).
|
|
20294
|
+
*/
|
|
20295
|
+
async _reconcileTranscribing(job) {
|
|
20296
|
+
let recordingId = job.recall_recording_id;
|
|
20297
|
+
if (!recordingId && !job.recall_transcript_id) {
|
|
20298
|
+
const bot = await recallClient.retrieveBot(job.recall_bot_id);
|
|
20299
|
+
recordingId = bot?.recordings?.[0]?.id ?? null;
|
|
20300
|
+
}
|
|
20301
|
+
let transcriptId = job.recall_transcript_id;
|
|
20302
|
+
if (!transcriptId) {
|
|
20303
|
+
if (!recordingId) return this._transcribingNotReady(job);
|
|
20304
|
+
const rec = await recallClient.retrieveRecording(recordingId);
|
|
20305
|
+
const shortcut = rec?.media_shortcuts?.transcript;
|
|
20306
|
+
if (shortcut?.id) {
|
|
20307
|
+
transcriptId = shortcut.id;
|
|
20308
|
+
await this._update(job.id, {
|
|
20309
|
+
recall_recording_id: recordingId,
|
|
20310
|
+
recall_transcript_id: transcriptId
|
|
20311
|
+
});
|
|
20312
|
+
} else {
|
|
20313
|
+
const { db: db2 } = await postgresClient();
|
|
20314
|
+
const claimed = await db2(TABLE3).where({ id: job.id, status: "transcribing" }).whereNull("recall_transcript_id").where("updatedAt", "<", new Date(Date.now() - RECONCILE_STALE_MS)).update({ recall_recording_id: recordingId, updatedAt: /* @__PURE__ */ new Date() });
|
|
20315
|
+
if (!claimed) return false;
|
|
20316
|
+
log4(
|
|
20317
|
+
`reconcile: re-requesting transcript for job ${job.id} (lost before createAsyncTranscript)`
|
|
20318
|
+
);
|
|
20319
|
+
const transcript2 = await recallClient.createAsyncTranscript(
|
|
20320
|
+
recordingId,
|
|
20321
|
+
job.language || "auto"
|
|
20322
|
+
);
|
|
20323
|
+
await this._update(job.id, {
|
|
20324
|
+
recall_transcript_id: transcript2.id ?? null
|
|
20325
|
+
});
|
|
20326
|
+
return true;
|
|
20327
|
+
}
|
|
20328
|
+
}
|
|
20329
|
+
const transcript = await recallClient.retrieveTranscript(transcriptId);
|
|
20330
|
+
const transcriptCode = transcript?.status?.code ?? null;
|
|
20331
|
+
if (transcriptCode === "error" || transcriptCode === "failed") {
|
|
20332
|
+
await this._fail(
|
|
20333
|
+
job.id,
|
|
20334
|
+
`transcript failed at Recall: ${transcript?.status?.sub_code || transcriptCode}`
|
|
20335
|
+
);
|
|
20336
|
+
return true;
|
|
20337
|
+
}
|
|
20338
|
+
if (transcript?.data?.download_url) {
|
|
20339
|
+
log4(`reconcile: driving lost transcript.done for job ${job.id}`);
|
|
20340
|
+
await this._onTranscriptDone(job.id, transcriptId, recordingId);
|
|
20341
|
+
return true;
|
|
20342
|
+
}
|
|
20343
|
+
return this._transcribingNotReady(job);
|
|
20344
|
+
},
|
|
20345
|
+
/** Transcript not ready yet: wait (touch) or give up after the hard cap. */
|
|
20346
|
+
async _transcribingNotReady(job) {
|
|
20347
|
+
if (this._pastGiveUp(job)) {
|
|
20348
|
+
await this._fail(
|
|
20349
|
+
job.id,
|
|
20350
|
+
"transcript was not ready within 24 hours of the meeting start"
|
|
20351
|
+
);
|
|
20352
|
+
return true;
|
|
20353
|
+
}
|
|
20354
|
+
await this._update(job.id, {});
|
|
20355
|
+
return false;
|
|
20356
|
+
},
|
|
20357
|
+
_pastGiveUp(job) {
|
|
20358
|
+
const startedAt = new Date(job.join_at ?? job.createdAt).getTime();
|
|
20359
|
+
return Number.isFinite(startedAt) && Date.now() - startedAt > RECONCILE_GIVE_UP_MS;
|
|
20360
|
+
},
|
|
19180
20361
|
async _findJob(botId, recordingId, transcriptId) {
|
|
19181
20362
|
const { db: db2 } = await postgresClient();
|
|
19182
20363
|
let dbRow;
|
|
@@ -19211,7 +20392,7 @@ ${transcriptText}`,
|
|
|
19211
20392
|
|
|
19212
20393
|
// src/exulu/email-inbound/config.ts
|
|
19213
20394
|
init_cjs_shims();
|
|
19214
|
-
|
|
20395
|
+
init_credential_store();
|
|
19215
20396
|
var EMAIL_INBOUND_CONFIG_KEY = "email_inbound";
|
|
19216
20397
|
var parseValue = (value) => {
|
|
19217
20398
|
if (!value) return {};
|
|
@@ -19410,7 +20591,8 @@ function createExuluContextsTypeDefs(table) {
|
|
|
19410
20591
|
${enumValues}
|
|
19411
20592
|
}`;
|
|
19412
20593
|
}).filter((enumDef) => enumDef !== null).join("\n");
|
|
19413
|
-
|
|
20594
|
+
const graphqlFields = table.fields.filter((field) => field.hidden !== true);
|
|
20595
|
+
let fields = graphqlFields.map((field) => {
|
|
19414
20596
|
let type;
|
|
19415
20597
|
type = mapExuluFieldTypesToGraphqlTypes(field);
|
|
19416
20598
|
const required = field.required ? "!" : "";
|
|
@@ -19431,6 +20613,7 @@ function createExuluContextsTypeDefs(table) {
|
|
|
19431
20613
|
fields.push(" systemInstructions: String");
|
|
19432
20614
|
fields.push(" workflows: AgentWorkflows");
|
|
19433
20615
|
fields.push(" slug: String");
|
|
20616
|
+
fields.push(" guest_has_password: Boolean");
|
|
19434
20617
|
}
|
|
19435
20618
|
if (table.name.singular === "workflow_template") {
|
|
19436
20619
|
fields.push(" variables: [String]");
|
|
@@ -19447,16 +20630,20 @@ function createExuluContextsTypeDefs(table) {
|
|
|
19447
20630
|
}
|
|
19448
20631
|
`;
|
|
19449
20632
|
const rbacInputField = table.RBAC ? " RBAC: RBACInput" : "";
|
|
20633
|
+
const inputFields = table.fields.filter((f) => f.name !== "guest_password_hash");
|
|
20634
|
+
const inputExtra = table.name.singular === "agent" ? " guest_password: String" : "";
|
|
19450
20635
|
const inputDef = `
|
|
19451
20636
|
input ${table.name.singular}Input {
|
|
19452
|
-
${
|
|
20637
|
+
${inputFields.map((f) => ` ${f.name}: ${mapExuluFieldTypesToGraphqlTypes(f)}`).join("\n")}
|
|
20638
|
+
${inputExtra}
|
|
19453
20639
|
${rbacInputField}
|
|
19454
20640
|
}
|
|
19455
20641
|
`;
|
|
19456
20642
|
return enumDefs + typeDef + inputDef;
|
|
19457
20643
|
}
|
|
19458
20644
|
function createExuluContextsFilterTypeDefs(table) {
|
|
19459
|
-
const
|
|
20645
|
+
const filterFields = table.fields.filter((field) => field.hidden !== true);
|
|
20646
|
+
const fieldFilters = filterFields.map((field) => {
|
|
19460
20647
|
let type;
|
|
19461
20648
|
if (field.type === "enum" && field.enumValues) {
|
|
19462
20649
|
type = `${field.name}Enum`;
|
|
@@ -19469,7 +20656,7 @@ function createExuluContextsFilterTypeDefs(table) {
|
|
|
19469
20656
|
let operatorTypes = "";
|
|
19470
20657
|
let enumFilterOperators = [];
|
|
19471
20658
|
const tableNameSingularUpperCaseFirst = table.name.singular.charAt(0).toUpperCase() + table.name.singular.slice(1);
|
|
19472
|
-
enumFilterOperators =
|
|
20659
|
+
enumFilterOperators = filterFields.filter((field) => field.type === "enum" && field.enumValues).map((field) => {
|
|
19473
20660
|
const enumTypeName = `${field.name}Enum`;
|
|
19474
20661
|
return `
|
|
19475
20662
|
input FilterOperator${enumTypeName} {
|
|
@@ -21028,12 +22215,12 @@ type LiteLLMModel {
|
|
|
21028
22215
|
nonArchived().andWhere((b) => b.whereNull("chunks_count").orWhere("chunks_count", "<=", 0)).count("* as c").first(),
|
|
21029
22216
|
nonArchived().where("embeddings_updated_at", "<=", staleCutoff).count("* as c").first()
|
|
21030
22217
|
]);
|
|
21031
|
-
const
|
|
22218
|
+
const num2 = (v) => v == null ? 0 : Number(v);
|
|
21032
22219
|
return {
|
|
21033
|
-
item_count:
|
|
21034
|
-
chunk_total:
|
|
21035
|
-
stuck_count:
|
|
21036
|
-
stale_count:
|
|
22220
|
+
item_count: num2(itemRow?.c),
|
|
22221
|
+
chunk_total: num2(chunkRow?.s),
|
|
22222
|
+
stuck_count: num2(stuckRow?.c),
|
|
22223
|
+
stale_count: num2(staleRow?.c)
|
|
21037
22224
|
};
|
|
21038
22225
|
} catch (err) {
|
|
21039
22226
|
console.error("[EXULU] computeContextAggregates failed for", contextId, err);
|
|
@@ -21239,6 +22426,7 @@ type LiteLLMModel {
|
|
|
21239
22426
|
if (agenticRetrievalTool) {
|
|
21240
22427
|
allTools.push(agenticRetrievalTool);
|
|
21241
22428
|
}
|
|
22429
|
+
allTools.push(createKbEditorPickerTool());
|
|
21242
22430
|
}
|
|
21243
22431
|
if (search && search.trim()) {
|
|
21244
22432
|
const searchTerm = search.toLowerCase().trim();
|
|
@@ -21808,19 +22996,19 @@ var import_node_crypto14 = require("crypto");
|
|
|
21808
22996
|
var import_api2 = require("@opentelemetry/api");
|
|
21809
22997
|
init_check_record_access();
|
|
21810
22998
|
var import_jszip3 = __toESM(require("jszip"), 1);
|
|
21811
|
-
var
|
|
22999
|
+
var import_ai12 = require("ai");
|
|
21812
23000
|
var import_cookie_parser = __toESM(require("cookie-parser"), 1);
|
|
21813
23001
|
init_statistics2();
|
|
21814
23002
|
|
|
21815
23003
|
// src/exulu/suggestions.ts
|
|
21816
23004
|
init_cjs_shims();
|
|
21817
|
-
var
|
|
21818
|
-
var
|
|
23005
|
+
var import_ai9 = require("ai");
|
|
23006
|
+
var import_zod17 = require("zod");
|
|
21819
23007
|
var SUGGESTIONS_SYSTEM_PROMPT = "You generate short follow-up message suggestions for the user. You are NOT continuing the conversation as the assistant \u2014 you are predicting what the user might want to say next. Suggest up to 3 short follow-up questions or messages the user might want to send next. Each suggestion must be written from the user's perspective (first person) and be 12 words or fewer. You MUST submit your answer by calling the `submit_suggestions` tool exactly once. Do not emit any plain text \u2014 only the tool call.";
|
|
21820
|
-
var submitSuggestionsTool = (0,
|
|
23008
|
+
var submitSuggestionsTool = (0, import_ai9.tool)({
|
|
21821
23009
|
description: "Submit the final list of follow-up message suggestions for the user. Must be called exactly once. Each suggestion is written from the user's perspective (first person) and is 12 words or fewer.",
|
|
21822
|
-
inputSchema:
|
|
21823
|
-
suggestions:
|
|
23010
|
+
inputSchema: import_zod17.z.object({
|
|
23011
|
+
suggestions: import_zod17.z.array(import_zod17.z.string()).max(3)
|
|
21824
23012
|
})
|
|
21825
23013
|
});
|
|
21826
23014
|
var MAX_CHARS_PER_MESSAGE = 1e4;
|
|
@@ -21856,11 +23044,11 @@ var generateSuggestions = async ({
|
|
|
21856
23044
|
|
|
21857
23045
|
${SUGGESTIONS_SYSTEM_PROMPT}` : SUGGESTIONS_SYSTEM_PROMPT;
|
|
21858
23046
|
const trimmed = trimMessagesForSuggestions(messages);
|
|
21859
|
-
const { toolCalls, totalUsage } = await (0,
|
|
23047
|
+
const { toolCalls, totalUsage } = await (0, import_ai9.generateText)({
|
|
21860
23048
|
temperature: 0,
|
|
21861
23049
|
model: languageModel,
|
|
21862
23050
|
system,
|
|
21863
|
-
messages: await (0,
|
|
23051
|
+
messages: await (0, import_ai9.convertToModelMessages)(trimmed, {
|
|
21864
23052
|
ignoreIncompleteToolCalls: true
|
|
21865
23053
|
}),
|
|
21866
23054
|
tools: { submit_suggestions: submitSuggestionsTool },
|
|
@@ -21914,7 +23102,7 @@ init_context_budget();
|
|
|
21914
23102
|
// src/exulu/compact-session.ts
|
|
21915
23103
|
init_cjs_shims();
|
|
21916
23104
|
var import_node_crypto9 = require("crypto");
|
|
21917
|
-
var
|
|
23105
|
+
var import_ai10 = require("ai");
|
|
21918
23106
|
init_truncate_tool_output();
|
|
21919
23107
|
init_context_budget();
|
|
21920
23108
|
var CompactionInsufficientError = class extends Error {
|
|
@@ -21973,7 +23161,7 @@ var compactSession = async ({
|
|
|
21973
23161
|
}) => {
|
|
21974
23162
|
const budget = deriveContextBudget(contextWindow);
|
|
21975
23163
|
const rows = await getAgentMessages({ session: sessionID, user: user.id });
|
|
21976
|
-
const all = await (0,
|
|
23164
|
+
const all = await (0, import_ai10.validateUIMessages)({ messages: rows.map((r) => JSON.parse(r.content)) });
|
|
21977
23165
|
const history = sliceHistoryAtCheckpoint(all);
|
|
21978
23166
|
const { head, tail } = splitTail(history, budget.compactionTailTokens);
|
|
21979
23167
|
if (head.length === 0) {
|
|
@@ -21988,7 +23176,7 @@ var compactSession = async ({
|
|
|
21988
23176
|
|
|
21989
23177
|
Focus especially on: ${steer.trim()}` : SUMMARY_SYSTEM;
|
|
21990
23178
|
const doSummarize = summarize ?? (async ({ system: sys, prompt, maxOutputTokens }) => {
|
|
21991
|
-
const { text } = await (0,
|
|
23179
|
+
const { text } = await (0, import_ai10.generateText)({
|
|
21992
23180
|
model: languageModel,
|
|
21993
23181
|
system: sys,
|
|
21994
23182
|
prompt,
|
|
@@ -22377,6 +23565,129 @@ init_admin_client();
|
|
|
22377
23565
|
init_env();
|
|
22378
23566
|
init_activity_client();
|
|
22379
23567
|
init_budget_service();
|
|
23568
|
+
|
|
23569
|
+
// src/exulu/litellm/usage-view.ts
|
|
23570
|
+
init_cjs_shims();
|
|
23571
|
+
init_tags();
|
|
23572
|
+
init_activity_client();
|
|
23573
|
+
init_budget_service();
|
|
23574
|
+
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
23575
|
+
var DATE_ONLY_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
23576
|
+
var MAX_WINDOW_DAYS = 92;
|
|
23577
|
+
var DEFAULT_WINDOW_DAYS = 30;
|
|
23578
|
+
var parseDateParam = (raw) => {
|
|
23579
|
+
if (raw === void 0 || raw === null || raw === "") return void 0;
|
|
23580
|
+
if (typeof raw !== "string") return null;
|
|
23581
|
+
if (DATE_ONLY_RE.test(raw)) return raw;
|
|
23582
|
+
const dt = new Date(raw);
|
|
23583
|
+
if (Number.isNaN(dt.getTime())) return null;
|
|
23584
|
+
return dt.toISOString().slice(0, 10);
|
|
23585
|
+
};
|
|
23586
|
+
var ymdShift = (ymd2, days) => new Date(Date.parse(ymd2) + days * DAY_MS2).toISOString().slice(0, 10);
|
|
23587
|
+
function resolveUsageWindow(startRaw, endRaw, now = /* @__PURE__ */ new Date()) {
|
|
23588
|
+
const start = parseDateParam(startRaw);
|
|
23589
|
+
const end = parseDateParam(endRaw);
|
|
23590
|
+
if (start === null || end === null) return null;
|
|
23591
|
+
const end_date = end ?? now.toISOString().slice(0, 10);
|
|
23592
|
+
let start_date = start ?? ymdShift(end_date, -(DEFAULT_WINDOW_DAYS - 1));
|
|
23593
|
+
if (start_date > end_date) return null;
|
|
23594
|
+
const days = Math.round((Date.parse(end_date) - Date.parse(start_date)) / DAY_MS2) + 1;
|
|
23595
|
+
if (days > MAX_WINDOW_DAYS) {
|
|
23596
|
+
start_date = ymdShift(end_date, -(MAX_WINDOW_DAYS - 1));
|
|
23597
|
+
}
|
|
23598
|
+
return { start_date, end_date };
|
|
23599
|
+
}
|
|
23600
|
+
var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
23601
|
+
var zeroMetrics = () => ({
|
|
23602
|
+
spend: 0,
|
|
23603
|
+
prompt_tokens: 0,
|
|
23604
|
+
completion_tokens: 0,
|
|
23605
|
+
total_tokens: 0,
|
|
23606
|
+
successful_requests: 0,
|
|
23607
|
+
failed_requests: 0,
|
|
23608
|
+
api_requests: 0
|
|
23609
|
+
});
|
|
23610
|
+
var readMetrics = (source) => {
|
|
23611
|
+
const m = source?.metrics ?? {};
|
|
23612
|
+
return {
|
|
23613
|
+
spend: num(m.spend ?? source?.spend),
|
|
23614
|
+
prompt_tokens: num(m.prompt_tokens ?? source?.prompt_tokens),
|
|
23615
|
+
completion_tokens: num(m.completion_tokens ?? source?.completion_tokens),
|
|
23616
|
+
total_tokens: num(m.total_tokens ?? source?.total_tokens),
|
|
23617
|
+
successful_requests: num(
|
|
23618
|
+
m.successful_requests ?? source?.successful_requests
|
|
23619
|
+
),
|
|
23620
|
+
failed_requests: num(m.failed_requests ?? source?.failed_requests),
|
|
23621
|
+
api_requests: num(m.api_requests ?? source?.api_requests)
|
|
23622
|
+
};
|
|
23623
|
+
};
|
|
23624
|
+
var addMetrics = (into, add) => {
|
|
23625
|
+
into.spend += add.spend;
|
|
23626
|
+
into.prompt_tokens += add.prompt_tokens;
|
|
23627
|
+
into.completion_tokens += add.completion_tokens;
|
|
23628
|
+
into.total_tokens += add.total_tokens;
|
|
23629
|
+
into.successful_requests += add.successful_requests;
|
|
23630
|
+
into.failed_requests += add.failed_requests;
|
|
23631
|
+
into.api_requests += add.api_requests;
|
|
23632
|
+
};
|
|
23633
|
+
function projectMyUsage(raw) {
|
|
23634
|
+
const results = Array.isArray(raw?.results) ? raw.results : Array.isArray(raw) ? raw : [];
|
|
23635
|
+
const totals = zeroMetrics();
|
|
23636
|
+
const byDate = /* @__PURE__ */ new Map();
|
|
23637
|
+
const byModelMap = /* @__PURE__ */ new Map();
|
|
23638
|
+
for (const result of results) {
|
|
23639
|
+
const date = typeof result?.date === "string" ? result.date : null;
|
|
23640
|
+
if (!date) continue;
|
|
23641
|
+
const metrics = readMetrics(result);
|
|
23642
|
+
addMetrics(totals, metrics);
|
|
23643
|
+
const day = byDate.get(date) ?? zeroMetrics();
|
|
23644
|
+
addMetrics(day, metrics);
|
|
23645
|
+
byDate.set(date, day);
|
|
23646
|
+
const models2 = result?.breakdown && typeof result.breakdown.models === "object" ? result.breakdown.models : {};
|
|
23647
|
+
for (const [model, entry] of Object.entries(models2 ?? {})) {
|
|
23648
|
+
const acc = byModelMap.get(model) ?? zeroMetrics();
|
|
23649
|
+
addMetrics(acc, readMetrics(entry));
|
|
23650
|
+
byModelMap.set(model, acc);
|
|
23651
|
+
}
|
|
23652
|
+
}
|
|
23653
|
+
const daily = [...byDate.entries()].map(([date, m]) => ({ date, ...m })).sort((a, b) => a.date.localeCompare(b.date));
|
|
23654
|
+
const byModel = [...byModelMap.entries()].map(([model, m]) => ({
|
|
23655
|
+
model,
|
|
23656
|
+
spend: m.spend,
|
|
23657
|
+
prompt_tokens: m.prompt_tokens,
|
|
23658
|
+
completion_tokens: m.completion_tokens,
|
|
23659
|
+
total_tokens: m.total_tokens,
|
|
23660
|
+
successful_requests: m.successful_requests,
|
|
23661
|
+
failed_requests: m.failed_requests
|
|
23662
|
+
})).sort((a, b) => b.spend - a.spend);
|
|
23663
|
+
return { totals, daily, byModel };
|
|
23664
|
+
}
|
|
23665
|
+
async function getMyUsageView(userId, window) {
|
|
23666
|
+
const settings = await getBudgetSettings();
|
|
23667
|
+
if (!settings.show_user_budget_in_chat) return null;
|
|
23668
|
+
const tag = budgetTagFor("user", userId);
|
|
23669
|
+
if (!tag) return null;
|
|
23670
|
+
const daysInRange = Math.round(
|
|
23671
|
+
(Date.parse(window.end_date) - Date.parse(window.start_date)) / DAY_MS2
|
|
23672
|
+
) + 1;
|
|
23673
|
+
const raw = await getTagDailyActivity({
|
|
23674
|
+
startDate: window.start_date,
|
|
23675
|
+
endDate: window.end_date,
|
|
23676
|
+
tags: [tag],
|
|
23677
|
+
page: 1,
|
|
23678
|
+
pageSize: Math.min(daysInRange + 100, 1e4)
|
|
23679
|
+
});
|
|
23680
|
+
const { totals, daily, byModel } = projectMyUsage(raw);
|
|
23681
|
+
return {
|
|
23682
|
+
window,
|
|
23683
|
+
display: settings.user_budget_display,
|
|
23684
|
+
totals,
|
|
23685
|
+
daily,
|
|
23686
|
+
byModel
|
|
23687
|
+
};
|
|
23688
|
+
}
|
|
23689
|
+
|
|
23690
|
+
// src/exulu/routes.ts
|
|
22380
23691
|
var import_multer = __toESM(require("multer"), 1);
|
|
22381
23692
|
var import_busboy = __toESM(require("busboy"), 1);
|
|
22382
23693
|
|
|
@@ -22521,7 +23832,7 @@ function checkApiKeyScope(user, agentId) {
|
|
|
22521
23832
|
// src/exulu/openai-gateway.ts
|
|
22522
23833
|
init_cjs_shims();
|
|
22523
23834
|
var import_express2 = require("express");
|
|
22524
|
-
var
|
|
23835
|
+
var import_ai11 = require("ai");
|
|
22525
23836
|
|
|
22526
23837
|
// src/exulu/openai-transformer.ts
|
|
22527
23838
|
init_cjs_shims();
|
|
@@ -22629,7 +23940,7 @@ function convertOpenAIToolsToAiSdkTools(tools) {
|
|
|
22629
23940
|
t.function.name,
|
|
22630
23941
|
{
|
|
22631
23942
|
description: t.function.description ?? "",
|
|
22632
|
-
inputSchema: (0,
|
|
23943
|
+
inputSchema: (0, import_ai11.jsonSchema)({
|
|
22633
23944
|
type: "object",
|
|
22634
23945
|
properties: params.properties ?? {},
|
|
22635
23946
|
...params.required ? { required: params.required } : {}
|
|
@@ -23021,14 +24332,14 @@ ${project.description}` : ""}` : "",
|
|
|
23021
24332
|
res.setHeader("Content-Type", "text/event-stream");
|
|
23022
24333
|
res.setHeader("Cache-Control", "no-cache");
|
|
23023
24334
|
res.setHeader("Connection", "keep-alive");
|
|
23024
|
-
const result = (0,
|
|
24335
|
+
const result = (0, import_ai11.streamText)({
|
|
23025
24336
|
model: languageModel,
|
|
23026
24337
|
system: systemPrompt || void 0,
|
|
23027
24338
|
messages: coreMessages,
|
|
23028
24339
|
tools: hasTools ? activeTools : void 0,
|
|
23029
24340
|
maxRetries: 2,
|
|
23030
24341
|
prepareStep: clientTools.length > 0 ? void 0 : composePrepareSteps(contextGuard(contextWindow), gatewayRetrievalGuard, finalAnswerGuard(turnBudget), imageAttachmentGuard()),
|
|
23031
|
-
stopWhen: clientTools.length > 0 ? void 0 : [(0,
|
|
24342
|
+
stopWhen: clientTools.length > 0 ? void 0 : [(0, import_ai11.stepCountIs)(turnBudget)],
|
|
23032
24343
|
onError: (error) => {
|
|
23033
24344
|
console.error("[OPENAI GATEWAY] stream error:", error);
|
|
23034
24345
|
}
|
|
@@ -23061,14 +24372,14 @@ ${project.description}` : ""}` : "",
|
|
|
23061
24372
|
const usage = await result.usage;
|
|
23062
24373
|
await writeStatistics(agent, project, user, usage.inputTokens ?? 0, usage.outputTokens ?? 0);
|
|
23063
24374
|
} else {
|
|
23064
|
-
const { text, usage } = await (0,
|
|
24375
|
+
const { text, usage } = await (0, import_ai11.generateText)({
|
|
23065
24376
|
model: languageModel,
|
|
23066
24377
|
system: systemPrompt || void 0,
|
|
23067
24378
|
messages: coreMessages,
|
|
23068
24379
|
tools: hasTools ? activeTools : void 0,
|
|
23069
24380
|
maxRetries: 2,
|
|
23070
24381
|
prepareStep: clientTools.length > 0 ? void 0 : composePrepareSteps(contextGuard(contextWindow), gatewayRetrievalGuard, finalAnswerGuard(turnBudget), imageAttachmentGuard()),
|
|
23071
|
-
stopWhen: clientTools.length > 0 ? void 0 : [(0,
|
|
24382
|
+
stopWhen: clientTools.length > 0 ? void 0 : [(0, import_ai11.stepCountIs)(turnBudget)]
|
|
23072
24383
|
});
|
|
23073
24384
|
res.json(transformCompletion(text, usage.inputTokens ?? 0, usage.outputTokens ?? 0, ctx));
|
|
23074
24385
|
await writeStatistics(agent, project, user, usage.inputTokens ?? 0, usage.outputTokens ?? 0);
|
|
@@ -23100,11 +24411,11 @@ var getEnabledSkills = async (agent, disabledSkills = []) => {
|
|
|
23100
24411
|
return enabledSkills;
|
|
23101
24412
|
};
|
|
23102
24413
|
|
|
23103
|
-
// src/exulu/
|
|
24414
|
+
// src/exulu/auth/callback-handler.ts
|
|
23104
24415
|
init_cjs_shims();
|
|
23105
24416
|
init_registry();
|
|
23106
24417
|
init_flow();
|
|
23107
|
-
|
|
24418
|
+
init_credential_store();
|
|
23108
24419
|
var escapeHtml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
23109
24420
|
var renderResultPage = ({ success, message }) => `<!doctype html>
|
|
23110
24421
|
<html lang="en">
|
|
@@ -23156,7 +24467,7 @@ var handleOauthCallback = async (req, res) => {
|
|
|
23156
24467
|
"This authorization link is invalid or has expired. Return to your chat and run the tool again to get a fresh link."
|
|
23157
24468
|
);
|
|
23158
24469
|
}
|
|
23159
|
-
const config =
|
|
24470
|
+
const config = authRegistry.getByProvider(parsed.provider);
|
|
23160
24471
|
if (!config) {
|
|
23161
24472
|
return send(
|
|
23162
24473
|
404,
|
|
@@ -23164,13 +24475,31 @@ var handleOauthCallback = async (req, res) => {
|
|
|
23164
24475
|
`No OAuth configuration is registered for provider "${parsed.provider}".`
|
|
23165
24476
|
);
|
|
23166
24477
|
}
|
|
24478
|
+
if (config.authType !== "oauth") {
|
|
24479
|
+
return send(
|
|
24480
|
+
500,
|
|
24481
|
+
false,
|
|
24482
|
+
`Provider "${parsed.provider}" is not configured for OAuth. This callback only handles OAuth flows.`
|
|
24483
|
+
);
|
|
24484
|
+
}
|
|
23167
24485
|
try {
|
|
23168
24486
|
const record = await exchangeCodeForTokens({
|
|
23169
24487
|
config,
|
|
23170
24488
|
code,
|
|
23171
24489
|
codeVerifier: parsed.codeVerifier
|
|
23172
24490
|
});
|
|
23173
|
-
await
|
|
24491
|
+
await credentialStore.upsert({
|
|
24492
|
+
provider: parsed.provider,
|
|
24493
|
+
userId: parsed.userId,
|
|
24494
|
+
authType: "oauth",
|
|
24495
|
+
data: {
|
|
24496
|
+
accessToken: record.accessToken,
|
|
24497
|
+
refreshToken: record.refreshToken ?? null,
|
|
24498
|
+
tokenType: record.tokenType ?? null,
|
|
24499
|
+
scopes: record.scopes ?? null,
|
|
24500
|
+
expiresAt: record.expiresAt ? record.expiresAt.toISOString() : null
|
|
24501
|
+
}
|
|
24502
|
+
});
|
|
23174
24503
|
} catch (caught) {
|
|
23175
24504
|
console.error("[EXULU] OAuth code exchange failed:", caught);
|
|
23176
24505
|
return send(
|
|
@@ -23182,6 +24511,113 @@ var handleOauthCallback = async (req, res) => {
|
|
|
23182
24511
|
return send(200, true, "You can close this tab and return to your chat.");
|
|
23183
24512
|
};
|
|
23184
24513
|
|
|
24514
|
+
// src/exulu/auth/submit-handler.ts
|
|
24515
|
+
init_cjs_shims();
|
|
24516
|
+
var import_zod18 = require("zod");
|
|
24517
|
+
init_registry();
|
|
24518
|
+
init_credential_store();
|
|
24519
|
+
init_credentials_request();
|
|
24520
|
+
var bodySchema = import_zod18.z.object({
|
|
24521
|
+
nonce: import_zod18.z.string().min(1),
|
|
24522
|
+
values: import_zod18.z.record(import_zod18.z.string(), import_zod18.z.string())
|
|
24523
|
+
});
|
|
24524
|
+
async function handleCredentialSubmit(req, res) {
|
|
24525
|
+
const authResult = await requestValidators.authenticate(req);
|
|
24526
|
+
if (!authResult.user?.id) {
|
|
24527
|
+
res.status(401).json({ ok: false, error: "authentication required" });
|
|
24528
|
+
return;
|
|
24529
|
+
}
|
|
24530
|
+
const sessionUserId = authResult.user.id;
|
|
24531
|
+
let parsed;
|
|
24532
|
+
try {
|
|
24533
|
+
parsed = bodySchema.parse(req.body);
|
|
24534
|
+
} catch (caught) {
|
|
24535
|
+
res.status(400).json({ ok: false, error: "invalid body" });
|
|
24536
|
+
return;
|
|
24537
|
+
}
|
|
24538
|
+
const { nonce, values } = parsed;
|
|
24539
|
+
let nonceData;
|
|
24540
|
+
try {
|
|
24541
|
+
nonceData = verifyCredentialNonce(nonce);
|
|
24542
|
+
} catch (e) {
|
|
24543
|
+
res.status(401).json({
|
|
24544
|
+
ok: false,
|
|
24545
|
+
error: /expired/i.test(e.message) ? "nonce expired" : "nonce invalid"
|
|
24546
|
+
});
|
|
24547
|
+
return;
|
|
24548
|
+
}
|
|
24549
|
+
if (sessionUserId !== Number(nonceData.userId)) {
|
|
24550
|
+
res.status(403).json({ ok: false, error: "userId mismatch" });
|
|
24551
|
+
return;
|
|
24552
|
+
}
|
|
24553
|
+
const config = authRegistry.getByProvider(nonceData.provider);
|
|
24554
|
+
if (!config || config.authType !== "user_credentials") {
|
|
24555
|
+
res.status(400).json({
|
|
24556
|
+
ok: false,
|
|
24557
|
+
error: "provider is not a user_credentials provider"
|
|
24558
|
+
});
|
|
24559
|
+
return;
|
|
24560
|
+
}
|
|
24561
|
+
const expectedFields = new Set(config.fields.map((f) => f.name));
|
|
24562
|
+
const submittedFields = new Set(Object.keys(values));
|
|
24563
|
+
if (expectedFields.size !== submittedFields.size || [...expectedFields].some((f) => !submittedFields.has(f))) {
|
|
24564
|
+
res.status(400).json({
|
|
24565
|
+
ok: false,
|
|
24566
|
+
error: "field set mismatch"
|
|
24567
|
+
});
|
|
24568
|
+
return;
|
|
24569
|
+
}
|
|
24570
|
+
if (config.validate) {
|
|
24571
|
+
try {
|
|
24572
|
+
await config.validate(values);
|
|
24573
|
+
} catch (e) {
|
|
24574
|
+
res.status(400).json({
|
|
24575
|
+
ok: false,
|
|
24576
|
+
error: `validation failed: ${e.message}`
|
|
24577
|
+
});
|
|
24578
|
+
return;
|
|
24579
|
+
}
|
|
24580
|
+
}
|
|
24581
|
+
if (!Number.isInteger(sessionUserId) || sessionUserId <= 0) {
|
|
24582
|
+
res.status(401).json({ ok: false, error: "session invalid" });
|
|
24583
|
+
return;
|
|
24584
|
+
}
|
|
24585
|
+
await credentialStore.upsert({
|
|
24586
|
+
provider: nonceData.provider,
|
|
24587
|
+
userId: sessionUserId,
|
|
24588
|
+
authType: "user_credentials",
|
|
24589
|
+
data: values
|
|
24590
|
+
});
|
|
24591
|
+
res.status(200).json({ ok: true });
|
|
24592
|
+
}
|
|
24593
|
+
|
|
24594
|
+
// src/exulu/auth/manage-handlers.ts
|
|
24595
|
+
init_cjs_shims();
|
|
24596
|
+
init_credential_store();
|
|
24597
|
+
var handleCredentialList = async (req, res) => {
|
|
24598
|
+
const authResult = await requestValidators.authenticate(req);
|
|
24599
|
+
if (!authResult.user?.id) {
|
|
24600
|
+
res.status(authResult.code ?? 401).json({ ok: false, error: "authentication required" });
|
|
24601
|
+
return;
|
|
24602
|
+
}
|
|
24603
|
+
const credentials = await credentialStore.listByUser(authResult.user.id);
|
|
24604
|
+
res.status(200).json({ ok: true, credentials });
|
|
24605
|
+
};
|
|
24606
|
+
var handleCredentialDelete = async (req, res) => {
|
|
24607
|
+
const authResult = await requestValidators.authenticate(req);
|
|
24608
|
+
if (!authResult.user?.id) {
|
|
24609
|
+
res.status(authResult.code ?? 401).json({ ok: false, error: "authentication required" });
|
|
24610
|
+
return;
|
|
24611
|
+
}
|
|
24612
|
+
const provider = req.params.provider;
|
|
24613
|
+
if (!provider) {
|
|
24614
|
+
res.status(400).json({ ok: false, error: "provider is required" });
|
|
24615
|
+
return;
|
|
24616
|
+
}
|
|
24617
|
+
await credentialStore.delete(provider, authResult.user.id);
|
|
24618
|
+
res.status(200).json({ ok: true });
|
|
24619
|
+
};
|
|
24620
|
+
|
|
23185
24621
|
// src/exulu/routes.ts
|
|
23186
24622
|
init_flow();
|
|
23187
24623
|
|
|
@@ -23235,52 +24671,125 @@ var verifyRecallRequest = (headers, rawBody, secret = recallVerificationSecret()
|
|
|
23235
24671
|
return passed ? { ok: true } : { ok: false, reason: "signature mismatch" };
|
|
23236
24672
|
};
|
|
23237
24673
|
|
|
23238
|
-
// src/exulu/
|
|
24674
|
+
// src/exulu/public-agents.ts
|
|
23239
24675
|
init_cjs_shims();
|
|
23240
|
-
var
|
|
23241
|
-
|
|
23242
|
-
|
|
23243
|
-
|
|
23244
|
-
|
|
23245
|
-
|
|
23246
|
-
|
|
23247
|
-
|
|
23248
|
-
|
|
23249
|
-
|
|
23250
|
-
|
|
23251
|
-
|
|
23252
|
-
|
|
23253
|
-
|
|
23254
|
-
|
|
23255
|
-
|
|
23256
|
-
|
|
23257
|
-
|
|
23258
|
-
|
|
23259
|
-
|
|
23260
|
-
|
|
23261
|
-
|
|
23262
|
-
|
|
23263
|
-
|
|
23264
|
-
return { ok: false, message: "A password is required for password mode." };
|
|
24676
|
+
var publicAgentView = (row, slug) => ({
|
|
24677
|
+
id: row.id,
|
|
24678
|
+
name: row.name ?? "",
|
|
24679
|
+
description: row.description ?? "",
|
|
24680
|
+
image: row.image ?? null,
|
|
24681
|
+
welcomemessage: row.welcomemessage ?? "",
|
|
24682
|
+
slug,
|
|
24683
|
+
guest_auth_mode: row.guest_auth_mode || "regular",
|
|
24684
|
+
guest_has_cover: !!row.guest_cover_image
|
|
24685
|
+
});
|
|
24686
|
+
var evaluateGuestChatAccess = async (agent, userId, guestPassword) => {
|
|
24687
|
+
if (agent.guest_access) {
|
|
24688
|
+
if (userId != null) return { allowed: true, via: "guest" };
|
|
24689
|
+
const mode = agent.guest_auth_mode || "regular";
|
|
24690
|
+
if (mode === "public") return { allowed: true, via: "guest" };
|
|
24691
|
+
if (mode === "password") {
|
|
24692
|
+
if (agent.guest_password_hash && guestPassword) {
|
|
24693
|
+
const ok = await verifySharePassword(guestPassword, agent.guest_password_hash);
|
|
24694
|
+
if (ok) return { allowed: true, via: "guest" };
|
|
24695
|
+
return { allowed: false, status: 401, message: "Incorrect password." };
|
|
24696
|
+
}
|
|
24697
|
+
return { allowed: false, status: 401, message: "Password required." };
|
|
24698
|
+
}
|
|
24699
|
+
return { allowed: false, status: 401, message: "Authentication required." };
|
|
23265
24700
|
}
|
|
23266
|
-
if (
|
|
23267
|
-
return {
|
|
24701
|
+
if (userId == null && agent.rights_mode === "public") {
|
|
24702
|
+
return { allowed: true, via: "rbac-public" };
|
|
23268
24703
|
}
|
|
23269
|
-
|
|
23270
|
-
|
|
24704
|
+
return { allowed: false, status: 401, message: "Authentication required." };
|
|
24705
|
+
};
|
|
24706
|
+
|
|
24707
|
+
// src/exulu/guest-rate-limit.ts
|
|
24708
|
+
init_cjs_shims();
|
|
24709
|
+
var MINUTE_MS = 6e4;
|
|
24710
|
+
var HOUR_MS2 = 36e5;
|
|
24711
|
+
var perMinuteLimit = () => parseInt(process.env.EXULU_GUEST_RATE_PER_MINUTE || "10", 10);
|
|
24712
|
+
var perHourLimit = () => parseInt(process.env.EXULU_GUEST_RATE_PER_HOUR || "60", 10);
|
|
24713
|
+
var maxMessageChars = () => parseInt(process.env.EXULU_GUEST_MAX_MESSAGE_CHARS || "8000", 10);
|
|
24714
|
+
var maxTotalChars = () => parseInt(process.env.EXULU_GUEST_MAX_TOTAL_CHARS || "32000", 10);
|
|
24715
|
+
var MAX_PART_COUNT = 100;
|
|
24716
|
+
var windows = /* @__PURE__ */ new Map();
|
|
24717
|
+
var guestRateLimitExceeded = (ip, now = Date.now()) => {
|
|
24718
|
+
const state = windows.get(ip) ?? {
|
|
24719
|
+
minuteStart: now,
|
|
24720
|
+
minuteCount: 0,
|
|
24721
|
+
hourStart: now,
|
|
24722
|
+
hourCount: 0,
|
|
24723
|
+
lastSeen: now
|
|
24724
|
+
};
|
|
24725
|
+
if (now - state.minuteStart >= MINUTE_MS) {
|
|
24726
|
+
state.minuteStart = now;
|
|
24727
|
+
state.minuteCount = 0;
|
|
24728
|
+
}
|
|
24729
|
+
if (now - state.hourStart >= HOUR_MS2) {
|
|
24730
|
+
state.hourStart = now;
|
|
24731
|
+
state.hourCount = 0;
|
|
24732
|
+
}
|
|
24733
|
+
state.minuteCount += 1;
|
|
24734
|
+
state.hourCount += 1;
|
|
24735
|
+
state.lastSeen = now;
|
|
24736
|
+
windows.set(ip, state);
|
|
24737
|
+
if (windows.size > 1e4) {
|
|
24738
|
+
for (const [key, value] of windows) {
|
|
24739
|
+
if (now - value.lastSeen >= HOUR_MS2) windows.delete(key);
|
|
24740
|
+
}
|
|
24741
|
+
if (windows.size > 1e4) {
|
|
24742
|
+
const sorted = [...windows.entries()].sort(
|
|
24743
|
+
(a, b) => a[1].lastSeen - b[1].lastSeen
|
|
24744
|
+
);
|
|
24745
|
+
for (const [key] of sorted) {
|
|
24746
|
+
if (windows.size <= 1e4) break;
|
|
24747
|
+
windows.delete(key);
|
|
24748
|
+
}
|
|
24749
|
+
}
|
|
23271
24750
|
}
|
|
23272
|
-
return
|
|
24751
|
+
return state.minuteCount > perMinuteLimit() || state.hourCount > perHourLimit();
|
|
23273
24752
|
};
|
|
23274
|
-
var
|
|
23275
|
-
|
|
23276
|
-
|
|
23277
|
-
|
|
23278
|
-
|
|
23279
|
-
|
|
23280
|
-
|
|
24753
|
+
var partsTooLong = (parts) => Array.isArray(parts) && parts.some(
|
|
24754
|
+
(p) => typeof p?.text === "string" && p.text.length > maxMessageChars()
|
|
24755
|
+
);
|
|
24756
|
+
var collectTextParts = (b) => {
|
|
24757
|
+
const parts = [];
|
|
24758
|
+
const addFromParts = (ps) => {
|
|
24759
|
+
if (!Array.isArray(ps)) return;
|
|
24760
|
+
for (const p of ps) {
|
|
24761
|
+
if (typeof p?.text === "string") parts.push(p.text);
|
|
24762
|
+
}
|
|
23281
24763
|
};
|
|
24764
|
+
if (b.message) addFromParts(b.message.parts);
|
|
24765
|
+
if (Array.isArray(b.messages)) {
|
|
24766
|
+
for (const m of b.messages) addFromParts(m?.parts);
|
|
24767
|
+
}
|
|
24768
|
+
return parts;
|
|
24769
|
+
};
|
|
24770
|
+
var guestMessageTooLong = (body) => {
|
|
24771
|
+
const b = body;
|
|
24772
|
+
if (!b) return false;
|
|
24773
|
+
if (b.message && partsTooLong(b.message.parts)) return true;
|
|
24774
|
+
if (Array.isArray(b.messages)) {
|
|
24775
|
+
if (b.messages.some((m) => partsTooLong(m?.parts))) return true;
|
|
24776
|
+
}
|
|
24777
|
+
const allParts = collectTextParts(b);
|
|
24778
|
+
if (allParts.length > MAX_PART_COUNT) return true;
|
|
24779
|
+
const totalChars = allParts.reduce((sum, t) => sum + t.length, 0);
|
|
24780
|
+
if (totalChars > maxTotalChars()) return true;
|
|
24781
|
+
return false;
|
|
24782
|
+
};
|
|
24783
|
+
var extractClientIp = (req) => {
|
|
24784
|
+
const forwarded = req.headers["x-forwarded-for"];
|
|
24785
|
+
if (process.env.EXULU_TRUST_PROXY === "true") {
|
|
24786
|
+
if (typeof forwarded === "string" && forwarded.length > 0) {
|
|
24787
|
+
const parts = forwarded.split(",");
|
|
24788
|
+
return parts[parts.length - 1].trim();
|
|
24789
|
+
}
|
|
24790
|
+
}
|
|
24791
|
+
return req.ip || req.socket?.remoteAddress || "unknown";
|
|
23282
24792
|
};
|
|
23283
|
-
var getSharedArtifactByName = (db2, name) => db2("shared_artifacts").where({ name }).first();
|
|
23284
24793
|
|
|
23285
24794
|
// src/skills/skill-access.ts
|
|
23286
24795
|
init_cjs_shims();
|
|
@@ -23645,6 +25154,9 @@ var createExpressRoutes = async (app, providers, tools, contexts, config, evals,
|
|
|
23645
25154
|
})
|
|
23646
25155
|
);
|
|
23647
25156
|
app.get(OAUTH_CALLBACK_PATH, handleOauthCallback);
|
|
25157
|
+
app.post("/credentials/submit", handleCredentialSubmit);
|
|
25158
|
+
app.get("/credentials", handleCredentialList);
|
|
25159
|
+
app.delete("/credentials/:provider", handleCredentialDelete);
|
|
23648
25160
|
app.post("/test", async (req, res) => {
|
|
23649
25161
|
const { item_name, context_id } = req.body;
|
|
23650
25162
|
let itemFilters = [];
|
|
@@ -23988,17 +25500,33 @@ Mood: friendly and intelligent.
|
|
|
23988
25500
|
}
|
|
23989
25501
|
console.log("[EXULU] agent.rights_mode", agent.rights_mode);
|
|
23990
25502
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
23991
|
-
|
|
23992
|
-
|
|
25503
|
+
const user = authenticationResult.user;
|
|
25504
|
+
if (!user?.id) {
|
|
25505
|
+
const ip = extractClientIp(req);
|
|
25506
|
+
if (guestRateLimitExceeded(ip)) {
|
|
25507
|
+
res.status(429).json({ detail: "Too many requests. Try again later." });
|
|
25508
|
+
return;
|
|
25509
|
+
}
|
|
25510
|
+
if (guestMessageTooLong(req.body)) {
|
|
25511
|
+
res.status(413).json({ detail: "Message too long." });
|
|
25512
|
+
return;
|
|
25513
|
+
}
|
|
25514
|
+
}
|
|
25515
|
+
const guestGate = await evaluateGuestChatAccess(
|
|
25516
|
+
agent,
|
|
25517
|
+
user?.id,
|
|
25518
|
+
req.headers["x-guest-password"]
|
|
25519
|
+
);
|
|
25520
|
+
if (!user?.id && !guestGate.allowed) {
|
|
25521
|
+
res.status(guestGate.status).json({ detail: guestGate.message });
|
|
23993
25522
|
return;
|
|
23994
25523
|
}
|
|
23995
|
-
const user = authenticationResult.user;
|
|
23996
25524
|
const scopeCheck = checkApiKeyScope(user, instance2);
|
|
23997
25525
|
if (!scopeCheck.allowed) {
|
|
23998
25526
|
res.status(scopeCheck.code).json({ detail: scopeCheck.reason });
|
|
23999
25527
|
return;
|
|
24000
25528
|
}
|
|
24001
|
-
const hasAccessToAgent = await checkRecordAccess(agent, "read", user);
|
|
25529
|
+
const hasAccessToAgent = guestGate.allowed || await checkRecordAccess(agent, "read", user);
|
|
24002
25530
|
if (!hasAccessToAgent) {
|
|
24003
25531
|
res.status(401).json({
|
|
24004
25532
|
message: "You don't have access to this agent."
|
|
@@ -24148,7 +25676,7 @@ ${customInstructions}` : agent.instructions;
|
|
|
24148
25676
|
else message2 = JSON.stringify(error);
|
|
24149
25677
|
return mapStreamErrorMessage(message2);
|
|
24150
25678
|
},
|
|
24151
|
-
generateMessageId: (0,
|
|
25679
|
+
generateMessageId: (0, import_ai12.createIdGenerator)({
|
|
24152
25680
|
prefix: "msg_",
|
|
24153
25681
|
size: 16
|
|
24154
25682
|
}),
|
|
@@ -25356,6 +26884,32 @@ ${style.markdown}` : params.prompt;
|
|
|
25356
26884
|
}
|
|
25357
26885
|
res.status(200).json({ budget: await getUserBudgetView(authResult.user.id) });
|
|
25358
26886
|
});
|
|
26887
|
+
app.get("/me/usage", async (req, res) => {
|
|
26888
|
+
const authResult = await requestValidators.authenticate(req);
|
|
26889
|
+
if (!authResult.user?.id) {
|
|
26890
|
+
res.status(authResult.code ?? 401).json({ detail: authResult.message });
|
|
26891
|
+
return;
|
|
26892
|
+
}
|
|
26893
|
+
const window = resolveUsageWindow(req.query.start_date, req.query.end_date);
|
|
26894
|
+
if (!window) {
|
|
26895
|
+
res.status(400).json({
|
|
26896
|
+
detail: "start_date and end_date must be YYYY-MM-DD or ISO datetimes, with start_date <= end_date."
|
|
26897
|
+
});
|
|
26898
|
+
return;
|
|
26899
|
+
}
|
|
26900
|
+
try {
|
|
26901
|
+
res.status(200).json({ usage: await getMyUsageView(authResult.user.id, window) });
|
|
26902
|
+
} catch (err) {
|
|
26903
|
+
if (err instanceof LiteLLMAdminError) {
|
|
26904
|
+
res.status(502).json({ detail: err.message });
|
|
26905
|
+
return;
|
|
26906
|
+
}
|
|
26907
|
+
console.error("[EXULU] /me/usage failed", err);
|
|
26908
|
+
res.status(500).json({
|
|
26909
|
+
detail: err instanceof Error ? err.message : "Usage query failed."
|
|
26910
|
+
});
|
|
26911
|
+
}
|
|
26912
|
+
});
|
|
25359
26913
|
app.put(
|
|
25360
26914
|
"/admin/budgets/:entityType/bulk",
|
|
25361
26915
|
async (req, res) => {
|
|
@@ -25469,10 +27023,10 @@ ${style.markdown}` : params.prompt;
|
|
|
25469
27023
|
}
|
|
25470
27024
|
return { user: authResult.user };
|
|
25471
27025
|
};
|
|
25472
|
-
const
|
|
27026
|
+
const DATE_ONLY_RE2 = /^\d{4}-\d{2}-\d{2}$/;
|
|
25473
27027
|
const normaliseDateParam = (raw) => {
|
|
25474
27028
|
if (typeof raw !== "string" || raw.length === 0) return null;
|
|
25475
|
-
if (
|
|
27029
|
+
if (DATE_ONLY_RE2.test(raw)) return raw;
|
|
25476
27030
|
const dt = new Date(raw);
|
|
25477
27031
|
if (Number.isNaN(dt.getTime())) return null;
|
|
25478
27032
|
return dt.toISOString().slice(0, 10);
|
|
@@ -27069,6 +28623,93 @@ ${style.markdown}` : params.prompt;
|
|
|
27069
28623
|
if (headers.disposition) res.setHeader("Content-Disposition", headers.disposition);
|
|
27070
28624
|
res.send(bytes);
|
|
27071
28625
|
});
|
|
28626
|
+
const resolvePublicAgentSlug = async (agentModel) => {
|
|
28627
|
+
if (isLiteLLMEnabled()) return "/agents/litellm/run";
|
|
28628
|
+
if (!agentModel) return "";
|
|
28629
|
+
const { db: db2 } = await postgresClient();
|
|
28630
|
+
const modelRow = await db2.from("models").where({ id: agentModel }).first();
|
|
28631
|
+
const provider = modelRow?.provider ? providers.find((a) => a.id === modelRow.provider) : void 0;
|
|
28632
|
+
return provider?.slug || "";
|
|
28633
|
+
};
|
|
28634
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
28635
|
+
const getGuestAgentById = async (id) => {
|
|
28636
|
+
if (!UUID_RE.test(id)) return void 0;
|
|
28637
|
+
const { db: db2 } = await postgresClient();
|
|
28638
|
+
return db2.from("agents").where({ id, guest_access: true, active: true }).first();
|
|
28639
|
+
};
|
|
28640
|
+
app.get("/public-agents", async (_req, res) => {
|
|
28641
|
+
const { db: db2 } = await postgresClient();
|
|
28642
|
+
const rows = await db2.from("agents").where({ guest_access: true, active: true }).select(
|
|
28643
|
+
"id",
|
|
28644
|
+
"name",
|
|
28645
|
+
"description",
|
|
28646
|
+
"image",
|
|
28647
|
+
"welcomemessage",
|
|
28648
|
+
"model",
|
|
28649
|
+
"guest_auth_mode",
|
|
28650
|
+
"guest_cover_image"
|
|
28651
|
+
);
|
|
28652
|
+
const views = await Promise.all(
|
|
28653
|
+
rows.map(
|
|
28654
|
+
async (row) => publicAgentView(row, await resolvePublicAgentSlug(row.model))
|
|
28655
|
+
)
|
|
28656
|
+
);
|
|
28657
|
+
res.json(views);
|
|
28658
|
+
});
|
|
28659
|
+
app.get("/public-agents/:id/meta", async (req, res) => {
|
|
28660
|
+
const row = await getGuestAgentById(req.params.id ?? "");
|
|
28661
|
+
if (!row) {
|
|
28662
|
+
res.status(404).json({ detail: "Not found." });
|
|
28663
|
+
return;
|
|
28664
|
+
}
|
|
28665
|
+
res.json(publicAgentView(row, await resolvePublicAgentSlug(row.model)));
|
|
28666
|
+
});
|
|
28667
|
+
app.get("/public-agents/:id/cover", async (req, res) => {
|
|
28668
|
+
const row = await getGuestAgentById(req.params.id ?? "");
|
|
28669
|
+
if (!row?.guest_cover_image) {
|
|
28670
|
+
res.status(404).json({ detail: "Not found." });
|
|
28671
|
+
return;
|
|
28672
|
+
}
|
|
28673
|
+
let bytes;
|
|
28674
|
+
try {
|
|
28675
|
+
bytes = await getS3ObjectBytes(row.guest_cover_image, config);
|
|
28676
|
+
} catch (e) {
|
|
28677
|
+
if (e?.name === "NoSuchKey" || e?.name === "NotFound" || e?.$metadata?.httpStatusCode === 404) {
|
|
28678
|
+
res.status(404).json({ detail: "Cover not found." });
|
|
28679
|
+
return;
|
|
28680
|
+
}
|
|
28681
|
+
console.error("[EXULU] public-agent cover read failed", e);
|
|
28682
|
+
res.status(500).json({ detail: "Failed to read cover." });
|
|
28683
|
+
return;
|
|
28684
|
+
}
|
|
28685
|
+
const ext = row.guest_cover_image.split(".").pop()?.toLowerCase();
|
|
28686
|
+
const contentType = ext === "png" ? "image/png" : ext === "webp" ? "image/webp" : "image/jpeg";
|
|
28687
|
+
res.setHeader("Content-Type", contentType);
|
|
28688
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
28689
|
+
res.setHeader("Cache-Control", "public, max-age=300");
|
|
28690
|
+
res.send(bytes);
|
|
28691
|
+
});
|
|
28692
|
+
app.post(
|
|
28693
|
+
"/public-agents/:id/verify-password",
|
|
28694
|
+
async (req, res) => {
|
|
28695
|
+
const ip = extractClientIp(req);
|
|
28696
|
+
if (guestRateLimitExceeded(ip)) {
|
|
28697
|
+
res.status(429).json({ detail: "Too many requests. Try again later." });
|
|
28698
|
+
return;
|
|
28699
|
+
}
|
|
28700
|
+
const row = await getGuestAgentById(req.params.id ?? "");
|
|
28701
|
+
if (!row || row.guest_auth_mode !== "password") {
|
|
28702
|
+
res.status(404).json({ detail: "Not found." });
|
|
28703
|
+
return;
|
|
28704
|
+
}
|
|
28705
|
+
const password = typeof req.body?.password === "string" ? req.body.password : "";
|
|
28706
|
+
if (!row.guest_password_hash || !await verifySharePassword(password, row.guest_password_hash)) {
|
|
28707
|
+
res.status(401).json({ detail: "Incorrect password." });
|
|
28708
|
+
return;
|
|
28709
|
+
}
|
|
28710
|
+
res.status(204).end();
|
|
28711
|
+
}
|
|
28712
|
+
);
|
|
27072
28713
|
app.use(import_express5.default.static("public"));
|
|
27073
28714
|
await registerOpenAIGatewayRoutes(app, providers, tools, contexts, config);
|
|
27074
28715
|
return app;
|
|
@@ -27147,7 +28788,7 @@ init_check_record_access();
|
|
|
27147
28788
|
init_resolve_model();
|
|
27148
28789
|
init_supervisor();
|
|
27149
28790
|
init_client();
|
|
27150
|
-
var
|
|
28791
|
+
var import_zod19 = require("zod");
|
|
27151
28792
|
init_convert_exulu_tools_to_ai_sdk_tools();
|
|
27152
28793
|
init_singleton();
|
|
27153
28794
|
var SESSION_ID_HEADER = "mcp-session-id";
|
|
@@ -27221,7 +28862,7 @@ var ExuluMCP = class {
|
|
|
27221
28862
|
title: tool4.name + " agent",
|
|
27222
28863
|
description: tool4.description,
|
|
27223
28864
|
inputSchema: {
|
|
27224
|
-
inputs: tool4.inputSchema ||
|
|
28865
|
+
inputs: tool4.inputSchema || import_zod19.z.object({})
|
|
27225
28866
|
}
|
|
27226
28867
|
},
|
|
27227
28868
|
async ({ inputs }, args) => {
|
|
@@ -27273,7 +28914,7 @@ var ExuluMCP = class {
|
|
|
27273
28914
|
title: "Get List of Prompt Templates",
|
|
27274
28915
|
description: "Retrieves a list of prompt templates available for this agent. Returns the name, description, and ID of each template.",
|
|
27275
28916
|
inputSchema: {
|
|
27276
|
-
inputs:
|
|
28917
|
+
inputs: import_zod19.z.object({})
|
|
27277
28918
|
}
|
|
27278
28919
|
},
|
|
27279
28920
|
async ({ inputs }, args) => {
|
|
@@ -27319,8 +28960,8 @@ var ExuluMCP = class {
|
|
|
27319
28960
|
title: "Get Prompt Template Details",
|
|
27320
28961
|
description: "Retrieves the full details of a specific prompt template by ID, including the actual template content with variables.",
|
|
27321
28962
|
inputSchema: {
|
|
27322
|
-
inputs:
|
|
27323
|
-
id:
|
|
28963
|
+
inputs: import_zod19.z.object({
|
|
28964
|
+
id: import_zod19.z.string().describe("The ID of the prompt template to retrieve")
|
|
27324
28965
|
})
|
|
27325
28966
|
}
|
|
27326
28967
|
},
|
|
@@ -28395,7 +30036,7 @@ init_cjs_shims();
|
|
|
28395
30036
|
|
|
28396
30037
|
// src/exulu/evals.ts
|
|
28397
30038
|
init_cjs_shims();
|
|
28398
|
-
var
|
|
30039
|
+
var import_ai13 = require("ai");
|
|
28399
30040
|
init_entitlements();
|
|
28400
30041
|
var ExuluEval = class {
|
|
28401
30042
|
id;
|
|
@@ -28435,8 +30076,8 @@ var ExuluEval = class {
|
|
|
28435
30076
|
// src/templates/evals/index.ts
|
|
28436
30077
|
init_resolve_model();
|
|
28437
30078
|
init_singleton();
|
|
28438
|
-
var
|
|
28439
|
-
var
|
|
30079
|
+
var import_zod20 = require("zod");
|
|
30080
|
+
var import_ai14 = require("ai");
|
|
28440
30081
|
var llmAsJudgeEval = () => {
|
|
28441
30082
|
if (process.env.REDIS_HOST?.length && process.env.REDIS_PORT?.length) {
|
|
28442
30083
|
return new ExuluEval({
|
|
@@ -28481,15 +30122,15 @@ var llmAsJudgeEval = () => {
|
|
|
28481
30122
|
rbacBypass: true
|
|
28482
30123
|
});
|
|
28483
30124
|
console.log("[EXULU] prompt", prompt);
|
|
28484
|
-
const { output } = await (0,
|
|
30125
|
+
const { output } = await (0, import_ai14.generateText)({
|
|
28485
30126
|
temperature: 0,
|
|
28486
30127
|
model: resolved.languageModel,
|
|
28487
30128
|
system: "",
|
|
28488
30129
|
prompt,
|
|
28489
30130
|
maxRetries: 2,
|
|
28490
|
-
output:
|
|
28491
|
-
schema:
|
|
28492
|
-
score:
|
|
30131
|
+
output: import_ai14.Output.object({
|
|
30132
|
+
schema: import_zod20.z.object({
|
|
30133
|
+
score: import_zod20.z.number().min(0).max(100).describe("The score between 0 and 100.")
|
|
28493
30134
|
})
|
|
28494
30135
|
})
|
|
28495
30136
|
});
|
|
@@ -28720,15 +30361,15 @@ Usage:
|
|
|
28720
30361
|
- If no todos exist yet, an empty list will be returned`;
|
|
28721
30362
|
|
|
28722
30363
|
// src/templates/tools/todo/todo.ts
|
|
28723
|
-
var
|
|
30364
|
+
var import_zod21 = __toESM(require("zod"), 1);
|
|
28724
30365
|
init_tool();
|
|
28725
30366
|
init_check_record_access();
|
|
28726
30367
|
init_client();
|
|
28727
|
-
var TodoSchema =
|
|
28728
|
-
content:
|
|
28729
|
-
status:
|
|
28730
|
-
priority:
|
|
28731
|
-
id:
|
|
30368
|
+
var TodoSchema = import_zod21.default.object({
|
|
30369
|
+
content: import_zod21.default.string().describe("Brief description of the task"),
|
|
30370
|
+
status: import_zod21.default.string().describe("Current status of the task: pending, in_progress, completed, cancelled"),
|
|
30371
|
+
priority: import_zod21.default.string().describe("Priority level of the task: high, medium, low"),
|
|
30372
|
+
id: import_zod21.default.string().describe("Unique identifier for the todo item")
|
|
28732
30373
|
});
|
|
28733
30374
|
var TodoWriteTool = new ExuluTool({
|
|
28734
30375
|
id: "todo_write",
|
|
@@ -28744,8 +30385,8 @@ var TodoWriteTool = new ExuluTool({
|
|
|
28744
30385
|
default: todowrite_default
|
|
28745
30386
|
}
|
|
28746
30387
|
],
|
|
28747
|
-
inputSchema:
|
|
28748
|
-
todos:
|
|
30388
|
+
inputSchema: import_zod21.default.object({
|
|
30389
|
+
todos: import_zod21.default.array(TodoSchema).describe("The updated todo list")
|
|
28749
30390
|
}),
|
|
28750
30391
|
execute: async (inputs) => {
|
|
28751
30392
|
const { sessionID, todos, user } = inputs;
|
|
@@ -28780,7 +30421,7 @@ var TodoReadTool = new ExuluTool({
|
|
|
28780
30421
|
id: "todo_read",
|
|
28781
30422
|
name: "Todo Read",
|
|
28782
30423
|
description: "Use this tool to read your todo list",
|
|
28783
|
-
inputSchema:
|
|
30424
|
+
inputSchema: import_zod21.default.object({}),
|
|
28784
30425
|
type: "function",
|
|
28785
30426
|
category: "todo",
|
|
28786
30427
|
config: [
|
|
@@ -28825,7 +30466,7 @@ init_cjs_shims();
|
|
|
28825
30466
|
var questionread_default = 'Use this tool to read questions you\'ve asked and check if they\'ve been answered by the user. This tool helps you track the status of questions and retrieve the user\'s selected answers.\n\n## When to Use This Tool\n\nUse this tool proactively in these situations:\n- After asking a question to check if the user has responded\n- To retrieve the user\'s answer before proceeding with implementation\n- To review all questions and answers in the current session\n- When you need to reference a previous answer\n\n## How It Works\n\n- This tool takes no parameters (leave the input blank or empty)\n- Returns an array of all questions in the session\n- Each question includes:\n - `id`: Unique identifier for the question\n - `question`: The question text\n - `answerOptions`: Array of answer options with their IDs and text\n - `status`: Either "pending" (not answered) or "answered"\n - `selectedAnswerId`: The ID of the chosen answer (only present if answered)\n\n## Usage Pattern\n\nTypically you\'ll:\n1. Use Question Ask to pose a question\n2. Wait for the user to respond\n3. Use Question Read to check the answer\n4. Find the selected answer by matching the `selectedAnswerId` with an option in `answerOptions`\n5. Proceed with implementation based on the user\'s choice\n\n## Example Response\n\n```json\n[\n {\n "id": "question123",\n "question": "Which authentication method would you like to implement?",\n "answerOptions": [\n { "id": "ans1", "text": "JWT tokens" },\n { "id": "ans2", "text": "OAuth 2.0" },\n { "id": "ans3", "text": "Session-based auth" },\n { "id": "ans4", "text": "None of the above..." }\n ],\n "status": "answered",\n "selectedAnswerId": "ans1"\n }\n]\n```\n\nIn this example, the user selected "JWT tokens" (id: ans1).\n\n## Important Notes\n\n- If no questions exist in the session, an empty array will be returned\n- Questions remain in the session even after being answered for reference\n- Use the `selectedAnswerId` to find which answer option the user chose by matching it against the `id` field in `answerOptions`\n';
|
|
28826
30467
|
|
|
28827
30468
|
// src/templates/tools/question/question.ts
|
|
28828
|
-
var
|
|
30469
|
+
var import_zod23 = __toESM(require("zod"), 1);
|
|
28829
30470
|
init_tool();
|
|
28830
30471
|
init_client();
|
|
28831
30472
|
|
|
@@ -28917,21 +30558,21 @@ After asking a question, use the Question Read tool to check if the user has ans
|
|
|
28917
30558
|
`;
|
|
28918
30559
|
|
|
28919
30560
|
// src/templates/tools/question/question-ask.ts
|
|
28920
|
-
var
|
|
30561
|
+
var import_zod22 = __toESM(require("zod"), 1);
|
|
28921
30562
|
init_tool();
|
|
28922
30563
|
init_check_record_access();
|
|
28923
30564
|
init_client();
|
|
28924
30565
|
var import_node_crypto16 = require("crypto");
|
|
28925
|
-
var AnswerOptionSchema =
|
|
28926
|
-
id:
|
|
28927
|
-
text:
|
|
30566
|
+
var AnswerOptionSchema = import_zod22.default.object({
|
|
30567
|
+
id: import_zod22.default.string().describe("Unique identifier for the answer option"),
|
|
30568
|
+
text: import_zod22.default.string().describe("The text of the answer option")
|
|
28928
30569
|
});
|
|
28929
|
-
var _QuestionSchema =
|
|
28930
|
-
id:
|
|
28931
|
-
question:
|
|
28932
|
-
answerOptions:
|
|
28933
|
-
selectedAnswerId:
|
|
28934
|
-
status:
|
|
30570
|
+
var _QuestionSchema = import_zod22.default.object({
|
|
30571
|
+
id: import_zod22.default.string().describe("Unique identifier for the question"),
|
|
30572
|
+
question: import_zod22.default.string().describe("The question to ask the user"),
|
|
30573
|
+
answerOptions: import_zod22.default.array(AnswerOptionSchema).describe("Array of possible answer options"),
|
|
30574
|
+
selectedAnswerId: import_zod22.default.string().optional().describe("The ID of the answer option selected by the user"),
|
|
30575
|
+
status: import_zod22.default.enum(["pending", "answered"]).describe("Status of the question: pending or answered")
|
|
28935
30576
|
});
|
|
28936
30577
|
var QuestionAskTool = new ExuluTool({
|
|
28937
30578
|
id: "question_ask",
|
|
@@ -28948,9 +30589,9 @@ var QuestionAskTool = new ExuluTool({
|
|
|
28948
30589
|
default: questionask_default
|
|
28949
30590
|
}
|
|
28950
30591
|
],
|
|
28951
|
-
inputSchema:
|
|
28952
|
-
question:
|
|
28953
|
-
answerOptions:
|
|
30592
|
+
inputSchema: import_zod22.default.object({
|
|
30593
|
+
question: import_zod22.default.string().describe("The question to ask the user"),
|
|
30594
|
+
answerOptions: import_zod22.default.array(import_zod22.default.string()).describe("Array of possible answer options (strings)")
|
|
28954
30595
|
}),
|
|
28955
30596
|
execute: async (inputs) => {
|
|
28956
30597
|
const { sessionID, question, answerOptions, user } = inputs;
|
|
@@ -29023,7 +30664,7 @@ var QuestionReadTool = new ExuluTool({
|
|
|
29023
30664
|
name: "Question Read",
|
|
29024
30665
|
needsApproval: false,
|
|
29025
30666
|
description: "Use this tool to read questions and their answers",
|
|
29026
|
-
inputSchema:
|
|
30667
|
+
inputSchema: import_zod23.default.object({}),
|
|
29027
30668
|
type: "function",
|
|
29028
30669
|
category: "question",
|
|
29029
30670
|
config: [
|
|
@@ -29055,15 +30696,15 @@ var questionTools = [QuestionAskTool, QuestionReadTool];
|
|
|
29055
30696
|
// src/templates/tools/perplexity.ts
|
|
29056
30697
|
init_cjs_shims();
|
|
29057
30698
|
init_tool();
|
|
29058
|
-
var
|
|
30699
|
+
var import_zod24 = __toESM(require("zod"), 1);
|
|
29059
30700
|
var import_perplexity_ai = __toESM(require("@perplexity-ai/perplexity_ai"), 1);
|
|
29060
30701
|
var internetSearchTool = new ExuluTool({
|
|
29061
30702
|
id: "internet_search",
|
|
29062
30703
|
name: "Internet Search",
|
|
29063
30704
|
description: "Search the internet for information.",
|
|
29064
|
-
inputSchema:
|
|
29065
|
-
query:
|
|
29066
|
-
search_recency_filter:
|
|
30705
|
+
inputSchema: import_zod24.default.object({
|
|
30706
|
+
query: import_zod24.default.string().describe("The query to the tool."),
|
|
30707
|
+
search_recency_filter: import_zod24.default.enum(["day", "week", "month", "year"]).optional().describe("The recency filter for the search, can be day, week, month or year.")
|
|
29067
30708
|
}),
|
|
29068
30709
|
category: "internet_search",
|
|
29069
30710
|
type: "web_search",
|
|
@@ -29158,7 +30799,7 @@ var perplexityTools = [internetSearchTool];
|
|
|
29158
30799
|
init_cjs_shims();
|
|
29159
30800
|
init_tool();
|
|
29160
30801
|
var nodemailer = __toESM(require("nodemailer"), 1);
|
|
29161
|
-
var
|
|
30802
|
+
var import_zod25 = require("zod");
|
|
29162
30803
|
var transporter = null;
|
|
29163
30804
|
function getTransporter(config) {
|
|
29164
30805
|
if (!transporter) {
|
|
@@ -29182,11 +30823,11 @@ var emailTool = new ExuluTool({
|
|
|
29182
30823
|
id: "email",
|
|
29183
30824
|
name: "Email",
|
|
29184
30825
|
description: "Send an email.",
|
|
29185
|
-
inputSchema:
|
|
29186
|
-
recipient:
|
|
29187
|
-
subject:
|
|
29188
|
-
html:
|
|
29189
|
-
text:
|
|
30826
|
+
inputSchema: import_zod25.z.object({
|
|
30827
|
+
recipient: import_zod25.z.string().describe("The recipient of the email."),
|
|
30828
|
+
subject: import_zod25.z.string().describe("The subject of the email."),
|
|
30829
|
+
html: import_zod25.z.string().describe("The HTML body of the email."),
|
|
30830
|
+
text: import_zod25.z.string().describe("The text body of the email.")
|
|
29190
30831
|
}),
|
|
29191
30832
|
type: "function",
|
|
29192
30833
|
config: [{
|
|
@@ -29257,7 +30898,7 @@ init_tool();
|
|
|
29257
30898
|
init_supervisor();
|
|
29258
30899
|
init_client();
|
|
29259
30900
|
init_check_record_access();
|
|
29260
|
-
var
|
|
30901
|
+
var import_zod26 = require("zod");
|
|
29261
30902
|
var _cachedImageModels;
|
|
29262
30903
|
var setCachedImageModels = (models2) => {
|
|
29263
30904
|
_cachedImageModels = models2;
|
|
@@ -29318,8 +30959,8 @@ var createImageGenerationWidgetTool = (models2) => {
|
|
|
29318
30959
|
needsApproval: false,
|
|
29319
30960
|
type: "function",
|
|
29320
30961
|
config: [],
|
|
29321
|
-
inputSchema:
|
|
29322
|
-
prompt:
|
|
30962
|
+
inputSchema: import_zod26.z.object({
|
|
30963
|
+
prompt: import_zod26.z.string().describe(
|
|
29323
30964
|
"Initial image prompt. The user can edit it before generating."
|
|
29324
30965
|
)
|
|
29325
30966
|
}),
|
|
@@ -29646,6 +31287,43 @@ var startTranscriptionPollingLoop = () => {
|
|
|
29646
31287
|
process.on("SIGTERM", stop);
|
|
29647
31288
|
};
|
|
29648
31289
|
|
|
31290
|
+
// src/exulu/recall/reconcile-loop.ts
|
|
31291
|
+
init_cjs_shims();
|
|
31292
|
+
var RECONCILE_INTERVAL_MS = 6e4;
|
|
31293
|
+
var timer2 = null;
|
|
31294
|
+
var stopped2 = false;
|
|
31295
|
+
var tick2 = async () => {
|
|
31296
|
+
if (stopped2) return;
|
|
31297
|
+
try {
|
|
31298
|
+
const acted = await recallService.reconcileOnce();
|
|
31299
|
+
if (acted > 0) {
|
|
31300
|
+
console.log(`[EXULU-RECALL] reconcile tick recovered ${acted} job(s)`);
|
|
31301
|
+
}
|
|
31302
|
+
} catch (err) {
|
|
31303
|
+
console.error(
|
|
31304
|
+
`[EXULU-RECALL] reconcile tick failed: ${err.message}`
|
|
31305
|
+
);
|
|
31306
|
+
} finally {
|
|
31307
|
+
if (!stopped2) {
|
|
31308
|
+
timer2 = setTimeout(tick2, RECONCILE_INTERVAL_MS);
|
|
31309
|
+
}
|
|
31310
|
+
}
|
|
31311
|
+
};
|
|
31312
|
+
var startRecallReconcileLoop = () => {
|
|
31313
|
+
if (timer2) return;
|
|
31314
|
+
stopped2 = false;
|
|
31315
|
+
timer2 = setTimeout(tick2, RECONCILE_INTERVAL_MS);
|
|
31316
|
+
const stop = () => {
|
|
31317
|
+
stopped2 = true;
|
|
31318
|
+
if (timer2) {
|
|
31319
|
+
clearTimeout(timer2);
|
|
31320
|
+
timer2 = null;
|
|
31321
|
+
}
|
|
31322
|
+
};
|
|
31323
|
+
process.on("SIGINT", stop);
|
|
31324
|
+
process.on("SIGTERM", stop);
|
|
31325
|
+
};
|
|
31326
|
+
|
|
29649
31327
|
// src/exulu/app/index.ts
|
|
29650
31328
|
var isDev = process.env.NODE_ENV !== "production";
|
|
29651
31329
|
var lineLimitFormat = import_winston2.default.format((info) => {
|
|
@@ -29880,6 +31558,9 @@ var ExuluApp = class {
|
|
|
29880
31558
|
);
|
|
29881
31559
|
}
|
|
29882
31560
|
logRecallStartup();
|
|
31561
|
+
if (recallEnabled()) {
|
|
31562
|
+
startRecallReconcileLoop();
|
|
31563
|
+
}
|
|
29883
31564
|
return this._expressApp;
|
|
29884
31565
|
}
|
|
29885
31566
|
};
|
|
@@ -30834,6 +32515,7 @@ var RecursiveChunker = class _RecursiveChunker extends BaseChunker {
|
|
|
30834
32515
|
|
|
30835
32516
|
// src/exulu/read-api.ts
|
|
30836
32517
|
init_cjs_shims();
|
|
32518
|
+
init_table_names();
|
|
30837
32519
|
init_client();
|
|
30838
32520
|
var authorizedRead = async (context, user, role, opts = {}) => {
|
|
30839
32521
|
if (!opts.itemIds?.length && !opts.externalIds?.length) {
|
|
@@ -30937,7 +32619,6 @@ var {
|
|
|
30937
32619
|
promptFavoritesSchema: promptFavoritesSchema3,
|
|
30938
32620
|
transcriptionJobsSchema: transcriptionJobsSchema3,
|
|
30939
32621
|
imageGenerationsSchema: imageGenerationsSchema2,
|
|
30940
|
-
oauthTokensSchema: oauthTokensSchema2,
|
|
30941
32622
|
sharedArtifactsSchema: sharedArtifactsSchema2
|
|
30942
32623
|
} = coreSchemas.get();
|
|
30943
32624
|
var addMissingFields = async (knex, tableName, fields, skipFields = []) => {
|
|
@@ -30960,6 +32641,18 @@ var addMissingFields = async (knex, tableName, fields, skipFields = []) => {
|
|
|
30960
32641
|
console.log(`[EXULU] Field '${sanitizedName}' already exists in ${tableName} table.`);
|
|
30961
32642
|
}
|
|
30962
32643
|
};
|
|
32644
|
+
var migrateUserCredentialsDataColumn = async (knex) => {
|
|
32645
|
+
const dataType = await knex.raw(
|
|
32646
|
+
`SELECT data_type FROM information_schema.columns
|
|
32647
|
+
WHERE table_name = 'user_credentials' AND column_name = 'data'`
|
|
32648
|
+
);
|
|
32649
|
+
if (dataType.rows?.[0]?.data_type === "jsonb") {
|
|
32650
|
+
console.log("[EXULU] Migrating user_credentials.data jsonb -> text.");
|
|
32651
|
+
await knex.raw(
|
|
32652
|
+
"ALTER TABLE user_credentials ALTER COLUMN data TYPE text USING data::text"
|
|
32653
|
+
);
|
|
32654
|
+
}
|
|
32655
|
+
};
|
|
30963
32656
|
var up = async function(knex) {
|
|
30964
32657
|
console.log("[EXULU] Database up.");
|
|
30965
32658
|
const schemas = [
|
|
@@ -30981,7 +32674,6 @@ var up = async function(knex) {
|
|
|
30981
32674
|
promptFavoritesSchema3(),
|
|
30982
32675
|
transcriptionJobsSchema3(),
|
|
30983
32676
|
imageGenerationsSchema2(),
|
|
30984
|
-
oauthTokensSchema2(),
|
|
30985
32677
|
sharedArtifactsSchema2(),
|
|
30986
32678
|
rbacSchema3(),
|
|
30987
32679
|
agentsSchema3(),
|
|
@@ -31015,10 +32707,9 @@ var up = async function(knex) {
|
|
|
31015
32707
|
console.log(`[EXULU] Creating ${schema.name.plural} table.`, schema.fields);
|
|
31016
32708
|
await createTable(schema);
|
|
31017
32709
|
}
|
|
31018
|
-
await knex.raw("DROP
|
|
31019
|
-
await knex.raw(
|
|
31020
|
-
|
|
31021
|
-
);
|
|
32710
|
+
await knex.raw("DROP TABLE IF EXISTS oauth_tokens CASCADE;");
|
|
32711
|
+
await knex.raw(userCredentialsSchema());
|
|
32712
|
+
await migrateUserCredentialsDataColumn(knex);
|
|
31022
32713
|
if (await knex.schema.hasColumn("job_results", "workflow") && await knex.schema.hasColumn("job_results", "trigger_metadata")) {
|
|
31023
32714
|
await knex.raw(
|
|
31024
32715
|
"CREATE INDEX IF NOT EXISTS job_results_email_dedup_idx ON job_results (workflow, (trigger_metadata->>'message_id'))"
|
|
@@ -31212,6 +32903,11 @@ var execute = async ({ contexts }) => {
|
|
|
31212
32903
|
evals: "read"
|
|
31213
32904
|
}).returning("id");
|
|
31214
32905
|
}
|
|
32906
|
+
const existingExternalRole = await db2.from("roles").where({ name: "external" }).first();
|
|
32907
|
+
if (!existingExternalRole) {
|
|
32908
|
+
console.log("[EXULU] Creating external role.");
|
|
32909
|
+
await db2.from("roles").insert({ name: "external" }).returning("id");
|
|
32910
|
+
}
|
|
31215
32911
|
const existingUser = await db2.from("users").where({ email: "admin@exulu.com" }).first();
|
|
31216
32912
|
if (!existingUser) {
|
|
31217
32913
|
const password = await encryptString("admin");
|
|
@@ -32055,8 +33751,8 @@ var MarkdownChunker = class {
|
|
|
32055
33751
|
init_cjs_shims();
|
|
32056
33752
|
var fs4 = __toESM(require("fs"), 1);
|
|
32057
33753
|
var path = __toESM(require("path"), 1);
|
|
32058
|
-
var
|
|
32059
|
-
var
|
|
33754
|
+
var import_ai15 = require("ai");
|
|
33755
|
+
var import_zod27 = require("zod");
|
|
32060
33756
|
var import_p_limit = __toESM(require("p-limit"), 1);
|
|
32061
33757
|
var import_crypto2 = require("crypto");
|
|
32062
33758
|
init_with_retry();
|
|
@@ -32520,18 +34216,18 @@ If the page contains a flow-chart, schematic, technical drawing or control board
|
|
|
32520
34216
|
|
|
32521
34217
|
### 7. Only populate \`corrected_text\` when \`needs_correction\` is true. If the OCR output is accurate, return \`needs_correction: false\` and \`corrected_content: null\`.
|
|
32522
34218
|
`;
|
|
32523
|
-
const result = await (0,
|
|
34219
|
+
const result = await (0, import_ai15.generateText)({
|
|
32524
34220
|
model,
|
|
32525
|
-
output:
|
|
32526
|
-
schema:
|
|
32527
|
-
needs_correction:
|
|
32528
|
-
corrected_text:
|
|
32529
|
-
current_page_table:
|
|
32530
|
-
headers:
|
|
32531
|
-
is_continuation:
|
|
34221
|
+
output: import_ai15.Output.object({
|
|
34222
|
+
schema: import_zod27.z.object({
|
|
34223
|
+
needs_correction: import_zod27.z.boolean(),
|
|
34224
|
+
corrected_text: import_zod27.z.string().nullable(),
|
|
34225
|
+
current_page_table: import_zod27.z.object({
|
|
34226
|
+
headers: import_zod27.z.array(import_zod27.z.string()),
|
|
34227
|
+
is_continuation: import_zod27.z.boolean()
|
|
32532
34228
|
}).nullable(),
|
|
32533
|
-
confidence:
|
|
32534
|
-
reasoning:
|
|
34229
|
+
confidence: import_zod27.z.enum(["high", "medium", "low"]),
|
|
34230
|
+
reasoning: import_zod27.z.string()
|
|
32535
34231
|
})
|
|
32536
34232
|
}),
|
|
32537
34233
|
messages: [
|
|
@@ -33042,6 +34738,7 @@ async function rerank(input) {
|
|
|
33042
34738
|
// src/index.ts
|
|
33043
34739
|
init_pipeline();
|
|
33044
34740
|
init_statistics();
|
|
34741
|
+
init_errors();
|
|
33045
34742
|
var ExuluJobs = {
|
|
33046
34743
|
redis: redisClient
|
|
33047
34744
|
};
|
|
@@ -33156,6 +34853,7 @@ var ExuluPython = {
|
|
|
33156
34853
|
};
|
|
33157
34854
|
// Annotate the CommonJS export names for ESM import in node:
|
|
33158
34855
|
0 && (module.exports = {
|
|
34856
|
+
CredentialInvalidError,
|
|
33159
34857
|
EXULU_JOB_STATUS_ENUM,
|
|
33160
34858
|
EXULU_STATISTICS_TYPE_ENUM,
|
|
33161
34859
|
ExuluApp,
|