@mastra/libsql 1.22.4 → 1.22.5-alpha.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.
@@ -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.4"
6
+ version: "1.22.5-alpha.1"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.22.4",
2
+ "version": "1.22.5-alpha.1",
3
3
  "package": "@mastra/libsql",
4
4
  "exports": {},
5
5
  "modules": {}
@@ -178,7 +178,7 @@ const agent = mastra.getAgentById('test-agent')
178
178
  const memory = await agent.getMemory()
179
179
  ```
180
180
 
181
- The `Memory` instance gives you access to functions for listing threads and recalling messages, plus cloning conversations, and more.
181
+ Use the `Memory` instance to query stored threads and messages or clone a conversation.
182
182
 
183
183
  ## Querying
184
184
 
@@ -160,7 +160,7 @@ const memory = new Memory({
160
160
 
161
161
  OM requires a storage adapter that supports it: `@mastra/libsql`, `@mastra/pg`, `@mastra/mongodb`, or `@mastra/oracledb`.
162
162
 
163
- > **Note:** If you switch the Observer to a weaker model and see facts collapse to a generic `User`, use [`observation.instruction`](https://mastra.ai/reference/memory/observational-memory) to teach the Observer how to read the `<turn>` tag.
163
+ > **Note:** If you switch the Observer to a less capable model and see facts attributed to a generic `User` instead of individual participants, use [`observation.instruction`](https://mastra.ai/reference/memory/observational-memory) to explain how to interpret the `<turn>` tag.
164
164
 
165
165
  ### With working memory
166
166
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  # Memory
6
6
 
7
- Memory enables your agent to remember user messages and agent replies, and tool results across interactions, giving it the context it needs to stay consistent, maintain conversation flow, plus produce better answers over time.
7
+ Memory gives your agent access to earlier messages and tool results. The agent can use this context to answer follow-up questions and recall information from previous interactions.
8
8
 
9
9
  Mastra agents can be configured to store [message history](https://mastra.ai/docs/memory/message-history). Additionally, you can enable:
10
10
 
@@ -153,7 +153,7 @@ To list all threads for a resource, or retrieve a specific thread, [use the memo
153
153
 
154
154
  ## Observational Memory
155
155
 
156
- For long-running conversations, raw message history grows until it fills the context window, degrading agent performance. [Observational Memory](https://mastra.ai/docs/memory/observational-memory) solves this by running background agents that compress old messages into dense observations, keeping the context window small while preserving long-term memory.
156
+ Long conversations can fill the context window with raw message history and reduce agent performance. [Observational Memory](https://mastra.ai/docs/memory/observational-memory) uses background agents to compress older messages into observations. This reduces the context used by message history while retaining information for later turns.
157
157
 
158
158
  **For AI agents:** Using Observational Memory requires a storage provider! You either need to set it on the Mastra instance at `src/mastra/index.ts` or pass it to the Agent constructor.
159
159
 
@@ -176,7 +176,7 @@ See [Observational Memory](https://mastra.ai/docs/memory/observational-memory) f
176
176
 
177
177
  ## What the model sees
178
178
 
179
- Each memory feature is added to either the system messages or the conversation messages in the request sent to the model. The layers depend on the features you've enabled. Working memory and semantic recall only appear when configured. The same applies to Observational Memory, while message history is on by default. The diagram shows where each enabled layer is placed in the request. The list below describes what each layer contributes:
179
+ Memory adds context to the system messages or conversation messages sent to the model. Message history is enabled by default. Other memory features contribute context only when configured. The diagram shows where each feature adds its context, and the list below explains what it contributes:
180
180
 
181
181
  ![Diagram showing how Mastra assembles the model context: system messages containing agent instructions, call-time system messages, working memory, cross-thread semantic recall, and Observational Memory, followed by conversation messages where message history and same-thread semantic recall interleave by timestamp, then call-time context messages, and finally the new user message](/img/memory/memory-context-window-light.svg)
182
182
 
@@ -202,7 +202,7 @@ Each delegation creates a fresh `threadId` and a deterministic `resourceId` for
202
202
 
203
203
  > **Note:** Title generation (`generateTitle`) is a top-level thread concern and **isn't** applied to inherited subagent threads. Because each delegation creates an ephemeral thread that no one sees, running title generation for it would waste an LLM call per delegation. To generate titles for a subagent's own threads, give that subagent its own memory configuration.
204
204
 
205
- The supervisor forwards its conversation context to the subagent so it has enough background to complete the task. Only the delegation prompt and the subagent's response are saved, the full parent conversation isn't stored. You can control which messages reach the subagent with the [`messageFilter`](https://mastra.ai/docs/subagents) callback.
205
+ The supervisor forwards its conversation context to the subagent so it has enough background to complete the task. Only the delegation prompt and the subagent's response are saved; the full parent conversation isn't stored. You can control which messages reach the subagent with the [`messageFilter`](https://mastra.ai/docs/subagents) callback.
206
206
 
207
207
  > **Note:** Subagent resource IDs are always suffixed with the agent name (`{parentResourceId}-{agentName}`). Different subagents under the same supervisor never share a resource ID through delegation.
208
208
 
package/dist/index.cjs CHANGED
@@ -12,6 +12,84 @@ let crypto$1 = require("crypto");
12
12
  let _mastra_core_agent = require("@mastra/core/agent");
13
13
  let _mastra_core_evals = require("@mastra/core/evals");
14
14
  let _mastra_core_storage_domains_skills = require("@mastra/core/storage/domains/skills");
15
+ //#region src/shared/single-connection-client.ts
16
+ /**
17
+ * Whether `@libsql/client` backs this database with exactly one connection.
18
+ *
19
+ * An in-memory database exists only on the connection that opened it, and each
20
+ * embedded-replica connection carries its own sync state, so `@libsql/client`
21
+ * (>= 0.18.0) gives both a pool of one. Any `execute`/`batch` issued while an
22
+ * interactive `transaction()` holds that connection is rejected immediately
23
+ * with `TRANSACTION_ACTIVE` instead of waiting for the transaction to settle.
24
+ */
25
+ function isSingleConnectionDatabase({ url, syncUrl }) {
26
+ return url.includes(":memory:") || Boolean(syncUrl);
27
+ }
28
+ /**
29
+ * Wraps a single-connection client so client calls queue behind open
30
+ * transactions rather than failing with `TRANSACTION_ACTIVE`.
31
+ *
32
+ * `transaction()` takes the gate and releases it when the transaction commits,
33
+ * rolls back, or closes. `execute`, `batch`, `executeMultiple`, and `migrate`
34
+ * wait for the gate to be free before running but do not hold it — the driver
35
+ * executes them synchronously on the connection, so they cannot interleave
36
+ * with each other. Every other member passes through untouched.
37
+ *
38
+ * Callers must not issue client calls from inside their own open transaction
39
+ * (use `tx.execute`); such a call would wait for the transaction it is part of.
40
+ */
41
+ function gateSingleConnectionClient(client) {
42
+ let gate = Promise.resolve();
43
+ const waitForGate = (run) => gate.then(run, run);
44
+ const transaction = async (mode) => {
45
+ let release;
46
+ const held = new Promise((resolve) => {
47
+ release = resolve;
48
+ });
49
+ const previous = gate;
50
+ gate = previous.then(() => held, () => held);
51
+ await previous.then(() => void 0, () => void 0);
52
+ let tx;
53
+ try {
54
+ tx = await client.transaction(mode);
55
+ } catch (error) {
56
+ release();
57
+ throw error;
58
+ }
59
+ return new Proxy(tx, { get(target, prop) {
60
+ if (prop === "commit" || prop === "rollback") return async () => {
61
+ try {
62
+ await target[prop].call(target);
63
+ } finally {
64
+ release();
65
+ }
66
+ };
67
+ if (prop === "close") return () => {
68
+ try {
69
+ target.close();
70
+ } finally {
71
+ release();
72
+ }
73
+ };
74
+ const value = Reflect.get(target, prop);
75
+ return typeof value === "function" ? value.bind(target) : value;
76
+ } });
77
+ };
78
+ return new Proxy(client, { get(target, prop) {
79
+ switch (prop) {
80
+ case "transaction": return transaction;
81
+ case "execute":
82
+ case "batch":
83
+ case "executeMultiple":
84
+ case "migrate": return (...args) => waitForGate(() => target[prop].apply(target, args));
85
+ default: {
86
+ const value = Reflect.get(target, prop);
87
+ return typeof value === "function" ? value.bind(target) : value;
88
+ }
89
+ }
90
+ } });
91
+ }
92
+ //#endregion
15
93
  //#region src/vector/filter.ts
16
94
  /**
17
95
  * Translates MongoDB-style filters to LibSQL compatible filters.
@@ -427,13 +505,17 @@ var LibSQLVector = class extends _mastra_core_vector.MastraVector {
427
505
  this.isMemoryDb = url.includes(":memory:");
428
506
  const isLocalDb = (url.startsWith("file:") || this.isMemoryDb) && !syncUrl;
429
507
  const cwd = process.cwd();
430
- this.turso = (0, _libsql_client.createClient)({
508
+ const client = (0, _libsql_client.createClient)({
431
509
  url,
432
510
  syncUrl,
433
511
  authToken,
434
512
  syncInterval,
435
513
  ...isLocalDb ? { timeout: 5e3 } : {}
436
514
  });
515
+ this.turso = isSingleConnectionDatabase({
516
+ url,
517
+ syncUrl
518
+ }) ? gateSingleConnectionClient(client) : client;
437
519
  this.maxRetries = maxRetries;
438
520
  this.initialBackoffMs = initialBackoffMs;
439
521
  if (!Number.isInteger(vectorTopKOverFetchMultiplier) || vectorTopKOverFetchMultiplier < 1) throw new Error("vectorTopKOverFetchMultiplier must be a positive integer");
@@ -1280,24 +1362,26 @@ function transformFromSqlRow({ tableName, sqlRow }) {
1280
1362
  /**
1281
1363
  * Per-client write serialization.
1282
1364
  *
1283
- * `@libsql/client` backs a local (`file:`/`:memory:`) database with a single
1284
- * underlying connection. An interactive `client.transaction('write')` issues a
1285
- * `BEGIN` and then yields to the event loop on every `await tx.execute(...)`.
1286
- * Any autocommit write (`client.execute`/`client.batch`) issued on the same
1287
- * client during that window runs on the same connection so it is swept into
1288
- * the still-open transaction and is committed or rolled back with it, instead
1289
- * of as its own statement. Two concurrent interactive transactions collide the
1290
- * same way ("cannot start a transaction within a transaction").
1365
+ * `@libsql/client` >= 0.18.0 pools connections for local `file:` databases, but
1366
+ * SQLite still admits one writer at a time: an interactive
1367
+ * `client.transaction('write')` holds `BEGIN` open across every
1368
+ * `await tx.execute(...)`, and any other write on the same database in that
1369
+ * window contends on the file lock and can fail with `SQLITE_BUSY` once
1370
+ * `busy_timeout` expires.
1291
1371
  *
1292
1372
  * This is dormant under the default engine but the evented engine runs many
1293
1373
  * concurrent workflow snapshot writes per agent run, so a write issued by an
1294
- * unrelated domain (e.g. creating a dataset experiment) can silently vanish.
1374
+ * unrelated domain (e.g. creating a dataset experiment) can fail spuriously.
1295
1375
  *
1296
1376
  * Serializing every write on a given client closes that window: writes — both
1297
1377
  * autocommit statements and full interactive transactions — run one at a time,
1298
1378
  * so none can interleave with an open transaction. Reads are intentionally not
1299
1379
  * gated; WAL readers never observe a partial write and must not queue behind a
1300
1380
  * long-running writer.
1381
+ *
1382
+ * `:memory:` databases and embedded replicas get a single pooled connection
1383
+ * instead; see `shared/single-connection-client.ts`, which gates *all* calls
1384
+ * (reads included) behind open transactions for those clients.
1301
1385
  */
1302
1386
  const clientWriteChains = /* @__PURE__ */ new WeakMap();
1303
1387
  /**
@@ -4698,6 +4782,7 @@ var DatasetsLibSQL = class extends _mastra_core_storage.DatasetsStorage {
4698
4782
  //#endregion
4699
4783
  //#region src/storage/domains/experiments/index.ts
4700
4784
  const DEFAULT_PRUNE_BATCH_SIZE = 1e3;
4785
+ const TAGS_IS_JSON = `CASE typeof(tags) WHEN 'blob' THEN 1 WHEN 'text' THEN json_valid(tags) ELSE 0 END`;
4701
4786
  var ExperimentsLibSQL = class extends _mastra_core_storage.ExperimentsStorage {
4702
4787
  /**
4703
4788
  * An experiment is pruned as a whole unit: when `experiments.completedAt` is
@@ -5433,6 +5518,10 @@ var ExperimentsLibSQL = class extends _mastra_core_storage.ExperimentsStorage {
5433
5518
  conditions.push("status = ?");
5434
5519
  queryParams.push(args.status);
5435
5520
  }
5521
+ for (const tag of args.tags ?? []) {
5522
+ conditions.push(`CASE WHEN ${TAGS_IS_JSON} THEN EXISTS (SELECT 1 FROM json_each(tags) WHERE value = ?) ELSE 0 END`);
5523
+ queryParams.push(tag);
5524
+ }
5436
5525
  if (args.filters) {
5437
5526
  const { organizationId, projectId } = args.filters;
5438
5527
  if (organizationId !== void 0) {
@@ -14222,11 +14311,12 @@ var LibSQLFactoryStorage = class extends _mastra_core_storage.FactoryStorage {
14222
14311
  super();
14223
14312
  this.#config = config;
14224
14313
  const isLocalDb = config.url.startsWith("file:") || config.url.includes(":memory:");
14225
- this.#client = (0, _libsql_client.createClient)({
14314
+ const client = (0, _libsql_client.createClient)({
14226
14315
  url: config.url,
14227
14316
  ...config.authToken ? { authToken: config.authToken } : {},
14228
14317
  ...isLocalDb ? { timeout: DEFAULT_CONNECTION_TIMEOUT_MS } : {}
14229
14318
  });
14319
+ this.#client = isSingleConnectionDatabase(config) ? gateSingleConnectionClient(client) : client;
14230
14320
  this.ops = new LibSQLFactoryStorageOps(this.#client, this.#schemas, (fn) => withClientWriteLock(this.#client, fn));
14231
14321
  }
14232
14322
  getMastraStorage() {
@@ -14384,15 +14474,15 @@ var LibSQLStore = class extends _mastra_core_storage.MastraCompositeStore {
14384
14474
  mmapSize: config.localPragmas?.mmapSize ?? DEFAULT_LOCAL_MMAP_SIZE
14385
14475
  };
14386
14476
  if ("url" in config) {
14387
- if (config.url.includes(":memory:")) this.shouldCacheInit = false;
14388
14477
  this.isLocalDb = (config.url.startsWith("file:") || config.url.includes(":memory:")) && !config.syncUrl;
14389
- this.client = (0, _libsql_client.createClient)({
14478
+ const client = (0, _libsql_client.createClient)({
14390
14479
  url: config.url,
14391
14480
  ...config.authToken ? { authToken: config.authToken } : {},
14392
14481
  ...config.syncUrl ? { syncUrl: config.syncUrl } : {},
14393
14482
  ...config.syncInterval !== void 0 ? { syncInterval: config.syncInterval } : {},
14394
14483
  ...this.isLocalDb ? { timeout: this.connectionTimeoutMs } : {}
14395
14484
  });
14485
+ this.client = isSingleConnectionDatabase(config) ? gateSingleConnectionClient(client) : client;
14396
14486
  this.pragmasReady = this.isLocalDb ? this.applyLocalPragmas() : Promise.resolve();
14397
14487
  } else {
14398
14488
  this.client = config.client;