@mxf-dev/core 3.1.0 → 3.2.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/config/EmbeddingInputLimits.d.ts +43 -0
- package/dist/config/EmbeddingInputLimits.d.ts.map +1 -0
- package/dist/config/EmbeddingInputLimits.js +63 -0
- package/dist/config/EmbeddingInputLimits.js.map +1 -0
- package/dist/config/MeilisearchIngressLimits.d.ts +61 -0
- package/dist/config/MeilisearchIngressLimits.d.ts.map +1 -0
- package/dist/config/MeilisearchIngressLimits.js +61 -0
- package/dist/config/MeilisearchIngressLimits.js.map +1 -0
- package/dist/events/EventNames.d.ts +1 -0
- package/dist/events/EventNames.d.ts.map +1 -1
- package/dist/events/event-definitions/McpEvents.d.ts +3 -3
- package/dist/events/event-definitions/McpEvents.d.ts.map +1 -1
- package/dist/events/event-definitions/MeilisearchEvents.d.ts +3 -1
- package/dist/events/event-definitions/MeilisearchEvents.d.ts.map +1 -1
- package/dist/events/event-definitions/MeilisearchEvents.js +4 -0
- package/dist/events/event-definitions/MeilisearchEvents.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/models/task.d.ts.map +1 -1
- package/dist/models/task.js.map +1 -1
- package/dist/schemas/EventPayloadSchema.d.ts +1 -0
- package/dist/schemas/EventPayloadSchema.d.ts.map +1 -1
- package/dist/schemas/EventPayloadSchema.js +3 -0
- package/dist/schemas/EventPayloadSchema.js.map +1 -1
- package/dist/services/MxfMeilisearchService.d.ts +19 -3
- package/dist/services/MxfMeilisearchService.d.ts.map +1 -1
- package/dist/services/MxfMeilisearchService.js +46 -34
- package/dist/services/MxfMeilisearchService.js.map +1 -1
- package/dist/types/TaskTypes.d.ts +29 -0
- package/dist/types/TaskTypes.d.ts.map +1 -1
- package/dist/types/TaskTypes.js +41 -1
- package/dist/types/TaskTypes.js.map +1 -1
- package/package.json +1 -1
- package/src/config/EmbeddingInputLimits.ts +71 -0
- package/src/config/MeilisearchIngressLimits.ts +67 -0
- package/src/events/event-definitions/McpEvents.ts +1 -1
- package/src/events/event-definitions/MeilisearchEvents.ts +7 -1
- package/src/index.ts +7 -0
- package/src/models/task.ts +5 -1
- package/src/schemas/EventPayloadSchema.ts +4 -0
- package/src/services/MxfMeilisearchService.ts +55 -43
- package/src/types/TaskTypes.ts +69 -2
|
@@ -112,6 +112,11 @@ export interface SearchResult<T> {
|
|
|
112
112
|
*/
|
|
113
113
|
export type EmbeddingGenerator = (text: string, options?: { model?: string; dimensions?: number }) => Promise<number[]>;
|
|
114
114
|
|
|
115
|
+
/** The part of a Meilisearch enqueued-task promise the index methods use. */
|
|
116
|
+
interface IndexTaskPromise {
|
|
117
|
+
waitTask: () => Promise<{ uid: number; status: string; error: { message?: string } | null }>;
|
|
118
|
+
}
|
|
119
|
+
|
|
115
120
|
/**
|
|
116
121
|
* Meilisearch Service Configuration
|
|
117
122
|
*/
|
|
@@ -364,8 +369,13 @@ export class MxfMeilisearchService {
|
|
|
364
369
|
}
|
|
365
370
|
|
|
366
371
|
/**
|
|
367
|
-
*
|
|
368
|
-
*
|
|
372
|
+
* Embed text with the generator the server installed.
|
|
373
|
+
*
|
|
374
|
+
* Returns undefined only when embeddings are off or no generator is
|
|
375
|
+
* installed (keyword-only mode). A generator failure is thrown with the
|
|
376
|
+
* provider's reason: a document indexed without the vector its caller
|
|
377
|
+
* expects would still be counted as indexed, and semantic searches would
|
|
378
|
+
* quietly miss it, so the failure has to reach the caller.
|
|
369
379
|
*/
|
|
370
380
|
private async generateEmbedding(text: string): Promise<number[] | undefined> {
|
|
371
381
|
if (!this.config.enableEmbeddings || !this.embeddingGenerator) {
|
|
@@ -373,26 +383,42 @@ export class MxfMeilisearchService {
|
|
|
373
383
|
}
|
|
374
384
|
|
|
375
385
|
try {
|
|
376
|
-
|
|
377
|
-
const embedding = await this.embeddingGenerator(text, {
|
|
386
|
+
return await this.embeddingGenerator(text, {
|
|
378
387
|
model: this.config.embeddingModel,
|
|
379
388
|
dimensions: this.config.embeddingDimensions
|
|
380
389
|
});
|
|
381
|
-
|
|
382
|
-
return embedding;
|
|
383
390
|
} catch (error) {
|
|
384
|
-
|
|
385
|
-
|
|
391
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
392
|
+
throw new Error(`Embedding generation failed (${this.config.embeddingModel}): ${reason}`);
|
|
386
393
|
}
|
|
387
394
|
}
|
|
388
395
|
|
|
389
396
|
/**
|
|
390
|
-
*
|
|
397
|
+
* Wait for an indexing task and throw when Meilisearch did not complete
|
|
398
|
+
* it. waitTask() resolves for a task that ended `failed` or `canceled` —
|
|
399
|
+
* the outcome is on the task, not in the promise.
|
|
400
|
+
*/
|
|
401
|
+
private async awaitIndexTask(taskPromise: IndexTaskPromise, what: string): Promise<void> {
|
|
402
|
+
const task = await taskPromise.waitTask();
|
|
403
|
+
if (task.status !== 'succeeded') {
|
|
404
|
+
throw new Error(
|
|
405
|
+
`Meilisearch ${what} task ${task.uid} ${task.status}: ${task.error?.message ?? 'no error detail'}`
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Index a conversation message.
|
|
412
|
+
*
|
|
413
|
+
* Throws when the embedding cannot be generated, the document cannot be
|
|
414
|
+
* enqueued, or Meilisearch fails the task. The caller decides what a
|
|
415
|
+
* missing document means: the server reports it to the SDK, and the SDK
|
|
416
|
+
* counts it against the backfill or drops it from the live index queue.
|
|
391
417
|
*/
|
|
392
418
|
public async indexConversation(message: ConversationMessage): Promise<void> {
|
|
393
419
|
try {
|
|
394
420
|
const embedding = await this.generateEmbedding(message.content);
|
|
395
|
-
|
|
421
|
+
|
|
396
422
|
// Build document with proper _vectors format for Meilisearch
|
|
397
423
|
const document: ConversationDocument = {
|
|
398
424
|
id: message.id,
|
|
@@ -407,14 +433,11 @@ export class MxfMeilisearchService {
|
|
|
407
433
|
};
|
|
408
434
|
|
|
409
435
|
const index = this.client.index(MeilisearchIndex.CONVERSATIONS);
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
// Wait for indexing task to complete so documents are immediately searchable
|
|
413
|
-
await taskPromise.waitTask();
|
|
414
|
-
|
|
436
|
+
// Wait for the task so the document is searchable when this resolves.
|
|
437
|
+
await this.awaitIndexTask(index.addDocuments([document]), `conversation ${message.id}`);
|
|
415
438
|
} catch (error) {
|
|
416
|
-
this.logger.error(
|
|
417
|
-
|
|
439
|
+
this.logger.error(`Failed to index conversation ${message.id}`, error);
|
|
440
|
+
throw error;
|
|
418
441
|
}
|
|
419
442
|
}
|
|
420
443
|
|
|
@@ -444,13 +467,10 @@ export class MxfMeilisearchService {
|
|
|
444
467
|
};
|
|
445
468
|
|
|
446
469
|
const index = this.client.index(MeilisearchIndex.ACTIONS);
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
// Wait for indexing task to complete
|
|
450
|
-
await taskPromise.waitTask();
|
|
451
|
-
|
|
470
|
+
await this.awaitIndexTask(index.addDocuments([document]), `action ${action.id}`);
|
|
452
471
|
} catch (error) {
|
|
453
|
-
this.logger.error(
|
|
472
|
+
this.logger.error(`Failed to index action ${action.id}`, error);
|
|
473
|
+
throw error;
|
|
454
474
|
}
|
|
455
475
|
}
|
|
456
476
|
|
|
@@ -480,13 +500,10 @@ export class MxfMeilisearchService {
|
|
|
480
500
|
};
|
|
481
501
|
|
|
482
502
|
const index = this.client.index(MeilisearchIndex.PATTERNS);
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
// Wait for indexing task to complete
|
|
486
|
-
await taskPromise.waitTask();
|
|
487
|
-
|
|
503
|
+
await this.awaitIndexTask(index.addDocuments([document]), `pattern ${pattern.patternId}`);
|
|
488
504
|
} catch (error) {
|
|
489
|
-
this.logger.error(
|
|
505
|
+
this.logger.error(`Failed to index pattern ${pattern.patternId}`, error);
|
|
506
|
+
throw error;
|
|
490
507
|
}
|
|
491
508
|
}
|
|
492
509
|
|
|
@@ -535,21 +552,16 @@ export class MxfMeilisearchService {
|
|
|
535
552
|
searchParams.attributesToHighlight = params.attributesToHighlight;
|
|
536
553
|
}
|
|
537
554
|
|
|
538
|
-
//
|
|
539
|
-
//
|
|
555
|
+
// Hybrid search needs the query embedded by the same generator that
|
|
556
|
+
// embedded the documents. A generator failure is thrown (through the
|
|
557
|
+
// catch below) rather than quietly answering a semantic request with
|
|
558
|
+
// keyword-only results.
|
|
540
559
|
if (this.config.enableEmbeddings && this.embeddingGenerator && params.hybridRatio !== undefined) {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
semanticRatio: params.hybridRatio,
|
|
547
|
-
embedder: 'default'
|
|
548
|
-
};
|
|
549
|
-
// Provide the query embedding for user-provided embedder configuration
|
|
550
|
-
searchParams.vector = queryEmbedding;
|
|
551
|
-
}
|
|
552
|
-
// If embedding generation fails, fall back to keyword-only search
|
|
560
|
+
searchParams.hybrid = {
|
|
561
|
+
semanticRatio: params.hybridRatio,
|
|
562
|
+
embedder: 'default'
|
|
563
|
+
};
|
|
564
|
+
searchParams.vector = await this.generateEmbedding(params.query);
|
|
553
565
|
}
|
|
554
566
|
|
|
555
567
|
const result = await index.search<T>(params.query, searchParams);
|
package/src/types/TaskTypes.ts
CHANGED
|
@@ -109,7 +109,11 @@ export interface ChannelTask {
|
|
|
109
109
|
dependsOn?: string[]; // Task IDs this task depends on
|
|
110
110
|
blockedBy?: string[]; // Task IDs that block this task
|
|
111
111
|
|
|
112
|
-
// Results and outcomes
|
|
112
|
+
// Results and outcomes. `output` is whatever the caller passed to
|
|
113
|
+
// completeTask() or the REST completion route, or a TaskCompletionOutput
|
|
114
|
+
// when an agent completed the task by calling task_complete. There is no
|
|
115
|
+
// `result.summary` — use getTaskCompletionOutput() to read the summary
|
|
116
|
+
// safely regardless of which completion path produced this task.
|
|
113
117
|
result?: {
|
|
114
118
|
success?: boolean;
|
|
115
119
|
output?: any;
|
|
@@ -117,10 +121,73 @@ export interface ChannelTask {
|
|
|
117
121
|
completedAt?: number;
|
|
118
122
|
completedBy?: string;
|
|
119
123
|
};
|
|
120
|
-
|
|
124
|
+
|
|
121
125
|
assignmentStrategy: AssignmentStrategy;
|
|
122
126
|
}
|
|
123
127
|
|
|
128
|
+
/**
|
|
129
|
+
* What `task_complete` stores in `ChannelTask.result.output`.
|
|
130
|
+
*
|
|
131
|
+
* This is the shape TaskService.handleTaskCompletion writes when an agent
|
|
132
|
+
* completes a task through the task_complete tool. It is not the only shape
|
|
133
|
+
* `result.output` can hold: a caller can also set `result.output` directly
|
|
134
|
+
* through completeTask() or the REST completion route, so this type is a
|
|
135
|
+
* guard to check against, not a blanket narrowing of `output`.
|
|
136
|
+
*/
|
|
137
|
+
export interface TaskCompletionOutput {
|
|
138
|
+
agentId: string;
|
|
139
|
+
summary: string;
|
|
140
|
+
details?: Record<string, unknown>;
|
|
141
|
+
nextSteps?: string;
|
|
142
|
+
reportedSuccess: boolean;
|
|
143
|
+
requestId: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Checks whether a value matches the TaskCompletionOutput shape.
|
|
148
|
+
* Used to read `result.output` from a task without assuming which
|
|
149
|
+
* completion path (task_complete vs. completeTask()/REST) produced it.
|
|
150
|
+
*/
|
|
151
|
+
export function isTaskCompletionOutput(value: unknown): value is TaskCompletionOutput {
|
|
152
|
+
if (typeof value !== 'object' || value === null) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
const candidate = value as Record<string, unknown>;
|
|
156
|
+
if (typeof candidate.agentId !== 'string' || candidate.agentId.length === 0) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
if (typeof candidate.summary !== 'string') {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
if (typeof candidate.reportedSuccess !== 'boolean') {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
if (typeof candidate.requestId !== 'string' || candidate.requestId.length === 0) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
if (candidate.details !== undefined) {
|
|
169
|
+
if (typeof candidate.details !== 'object' || candidate.details === null || Array.isArray(candidate.details)) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (candidate.nextSteps !== undefined && typeof candidate.nextSteps !== 'string') {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Reads `task.result.output` as a TaskCompletionOutput, or returns undefined
|
|
181
|
+
* when the task has no result, no output, or an output that does not match
|
|
182
|
+
* (e.g. a value a caller passed directly to completeTask()/REST).
|
|
183
|
+
*/
|
|
184
|
+
export function getTaskCompletionOutput(
|
|
185
|
+
task: Pick<ChannelTask, 'result'> | null | undefined
|
|
186
|
+
): TaskCompletionOutput | undefined {
|
|
187
|
+
const output = task?.result?.output;
|
|
188
|
+
return isTaskCompletionOutput(output) ? output : undefined;
|
|
189
|
+
}
|
|
190
|
+
|
|
124
191
|
/**
|
|
125
192
|
* Task creation request
|
|
126
193
|
*/
|