@exulu/backend 3.6.0 → 3.7.1
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-7AZH4ETH.js → chunk-BNTL6LYY.js} +5 -1
- package/dist/{convert-exulu-tools-to-ai-sdk-tools-TZ2UKWR4.js → convert-exulu-tools-to-ai-sdk-tools-UQSLJDXE.js} +1 -1
- package/dist/index.cjs +199 -44
- package/dist/index.d.cts +42 -1
- package/dist/index.d.ts +42 -1
- package/dist/index.js +181 -45
- package/package.json +1 -1
|
@@ -96,6 +96,9 @@ var spawnLiteLLM = (cfg) => {
|
|
|
96
96
|
...rest,
|
|
97
97
|
DEBUG: "false"
|
|
98
98
|
};
|
|
99
|
+
if (!process.env.DISABLE_SCHEMA_UPDATE) {
|
|
100
|
+
childEnv.DISABLE_SCHEMA_UPDATE = "true";
|
|
101
|
+
}
|
|
99
102
|
if (LITELLM_UI_PATH && !process.env.SERVER_ROOT_PATH) {
|
|
100
103
|
childEnv.SERVER_ROOT_PATH = LITELLM_UI_PATH;
|
|
101
104
|
}
|
|
@@ -1713,7 +1716,7 @@ var ExuluTool = class _ExuluTool {
|
|
|
1713
1716
|
if (!agent) {
|
|
1714
1717
|
throw new Error("Agent not found.");
|
|
1715
1718
|
}
|
|
1716
|
-
const { convertExuluToolsToAiSdkTools: convertExuluToolsToAiSdkTools2 } = await import("./convert-exulu-tools-to-ai-sdk-tools-
|
|
1719
|
+
const { convertExuluToolsToAiSdkTools: convertExuluToolsToAiSdkTools2 } = await import("./convert-exulu-tools-to-ai-sdk-tools-UQSLJDXE.js");
|
|
1717
1720
|
const tools = await convertExuluToolsToAiSdkTools2(
|
|
1718
1721
|
[this],
|
|
1719
1722
|
[],
|
|
@@ -8183,5 +8186,6 @@ export {
|
|
|
8183
8186
|
ExuluTool,
|
|
8184
8187
|
resolveReranker,
|
|
8185
8188
|
withRetry,
|
|
8189
|
+
microCallProviderOptions,
|
|
8186
8190
|
createAgenticRetrievalTool
|
|
8187
8191
|
};
|
package/dist/index.cjs
CHANGED
|
@@ -1347,6 +1347,9 @@ var init_supervisor = __esm({
|
|
|
1347
1347
|
...rest,
|
|
1348
1348
|
DEBUG: "false"
|
|
1349
1349
|
};
|
|
1350
|
+
if (!process.env.DISABLE_SCHEMA_UPDATE) {
|
|
1351
|
+
childEnv.DISABLE_SCHEMA_UPDATE = "true";
|
|
1352
|
+
}
|
|
1350
1353
|
if (LITELLM_UI_PATH && !process.env.SERVER_ROOT_PATH) {
|
|
1351
1354
|
childEnv.SERVER_ROOT_PATH = LITELLM_UI_PATH;
|
|
1352
1355
|
}
|
|
@@ -8892,10 +8895,12 @@ __export(index_exports, {
|
|
|
8892
8895
|
ExuluDocumentProcessor: () => ExuluDocumentProcessor,
|
|
8893
8896
|
ExuluEval: () => ExuluEval,
|
|
8894
8897
|
ExuluJobs: () => ExuluJobs,
|
|
8898
|
+
ExuluModels: () => ExuluModels,
|
|
8895
8899
|
ExuluOtel: () => ExuluOtel,
|
|
8896
8900
|
ExuluPython: () => ExuluPython,
|
|
8897
8901
|
ExuluQueues: () => queues,
|
|
8898
8902
|
ExuluReadApi: () => ExuluReadApi,
|
|
8903
|
+
ExuluRecall: () => ExuluRecall,
|
|
8899
8904
|
ExuluReranker: () => ExuluReranker,
|
|
8900
8905
|
ExuluTool: () => ExuluTool,
|
|
8901
8906
|
ExuluVariables: () => ExuluVariables,
|
|
@@ -13714,7 +13719,7 @@ var ExuluContext2 = class {
|
|
|
13714
13719
|
}
|
|
13715
13720
|
console.log("[EXULU] context configuration", this.configuration);
|
|
13716
13721
|
let jobs = [];
|
|
13717
|
-
let
|
|
13722
|
+
let shouldGenerateEmbeddings2 = this.embedder && generateEmbeddingsOverwrite !== false && (generateEmbeddingsOverwrite || this.configuration.calculateVectors === "onInsert" || this.configuration.calculateVectors === "always");
|
|
13718
13723
|
if (this.processor) {
|
|
13719
13724
|
const processor = this.processor;
|
|
13720
13725
|
if (processor && (processor?.config?.trigger === "onInsert" || processor?.config?.trigger === "onUpdate" || processor?.config?.trigger === "always")) {
|
|
@@ -13736,12 +13741,12 @@ var ExuluContext2 = class {
|
|
|
13736
13741
|
...processorResult
|
|
13737
13742
|
});
|
|
13738
13743
|
if (processor.config?.generateEmbeddings) {
|
|
13739
|
-
|
|
13744
|
+
shouldGenerateEmbeddings2 = true;
|
|
13740
13745
|
}
|
|
13741
13746
|
}
|
|
13742
13747
|
}
|
|
13743
13748
|
}
|
|
13744
|
-
if (
|
|
13749
|
+
if (shouldGenerateEmbeddings2) {
|
|
13745
13750
|
console.log("[EXULU] generating embeddings for item", results[0].id);
|
|
13746
13751
|
const { job: embeddingsJob } = await this.embeddings.generate.one({
|
|
13747
13752
|
item: {
|
|
@@ -13785,7 +13790,7 @@ var ExuluContext2 = class {
|
|
|
13785
13790
|
}).returning("id");
|
|
13786
13791
|
await mutation;
|
|
13787
13792
|
let jobs = [];
|
|
13788
|
-
let
|
|
13793
|
+
let shouldGenerateEmbeddings2 = this.embedder && generateEmbeddingsOverwrite !== false && (generateEmbeddingsOverwrite || this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always");
|
|
13789
13794
|
if (this.processor) {
|
|
13790
13795
|
const processor = this.processor;
|
|
13791
13796
|
if (processor && runProcessorOverwrite !== false && (runProcessorOverwrite || processor?.config?.trigger === "onInsert" || processor?.config?.trigger === "onUpdate" || processor?.config?.trigger === "always")) {
|
|
@@ -13807,12 +13812,12 @@ var ExuluContext2 = class {
|
|
|
13807
13812
|
...processorResult
|
|
13808
13813
|
});
|
|
13809
13814
|
if (processor.config?.generateEmbeddings) {
|
|
13810
|
-
|
|
13815
|
+
shouldGenerateEmbeddings2 = true;
|
|
13811
13816
|
}
|
|
13812
13817
|
}
|
|
13813
13818
|
}
|
|
13814
13819
|
}
|
|
13815
|
-
if (
|
|
13820
|
+
if (shouldGenerateEmbeddings2) {
|
|
13816
13821
|
const { job: embeddingsJob } = await this.embeddings.generate.one({
|
|
13817
13822
|
item: record,
|
|
13818
13823
|
// important we need to full record here with all fields for the embedder
|
|
@@ -15668,6 +15673,18 @@ var applyAgentGuestFieldTransforms = async (input) => {
|
|
|
15668
15673
|
return input;
|
|
15669
15674
|
};
|
|
15670
15675
|
|
|
15676
|
+
// src/graphql/mutations/should-generate-embeddings.ts
|
|
15677
|
+
init_cjs_shims();
|
|
15678
|
+
var shouldGenerateEmbeddings = ({
|
|
15679
|
+
calculateVectors,
|
|
15680
|
+
operation,
|
|
15681
|
+
override
|
|
15682
|
+
}) => {
|
|
15683
|
+
if (override !== void 0) return override;
|
|
15684
|
+
if (calculateVectors === "always") return true;
|
|
15685
|
+
return operation === "create" ? calculateVectors === "onInsert" : calculateVectors === "onUpdate";
|
|
15686
|
+
};
|
|
15687
|
+
|
|
15671
15688
|
// src/graphql/mutations/index.ts
|
|
15672
15689
|
var VALID_RIGHTS_MODES = ["private", "users", "roles", "teams", "public"];
|
|
15673
15690
|
var postprocessDeletion = async ({
|
|
@@ -15736,7 +15753,9 @@ var postprocessUpdate = async ({
|
|
|
15736
15753
|
result,
|
|
15737
15754
|
user,
|
|
15738
15755
|
role,
|
|
15739
|
-
config
|
|
15756
|
+
config,
|
|
15757
|
+
operation,
|
|
15758
|
+
generateEmbeddings
|
|
15740
15759
|
}) => {
|
|
15741
15760
|
if (!result) {
|
|
15742
15761
|
return result;
|
|
@@ -15763,7 +15782,11 @@ var postprocessUpdate = async ({
|
|
|
15763
15782
|
if (!context.embedder) {
|
|
15764
15783
|
return result;
|
|
15765
15784
|
}
|
|
15766
|
-
if (
|
|
15785
|
+
if (shouldGenerateEmbeddings({
|
|
15786
|
+
calculateVectors: context.configuration.calculateVectors,
|
|
15787
|
+
operation,
|
|
15788
|
+
override: generateEmbeddings
|
|
15789
|
+
})) {
|
|
15767
15790
|
const { db: db2 } = await postgresClient();
|
|
15768
15791
|
console.log("[EXULU] Deleting chunks for item", result.id);
|
|
15769
15792
|
const exists = await context.chunksTableExists();
|
|
@@ -16013,7 +16036,9 @@ function createMutations(table, contexts, tools, config) {
|
|
|
16013
16036
|
result: results[0],
|
|
16014
16037
|
user: context.user.id,
|
|
16015
16038
|
role: context.user.role?.id,
|
|
16016
|
-
config
|
|
16039
|
+
config,
|
|
16040
|
+
operation: "create",
|
|
16041
|
+
generateEmbeddings: args.generateEmbeddings
|
|
16017
16042
|
});
|
|
16018
16043
|
return {
|
|
16019
16044
|
// Filter result to only include requested fields
|
|
@@ -16087,7 +16112,9 @@ function createMutations(table, contexts, tools, config) {
|
|
|
16087
16112
|
result,
|
|
16088
16113
|
user: context.user.id,
|
|
16089
16114
|
role: context.user.role?.id,
|
|
16090
|
-
config
|
|
16115
|
+
config,
|
|
16116
|
+
operation: "update",
|
|
16117
|
+
generateEmbeddings: args.generateEmbeddings
|
|
16091
16118
|
});
|
|
16092
16119
|
return {
|
|
16093
16120
|
item: finalizeRequestedFields({
|
|
@@ -16151,7 +16178,9 @@ function createMutations(table, contexts, tools, config) {
|
|
|
16151
16178
|
result,
|
|
16152
16179
|
user: context.user.id,
|
|
16153
16180
|
role: context.user.role?.id,
|
|
16154
|
-
config
|
|
16181
|
+
config,
|
|
16182
|
+
operation: "update",
|
|
16183
|
+
generateEmbeddings: args.generateEmbeddings
|
|
16155
16184
|
});
|
|
16156
16185
|
return {
|
|
16157
16186
|
item: finalizeRequestedFields({
|
|
@@ -16421,6 +16450,7 @@ function createMutations(table, contexts, tools, config) {
|
|
|
16421
16450
|
};
|
|
16422
16451
|
}
|
|
16423
16452
|
query = applyFilters(query, args.where, table);
|
|
16453
|
+
query = applyAccessControl(table, query, context.user);
|
|
16424
16454
|
if (args.limit) {
|
|
16425
16455
|
query = query.limit(args.limit);
|
|
16426
16456
|
}
|
|
@@ -19861,6 +19891,33 @@ var renderTranscript = (segments, speakers) => {
|
|
|
19861
19891
|
return blocks.map((b) => `${b.speaker}: ${b.text}`).join("\n");
|
|
19862
19892
|
};
|
|
19863
19893
|
|
|
19894
|
+
// src/exulu/transcription/build-transcript-item.ts
|
|
19895
|
+
init_cjs_shims();
|
|
19896
|
+
var buildTranscriptItemInput = ({
|
|
19897
|
+
row,
|
|
19898
|
+
title,
|
|
19899
|
+
speakers,
|
|
19900
|
+
transcriptText,
|
|
19901
|
+
rightsMode,
|
|
19902
|
+
isReSave
|
|
19903
|
+
}) => ({
|
|
19904
|
+
// Carrying the id on re-save makes context.createItem upsert in place.
|
|
19905
|
+
...isReSave && row.saved_item_id ? { id: row.saved_item_id } : {},
|
|
19906
|
+
name: title ?? row.title ?? "Transcript",
|
|
19907
|
+
transcript_text: transcriptText,
|
|
19908
|
+
audio_s3key: row.audio_s3key,
|
|
19909
|
+
language: row.language ?? void 0,
|
|
19910
|
+
duration_seconds: row.duration_seconds ?? void 0,
|
|
19911
|
+
speakers,
|
|
19912
|
+
raw_segments: row.raw_segments,
|
|
19913
|
+
// Recall meeting-bot post-processing results (null for Whisper jobs).
|
|
19914
|
+
post_processing: row.post_processing_outputs ?? void 0,
|
|
19915
|
+
// Handle for the meeting video; null for Whisper uploads.
|
|
19916
|
+
recall_recording_id: row.recall_recording_id ?? void 0,
|
|
19917
|
+
rights_mode: rightsMode,
|
|
19918
|
+
created_by: row.created_by
|
|
19919
|
+
});
|
|
19920
|
+
|
|
19864
19921
|
// src/exulu/transcription/service.ts
|
|
19865
19922
|
var TABLE2 = "transcription_jobs";
|
|
19866
19923
|
var log3 = (msg) => console.log(`[EXULU-TRANSCRIPTION] ${msg}`);
|
|
@@ -20053,21 +20110,14 @@ var transcriptionService = {
|
|
|
20053
20110
|
const transcriptText = renderTranscript(row.raw_segments, input.speakers);
|
|
20054
20111
|
const rightsMode = input.target_rights_mode ?? row.target_rights_mode ?? "private";
|
|
20055
20112
|
const isReSave = row.status === "saved" && !!row.saved_item_id;
|
|
20056
|
-
const itemInput = {
|
|
20057
|
-
|
|
20058
|
-
|
|
20059
|
-
name: input.title ?? row.title ?? "Transcript",
|
|
20060
|
-
transcript_text: transcriptText,
|
|
20061
|
-
audio_s3key: row.audio_s3key,
|
|
20062
|
-
language: row.language ?? void 0,
|
|
20063
|
-
duration_seconds: row.duration_seconds ?? void 0,
|
|
20113
|
+
const itemInput = buildTranscriptItemInput({
|
|
20114
|
+
row,
|
|
20115
|
+
title: input.title,
|
|
20064
20116
|
speakers: input.speakers,
|
|
20065
|
-
|
|
20066
|
-
|
|
20067
|
-
|
|
20068
|
-
|
|
20069
|
-
created_by: row.created_by
|
|
20070
|
-
};
|
|
20117
|
+
transcriptText,
|
|
20118
|
+
rightsMode,
|
|
20119
|
+
isReSave
|
|
20120
|
+
});
|
|
20071
20121
|
let item;
|
|
20072
20122
|
try {
|
|
20073
20123
|
const result = await context.createItem(
|
|
@@ -20313,25 +20363,34 @@ var recordingDurationSeconds = (rec) => {
|
|
|
20313
20363
|
}
|
|
20314
20364
|
return null;
|
|
20315
20365
|
};
|
|
20366
|
+
var RECORDING_RETENTION_HOURS = 2160;
|
|
20367
|
+
var buildCreateBotPayload = (input) => ({
|
|
20368
|
+
meeting_url: input.meeting_url,
|
|
20369
|
+
join_at: input.join_at,
|
|
20370
|
+
...input.bot_name ? { bot_name: input.bot_name } : {},
|
|
20371
|
+
recording_config: {
|
|
20372
|
+
video_mixed_mp4: {},
|
|
20373
|
+
video_mixed_layout: "speaker_view",
|
|
20374
|
+
participant_events: {},
|
|
20375
|
+
meeting_metadata: {},
|
|
20376
|
+
retention: { type: "timed", hours: RECORDING_RETENTION_HOURS }
|
|
20377
|
+
},
|
|
20378
|
+
...input.notifyChat ? {
|
|
20379
|
+
chat: {
|
|
20380
|
+
on_bot_join: {
|
|
20381
|
+
send_to: "everyone",
|
|
20382
|
+
message: input.notifyChat.message,
|
|
20383
|
+
pin: true
|
|
20384
|
+
}
|
|
20385
|
+
}
|
|
20386
|
+
} : {}
|
|
20387
|
+
});
|
|
20316
20388
|
var recallClient = {
|
|
20317
20389
|
isConfigured: () => !!recallApiKey() && !!process.env.RECALL_REGION,
|
|
20318
20390
|
/** POST /bot — schedule/launch a bot for a meeting. */
|
|
20319
20391
|
createBot: (input) => request2("/bot/", {
|
|
20320
20392
|
method: "POST",
|
|
20321
|
-
body: JSON.stringify(
|
|
20322
|
-
meeting_url: input.meeting_url,
|
|
20323
|
-
join_at: input.join_at,
|
|
20324
|
-
...input.bot_name ? { bot_name: input.bot_name } : {},
|
|
20325
|
-
...input.notifyChat ? {
|
|
20326
|
-
chat: {
|
|
20327
|
-
on_bot_join: {
|
|
20328
|
-
send_to: "everyone",
|
|
20329
|
-
message: input.notifyChat.message,
|
|
20330
|
-
pin: true
|
|
20331
|
-
}
|
|
20332
|
-
}
|
|
20333
|
-
} : {}
|
|
20334
|
-
})
|
|
20393
|
+
body: JSON.stringify(buildCreateBotPayload(input))
|
|
20335
20394
|
}),
|
|
20336
20395
|
/**
|
|
20337
20396
|
* POST /recording/{id}/create_transcript — start a post-meeting (async)
|
|
@@ -20647,6 +20706,28 @@ var recallService = {
|
|
|
20647
20706
|
}
|
|
20648
20707
|
return null;
|
|
20649
20708
|
},
|
|
20709
|
+
/**
|
|
20710
|
+
* Fresh signed URL for a recording's mixed MP4, or null when there is no
|
|
20711
|
+
* usable video (still processing, no video artifact, recording expired or
|
|
20712
|
+
* deleted).
|
|
20713
|
+
*
|
|
20714
|
+
* Never cache the result: the URL carries X-Amz-Expires=21600, so it dies
|
|
20715
|
+
* after six hours. Resolve at point of use.
|
|
20716
|
+
*/
|
|
20717
|
+
async getRecordingVideoUrl(recordingId) {
|
|
20718
|
+
try {
|
|
20719
|
+
const recording = await recallClient.retrieveRecording(recordingId);
|
|
20720
|
+
const video = recording?.media_shortcuts?.video_mixed;
|
|
20721
|
+
if (!video) return null;
|
|
20722
|
+
if (video.status?.code && video.status.code !== "done") return null;
|
|
20723
|
+
return video.data?.download_url ?? null;
|
|
20724
|
+
} catch (err) {
|
|
20725
|
+
log4(
|
|
20726
|
+
`could not resolve video url for recording ${recordingId}: ${err.message}`
|
|
20727
|
+
);
|
|
20728
|
+
return null;
|
|
20729
|
+
}
|
|
20730
|
+
},
|
|
20650
20731
|
/**
|
|
20651
20732
|
* Run the selected {prompt, agent} pairs against the transcript and store the
|
|
20652
20733
|
* results on the row. Idempotent (skips if outputs already exist). Each entry
|
|
@@ -21542,11 +21623,11 @@ function createSDL(tables, contexts, tools, config, evals) {
|
|
|
21542
21623
|
`;
|
|
21543
21624
|
}
|
|
21544
21625
|
mutationDefs += `
|
|
21545
|
-
${tableNamePlural}CreateOne(input: ${tableNameSingular}Input!, upsert: Boolean): ${tableNameSingular}MutationPayload
|
|
21626
|
+
${tableNamePlural}CreateOne(input: ${tableNameSingular}Input!, upsert: Boolean, generateEmbeddings: Boolean): ${tableNameSingular}MutationPayload
|
|
21546
21627
|
${tableNamePlural}CopyOneById(id: ID!): ${tableNameSingular}MutationPayload
|
|
21547
21628
|
|
|
21548
|
-
${tableNamePlural}UpdateOne(where: [Filter${tableNameSingularUpperCaseFirst}], input: ${tableNameSingular}Input
|
|
21549
|
-
${tableNamePlural}UpdateOneById(id: ID!, input: ${tableNameSingular}Input
|
|
21629
|
+
${tableNamePlural}UpdateOne(where: [Filter${tableNameSingularUpperCaseFirst}], input: ${tableNameSingular}Input!, generateEmbeddings: Boolean): ${tableNameSingular}MutationPayload
|
|
21630
|
+
${tableNamePlural}UpdateOneById(id: ID!, input: ${tableNameSingular}Input!, generateEmbeddings: Boolean): ${tableNameSingular}MutationPayload
|
|
21550
21631
|
${tableNamePlural}RemoveOneById(id: ID!): ${tableNameSingular}
|
|
21551
21632
|
${tableNamePlural}RemoveOne(where: JSON!): ${tableNameSingular}
|
|
21552
21633
|
`;
|
|
@@ -30252,7 +30333,11 @@ var transcriptionsContext = new ExuluContext2({
|
|
|
30252
30333
|
// Post-processing results carried from the transcription job at finalize:
|
|
30253
30334
|
// [{ prompt_id, agent_id, prompt_name, output, ran_at }]. Recall meeting
|
|
30254
30335
|
// transcripts only for now.
|
|
30255
|
-
{ name: "post_processing", type: "json", editable: false }
|
|
30336
|
+
{ name: "post_processing", type: "json", editable: false },
|
|
30337
|
+
// Link back to the Recall recording so the mixed video stays reachable
|
|
30338
|
+
// (resolve a fresh URL via ExuluRecall.getRecordingVideoUrl — it expires
|
|
30339
|
+
// after six hours). Null for Whisper uploads.
|
|
30340
|
+
{ name: "recall_recording_id", type: "text" }
|
|
30256
30341
|
],
|
|
30257
30342
|
sources: [],
|
|
30258
30343
|
active: true,
|
|
@@ -31606,6 +31691,16 @@ init_supervisor();
|
|
|
31606
31691
|
init_cjs_shims();
|
|
31607
31692
|
init_client();
|
|
31608
31693
|
init_sanitize_name();
|
|
31694
|
+
|
|
31695
|
+
// src/exulu/context-fields-for-sync.ts
|
|
31696
|
+
init_cjs_shims();
|
|
31697
|
+
var contextFieldsForSync = (context) => (context.fields ?? []).filter((field) => !!field?.name && !!field?.type).map((field) => ({
|
|
31698
|
+
...field,
|
|
31699
|
+
name: field.type === "file" ? `${field.name}_s3key` : field.name
|
|
31700
|
+
}));
|
|
31701
|
+
|
|
31702
|
+
// src/postgres/init-exulu-db.ts
|
|
31703
|
+
init_table_names();
|
|
31609
31704
|
var {
|
|
31610
31705
|
agentsSchema: agentsSchema3,
|
|
31611
31706
|
feedbackSchema: feedbackSchema3,
|
|
@@ -31652,8 +31747,9 @@ var addMissingFields = async (knex, tableName, fields, skipFields = []) => {
|
|
|
31652
31747
|
await knex.schema.alterTable(tableName, (table) => {
|
|
31653
31748
|
mapType(table, type, sanitizedName, defaultValue, unique);
|
|
31654
31749
|
});
|
|
31750
|
+
} else {
|
|
31751
|
+
console.log(`[EXULU] Field '${sanitizedName}' already exists in ${tableName} table.`);
|
|
31655
31752
|
}
|
|
31656
|
-
console.log(`[EXULU] Field '${sanitizedName}' already exists in ${tableName} table.`);
|
|
31657
31753
|
}
|
|
31658
31754
|
};
|
|
31659
31755
|
var migrateUserCredentialsDataColumn = async (knex) => {
|
|
@@ -31837,11 +31933,18 @@ var up = async function(knex) {
|
|
|
31837
31933
|
}
|
|
31838
31934
|
};
|
|
31839
31935
|
var contextDatabases = async (contexts) => {
|
|
31936
|
+
const { db: knex } = await postgresClient();
|
|
31840
31937
|
for (const context of contexts) {
|
|
31841
31938
|
const itemsTableExists = await context.tableExists();
|
|
31842
31939
|
if (!itemsTableExists) {
|
|
31843
31940
|
console.log("[EXULU] items table does not exist, creating it.");
|
|
31844
31941
|
await context.createItemsTable();
|
|
31942
|
+
} else {
|
|
31943
|
+
await addMissingFields(
|
|
31944
|
+
knex,
|
|
31945
|
+
getTableName(context.id),
|
|
31946
|
+
contextFieldsForSync(context)
|
|
31947
|
+
);
|
|
31845
31948
|
}
|
|
31846
31949
|
const chunksTableExists = await context.chunksTableExists();
|
|
31847
31950
|
if (!chunksTableExists && context.embedder) {
|
|
@@ -33722,8 +33825,58 @@ async function rerank(input) {
|
|
|
33722
33825
|
|
|
33723
33826
|
// src/index.ts
|
|
33724
33827
|
init_pipeline();
|
|
33828
|
+
|
|
33829
|
+
// src/exulu/models/public.ts
|
|
33830
|
+
init_cjs_shims();
|
|
33831
|
+
init_resolve_model();
|
|
33832
|
+
init_client();
|
|
33833
|
+
init_micro_call();
|
|
33834
|
+
var ExuluModels = {
|
|
33835
|
+
/**
|
|
33836
|
+
* A LanguageModel bound to the tenant's LiteLLM proxy.
|
|
33837
|
+
*
|
|
33838
|
+
* Pass `userId` wherever one is known — it is what carries the caller's
|
|
33839
|
+
* identity tags into LiteLLM, and therefore what makes the spend visible to
|
|
33840
|
+
* that user's team budget. Resolution still succeeds without it; the calls
|
|
33841
|
+
* are simply unattributed.
|
|
33842
|
+
*/
|
|
33843
|
+
resolve: async ({
|
|
33844
|
+
modelId,
|
|
33845
|
+
userId
|
|
33846
|
+
}) => {
|
|
33847
|
+
let user;
|
|
33848
|
+
if (userId != null) {
|
|
33849
|
+
const { db: db2 } = await postgresClient();
|
|
33850
|
+
user = await db2("users").where({ id: userId }).first();
|
|
33851
|
+
}
|
|
33852
|
+
const { languageModel } = await resolveModel({ modelId, user: user || void 0 });
|
|
33853
|
+
return languageModel;
|
|
33854
|
+
},
|
|
33855
|
+
/**
|
|
33856
|
+
* Provider options for a constrained structured-output call.
|
|
33857
|
+
*
|
|
33858
|
+
* Gemini 3+ counts thinking tokens against maxOutputTokens, so a call with a
|
|
33859
|
+
* token cap returns an empty 200 unless reasoning is disabled. Returns
|
|
33860
|
+
* undefined for non-Gemini models.
|
|
33861
|
+
*/
|
|
33862
|
+
providerOptions: (model) => microCallProviderOptions(model)
|
|
33863
|
+
};
|
|
33864
|
+
|
|
33865
|
+
// src/index.ts
|
|
33725
33866
|
init_statistics();
|
|
33726
33867
|
init_errors();
|
|
33868
|
+
|
|
33869
|
+
// src/exulu/recall/public.ts
|
|
33870
|
+
init_cjs_shims();
|
|
33871
|
+
var ExuluRecall = {
|
|
33872
|
+
/**
|
|
33873
|
+
* Fresh signed URL for a recording's mixed MP4, or null. Resolve at point
|
|
33874
|
+
* of use — the URL expires after six hours.
|
|
33875
|
+
*/
|
|
33876
|
+
getRecordingVideoUrl: (recordingId) => recallService.getRecordingVideoUrl(recordingId)
|
|
33877
|
+
};
|
|
33878
|
+
|
|
33879
|
+
// src/index.ts
|
|
33727
33880
|
var ExuluJobs = {
|
|
33728
33881
|
redis: redisClient
|
|
33729
33882
|
};
|
|
@@ -33821,10 +33974,12 @@ var ExuluPython = {
|
|
|
33821
33974
|
ExuluDocumentProcessor,
|
|
33822
33975
|
ExuluEval,
|
|
33823
33976
|
ExuluJobs,
|
|
33977
|
+
ExuluModels,
|
|
33824
33978
|
ExuluOtel,
|
|
33825
33979
|
ExuluPython,
|
|
33826
33980
|
ExuluQueues,
|
|
33827
33981
|
ExuluReadApi,
|
|
33982
|
+
ExuluRecall,
|
|
33828
33983
|
ExuluReranker,
|
|
33829
33984
|
ExuluTool,
|
|
33830
33985
|
ExuluVariables,
|
package/dist/index.d.cts
CHANGED
|
@@ -2506,6 +2506,37 @@ declare function createAgenticRetrievalTool(opts: {
|
|
|
2506
2506
|
projectScope?: ProjectScope;
|
|
2507
2507
|
}): ExuluTool | undefined;
|
|
2508
2508
|
|
|
2509
|
+
/**
|
|
2510
|
+
* Model access for consuming projects.
|
|
2511
|
+
*
|
|
2512
|
+
* Exists because ExuluApp exposes no model route and resolveModel is internal,
|
|
2513
|
+
* so a consumer's only alternative was building its own provider against
|
|
2514
|
+
* PROXY_BASE_URL — which produces untagged calls that per-team LiteLLM budgets
|
|
2515
|
+
* cannot attribute.
|
|
2516
|
+
*/
|
|
2517
|
+
declare const ExuluModels: {
|
|
2518
|
+
/**
|
|
2519
|
+
* A LanguageModel bound to the tenant's LiteLLM proxy.
|
|
2520
|
+
*
|
|
2521
|
+
* Pass `userId` wherever one is known — it is what carries the caller's
|
|
2522
|
+
* identity tags into LiteLLM, and therefore what makes the spend visible to
|
|
2523
|
+
* that user's team budget. Resolution still succeeds without it; the calls
|
|
2524
|
+
* are simply unattributed.
|
|
2525
|
+
*/
|
|
2526
|
+
resolve: ({ modelId, userId, }: {
|
|
2527
|
+
modelId: string;
|
|
2528
|
+
userId?: number;
|
|
2529
|
+
}) => Promise<LanguageModel>;
|
|
2530
|
+
/**
|
|
2531
|
+
* Provider options for a constrained structured-output call.
|
|
2532
|
+
*
|
|
2533
|
+
* Gemini 3+ counts thinking tokens against maxOutputTokens, so a call with a
|
|
2534
|
+
* token cap returns an empty 200 unless reasoning is disabled. Returns
|
|
2535
|
+
* undefined for non-Gemini models.
|
|
2536
|
+
*/
|
|
2537
|
+
providerOptions: (model: LanguageModel | string) => Record<string, Record<string, string>> | undefined;
|
|
2538
|
+
};
|
|
2539
|
+
|
|
2509
2540
|
type JOB_STATUS = "completed" | "failed" | "delayed" | "active" | "waiting" | "paused" | "stuck" | "waiting_approval" | "filtered" | "cancelled";
|
|
2510
2541
|
declare const JOB_STATUS_ENUM: {
|
|
2511
2542
|
completed: string;
|
|
@@ -2526,9 +2557,19 @@ declare class CredentialInvalidError extends Error {
|
|
|
2526
2557
|
constructor(provider: string, reason?: string);
|
|
2527
2558
|
}
|
|
2528
2559
|
|
|
2560
|
+
/** Public Recall surface for consuming projects. */
|
|
2561
|
+
declare const ExuluRecall: {
|
|
2562
|
+
/**
|
|
2563
|
+
* Fresh signed URL for a recording's mixed MP4, or null. Resolve at point
|
|
2564
|
+
* of use — the URL expires after six hours.
|
|
2565
|
+
*/
|
|
2566
|
+
getRecordingVideoUrl: (recordingId: string) => Promise<string | null>;
|
|
2567
|
+
};
|
|
2568
|
+
|
|
2529
2569
|
declare const ExuluJobs: {
|
|
2530
2570
|
redis: typeof redisClient;
|
|
2531
2571
|
};
|
|
2572
|
+
|
|
2532
2573
|
declare const ExuluDefaultTools: {
|
|
2533
2574
|
agentic: {
|
|
2534
2575
|
retrieval: {
|
|
@@ -2600,4 +2641,4 @@ declare const ExuluPython: {
|
|
|
2600
2641
|
instructions: typeof getPythonSetupInstructions;
|
|
2601
2642
|
};
|
|
2602
2643
|
|
|
2603
|
-
export { type AuditConfig, type AuditEvent, type AuditLogger, type ChunkerOperation, type ChunkerResponse, type CredentialField, CredentialInvalidError, type JOB_STATUS as EXULU_JOB_STATUS, JOB_STATUS_ENUM as EXULU_JOB_STATUS_ENUM, type STATISTICS_TYPE as EXULU_STATISTICS_TYPE, STATISTICS_TYPE_ENUM as EXULU_STATISTICS_TYPE_ENUM, type ExuluAgent, ExuluApp, type ExuluAuthConfig, ExuluAuthentication, ExuluChunkers, ExuluContext, type ExuluContextEmbedder, type ExuluCredentialsToolContext, ExuluDatabase, ExuluDefaultTools, ExuluDocumentProcessor, ExuluEval, type Item as ExuluItem, ExuluJobs, type ExuluOauthConfig, type ExuluOauthToolContext, ExuluOtel, ExuluPython, queues as ExuluQueues, ExuluReadApi, ExuluReranker, ExuluTool, type ExuluUserCredentialsConfig, ExuluVariables, type VectorSearchChunkResult, defaultChunker, enableLiteLLMClientMode, postgresClient };
|
|
2644
|
+
export { type AuditConfig, type AuditEvent, type AuditLogger, type ChunkerOperation, type ChunkerResponse, type CredentialField, CredentialInvalidError, type JOB_STATUS as EXULU_JOB_STATUS, JOB_STATUS_ENUM as EXULU_JOB_STATUS_ENUM, type STATISTICS_TYPE as EXULU_STATISTICS_TYPE, STATISTICS_TYPE_ENUM as EXULU_STATISTICS_TYPE_ENUM, type ExuluAgent, ExuluApp, type ExuluAuthConfig, ExuluAuthentication, ExuluChunkers, ExuluContext, type ExuluContextEmbedder, type ExuluCredentialsToolContext, ExuluDatabase, ExuluDefaultTools, ExuluDocumentProcessor, ExuluEval, type Item as ExuluItem, ExuluJobs, ExuluModels, type ExuluOauthConfig, type ExuluOauthToolContext, ExuluOtel, ExuluPython, queues as ExuluQueues, ExuluReadApi, ExuluRecall, ExuluReranker, ExuluTool, type ExuluUserCredentialsConfig, ExuluVariables, type VectorSearchChunkResult, defaultChunker, enableLiteLLMClientMode, postgresClient };
|
package/dist/index.d.ts
CHANGED
|
@@ -2506,6 +2506,37 @@ declare function createAgenticRetrievalTool(opts: {
|
|
|
2506
2506
|
projectScope?: ProjectScope;
|
|
2507
2507
|
}): ExuluTool | undefined;
|
|
2508
2508
|
|
|
2509
|
+
/**
|
|
2510
|
+
* Model access for consuming projects.
|
|
2511
|
+
*
|
|
2512
|
+
* Exists because ExuluApp exposes no model route and resolveModel is internal,
|
|
2513
|
+
* so a consumer's only alternative was building its own provider against
|
|
2514
|
+
* PROXY_BASE_URL — which produces untagged calls that per-team LiteLLM budgets
|
|
2515
|
+
* cannot attribute.
|
|
2516
|
+
*/
|
|
2517
|
+
declare const ExuluModels: {
|
|
2518
|
+
/**
|
|
2519
|
+
* A LanguageModel bound to the tenant's LiteLLM proxy.
|
|
2520
|
+
*
|
|
2521
|
+
* Pass `userId` wherever one is known — it is what carries the caller's
|
|
2522
|
+
* identity tags into LiteLLM, and therefore what makes the spend visible to
|
|
2523
|
+
* that user's team budget. Resolution still succeeds without it; the calls
|
|
2524
|
+
* are simply unattributed.
|
|
2525
|
+
*/
|
|
2526
|
+
resolve: ({ modelId, userId, }: {
|
|
2527
|
+
modelId: string;
|
|
2528
|
+
userId?: number;
|
|
2529
|
+
}) => Promise<LanguageModel>;
|
|
2530
|
+
/**
|
|
2531
|
+
* Provider options for a constrained structured-output call.
|
|
2532
|
+
*
|
|
2533
|
+
* Gemini 3+ counts thinking tokens against maxOutputTokens, so a call with a
|
|
2534
|
+
* token cap returns an empty 200 unless reasoning is disabled. Returns
|
|
2535
|
+
* undefined for non-Gemini models.
|
|
2536
|
+
*/
|
|
2537
|
+
providerOptions: (model: LanguageModel | string) => Record<string, Record<string, string>> | undefined;
|
|
2538
|
+
};
|
|
2539
|
+
|
|
2509
2540
|
type JOB_STATUS = "completed" | "failed" | "delayed" | "active" | "waiting" | "paused" | "stuck" | "waiting_approval" | "filtered" | "cancelled";
|
|
2510
2541
|
declare const JOB_STATUS_ENUM: {
|
|
2511
2542
|
completed: string;
|
|
@@ -2526,9 +2557,19 @@ declare class CredentialInvalidError extends Error {
|
|
|
2526
2557
|
constructor(provider: string, reason?: string);
|
|
2527
2558
|
}
|
|
2528
2559
|
|
|
2560
|
+
/** Public Recall surface for consuming projects. */
|
|
2561
|
+
declare const ExuluRecall: {
|
|
2562
|
+
/**
|
|
2563
|
+
* Fresh signed URL for a recording's mixed MP4, or null. Resolve at point
|
|
2564
|
+
* of use — the URL expires after six hours.
|
|
2565
|
+
*/
|
|
2566
|
+
getRecordingVideoUrl: (recordingId: string) => Promise<string | null>;
|
|
2567
|
+
};
|
|
2568
|
+
|
|
2529
2569
|
declare const ExuluJobs: {
|
|
2530
2570
|
redis: typeof redisClient;
|
|
2531
2571
|
};
|
|
2572
|
+
|
|
2532
2573
|
declare const ExuluDefaultTools: {
|
|
2533
2574
|
agentic: {
|
|
2534
2575
|
retrieval: {
|
|
@@ -2600,4 +2641,4 @@ declare const ExuluPython: {
|
|
|
2600
2641
|
instructions: typeof getPythonSetupInstructions;
|
|
2601
2642
|
};
|
|
2602
2643
|
|
|
2603
|
-
export { type AuditConfig, type AuditEvent, type AuditLogger, type ChunkerOperation, type ChunkerResponse, type CredentialField, CredentialInvalidError, type JOB_STATUS as EXULU_JOB_STATUS, JOB_STATUS_ENUM as EXULU_JOB_STATUS_ENUM, type STATISTICS_TYPE as EXULU_STATISTICS_TYPE, STATISTICS_TYPE_ENUM as EXULU_STATISTICS_TYPE_ENUM, type ExuluAgent, ExuluApp, type ExuluAuthConfig, ExuluAuthentication, ExuluChunkers, ExuluContext, type ExuluContextEmbedder, type ExuluCredentialsToolContext, ExuluDatabase, ExuluDefaultTools, ExuluDocumentProcessor, ExuluEval, type Item as ExuluItem, ExuluJobs, type ExuluOauthConfig, type ExuluOauthToolContext, ExuluOtel, ExuluPython, queues as ExuluQueues, ExuluReadApi, ExuluReranker, ExuluTool, type ExuluUserCredentialsConfig, ExuluVariables, type VectorSearchChunkResult, defaultChunker, enableLiteLLMClientMode, postgresClient };
|
|
2644
|
+
export { type AuditConfig, type AuditEvent, type AuditLogger, type ChunkerOperation, type ChunkerResponse, type CredentialField, CredentialInvalidError, type JOB_STATUS as EXULU_JOB_STATUS, JOB_STATUS_ENUM as EXULU_JOB_STATUS_ENUM, type STATISTICS_TYPE as EXULU_STATISTICS_TYPE, STATISTICS_TYPE_ENUM as EXULU_STATISTICS_TYPE_ENUM, type ExuluAgent, ExuluApp, type ExuluAuthConfig, ExuluAuthentication, ExuluChunkers, ExuluContext, type ExuluContextEmbedder, type ExuluCredentialsToolContext, ExuluDatabase, ExuluDefaultTools, ExuluDocumentProcessor, ExuluEval, type Item as ExuluItem, ExuluJobs, ExuluModels, type ExuluOauthConfig, type ExuluOauthToolContext, ExuluOtel, ExuluPython, queues as ExuluQueues, ExuluReadApi, ExuluRecall, ExuluReranker, ExuluTool, type ExuluUserCredentialsConfig, ExuluVariables, type VectorSearchChunkResult, defaultChunker, enableLiteLLMClientMode, postgresClient };
|
package/dist/index.js
CHANGED
|
@@ -66,6 +66,7 @@ import {
|
|
|
66
66
|
listS3ObjectsByPrefix,
|
|
67
67
|
listTagsByPrefix,
|
|
68
68
|
mapStreamErrorMessage,
|
|
69
|
+
microCallProviderOptions,
|
|
69
70
|
parseResetAt,
|
|
70
71
|
postgresClient,
|
|
71
72
|
provisionDefaultUserBudget,
|
|
@@ -87,7 +88,7 @@ import {
|
|
|
87
88
|
verifyCredentialNonce,
|
|
88
89
|
waitForLiteLLMReady,
|
|
89
90
|
withRetry
|
|
90
|
-
} from "./chunk-
|
|
91
|
+
} from "./chunk-BNTL6LYY.js";
|
|
91
92
|
import {
|
|
92
93
|
LiteLLMAdminError,
|
|
93
94
|
findLiteLLMModel,
|
|
@@ -4804,7 +4805,7 @@ var ExuluContext2 = class {
|
|
|
4804
4805
|
}
|
|
4805
4806
|
console.log("[EXULU] context configuration", this.configuration);
|
|
4806
4807
|
let jobs = [];
|
|
4807
|
-
let
|
|
4808
|
+
let shouldGenerateEmbeddings2 = this.embedder && generateEmbeddingsOverwrite !== false && (generateEmbeddingsOverwrite || this.configuration.calculateVectors === "onInsert" || this.configuration.calculateVectors === "always");
|
|
4808
4809
|
if (this.processor) {
|
|
4809
4810
|
const processor = this.processor;
|
|
4810
4811
|
if (processor && (processor?.config?.trigger === "onInsert" || processor?.config?.trigger === "onUpdate" || processor?.config?.trigger === "always")) {
|
|
@@ -4826,12 +4827,12 @@ var ExuluContext2 = class {
|
|
|
4826
4827
|
...processorResult
|
|
4827
4828
|
});
|
|
4828
4829
|
if (processor.config?.generateEmbeddings) {
|
|
4829
|
-
|
|
4830
|
+
shouldGenerateEmbeddings2 = true;
|
|
4830
4831
|
}
|
|
4831
4832
|
}
|
|
4832
4833
|
}
|
|
4833
4834
|
}
|
|
4834
|
-
if (
|
|
4835
|
+
if (shouldGenerateEmbeddings2) {
|
|
4835
4836
|
console.log("[EXULU] generating embeddings for item", results[0].id);
|
|
4836
4837
|
const { job: embeddingsJob } = await this.embeddings.generate.one({
|
|
4837
4838
|
item: {
|
|
@@ -4875,7 +4876,7 @@ var ExuluContext2 = class {
|
|
|
4875
4876
|
}).returning("id");
|
|
4876
4877
|
await mutation;
|
|
4877
4878
|
let jobs = [];
|
|
4878
|
-
let
|
|
4879
|
+
let shouldGenerateEmbeddings2 = this.embedder && generateEmbeddingsOverwrite !== false && (generateEmbeddingsOverwrite || this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always");
|
|
4879
4880
|
if (this.processor) {
|
|
4880
4881
|
const processor = this.processor;
|
|
4881
4882
|
if (processor && runProcessorOverwrite !== false && (runProcessorOverwrite || processor?.config?.trigger === "onInsert" || processor?.config?.trigger === "onUpdate" || processor?.config?.trigger === "always")) {
|
|
@@ -4897,12 +4898,12 @@ var ExuluContext2 = class {
|
|
|
4897
4898
|
...processorResult
|
|
4898
4899
|
});
|
|
4899
4900
|
if (processor.config?.generateEmbeddings) {
|
|
4900
|
-
|
|
4901
|
+
shouldGenerateEmbeddings2 = true;
|
|
4901
4902
|
}
|
|
4902
4903
|
}
|
|
4903
4904
|
}
|
|
4904
4905
|
}
|
|
4905
|
-
if (
|
|
4906
|
+
if (shouldGenerateEmbeddings2) {
|
|
4906
4907
|
const { job: embeddingsJob } = await this.embeddings.generate.one({
|
|
4907
4908
|
item: record,
|
|
4908
4909
|
// important we need to full record here with all fields for the embedder
|
|
@@ -6707,6 +6708,17 @@ var applyAgentGuestFieldTransforms = async (input) => {
|
|
|
6707
6708
|
return input;
|
|
6708
6709
|
};
|
|
6709
6710
|
|
|
6711
|
+
// src/graphql/mutations/should-generate-embeddings.ts
|
|
6712
|
+
var shouldGenerateEmbeddings = ({
|
|
6713
|
+
calculateVectors,
|
|
6714
|
+
operation,
|
|
6715
|
+
override
|
|
6716
|
+
}) => {
|
|
6717
|
+
if (override !== void 0) return override;
|
|
6718
|
+
if (calculateVectors === "always") return true;
|
|
6719
|
+
return operation === "create" ? calculateVectors === "onInsert" : calculateVectors === "onUpdate";
|
|
6720
|
+
};
|
|
6721
|
+
|
|
6710
6722
|
// src/graphql/mutations/index.ts
|
|
6711
6723
|
var VALID_RIGHTS_MODES = ["private", "users", "roles", "teams", "public"];
|
|
6712
6724
|
var postprocessDeletion = async ({
|
|
@@ -6775,7 +6787,9 @@ var postprocessUpdate = async ({
|
|
|
6775
6787
|
result,
|
|
6776
6788
|
user,
|
|
6777
6789
|
role,
|
|
6778
|
-
config
|
|
6790
|
+
config,
|
|
6791
|
+
operation,
|
|
6792
|
+
generateEmbeddings
|
|
6779
6793
|
}) => {
|
|
6780
6794
|
if (!result) {
|
|
6781
6795
|
return result;
|
|
@@ -6802,7 +6816,11 @@ var postprocessUpdate = async ({
|
|
|
6802
6816
|
if (!context.embedder) {
|
|
6803
6817
|
return result;
|
|
6804
6818
|
}
|
|
6805
|
-
if (
|
|
6819
|
+
if (shouldGenerateEmbeddings({
|
|
6820
|
+
calculateVectors: context.configuration.calculateVectors,
|
|
6821
|
+
operation,
|
|
6822
|
+
override: generateEmbeddings
|
|
6823
|
+
})) {
|
|
6806
6824
|
const { db } = await postgresClient();
|
|
6807
6825
|
console.log("[EXULU] Deleting chunks for item", result.id);
|
|
6808
6826
|
const exists = await context.chunksTableExists();
|
|
@@ -7052,7 +7070,9 @@ function createMutations(table, contexts, tools, config) {
|
|
|
7052
7070
|
result: results[0],
|
|
7053
7071
|
user: context.user.id,
|
|
7054
7072
|
role: context.user.role?.id,
|
|
7055
|
-
config
|
|
7073
|
+
config,
|
|
7074
|
+
operation: "create",
|
|
7075
|
+
generateEmbeddings: args.generateEmbeddings
|
|
7056
7076
|
});
|
|
7057
7077
|
return {
|
|
7058
7078
|
// Filter result to only include requested fields
|
|
@@ -7126,7 +7146,9 @@ function createMutations(table, contexts, tools, config) {
|
|
|
7126
7146
|
result,
|
|
7127
7147
|
user: context.user.id,
|
|
7128
7148
|
role: context.user.role?.id,
|
|
7129
|
-
config
|
|
7149
|
+
config,
|
|
7150
|
+
operation: "update",
|
|
7151
|
+
generateEmbeddings: args.generateEmbeddings
|
|
7130
7152
|
});
|
|
7131
7153
|
return {
|
|
7132
7154
|
item: finalizeRequestedFields({
|
|
@@ -7190,7 +7212,9 @@ function createMutations(table, contexts, tools, config) {
|
|
|
7190
7212
|
result,
|
|
7191
7213
|
user: context.user.id,
|
|
7192
7214
|
role: context.user.role?.id,
|
|
7193
|
-
config
|
|
7215
|
+
config,
|
|
7216
|
+
operation: "update",
|
|
7217
|
+
generateEmbeddings: args.generateEmbeddings
|
|
7194
7218
|
});
|
|
7195
7219
|
return {
|
|
7196
7220
|
item: finalizeRequestedFields({
|
|
@@ -7460,6 +7484,7 @@ function createMutations(table, contexts, tools, config) {
|
|
|
7460
7484
|
};
|
|
7461
7485
|
}
|
|
7462
7486
|
query = applyFilters(query, args.where, table);
|
|
7487
|
+
query = applyAccessControl(table, query, context.user);
|
|
7463
7488
|
if (args.limit) {
|
|
7464
7489
|
query = query.limit(args.limit);
|
|
7465
7490
|
}
|
|
@@ -10835,6 +10860,32 @@ var renderTranscript = (segments, speakers) => {
|
|
|
10835
10860
|
return blocks.map((b) => `${b.speaker}: ${b.text}`).join("\n");
|
|
10836
10861
|
};
|
|
10837
10862
|
|
|
10863
|
+
// src/exulu/transcription/build-transcript-item.ts
|
|
10864
|
+
var buildTranscriptItemInput = ({
|
|
10865
|
+
row,
|
|
10866
|
+
title,
|
|
10867
|
+
speakers,
|
|
10868
|
+
transcriptText,
|
|
10869
|
+
rightsMode,
|
|
10870
|
+
isReSave
|
|
10871
|
+
}) => ({
|
|
10872
|
+
// Carrying the id on re-save makes context.createItem upsert in place.
|
|
10873
|
+
...isReSave && row.saved_item_id ? { id: row.saved_item_id } : {},
|
|
10874
|
+
name: title ?? row.title ?? "Transcript",
|
|
10875
|
+
transcript_text: transcriptText,
|
|
10876
|
+
audio_s3key: row.audio_s3key,
|
|
10877
|
+
language: row.language ?? void 0,
|
|
10878
|
+
duration_seconds: row.duration_seconds ?? void 0,
|
|
10879
|
+
speakers,
|
|
10880
|
+
raw_segments: row.raw_segments,
|
|
10881
|
+
// Recall meeting-bot post-processing results (null for Whisper jobs).
|
|
10882
|
+
post_processing: row.post_processing_outputs ?? void 0,
|
|
10883
|
+
// Handle for the meeting video; null for Whisper uploads.
|
|
10884
|
+
recall_recording_id: row.recall_recording_id ?? void 0,
|
|
10885
|
+
rights_mode: rightsMode,
|
|
10886
|
+
created_by: row.created_by
|
|
10887
|
+
});
|
|
10888
|
+
|
|
10838
10889
|
// src/exulu/transcription/service.ts
|
|
10839
10890
|
var TABLE = "transcription_jobs";
|
|
10840
10891
|
var log2 = (msg) => console.log(`[EXULU-TRANSCRIPTION] ${msg}`);
|
|
@@ -11027,21 +11078,14 @@ var transcriptionService = {
|
|
|
11027
11078
|
const transcriptText = renderTranscript(row.raw_segments, input.speakers);
|
|
11028
11079
|
const rightsMode = input.target_rights_mode ?? row.target_rights_mode ?? "private";
|
|
11029
11080
|
const isReSave = row.status === "saved" && !!row.saved_item_id;
|
|
11030
|
-
const itemInput = {
|
|
11031
|
-
|
|
11032
|
-
|
|
11033
|
-
name: input.title ?? row.title ?? "Transcript",
|
|
11034
|
-
transcript_text: transcriptText,
|
|
11035
|
-
audio_s3key: row.audio_s3key,
|
|
11036
|
-
language: row.language ?? void 0,
|
|
11037
|
-
duration_seconds: row.duration_seconds ?? void 0,
|
|
11081
|
+
const itemInput = buildTranscriptItemInput({
|
|
11082
|
+
row,
|
|
11083
|
+
title: input.title,
|
|
11038
11084
|
speakers: input.speakers,
|
|
11039
|
-
|
|
11040
|
-
|
|
11041
|
-
|
|
11042
|
-
|
|
11043
|
-
created_by: row.created_by
|
|
11044
|
-
};
|
|
11085
|
+
transcriptText,
|
|
11086
|
+
rightsMode,
|
|
11087
|
+
isReSave
|
|
11088
|
+
});
|
|
11045
11089
|
let item;
|
|
11046
11090
|
try {
|
|
11047
11091
|
const result = await context.createItem(
|
|
@@ -11279,25 +11323,34 @@ var recordingDurationSeconds = (rec) => {
|
|
|
11279
11323
|
}
|
|
11280
11324
|
return null;
|
|
11281
11325
|
};
|
|
11326
|
+
var RECORDING_RETENTION_HOURS = 2160;
|
|
11327
|
+
var buildCreateBotPayload = (input) => ({
|
|
11328
|
+
meeting_url: input.meeting_url,
|
|
11329
|
+
join_at: input.join_at,
|
|
11330
|
+
...input.bot_name ? { bot_name: input.bot_name } : {},
|
|
11331
|
+
recording_config: {
|
|
11332
|
+
video_mixed_mp4: {},
|
|
11333
|
+
video_mixed_layout: "speaker_view",
|
|
11334
|
+
participant_events: {},
|
|
11335
|
+
meeting_metadata: {},
|
|
11336
|
+
retention: { type: "timed", hours: RECORDING_RETENTION_HOURS }
|
|
11337
|
+
},
|
|
11338
|
+
...input.notifyChat ? {
|
|
11339
|
+
chat: {
|
|
11340
|
+
on_bot_join: {
|
|
11341
|
+
send_to: "everyone",
|
|
11342
|
+
message: input.notifyChat.message,
|
|
11343
|
+
pin: true
|
|
11344
|
+
}
|
|
11345
|
+
}
|
|
11346
|
+
} : {}
|
|
11347
|
+
});
|
|
11282
11348
|
var recallClient = {
|
|
11283
11349
|
isConfigured: () => !!recallApiKey() && !!process.env.RECALL_REGION,
|
|
11284
11350
|
/** POST /bot — schedule/launch a bot for a meeting. */
|
|
11285
11351
|
createBot: (input) => request2("/bot/", {
|
|
11286
11352
|
method: "POST",
|
|
11287
|
-
body: JSON.stringify(
|
|
11288
|
-
meeting_url: input.meeting_url,
|
|
11289
|
-
join_at: input.join_at,
|
|
11290
|
-
...input.bot_name ? { bot_name: input.bot_name } : {},
|
|
11291
|
-
...input.notifyChat ? {
|
|
11292
|
-
chat: {
|
|
11293
|
-
on_bot_join: {
|
|
11294
|
-
send_to: "everyone",
|
|
11295
|
-
message: input.notifyChat.message,
|
|
11296
|
-
pin: true
|
|
11297
|
-
}
|
|
11298
|
-
}
|
|
11299
|
-
} : {}
|
|
11300
|
-
})
|
|
11353
|
+
body: JSON.stringify(buildCreateBotPayload(input))
|
|
11301
11354
|
}),
|
|
11302
11355
|
/**
|
|
11303
11356
|
* POST /recording/{id}/create_transcript — start a post-meeting (async)
|
|
@@ -11612,6 +11665,28 @@ var recallService = {
|
|
|
11612
11665
|
}
|
|
11613
11666
|
return null;
|
|
11614
11667
|
},
|
|
11668
|
+
/**
|
|
11669
|
+
* Fresh signed URL for a recording's mixed MP4, or null when there is no
|
|
11670
|
+
* usable video (still processing, no video artifact, recording expired or
|
|
11671
|
+
* deleted).
|
|
11672
|
+
*
|
|
11673
|
+
* Never cache the result: the URL carries X-Amz-Expires=21600, so it dies
|
|
11674
|
+
* after six hours. Resolve at point of use.
|
|
11675
|
+
*/
|
|
11676
|
+
async getRecordingVideoUrl(recordingId) {
|
|
11677
|
+
try {
|
|
11678
|
+
const recording = await recallClient.retrieveRecording(recordingId);
|
|
11679
|
+
const video = recording?.media_shortcuts?.video_mixed;
|
|
11680
|
+
if (!video) return null;
|
|
11681
|
+
if (video.status?.code && video.status.code !== "done") return null;
|
|
11682
|
+
return video.data?.download_url ?? null;
|
|
11683
|
+
} catch (err) {
|
|
11684
|
+
log3(
|
|
11685
|
+
`could not resolve video url for recording ${recordingId}: ${err.message}`
|
|
11686
|
+
);
|
|
11687
|
+
return null;
|
|
11688
|
+
}
|
|
11689
|
+
},
|
|
11615
11690
|
/**
|
|
11616
11691
|
* Run the selected {prompt, agent} pairs against the transcript and store the
|
|
11617
11692
|
* results on the row. Idempotent (skips if outputs already exist). Each entry
|
|
@@ -12499,11 +12574,11 @@ function createSDL(tables, contexts, tools, config, evals) {
|
|
|
12499
12574
|
`;
|
|
12500
12575
|
}
|
|
12501
12576
|
mutationDefs += `
|
|
12502
|
-
${tableNamePlural}CreateOne(input: ${tableNameSingular}Input!, upsert: Boolean): ${tableNameSingular}MutationPayload
|
|
12577
|
+
${tableNamePlural}CreateOne(input: ${tableNameSingular}Input!, upsert: Boolean, generateEmbeddings: Boolean): ${tableNameSingular}MutationPayload
|
|
12503
12578
|
${tableNamePlural}CopyOneById(id: ID!): ${tableNameSingular}MutationPayload
|
|
12504
12579
|
|
|
12505
|
-
${tableNamePlural}UpdateOne(where: [Filter${tableNameSingularUpperCaseFirst}], input: ${tableNameSingular}Input
|
|
12506
|
-
${tableNamePlural}UpdateOneById(id: ID!, input: ${tableNameSingular}Input
|
|
12580
|
+
${tableNamePlural}UpdateOne(where: [Filter${tableNameSingularUpperCaseFirst}], input: ${tableNameSingular}Input!, generateEmbeddings: Boolean): ${tableNameSingular}MutationPayload
|
|
12581
|
+
${tableNamePlural}UpdateOneById(id: ID!, input: ${tableNameSingular}Input!, generateEmbeddings: Boolean): ${tableNameSingular}MutationPayload
|
|
12507
12582
|
${tableNamePlural}RemoveOneById(id: ID!): ${tableNameSingular}
|
|
12508
12583
|
${tableNamePlural}RemoveOne(where: JSON!): ${tableNameSingular}
|
|
12509
12584
|
`;
|
|
@@ -20890,7 +20965,11 @@ var transcriptionsContext = new ExuluContext2({
|
|
|
20890
20965
|
// Post-processing results carried from the transcription job at finalize:
|
|
20891
20966
|
// [{ prompt_id, agent_id, prompt_name, output, ran_at }]. Recall meeting
|
|
20892
20967
|
// transcripts only for now.
|
|
20893
|
-
{ name: "post_processing", type: "json", editable: false }
|
|
20968
|
+
{ name: "post_processing", type: "json", editable: false },
|
|
20969
|
+
// Link back to the Recall recording so the mixed video stays reachable
|
|
20970
|
+
// (resolve a fresh URL via ExuluRecall.getRecordingVideoUrl — it expires
|
|
20971
|
+
// after six hours). Null for Whisper uploads.
|
|
20972
|
+
{ name: "recall_recording_id", type: "text" }
|
|
20894
20973
|
],
|
|
20895
20974
|
sources: [],
|
|
20896
20975
|
active: true,
|
|
@@ -22219,6 +22298,12 @@ var ExuluReadApi = {
|
|
|
22219
22298
|
embedQuery
|
|
22220
22299
|
};
|
|
22221
22300
|
|
|
22301
|
+
// src/exulu/context-fields-for-sync.ts
|
|
22302
|
+
var contextFieldsForSync = (context) => (context.fields ?? []).filter((field) => !!field?.name && !!field?.type).map((field) => ({
|
|
22303
|
+
...field,
|
|
22304
|
+
name: field.type === "file" ? `${field.name}_s3key` : field.name
|
|
22305
|
+
}));
|
|
22306
|
+
|
|
22222
22307
|
// src/postgres/init-exulu-db.ts
|
|
22223
22308
|
var {
|
|
22224
22309
|
agentsSchema: agentsSchema3,
|
|
@@ -22266,8 +22351,9 @@ var addMissingFields = async (knex, tableName, fields, skipFields = []) => {
|
|
|
22266
22351
|
await knex.schema.alterTable(tableName, (table) => {
|
|
22267
22352
|
mapType(table, type, sanitizedName, defaultValue, unique);
|
|
22268
22353
|
});
|
|
22354
|
+
} else {
|
|
22355
|
+
console.log(`[EXULU] Field '${sanitizedName}' already exists in ${tableName} table.`);
|
|
22269
22356
|
}
|
|
22270
|
-
console.log(`[EXULU] Field '${sanitizedName}' already exists in ${tableName} table.`);
|
|
22271
22357
|
}
|
|
22272
22358
|
};
|
|
22273
22359
|
var migrateUserCredentialsDataColumn = async (knex) => {
|
|
@@ -22451,11 +22537,18 @@ var up = async function(knex) {
|
|
|
22451
22537
|
}
|
|
22452
22538
|
};
|
|
22453
22539
|
var contextDatabases = async (contexts) => {
|
|
22540
|
+
const { db: knex } = await postgresClient();
|
|
22454
22541
|
for (const context of contexts) {
|
|
22455
22542
|
const itemsTableExists = await context.tableExists();
|
|
22456
22543
|
if (!itemsTableExists) {
|
|
22457
22544
|
console.log("[EXULU] items table does not exist, creating it.");
|
|
22458
22545
|
await context.createItemsTable();
|
|
22546
|
+
} else {
|
|
22547
|
+
await addMissingFields(
|
|
22548
|
+
knex,
|
|
22549
|
+
getTableName(context.id),
|
|
22550
|
+
contextFieldsForSync(context)
|
|
22551
|
+
);
|
|
22459
22552
|
}
|
|
22460
22553
|
const chunksTableExists = await context.chunksTableExists();
|
|
22461
22554
|
if (!chunksTableExists && context.embedder) {
|
|
@@ -24312,6 +24405,47 @@ async function rerank(input) {
|
|
|
24312
24405
|
return resolved.rerank(query, items, { topN });
|
|
24313
24406
|
}
|
|
24314
24407
|
|
|
24408
|
+
// src/exulu/models/public.ts
|
|
24409
|
+
var ExuluModels = {
|
|
24410
|
+
/**
|
|
24411
|
+
* A LanguageModel bound to the tenant's LiteLLM proxy.
|
|
24412
|
+
*
|
|
24413
|
+
* Pass `userId` wherever one is known — it is what carries the caller's
|
|
24414
|
+
* identity tags into LiteLLM, and therefore what makes the spend visible to
|
|
24415
|
+
* that user's team budget. Resolution still succeeds without it; the calls
|
|
24416
|
+
* are simply unattributed.
|
|
24417
|
+
*/
|
|
24418
|
+
resolve: async ({
|
|
24419
|
+
modelId,
|
|
24420
|
+
userId
|
|
24421
|
+
}) => {
|
|
24422
|
+
let user;
|
|
24423
|
+
if (userId != null) {
|
|
24424
|
+
const { db } = await postgresClient();
|
|
24425
|
+
user = await db("users").where({ id: userId }).first();
|
|
24426
|
+
}
|
|
24427
|
+
const { languageModel } = await resolveModel({ modelId, user: user || void 0 });
|
|
24428
|
+
return languageModel;
|
|
24429
|
+
},
|
|
24430
|
+
/**
|
|
24431
|
+
* Provider options for a constrained structured-output call.
|
|
24432
|
+
*
|
|
24433
|
+
* Gemini 3+ counts thinking tokens against maxOutputTokens, so a call with a
|
|
24434
|
+
* token cap returns an empty 200 unless reasoning is disabled. Returns
|
|
24435
|
+
* undefined for non-Gemini models.
|
|
24436
|
+
*/
|
|
24437
|
+
providerOptions: (model) => microCallProviderOptions(model)
|
|
24438
|
+
};
|
|
24439
|
+
|
|
24440
|
+
// src/exulu/recall/public.ts
|
|
24441
|
+
var ExuluRecall = {
|
|
24442
|
+
/**
|
|
24443
|
+
* Fresh signed URL for a recording's mixed MP4, or null. Resolve at point
|
|
24444
|
+
* of use — the URL expires after six hours.
|
|
24445
|
+
*/
|
|
24446
|
+
getRecordingVideoUrl: (recordingId) => recallService.getRecordingVideoUrl(recordingId)
|
|
24447
|
+
};
|
|
24448
|
+
|
|
24315
24449
|
// src/index.ts
|
|
24316
24450
|
var ExuluJobs = {
|
|
24317
24451
|
redis: redisClient
|
|
@@ -24409,10 +24543,12 @@ export {
|
|
|
24409
24543
|
ExuluDocumentProcessor,
|
|
24410
24544
|
ExuluEval,
|
|
24411
24545
|
ExuluJobs,
|
|
24546
|
+
ExuluModels,
|
|
24412
24547
|
ExuluOtel,
|
|
24413
24548
|
ExuluPython,
|
|
24414
24549
|
queues as ExuluQueues,
|
|
24415
24550
|
ExuluReadApi,
|
|
24551
|
+
ExuluRecall,
|
|
24416
24552
|
ExuluReranker,
|
|
24417
24553
|
ExuluTool,
|
|
24418
24554
|
ExuluVariables,
|