@mastra/convex 1.3.2 → 1.4.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +28 -17
  3. package/dist/{chunk-6P3LEDHY.cjs → chunk-2KILIA33.cjs} +315 -2
  4. package/dist/chunk-2KILIA33.cjs.map +1 -0
  5. package/dist/{chunk-77UWNT5X.js → chunk-MC2EQATP.js} +315 -2
  6. package/dist/chunk-MC2EQATP.js.map +1 -0
  7. package/dist/{chunk-MC75WADX.js → chunk-QWWFHN52.js} +41 -3
  8. package/dist/chunk-QWWFHN52.js.map +1 -0
  9. package/dist/{chunk-SFRHJGSM.cjs → chunk-ZCCKLZQH.cjs} +42 -2
  10. package/dist/chunk-ZCCKLZQH.cjs.map +1 -0
  11. package/dist/docs/SKILL.md +1 -1
  12. package/dist/docs/assets/SOURCE_MAP.json +41 -33
  13. package/dist/docs/references/reference-storage-convex.md +18 -11
  14. package/dist/index.cjs +586 -44
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.js +542 -4
  17. package/dist/index.js.map +1 -1
  18. package/dist/schema.cjs +38 -30
  19. package/dist/schema.d.ts +95 -1
  20. package/dist/schema.d.ts.map +1 -1
  21. package/dist/schema.js +1 -1
  22. package/dist/server/index-map.d.ts.map +1 -1
  23. package/dist/server/index.cjs +41 -33
  24. package/dist/server/index.d.ts +1 -1
  25. package/dist/server/index.d.ts.map +1 -1
  26. package/dist/server/index.js +2 -2
  27. package/dist/server/observational-memory.d.ts +52 -0
  28. package/dist/server/observational-memory.d.ts.map +1 -0
  29. package/dist/server/storage.d.ts.map +1 -1
  30. package/dist/storage/db/index.d.ts +65 -14
  31. package/dist/storage/db/index.d.ts.map +1 -1
  32. package/dist/storage/domains/memory/index.d.ts +23 -1
  33. package/dist/storage/domains/memory/index.d.ts.map +1 -1
  34. package/dist/storage/domains/schedules/index.d.ts.map +1 -1
  35. package/dist/storage/domains/scores/index.d.ts.map +1 -1
  36. package/dist/storage/types.d.ts +133 -0
  37. package/dist/storage/types.d.ts.map +1 -1
  38. package/dist/vector/index.d.ts.map +1 -1
  39. package/dist/vector/native.d.ts.map +1 -1
  40. package/package.json +11 -11
  41. package/dist/chunk-6P3LEDHY.cjs.map +0 -1
  42. package/dist/chunk-77UWNT5X.js.map +0 -1
  43. package/dist/chunk-MC75WADX.js.map +0 -1
  44. package/dist/chunk-SFRHJGSM.cjs.map +0 -1
@@ -265,6 +265,10 @@ var TABLE_INDEX_MAP = {
265
265
  mastra_vector_indexes: [
266
266
  { name: "by_name", fields: ["indexName"] },
267
267
  { name: "by_record_id", fields: ["id"] }
268
+ ],
269
+ mastra_observational_memory: [
270
+ { name: "by_lookup_key", fields: ["lookupKey", "generationCount"] },
271
+ { name: "by_record_id", fields: ["id"] }
268
272
  ]
269
273
  };
