@mastra/libsql 1.22.6-alpha.0 → 1.23.0-alpha.2

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.
@@ -3,7 +3,7 @@ name: mastra-libsql
3
3
  description: Documentation for @mastra/libsql. Use when working with @mastra/libsql APIs, configuration, or implementation.
4
4
  metadata:
5
5
  package: "@mastra/libsql"
6
- version: "1.22.6-alpha.0"
6
+ version: "1.23.0-alpha.2"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.22.6-alpha.0",
2
+ "version": "1.23.0-alpha.2",
3
3
  "package": "@mastra/libsql",
4
4
  "exports": {},
5
5
  "modules": {}
@@ -122,6 +122,8 @@ You can use this history in two ways:
122
122
  - **Automatic inclusion**: Mastra automatically includes recent messages in the context window. The default of 10 messages keeps agents grounded in the conversation. Adjust it with `lastMessages` when needed.
123
123
  - [**Manual querying**](#querying): For more control, query threads and messages directly with `recall()`. Use the results to choose which memories enter the context window or to render conversation history in your UI.
124
124
 
125
+ > **Note:** `lastMessages` counts every stored message, including tool calls, tool results, and [signals](https://mastra.ai/docs/harness/signals) of any kind, so a single turn can add several messages to the count. The window also slides forward on every request: once a thread grows past the limit, the oldest message leaves context on each turn, which changes the start of the prompt and invalidates the provider prompt cache. For long-running conversations, use [Observational Memory](https://mastra.ai/docs/memory/observational-memory), which keeps the prompt prefix stable.
126
+
125
127
  > **Tip:** When memory is enabled, [Studio](https://mastra.ai/docs/studio/overview) uses message history to display past conversations in the chat sidebar.
126
128
 
127
129
  ## Thread title generation
@@ -343,7 +345,9 @@ const { thread, clonedMessages } = await memory.cloneThread({
343
345
 
344
346
  You can filter cloned messages by count or date range and specify custom thread IDs. Utility methods are also available to inspect clone relationships.
345
347
 
346
- See [`cloneThread()`](https://mastra.ai/reference/memory/cloneThread) and [clone utilities](https://mastra.ai/reference/memory/clone-utilities) for the full API.
348
+ If you don't need the copied messages returned, for example when forking a long thread, use `copyThread()`. It never returns message payloads, and on LibSQL and PostgreSQL the rows are copied inside the database. When semantic recall is enabled, the copied messages are still read back in batches to generate embeddings.
349
+
350
+ See [`cloneThread()`](https://mastra.ai/reference/memory/cloneThread), [`copyThread()`](https://mastra.ai/reference/memory/copyThread), and [clone utilities](https://mastra.ai/reference/memory/clone-utilities) for the full API.
347
351
 
348
352
  ## Deleting messages
349
353
 
@@ -39,7 +39,7 @@ export const agent = new Agent({
39
39
 
40
40
  **options** (`MemoryConfig`): Memory configuration options.
41
41
 
42
- **options.lastMessages** (`number | false`): Number of most recent messages to include in context. Set to false to disable the message history feature entirely (messages are not loaded into context or saved). Use Number.MAX\_SAFE\_INTEGER to retrieve all messages with no limit. To load messages without saving new ones, use the readOnly option.
42
+ **options.lastMessages** (`number | false`): Number of most recent messages to include in context. Set to false to disable the message history feature entirely (messages are not loaded into context or saved). Use Number.MAX\_SAFE\_INTEGER to retrieve all messages with no limit. To load messages without saving new ones, use the readOnly option. The window slides forward on every request, so once a thread exceeds the limit, each turn invalidates the provider prompt cache. For long-running conversations, use Observational Memory instead.
43
43
 
44
44
  **options.readOnly** (`boolean`): When true, prevents memory from saving new messages and provides working memory as read-only context (without the updateWorkingMemory tool). Useful for read-only operations like previews, internal routing agents, or sub agents that should reference but not modify memory.
45
45
 
@@ -147,5 +147,6 @@ export const agent = new Agent({
147
147
  - [listThreads](https://mastra.ai/reference/memory/listThreads)
148
148
  - [deleteMessages](https://mastra.ai/reference/memory/deleteMessages)
149
149
  - [cloneThread](https://mastra.ai/reference/memory/cloneThread)
150
+ - [copyThread](https://mastra.ai/reference/memory/copyThread)
150
151
  - [settled](https://mastra.ai/reference/memory/settled)
151
152
  - [Clone Utility Methods](https://mastra.ai/reference/memory/clone-utilities)
package/dist/index.cjs CHANGED
@@ -1850,6 +1850,9 @@ MIGRATION REQUIRED: Duplicate spans detected in ${_mastra_core_storage.TABLE_SPA
1850
1850
  this.logger.debug(`LibSQLDB: Created unique index on (spanId, traceId) for ${_mastra_core_storage.TABLE_SPANS}`);
1851
1851
  }
1852
1852
  }
1853
+ await this.client.execute(`CREATE INDEX IF NOT EXISTS "mastra_ai_spans_roots_started_at_idx" ON "${_mastra_core_storage.TABLE_SPANS}" ("startedAt" DESC) WHERE "parentSpanId" IS NULL`);
1854
+ await this.client.execute(`CREATE INDEX IF NOT EXISTS "mastra_ai_spans_trace_started_at_idx" ON "${_mastra_core_storage.TABLE_SPANS}" ("traceId", "startedAt")`);
1855
+ this.logger.debug(`LibSQLDB: Ensured trace read indexes for ${_mastra_core_storage.TABLE_SPANS}`);
1853
1856
  this.logger.info(`LibSQLDB: Migration completed for ${_mastra_core_storage.TABLE_SPANS}`);
1854
1857
  } catch (error) {
1855
1858
  if (error instanceof _mastra_core_error.MastraError) throw error;
@@ -3021,8 +3024,15 @@ function resolveTargets({ policies, descriptor, order }) {
3021
3024
  //#endregion
3022
3025
  //#region src/storage/domains/background-tasks/index.ts
3023
3026
  function serializeJson(v) {
3024
- if (typeof v === "object" && v != null) return JSON.stringify(v);
3025
- return v ?? null;
3027
+ if (v === void 0) return null;
3028
+ let serialized;
3029
+ try {
3030
+ serialized = JSON.stringify(v);
3031
+ } catch (error) {
3032
+ throw new Error("Failed to serialize background task value as JSON", { cause: error });
3033
+ }
3034
+ if (serialized === void 0) throw new Error("Failed to serialize background task value as JSON");
3035
+ return serialized;
3026
3036
  }
3027
3037
  function parseJson$4(val) {
3028
3038
  if (val == null) return void 0;
@@ -8607,6 +8617,71 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
8607
8617
  }, error);
8608
8618
  }
8609
8619
  }
8620
+ /**
8621
+ * Atomically reassign a thread and all of its messages to a different resource.
8622
+ *
8623
+ * Runs inside a single write transaction. SQLite serializes write transactions, so overlapping
8624
+ * transfers of the same thread cannot interleave the thread update with the message update:
8625
+ * either both the thread and every message move to the new resource, or neither does. The
8626
+ * thread's `createdAt` is preserved. Callers are responsible for authorizing the reassignment.
8627
+ */
8628
+ async updateThreadResourceId({ threadId, resourceId }) {
8629
+ try {
8630
+ const tx = await this.#client.transaction("write");
8631
+ try {
8632
+ const row = (await tx.execute({
8633
+ sql: `SELECT * FROM "${_mastra_core_storage.TABLE_THREADS}" WHERE id = ?`,
8634
+ args: [threadId]
8635
+ })).rows?.[0];
8636
+ if (!row) throw new Error(`Thread "${threadId}" not found`);
8637
+ const currentResourceId = row.resourceId;
8638
+ const normalized = {
8639
+ id: row.id,
8640
+ resourceId: currentResourceId,
8641
+ title: row.title,
8642
+ metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata,
8643
+ createdAt: new Date(row.createdAt),
8644
+ updatedAt: new Date(row.updatedAt)
8645
+ };
8646
+ if (currentResourceId === resourceId) {
8647
+ await tx.commit();
8648
+ return normalized;
8649
+ }
8650
+ const now = /* @__PURE__ */ new Date();
8651
+ await tx.execute({
8652
+ sql: `UPDATE "${_mastra_core_storage.TABLE_THREADS}" SET "resourceId" = ?, "updatedAt" = ? WHERE id = ?`,
8653
+ args: [
8654
+ resourceId,
8655
+ now.toISOString(),
8656
+ threadId
8657
+ ]
8658
+ });
8659
+ await tx.execute({
8660
+ sql: `UPDATE "${_mastra_core_storage.TABLE_MESSAGES}" SET "resourceId" = ? WHERE thread_id = ?`,
8661
+ args: [resourceId, threadId]
8662
+ });
8663
+ await tx.commit();
8664
+ return {
8665
+ ...normalized,
8666
+ resourceId,
8667
+ updatedAt: now
8668
+ };
8669
+ } catch (error) {
8670
+ await tx.rollback();
8671
+ throw error;
8672
+ }
8673
+ } catch (error) {
8674
+ throw new _mastra_core_error.MastraError({
8675
+ id: (0, _mastra_core_storage.createStorageErrorId)("LIBSQL", "UPDATE_THREAD_RESOURCE_ID", "FAILED"),
8676
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
8677
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
8678
+ details: {
8679
+ threadId,
8680
+ resourceId
8681
+ }
8682
+ }, error);
8683
+ }
8684
+ }
8610
8685
  async listThreads(args) {
8611
8686
  const { page = 0, perPage: perPageInput, orderBy, filter } = args;
8612
8687
  try {
@@ -8795,7 +8870,7 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
8795
8870
  }, error);
8796
8871
  }
8797
8872
  }
8798
- async cloneThread(args) {
8873
+ async copyThread(args) {
8799
8874
  const { sourceThreadId, newThreadId: providedThreadId, resourceId, title, metadata, options } = args;
8800
8875
  const sourceThread = await this.getThreadById({ threadId: sourceThreadId });
8801
8876
  if (!sourceThread) throw new _mastra_core_error.MastraError({
@@ -8813,9 +8888,8 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
8813
8888
  text: `Thread with id ${newThreadId} already exists`,
8814
8889
  details: { newThreadId }
8815
8890
  });
8816
- const hydrateMessages = options?.hydrateMessages ?? true;
8817
8891
  try {
8818
- let messageQuery = `SELECT ${hydrateMessages ? `id, content, role, type, "createdAt", thread_id, "resourceId"` : `id, "createdAt"`}
8892
+ let messageQuery = `SELECT id, "createdAt"
8819
8893
  FROM "${_mastra_core_storage.TABLE_MESSAGES}" WHERE thread_id = ?`;
8820
8894
  const messageParams = [sourceThreadId];
8821
8895
  if (options?.messageFilter?.startDate) {
@@ -8873,68 +8947,28 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
8873
8947
  nowStr
8874
8948
  ]
8875
8949
  });
8876
- const clonedMessages = [];
8877
8950
  const messageIdMap = {};
8878
8951
  const targetResourceId = resourceId || sourceThread.resourceId;
8879
8952
  for (const sourceMsg of sourceMessages) {
8880
8953
  const newMessageId = crypto.randomUUID();
8881
8954
  const sourceMsgId = sourceMsg.id;
8882
8955
  messageIdMap[sourceMsgId] = newMessageId;
8883
- if (!hydrateMessages) {
8884
- const insertResult = await tx.execute({
8885
- sql: `INSERT INTO "${_mastra_core_storage.TABLE_MESSAGES}" (id, thread_id, content, role, type, "createdAt", "resourceId")
8886
- SELECT ?, ?, content, role, type, "createdAt", ?
8887
- FROM "${_mastra_core_storage.TABLE_MESSAGES}" WHERE id = ?`,
8888
- args: [
8889
- newMessageId,
8890
- newThreadId,
8891
- targetResourceId,
8892
- sourceMsgId
8893
- ]
8894
- });
8895
- if (insertResult.rowsAffected !== 1) throw new Error(`Failed to clone message ${sourceMsgId}: expected 1 row copied but got ${insertResult.rowsAffected}`);
8896
- continue;
8897
- }
8898
- const contentStr = sourceMsg.content;
8899
- let parsedContent;
8900
- try {
8901
- parsedContent = JSON.parse(contentStr);
8902
- } catch {
8903
- parsedContent = {
8904
- format: 2,
8905
- parts: [{
8906
- type: "text",
8907
- text: contentStr
8908
- }]
8909
- };
8910
- }
8911
- await tx.execute({
8956
+ const insertResult = await tx.execute({
8912
8957
  sql: `INSERT INTO "${_mastra_core_storage.TABLE_MESSAGES}" (id, thread_id, content, role, type, "createdAt", "resourceId")
8913
- VALUES (?, ?, ?, ?, ?, ?, ?)`,
8958
+ SELECT ?, ?, content, role, type, "createdAt", ?
8959
+ FROM "${_mastra_core_storage.TABLE_MESSAGES}" WHERE id = ?`,
8914
8960
  args: [
8915
8961
  newMessageId,
8916
8962
  newThreadId,
8917
- contentStr,
8918
- sourceMsg.role,
8919
- sourceMsg.type || "v2",
8920
- sourceMsg.createdAt,
8921
- targetResourceId
8963
+ targetResourceId,
8964
+ sourceMsgId
8922
8965
  ]
8923
8966
  });
8924
- clonedMessages.push({
8925
- id: newMessageId,
8926
- threadId: newThreadId,
8927
- content: parsedContent,
8928
- role: sourceMsg.role,
8929
- type: sourceMsg.type || void 0,
8930
- createdAt: new Date(sourceMsg.createdAt),
8931
- resourceId: targetResourceId
8932
- });
8967
+ if (insertResult.rowsAffected !== 1) throw new Error(`Failed to copy message ${sourceMsgId}: expected 1 row copied but got ${insertResult.rowsAffected}`);
8933
8968
  }
8934
8969
  await tx.commit();
8935
8970
  return {
8936
8971
  thread: newThread,
8937
- clonedMessages,
8938
8972
  messageIdMap
8939
8973
  };
8940
8974
  } catch (error) {