270
274
  function findBestIndex(convexTable, filters) {
@@ -294,6 +298,299 @@ function findBestIndex(convexTable, filters) {
294
298
  return best ? { indexName: best.indexName, indexedFilters: best.indexedFilters } : null;
295
299
  }
296
300
 
301
+ // src/server/observational-memory.ts
302
+ var OM_QUERY_MAX_DOCS = 1e4;
303
+ function parseStoredChunks(value) {
304
+ if (typeof value !== "string" || !value) return [];
305
+ try {
306
+ const parsed = JSON.parse(value);
307
+ return Array.isArray(parsed) ? parsed : [];
308
+ } catch {
309
+ return [];
310
+ }
311
+ }
312
+ function selectActivationBoundary(chunks, opts) {
313
+ const retentionFloor = opts.messageTokensThreshold * (1 - opts.activationRatio);
314
+ const targetMessageTokens = Math.max(0, opts.currentPendingTokens - retentionFloor);
315
+ let cumulativeMessageTokens = 0;
316
+ let bestOverBoundary = 0;
317
+ let bestOverTokens = 0;
318
+ let bestUnderBoundary = 0;
319
+ let bestUnderTokens = 0;
320
+ for (let i = 0; i < chunks.length; i++) {
321
+ cumulativeMessageTokens += chunks[i].messageTokens ?? 0;
322
+ const boundary = i + 1;
323
+ if (cumulativeMessageTokens >= targetMessageTokens) {
324
+ if (bestOverBoundary === 0 || cumulativeMessageTokens < bestOverTokens) {
325
+ bestOverBoundary = boundary;
326
+ bestOverTokens = cumulativeMessageTokens;
327
+ }
328
+ } else {
329
+ if (cumulativeMessageTokens > bestUnderTokens) {
330
+ bestUnderBoundary = boundary;
331
+ bestUnderTokens = cumulativeMessageTokens;
332
+ }
333
+ }
334
+ }
335
+ const maxOvershoot = retentionFloor * 0.95;
336
+ const overshoot = bestOverTokens - targetMessageTokens;
337
+ const remainingAfterOver = opts.currentPendingTokens - bestOverTokens;
338
+ const remainingAfterUnder = opts.currentPendingTokens - bestUnderTokens;
339
+ const minRemaining = Math.min(1e3, retentionFloor);
340
+ if (opts.forceMaxActivation && bestOverBoundary > 0 && remainingAfterOver >= minRemaining) {
341
+ return bestOverBoundary;
342
+ }
343
+ if (bestOverBoundary > 0 && overshoot <= maxOvershoot && remainingAfterOver >= minRemaining) {
344
+ return bestOverBoundary;
345
+ }
346
+ if (bestUnderBoundary > 0 && remainingAfterUnder >= minRemaining) {
347
+ return bestUnderBoundary;
348
+ }
349
+ if (bestOverBoundary > 0) {
350
+ return bestOverBoundary;
351
+ }
352
+ return 1;
353
+ }
354
+ function mergeReflectionWithUnreflected(activeObservations, bufferedReflection, reflectedLineCount) {
355
+ const allLines = (activeObservations || "").split("\n");
356
+ const unreflectedLines = allLines.slice(reflectedLineCount);
357
+ const unreflectedContent = unreflectedLines.join("\n").trim();
358
+ return unreflectedContent ? `${bufferedReflection}
359
+
360
+ ${unreflectedContent}` : bufferedReflection;
361
+ }
362
+ function isPlainObj(value) {
363
+ return typeof value === "object" && value !== null && !Array.isArray(value);
364
+ }
365
+ function deepMergeOMConfig(target, source) {
366
+ const output = { ...target };
367
+ for (const key of Object.keys(source)) {
368
+ const tVal = target[key];
369
+ const sVal = source[key];
370
+ if (isPlainObj(tVal) && isPlainObj(sVal)) {
371
+ output[key] = deepMergeOMConfig(tVal, sVal);
372
+ } else if (sVal !== void 0) {
373
+ output[key] = sVal;
374
+ }
375
+ }
376
+ return output;
377
+ }
378
+ function parseJsonObject(value) {
379
+ if (typeof value !== "string" || !value) return {};
380
+ try {
381
+ const parsed = JSON.parse(value);
382
+ return isPlainObj(parsed) ? parsed : {};
383
+ } catch {
384
+ return {};
385
+ }
386
+ }
387
+ async function findRecordById(ctx, convexTable, id) {
388
+ return await ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", id)).unique();
389
+ }
390
+ function requireRecord(doc, id) {
391
+ if (!doc) {
392
+ throw new Error(`Observational memory record not found: ${id}`);
393
+ }
394
+ return doc;
395
+ }
396
+ var EMPTY_SWAP_RESULT = {
397
+ chunksActivated: 0,
398
+ messageTokensActivated: 0,
399
+ observationTokensActivated: 0,
400
+ messagesActivated: 0,
401
+ activatedCycleIds: [],
402
+ activatedMessageIds: []
403
+ };
404
+ async function handleObservationalMemoryOperation(ctx, convexTable, request) {
405
+ switch (request.op) {
406
+ case "omGetLatest": {
407
+ const doc = await ctx.db.query(convexTable).withIndex("by_lookup_key", (q) => q.eq("lookupKey", request.lookupKey)).order("desc").first();
408
+ return { ok: true, result: doc ?? null };
409
+ }
410
+ case "omGetHistory": {
411
+ let docs = await ctx.db.query(convexTable).withIndex("by_lookup_key", (q) => q.eq("lookupKey", request.lookupKey)).order("desc").take(OM_QUERY_MAX_DOCS);
412
+ if (request.from) {
413
+ docs = docs.filter((doc) => typeof doc.createdAt === "string" && doc.createdAt >= request.from);
414
+ }
415
+ if (request.to) {
416
+ docs = docs.filter((doc) => typeof doc.createdAt === "string" && doc.createdAt <= request.to);
417
+ }
418
+ if (request.offset != null) {
419
+ docs = docs.slice(request.offset);
420
+ }
421
+ return { ok: true, result: docs.slice(0, request.limit) };
422
+ }
423
+ case "omUpdateActive": {
424
+ const doc = requireRecord(await findRecordById(ctx, convexTable, request.id), request.id);
425
+ const safeTokenCount = Number.isFinite(request.tokenCount) && request.tokenCount >= 0 ? request.tokenCount : 0;
426
+ await ctx.db.patch(doc._id, {
427
+ activeObservations: request.observations,
428
+ lastObservedAt: request.lastObservedAt,
429
+ // Reset pending tokens since we've now observed them
430
+ pendingMessageTokens: 0,
431
+ observationTokenCount: safeTokenCount,
432
+ totalTokensObserved: Number(doc.totalTokensObserved || 0) + safeTokenCount,
433
+ observedMessageIds: request.observedMessageIds,
434
+ updatedAt: request.updatedAt
435
+ });
436
+ return { ok: true };
437
+ }
438
+ case "omAppendBufferedChunk": {
439
+ const doc = requireRecord(await findRecordById(ctx, convexTable, request.id), request.id);
440
+ const chunks = parseStoredChunks(doc.bufferedObservationChunks);
441
+ chunks.push(request.chunk);
442
+ const patch = {
443
+ bufferedObservationChunks: JSON.stringify(chunks),
444
+ updatedAt: request.updatedAt
445
+ };
446
+ if (request.lastBufferedAtTime) {
447
+ patch.lastBufferedAtTime = request.lastBufferedAtTime;
448
+ }
449
+ await ctx.db.patch(doc._id, patch);
450
+ return { ok: true };
451
+ }
452
+ case "omSwapBuffered": {
453
+ const doc = requireRecord(await findRecordById(ctx, convexTable, request.id), request.id);
454
+ const persistedChunks = parseStoredChunks(doc.bufferedObservationChunks);
455
+ if (persistedChunks.length === 0) {
456
+ return { ok: true, result: EMPTY_SWAP_RESULT };
457
+ }
458
+ const chunks = Array.isArray(request.bufferedChunks) ? request.bufferedChunks : persistedChunks;
459
+ if (chunks.length === 0) {
460
+ return { ok: true, result: EMPTY_SWAP_RESULT };
461
+ }
462
+ const chunksToActivate = selectActivationBoundary(chunks, {
463
+ activationRatio: request.activationRatio,
464
+ messageTokensThreshold: request.messageTokensThreshold,
465
+ currentPendingTokens: request.currentPendingTokens,
466
+ forceMaxActivation: request.forceMaxActivation
467
+ });
468
+ const activatedChunks = chunks.slice(0, chunksToActivate);
469
+ const remainingChunks = chunks.slice(chunksToActivate);
470
+ const activatedContent = activatedChunks.map((c) => c.observations).join("\n\n");
471
+ const activatedTokens = activatedChunks.reduce((sum, c) => sum + c.tokenCount, 0);
472
+ const activatedMessageTokens = activatedChunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
473
+ const activatedMessageCount = activatedChunks.reduce((sum, c) => sum + (c.messageIds?.length ?? 0), 0);
474
+ const activatedCycleIds = activatedChunks.map((c) => c.cycleId).filter((id) => !!id);
475
+ const activatedMessageIds = activatedChunks.flatMap((c) => c.messageIds ?? []);
476
+ const latestChunk = activatedChunks[activatedChunks.length - 1];
477
+ const lastObservedAt = request.lastObservedAt ?? latestChunk?.lastObservedAt ?? request.now;
478
+ const existingActive = doc.activeObservations || "";
479
+ const boundary = `
480
+
481
+ --- message boundary (${lastObservedAt}) ---
482
+
483
+ `;
484
+ const newActive = existingActive ? `${existingActive}${boundary}${activatedContent}` : activatedContent;
485
+ await ctx.db.patch(doc._id, {
486
+ activeObservations: newActive,
487
+ observationTokenCount: Number(doc.observationTokenCount || 0) + activatedTokens,
488
+ // Decrement pending message tokens (clamped to zero)
489
+ pendingMessageTokens: Math.max(0, Number(doc.pendingMessageTokens || 0) - activatedMessageTokens),
490
+ bufferedObservationChunks: remainingChunks.length > 0 ? JSON.stringify(remainingChunks) : null,
491
+ lastObservedAt,
492
+ updatedAt: request.now
493
+ });
494
+ const latestChunkHints = activatedChunks[activatedChunks.length - 1];
495
+ return {
496
+ ok: true,
497
+ result: {
498
+ chunksActivated: activatedChunks.length,
499
+ messageTokensActivated: activatedMessageTokens,
500
+ observationTokensActivated: activatedTokens,
501
+ messagesActivated: activatedMessageCount,
502
+ activatedCycleIds,
503
+ activatedMessageIds,
504
+ observations: activatedContent,
505
+ perChunk: activatedChunks.map((c) => ({
506
+ cycleId: c.cycleId ?? "",
507
+ messageTokens: c.messageTokens ?? 0,
508
+ observationTokens: c.tokenCount,
509
+ messageCount: c.messageIds?.length ?? 0,
510
+ observations: c.observations
511
+ })),
512
+ suggestedContinuation: latestChunkHints?.suggestedContinuation ?? void 0,
513
+ currentTask: latestChunkHints?.currentTask ?? void 0
514
+ }
515
+ };
516
+ }
517
+ case "omUpdateBufferedReflection": {
518
+ const doc = requireRecord(await findRecordById(ctx, convexTable, request.id), request.id);
519
+ const existingContent = doc.bufferedReflection || "";
520
+ await ctx.db.patch(doc._id, {
521
+ bufferedReflection: existingContent ? `${existingContent}
522
+
523
+ ${request.reflection}` : request.reflection,
524
+ bufferedReflectionTokens: Number(doc.bufferedReflectionTokens || 0) + request.tokenCount,
525
+ bufferedReflectionInputTokens: Number(doc.bufferedReflectionInputTokens || 0) + request.inputTokenCount,
526
+ reflectedObservationLineCount: request.reflectedObservationLineCount,
527
+ updatedAt: request.updatedAt
528
+ });
529
+ return { ok: true };
530
+ }
531
+ case "omSwapBufferedReflection": {
532
+ const { currentRecord, newId, tokenCount, now } = request;
533
+ const doc = requireRecord(await findRecordById(ctx, convexTable, currentRecord.id), currentRecord.id);
534
+ const bufferedReflection = doc.bufferedReflection || "";
535
+ if (!bufferedReflection) {
536
+ throw new Error("No buffered reflection to swap");
537
+ }
538
+ const newObservations = mergeReflectionWithUnreflected(
539
+ doc.activeObservations || "",
540
+ bufferedReflection,
541
+ Number(doc.reflectedObservationLineCount || 0)
542
+ );
543
+ const newRecord = {
544
+ id: newId,
545
+ lookupKey: currentRecord.lookupKey,
546
+ scope: currentRecord.scope,
547
+ resourceId: currentRecord.resourceId,
548
+ threadId: currentRecord.threadId,
549
+ activeObservations: newObservations,
550
+ activeObservationsPendingUpdate: null,
551
+ originType: "reflection",
552
+ config: currentRecord.config,
553
+ generationCount: currentRecord.generationCount + 1,
554
+ lastObservedAt: currentRecord.lastObservedAt,
555
+ lastReflectionAt: now,
556
+ pendingMessageTokens: 0,
557
+ totalTokensObserved: currentRecord.totalTokensObserved,
558
+ observationTokenCount: tokenCount,
559
+ isObserving: false,
560
+ isReflecting: false,
561
+ isBufferingObservation: false,
562
+ isBufferingReflection: false,
563
+ lastBufferedAtTokens: 0,
564
+ lastBufferedAtTime: null,
565
+ observedTimezone: currentRecord.observedTimezone,
566
+ metadata: currentRecord.metadata,
567
+ createdAt: now,
568
+ updatedAt: now
569
+ };
570
+ await ctx.db.insert(convexTable, newRecord);
571
+ await ctx.db.patch(doc._id, {
572
+ bufferedReflection: null,
573
+ bufferedReflectionTokens: null,
574
+ bufferedReflectionInputTokens: null,
575
+ reflectedObservationLineCount: null,
576
+ updatedAt: now
577
+ });
578
+ return { ok: true, result: newRecord };
579
+ }
580
+ case "omUpdateConfig": {
581
+ const doc = requireRecord(await findRecordById(ctx, convexTable, request.id), request.id);
582
+ const existing = parseJsonObject(doc.config);
583
+ const incoming = parseJsonObject(request.config);
584
+ const merged = deepMergeOMConfig(existing, incoming);
585
+ await ctx.db.patch(doc._id, {
586
+ config: JSON.stringify(merged),
587
+ updatedAt: request.updatedAt
588
+ });
589
+ return { ok: true };
590
+ }
591
+ }
592
+ }
593
+
297
594
  // src/server/workflow-snapshot.ts
298
595
  var PENDING_MARKER_KEY = "__mastra_pending__";
299
596
  function isPendingMarker(val) {
@@ -373,6 +670,7 @@ var VECTOR_TABLE_PREFIX = "mastra_vector_";
373
670
  var CONVEX_TABLE_WORKFLOW_SNAPSHOTS = "mastra_workflow_snapshots";
374
671
  var CONVEX_TABLE_BACKGROUND_TASKS = "mastra_background_tasks";
375
672
  var CONVEX_TABLE_DOCUMENTS = "mastra_documents";
673
+ var CONVEX_TABLE_OBSERVATIONAL_MEMORY = "mastra_observational_memory";
376
674
  var STORAGE_MUTATION_BATCH_SIZE = 25;
377
675
  var LOAD_MANY_MAX_IDS_PER_REQUEST = 10;
378
676
  var DEFAULT_SCHEDULE_QUERY_LIMIT = 100;
@@ -525,6 +823,8 @@ function resolveTable(tableName) {
525
823
  return { convexTable: CONVEX_TABLE_BACKGROUND_TASKS, isTyped: true };
526
824
  case TABLE_VECTOR_INDEXES:
527
825
  return { convexTable: "mastra_vector_indexes", isTyped: true };
826
+ case CONVEX_TABLE_OBSERVATIONAL_MEMORY:
827
+ return { convexTable: CONVEX_TABLE_OBSERVATIONAL_MEMORY, isTyped: true };
528
828
  default:
529
829
  if (tableName.startsWith(VECTOR_TABLE_PREFIX)) {
530
830
  return { convexTable: "mastra_vectors", isTyped: true };
@@ -585,6 +885,19 @@ function mergeMetadata(existing, update) {
585
885
  }
586
886
  async function handleTypedOperation(ctx, convexTable, request) {
587
887
  switch (request.op) {
888
+ case "omGetLatest":
889
+ case "omGetHistory":
890
+ case "omUpdateActive":
891
+ case "omAppendBufferedChunk":
892
+ case "omSwapBuffered":
893
+ case "omUpdateBufferedReflection":
894
+ case "omSwapBufferedReflection":
895
+ case "omUpdateConfig": {
896
+ if (convexTable !== CONVEX_TABLE_OBSERVATIONAL_MEMORY) {
897
+ throw new Error(`${request.op} is only supported for ${CONVEX_TABLE_OBSERVATIONAL_MEMORY}`);
898
+ }
899
+ return handleObservationalMemoryOperation(ctx, convexTable, request);
900
+ }
588
901
  case "createSchedule": {
589
902
  if (convexTable !== "mastra_schedules") {
590
903
  throw new Error(`createSchedule is only supported for mastra_schedules`);
@@ -1453,5 +1766,5 @@ var mastraNativeVectorMutation = mutationGeneric({
1453
1766
  });
1454
1767
 
1455
1768
  export { mastraCache, mastraNativeVectorAction, mastraNativeVectorMutation, mastraNativeVectorQuery, mastraStorage };
1456
- //# sourceMappingURL=chunk-77UWNT5X.js.map
1457
- //# sourceMappingURL=chunk-77UWNT5X.js.map
1769
+ //# sourceMappingURL=chunk-MC2EQATP.js.map
1770
+ //# sourceMappingURL=chunk-MC2EQATP.js.map