@nxgt/ai-memory 0.0.0-stage → 0.1.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 (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +173 -2
  3. package/dist/actions.d.ts +9 -0
  4. package/dist/actions.d.ts.map +1 -0
  5. package/dist/chunks/index-082dnty9.js +188 -0
  6. package/dist/chunks/index-082dnty9.js.map +14 -0
  7. package/dist/chunks/index-vj24rcgc.js +119 -0
  8. package/dist/chunks/index-vj24rcgc.js.map +13 -0
  9. package/dist/compact.d.ts +38 -0
  10. package/dist/compact.d.ts.map +1 -0
  11. package/dist/contract-actions.d.ts +3 -0
  12. package/dist/contract-actions.d.ts.map +1 -0
  13. package/dist/contract-messages.d.ts +3 -0
  14. package/dist/contract-messages.d.ts.map +1 -0
  15. package/dist/contract-owner.d.ts +3 -0
  16. package/dist/contract-owner.d.ts.map +1 -0
  17. package/dist/contract-samples.d.ts +6 -0
  18. package/dist/contract-samples.d.ts.map +1 -0
  19. package/dist/contract-threads.d.ts +3 -0
  20. package/dist/contract-threads.d.ts.map +1 -0
  21. package/dist/contract.d.ts +9 -0
  22. package/dist/contract.d.ts.map +1 -0
  23. package/dist/errors.d.ts +11 -0
  24. package/dist/errors.d.ts.map +1 -0
  25. package/dist/guards.d.ts +5 -0
  26. package/dist/guards.d.ts.map +1 -0
  27. package/dist/in-process.d.ts +8 -0
  28. package/dist/in-process.d.ts.map +1 -0
  29. package/dist/index.d.ts +8 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +167 -0
  32. package/dist/index.js.map +13 -0
  33. package/dist/messages.d.ts +7 -0
  34. package/dist/messages.d.ts.map +1 -0
  35. package/dist/mongo-actions.d.ts +8 -0
  36. package/dist/mongo-actions.d.ts.map +1 -0
  37. package/dist/mongo-collection.d.ts +32 -0
  38. package/dist/mongo-collection.d.ts.map +1 -0
  39. package/dist/mongo-ctx.d.ts +12 -0
  40. package/dist/mongo-ctx.d.ts.map +1 -0
  41. package/dist/mongo-fields.d.ts +41 -0
  42. package/dist/mongo-fields.d.ts.map +1 -0
  43. package/dist/mongo-map.d.ts +8 -0
  44. package/dist/mongo-map.d.ts.map +1 -0
  45. package/dist/mongo-messages.d.ts +7 -0
  46. package/dist/mongo-messages.d.ts.map +1 -0
  47. package/dist/mongo-store.d.ts +16 -0
  48. package/dist/mongo-store.d.ts.map +1 -0
  49. package/dist/mongo-threads.d.ts +13 -0
  50. package/dist/mongo-threads.d.ts.map +1 -0
  51. package/dist/mongo.d.ts +4 -0
  52. package/dist/mongo.d.ts.map +1 -0
  53. package/dist/mongo.js +271 -0
  54. package/dist/mongo.js.map +16 -0
  55. package/dist/paging.d.ts +8 -0
  56. package/dist/paging.d.ts.map +1 -0
  57. package/dist/pairs.d.ts +14 -0
  58. package/dist/pairs.d.ts.map +1 -0
  59. package/dist/redis-actions.d.ts +8 -0
  60. package/dist/redis-actions.d.ts.map +1 -0
  61. package/dist/redis-ctx.d.ts +37 -0
  62. package/dist/redis-ctx.d.ts.map +1 -0
  63. package/dist/redis-map.d.ts +13 -0
  64. package/dist/redis-map.d.ts.map +1 -0
  65. package/dist/redis-messages.d.ts +7 -0
  66. package/dist/redis-messages.d.ts.map +1 -0
  67. package/dist/redis-scripts.d.ts +40 -0
  68. package/dist/redis-scripts.d.ts.map +1 -0
  69. package/dist/redis-store.d.ts +21 -0
  70. package/dist/redis-store.d.ts.map +1 -0
  71. package/dist/redis-threads.d.ts +16 -0
  72. package/dist/redis-threads.d.ts.map +1 -0
  73. package/dist/redis.d.ts +3 -0
  74. package/dist/redis.d.ts.map +1 -0
  75. package/dist/redis.js +373 -0
  76. package/dist/redis.js.map +16 -0
  77. package/dist/schemas.d.ts +124 -0
  78. package/dist/schemas.d.ts.map +1 -0
  79. package/dist/state.d.ts +28 -0
  80. package/dist/state.d.ts.map +1 -0
  81. package/dist/summarize.d.ts +11 -0
  82. package/dist/summarize.d.ts.map +1 -0
  83. package/dist/testing.d.ts +3 -0
  84. package/dist/testing.d.ts.map +1 -0
  85. package/dist/testing.js +264 -0
  86. package/dist/testing.js.map +15 -0
  87. package/dist/threads.d.ts +7 -0
  88. package/dist/threads.d.ts.map +1 -0
  89. package/dist/truncate.d.ts +17 -0
  90. package/dist/truncate.d.ts.map +1 -0
  91. package/dist/types.d.ts +92 -0
  92. package/dist/types.d.ts.map +1 -0
  93. package/docs/README.md +9 -0
  94. package/docs/guide/memory.md +327 -0
  95. package/docs/roadmap.md +31 -0
  96. package/docs/troubleshooting.md +171 -0
  97. package/package.json +85 -5
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Steve Tsala
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,174 @@
1
- # Temporary Holding Version
1
+ # @nxgt/ai-memory
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Conversation memory for [`@nxgt/ai-core`](https://www.npmjs.com/package/@nxgt/ai-core) agents: a `MemoryStore`
4
+ port scoped by owner on every call, an in-process store, and a contract suite
5
+ to test your own stores. Peers: `@nxgt/ai-core`, `zod`.
6
+
7
+ ```ts
8
+ import { inProcessMemory } from '@nxgt/ai-memory';
9
+
10
+ const store = inProcessMemory();
11
+ const thread = await store.createThread({ ownerId: 'user-1', title: 'Trip' });
12
+
13
+ await store.append(thread.id, 'user-1', [
14
+ { role: 'user', parts: [{ type: 'text', text: 'Hello' }] },
15
+ ]);
16
+
17
+ const loaded = await store.load(thread.id, 'user-1', { sinceSummary: true });
18
+ // loaded.messages feed runAgent({ messages: loaded.messages.map((m) => m.message) })
19
+ ```
20
+
21
+ Another owner's thread is invisible: `getThread` and `load` return
22
+ `undefined`, `findAction` and `updateAction` find nothing, and writes raise
23
+ `MemoryError('THREAD_NOT_FOUND')`.
24
+
25
+ `updateAction` is a compare-and-set on `pending`: of two concurrent
26
+ confirmations of one proposed action, one gets the action back and the other
27
+ gets `undefined`.
28
+
29
+ ## Compact a long thread
30
+
31
+ ```ts
32
+ import { estimateTokens } from '@nxgt/ai-core';
33
+ import { compact, summarizeWith } from '@nxgt/ai-memory';
34
+
35
+ const loaded = await store.load(threadId, ownerId, { sinceSummary: true });
36
+ if (!loaded) throw new Error('thread not found');
37
+ const out = await compact(loaded.messages, {
38
+ budget: 8000,
39
+ summary: loaded.summary,
40
+ summarize: summarizeWith(model),
41
+ });
42
+ if (out.summary && out.throughMessageId) {
43
+ await store.setSummary(threadId, ownerId, {
44
+ text: out.summary,
45
+ throughMessageId: out.throughMessageId,
46
+ tokens: estimateTokens(out.summary),
47
+ });
48
+ }
49
+ ```
50
+
51
+ Tool results are cut first (oldest first), then the oldest messages are folded
52
+ into a rolling summary; the last 6 are kept and a tool call never loses its result.
53
+
54
+ ## MongoDB store
55
+
56
+ `@nxgt/ai-memory/mongo` keeps threads and messages in two collections. It takes
57
+ any object with the few collection methods it calls: a `Collection` of the
58
+ `mongodb` driver, or a scoped collection of `@nxgt/mongo`. It imports no client.
59
+ It needs the `mongodb` driver 6 or later.
60
+
61
+ ```ts
62
+ import {
63
+ expiresAtIndex,
64
+ messageIndexes,
65
+ mongoMemory,
66
+ threadIndexes,
67
+ } from '@nxgt/ai-memory/mongo';
68
+
69
+ // Once, at start-up: the indexes the store relies on.
70
+ for (const { key, ...options } of [...threadIndexes, expiresAtIndex]) {
71
+ await db.collection('threads').createIndex(key, options);
72
+ }
73
+ for (const { key, ...options } of [...messageIndexes, expiresAtIndex]) {
74
+ await db.collection('messages').createIndex(key, options);
75
+ }
76
+
77
+ const store = mongoMemory({
78
+ threads: db.collection('threads'),
79
+ messages: db.collection('messages'),
80
+ expireAfterSeconds: 60 * 60 * 24 * 30, // optional; omit it to keep everything
81
+ });
82
+ ```
83
+
84
+ It passes `memoryStoreContract`. `updateAction` is one atomic write that
85
+ matches only a `pending` action. Without `expireAfterSeconds`, leave
86
+ `expiresAtIndex` out. See the [guide](docs/guide/memory.md#mongodb-store).
87
+
88
+ ## Redis store
89
+
90
+ `@nxgt/ai-memory/redis` keeps threads and messages in Redis keys. It takes the
91
+ client of [`@nxgt/redis`](https://www.npmjs.com/package/@nxgt/redis) (the
92
+ `client` of `connectRedis` or of an `openRedis` instance) and calls only its
93
+ `send` method. It needs Redis 6.2 or later, not in cluster mode.
94
+
95
+ ```ts
96
+ import { connectRedis } from '@nxgt/redis';
97
+ import { redisMemory } from '@nxgt/ai-memory/redis';
98
+
99
+ const redis = await connectRedis('redis://localhost:6379');
100
+
101
+ const store = redisMemory({
102
+ client: redis.client,
103
+ prefix: 'support-chat', // optional; default "ai-memory"
104
+ expireAfterSeconds: 60 * 60 * 24 * 30, // optional; omit it to keep everything
105
+ });
106
+
107
+ const thread = await store.createThread({ ownerId: 'user-1' });
108
+ await store.append(thread.id, 'user-1', [
109
+ { role: 'user', parts: [{ type: 'text', text: 'Hello' }] },
110
+ ]);
111
+ ```
112
+
113
+ It passes `memoryStoreContract`. Each `append` is one Lua script that reserves
114
+ its `seq` range and writes its messages, and `updateAction` is one script that
115
+ matches only a `pending` action; `deleteThread` is one script. Nothing is indexed by hand and nothing needs
116
+ creating first. See the [guide](docs/guide/memory.md#redis-store).
117
+
118
+ ## Test your own store
119
+
120
+ ```ts
121
+ // my-store.spec.ts
122
+ import { memoryStoreContract } from '@nxgt/ai-memory/testing';
123
+
124
+ memoryStoreContract(() => myStore());
125
+ ```
126
+
127
+ The factory returns a fresh, empty store each time. The suite runs under
128
+ `bun test`.
129
+
130
+ ## API
131
+
132
+ | Export | What it is |
133
+ | -------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
134
+ | `MemoryStore` | the port: `createThread`, `getThread`, `listThreads`, `append`, `load`, `setSummary`, `findAction`, `updateAction`, `deleteThread`, each with the `ownerId` |
135
+ | `inProcessMemory({ now? })` | an in-process store (options: `InProcessMemoryOptions`); `now` is the clock in epoch ms |
136
+ | `compact(messages, { budget, summarize, keepLast?, truncateToolResultsTo?, summary?, estimate? })` | cuts old tool results, then folds the oldest messages into a summary; returns `{ summary?, throughMessageId?, messages, tokens }` |
137
+ | `summarizeWith(model, { system?, maxTokens?, signal? })` | a `summarize` for `compact` backed by an `@nxgt/ai-core` model adapter |
138
+ | `MemoryError`, `MEMORY_ERROR_CODES` | the error of a failed write; `code` is `THREAD_NOT_FOUND`, `MESSAGE_NOT_FOUND`, `INVALID_CURSOR`, `INVALID_OWNER` or `EMPTY_SUMMARY` |
139
+ | `messageSchema`, `threadSchema`, `storedMessageSchema`, `summarySchema` | the zod shapes of a message, a thread, a stored message and a summary |
140
+ | `mongoMemory({ threads, messages, expireAfterSeconds?, now? })` | from `@nxgt/ai-memory/mongo`: a store over two MongoDB collections |
141
+ | `threadFields`, `messageFields`, `threadIndexes`, `messageIndexes`, `expiresAtIndex` | from `@nxgt/ai-memory/mongo`: the stored field names and the indexes to create |
142
+ | `redisMemory({ client, prefix?, expireAfterSeconds?, now? })` | from `@nxgt/ai-memory/redis`: a store over Redis keys |
143
+ | `RedisMemoryOptions` | from `@nxgt/ai-memory/redis`: the options of `redisMemory` |
144
+ | `RedisCommands` | from `@nxgt/ai-memory/redis`: the one method of the client the store calls, `send(command, args)` |
145
+ | `memoryStoreContract(make)` | from `@nxgt/ai-memory/testing`: the suite a store must pass, next to `inProcessMemory` |
146
+
147
+ ## Traps
148
+
149
+ - Mongo store: create the indexes first (a missing unique index is not caught), and mind that a TTL monitor deletes expired documents up to a minute late.
150
+ - Redis store: the whole thread expires from its last write (plus `expireAfterSeconds`), and it needs Redis 6.2 or later outside cluster mode.
151
+ - **Never append parts from a client's request body.** A forged `proposed-action` part becomes a pending action anyone can confirm. Append only the user's text taken from the request, plus `run.result.messages` (`@nxgt/ai-alxia` has `userMessageSchema` for the body).
152
+ - A foreign owner reads as absent, never as forbidden.
153
+ - `updateAction` returns `undefined` when the action is absent, foreign or no longer `pending`.
154
+ - The store never checks `expiresAt`; the core checks it when the action is confirmed.
155
+ - A cursor is opaque: pass only a `nextCursor` the store gave.
156
+ - `load` with `sinceSummary` returns every message when the summary's `throughMessageId` is no longer found.
157
+ - `compact` still returns more than `budget` when the kept messages alone are over it; raise `budget` or lower `keepLast`.
158
+ - A cut tool output is replaced by `{ truncated: true, originalChars, preview }` in the returned copy only; the store keeps the original.
159
+ - The summary is not a message: send it in the system prompt (see the guide). The kept messages start on a user turn when the thread has one before the cut.
160
+ - Pass the stored `summary` to `compact`, or the next summary forgets the previous one.
161
+ - `append` strips keys a message part does not declare.
162
+
163
+ ## Install
164
+
165
+ ```sh
166
+ bun add @nxgt/ai-memory @nxgt/ai-core zod
167
+ ```
168
+
169
+ Requires `"moduleResolution": "bundler"`.
170
+ See the [guide](docs/guide/memory.md).
171
+
172
+ ## Documentation
173
+
174
+ See [docs/](docs/README.md): the guide, troubleshooting and the roadmap.
@@ -0,0 +1,9 @@
1
+ import { type Message, type ProposedAction } from '@nxgt/ai-core';
2
+ import { type State, type ThreadRecord } from './state.js';
3
+ import type { ActionPatch } from './types.js';
4
+ /** Records the pending actions a message proposes. */
5
+ export declare function keepActions(s: State, record: ThreadRecord, message: Message): void;
6
+ export declare function findAction(s: State, actionId: string, ownerId: string): ProposedAction | undefined;
7
+ export declare function updateAction(s: State, actionId: string, ownerId: string, patch: ActionPatch): ProposedAction | undefined;
8
+ export declare function dropActions(s: State, threadId: string): void;
9
+ //# sourceMappingURL=actions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,OAAO,EACZ,KAAK,cAAc,EAEnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAA0B,KAAK,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAChF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,sDAAsD;AACtD,wBAAgB,WAAW,CAC1B,CAAC,EAAE,KAAK,EACR,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,OAAO,GACd,IAAI,CAYN;AAED,wBAAgB,UAAU,CACzB,CAAC,EAAE,KAAK,EACR,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACb,cAAc,GAAG,SAAS,CAI5B;AAED,wBAAgB,YAAY,CAC3B,CAAC,EAAE,KAAK,EACR,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,WAAW,GAChB,cAAc,GAAG,SAAS,CAa5B;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAI5D"}
@@ -0,0 +1,188 @@
1
+ import {
2
+ MemoryError2,
3
+ assertOwner,
4
+ notFound,
5
+ messageSchema2,
6
+ pageLimit,
7
+ invalidCursor
8
+ } from "./index-vj24rcgc.js";
9
+
10
+ // src/actions.ts
11
+ import {
12
+ toProposedAction
13
+ } from "@nxgt/ai-core";
14
+
15
+ // src/state.ts
16
+ var actionKey = (ownerId, actionId) => `${ownerId}\x00${actionId}`;
17
+ function own(s, id, ownerId) {
18
+ assertOwner(ownerId);
19
+ const record = s.threads.get(id);
20
+ return record?.thread.ownerId === ownerId ? record : undefined;
21
+ }
22
+ function ownOrThrow(s, id, ownerId) {
23
+ const record = own(s, id, ownerId);
24
+ if (!record) {
25
+ throw notFound(id);
26
+ }
27
+ return record;
28
+ }
29
+
30
+ // src/actions.ts
31
+ function keepActions(s, record, message) {
32
+ const ownerId = record.thread.ownerId;
33
+ for (const part of message.parts) {
34
+ if (part.type !== "proposed-action")
35
+ continue;
36
+ const key = actionKey(ownerId, part.id);
37
+ if (s.actions.has(key))
38
+ continue;
39
+ s.actions.set(key, {
40
+ ownerId,
41
+ threadId: record.thread.id,
42
+ action: toProposedAction(part)
43
+ });
44
+ }
45
+ }
46
+ function findAction(s, actionId, ownerId) {
47
+ assertOwner(ownerId);
48
+ const record = s.actions.get(actionKey(ownerId, actionId));
49
+ return record && structuredClone(record.action);
50
+ }
51
+ function updateAction(s, actionId, ownerId, patch) {
52
+ assertOwner(ownerId);
53
+ const record = s.actions.get(actionKey(ownerId, actionId));
54
+ if (record?.action.status !== "pending")
55
+ return;
56
+ record.action = {
57
+ ...record.action,
58
+ status: patch.status,
59
+ ...patch.result === undefined ? {} : { result: structuredClone(patch.result) }
60
+ };
61
+ return structuredClone(record.action);
62
+ }
63
+ function dropActions(s, threadId) {
64
+ for (const [key, a] of s.actions) {
65
+ if (a.threadId === threadId)
66
+ s.actions.delete(key);
67
+ }
68
+ }
69
+
70
+ // src/messages.ts
71
+ function append(s, threadId, ownerId, messages) {
72
+ const record = ownOrThrow(s, threadId, ownerId);
73
+ const parsed = messages.map((m) => messageSchema2.parse(m));
74
+ const at = s.now();
75
+ const stored = parsed.map((message, i) => ({
76
+ id: crypto.randomUUID(),
77
+ threadId,
78
+ seq: record.messages.length + i + 1,
79
+ message: structuredClone(message),
80
+ createdAt: at
81
+ }));
82
+ for (const m of stored) {
83
+ record.messages.push(m);
84
+ keepActions(s, record, m.message);
85
+ }
86
+ record.thread.messageCount = record.messages.length;
87
+ record.thread.updatedAt = at;
88
+ return structuredClone(stored);
89
+ }
90
+ function load(s, threadId, ownerId, options) {
91
+ const record = own(s, threadId, ownerId);
92
+ if (!record)
93
+ return;
94
+ const { summary } = record.thread;
95
+ let messages = record.messages;
96
+ if (options.sinceSummary && summary) {
97
+ const through = messages.find((m) => m.id === summary.throughMessageId);
98
+ messages = messages.filter((m) => m.seq > (through?.seq ?? 0));
99
+ }
100
+ return structuredClone({
101
+ thread: record.thread,
102
+ ...summary ? { summary } : {},
103
+ messages
104
+ });
105
+ }
106
+ function setSummary(s, threadId, ownerId, input) {
107
+ const record = ownOrThrow(s, threadId, ownerId);
108
+ if (!record.messages.some((m) => m.id === input.throughMessageId)) {
109
+ throw new MemoryError2("MESSAGE_NOT_FOUND", `Message "${input.throughMessageId}" is not in thread "${threadId}".`);
110
+ }
111
+ const at = s.now();
112
+ record.thread.summary = { ...input, createdAt: at };
113
+ record.thread.updatedAt = at;
114
+ return structuredClone(record.thread.summary);
115
+ }
116
+
117
+ // src/threads.ts
118
+ function parseCursor(cursor) {
119
+ const order = /^\d+$/.test(cursor) ? Number(cursor) : Number.NaN;
120
+ if (!Number.isSafeInteger(order)) {
121
+ throw invalidCursor(cursor);
122
+ }
123
+ return order;
124
+ }
125
+ function createThread(s, input) {
126
+ assertOwner(input.ownerId);
127
+ const at = s.now();
128
+ const thread = {
129
+ id: crypto.randomUUID(),
130
+ ownerId: input.ownerId,
131
+ ...input.title === undefined ? {} : { title: input.title },
132
+ ...input.meta === undefined ? {} : { meta: structuredClone(input.meta) },
133
+ messageCount: 0,
134
+ createdAt: at,
135
+ updatedAt: at
136
+ };
137
+ s.threads.set(thread.id, { thread, order: ++s.counter, messages: [] });
138
+ return structuredClone(thread);
139
+ }
140
+ function getThread(s, id, ownerId) {
141
+ const record = own(s, id, ownerId);
142
+ return record && structuredClone(record.thread);
143
+ }
144
+ function listThreads(s, ownerId, page) {
145
+ assertOwner(ownerId);
146
+ const limit = pageLimit(page);
147
+ const before = page.cursor === undefined ? Infinity : parseCursor(page.cursor);
148
+ const mine = [...s.threads.values()].filter((r) => r.thread.ownerId === ownerId && r.order < before).sort((a, b) => b.order - a.order);
149
+ const items = mine.slice(0, limit);
150
+ const last = items[items.length - 1];
151
+ return {
152
+ items: items.map((r) => structuredClone(r.thread)),
153
+ ...mine.length > limit && last ? { nextCursor: String(last.order) } : {}
154
+ };
155
+ }
156
+ function deleteThread(s, id, ownerId) {
157
+ if (!own(s, id, ownerId))
158
+ return false;
159
+ s.threads.delete(id);
160
+ dropActions(s, id);
161
+ return true;
162
+ }
163
+
164
+ // src/in-process.ts
165
+ function inProcessMemory2(options = {}) {
166
+ const s = {
167
+ now: options.now ?? Date.now,
168
+ threads: new Map,
169
+ actions: new Map,
170
+ counter: 0
171
+ };
172
+ return {
173
+ createThread: async (input) => createThread(s, input),
174
+ getThread: async (id, ownerId) => getThread(s, id, ownerId),
175
+ listThreads: async (ownerId, page = {}) => listThreads(s, ownerId, page),
176
+ append: async (id, ownerId, list) => append(s, id, ownerId, list),
177
+ load: async (id, ownerId, opts) => load(s, id, ownerId, opts),
178
+ setSummary: async (id, ownerId, input) => setSummary(s, id, ownerId, input),
179
+ findAction: async (id, ownerId) => findAction(s, id, ownerId),
180
+ updateAction: async (id, ownerId, patch) => updateAction(s, id, ownerId, patch),
181
+ deleteThread: async (id, ownerId) => deleteThread(s, id, ownerId)
182
+ };
183
+ }
184
+
185
+ export { inProcessMemory2 };
186
+
187
+ //# debugId=6B46FC5865C2FC0E64756E2164756E21
188
+ //# sourceMappingURL=index-082dnty9.js.map
@@ -0,0 +1,14 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/actions.ts", "../src/state.ts", "../src/messages.ts", "../src/threads.ts", "../src/in-process.ts"],
4
+ "sourcesContent": [
5
+ "import {\n\ttype Message,\n\ttype ProposedAction,\n\ttoProposedAction,\n} from '@nxgt/ai-core';\nimport { actionKey, assertOwner, type State, type ThreadRecord } from './state';\nimport type { ActionPatch } from './types';\n\n/** Records the pending actions a message proposes. */\nexport function keepActions(\n\ts: State,\n\trecord: ThreadRecord,\n\tmessage: Message,\n): void {\n\tconst ownerId = record.thread.ownerId;\n\tfor (const part of message.parts) {\n\t\tif (part.type !== 'proposed-action') continue;\n\t\tconst key = actionKey(ownerId, part.id);\n\t\tif (s.actions.has(key)) continue;\n\t\ts.actions.set(key, {\n\t\t\townerId,\n\t\t\tthreadId: record.thread.id,\n\t\t\taction: toProposedAction(part),\n\t\t});\n\t}\n}\n\nexport function findAction(\n\ts: State,\n\tactionId: string,\n\townerId: string,\n): ProposedAction | undefined {\n\tassertOwner(ownerId);\n\tconst record = s.actions.get(actionKey(ownerId, actionId));\n\treturn record && structuredClone(record.action);\n}\n\nexport function updateAction(\n\ts: State,\n\tactionId: string,\n\townerId: string,\n\tpatch: ActionPatch,\n): ProposedAction | undefined {\n\tassertOwner(ownerId);\n\tconst record = s.actions.get(actionKey(ownerId, actionId));\n\t// Synchronous from this check to the write: that is the compare-and-set.\n\tif (record?.action.status !== 'pending') return undefined;\n\trecord.action = {\n\t\t...record.action,\n\t\tstatus: patch.status,\n\t\t...(patch.result === undefined\n\t\t\t? {}\n\t\t\t: { result: structuredClone(patch.result) }),\n\t};\n\treturn structuredClone(record.action);\n}\n\nexport function dropActions(s: State, threadId: string): void {\n\tfor (const [key, a] of s.actions) {\n\t\tif (a.threadId === threadId) s.actions.delete(key);\n\t}\n}\n",
6
+ "import type { ProposedAction } from '@nxgt/ai-core';\nimport { assertOwner, notFound } from './guards';\nimport type { StoredMessage, Thread } from './types';\n\nexport interface ThreadRecord {\n\tthread: Thread;\n\t/** Creation order across the store; the list cursor. */\n\torder: number;\n\tmessages: StoredMessage[];\n}\n\nexport interface ActionRecord {\n\townerId: string;\n\tthreadId: string;\n\taction: ProposedAction;\n}\n\n/** What the store keeps; the functions of the sibling files change it. */\nexport interface State {\n\tnow: () => number;\n\tthreads: Map<string, ThreadRecord>;\n\t/** Keyed by `actionKey`, so an id is only ever taken within one owner. */\n\tactions: Map<string, ActionRecord>;\n\tcounter: number;\n}\n\nexport { assertOwner };\n\nexport const actionKey = (ownerId: string, actionId: string): string =>\n\t`${ownerId}\\u0000${actionId}`;\n\n/** The owner's thread, or undefined for an absent or foreign one. */\nexport function own(\n\ts: State,\n\tid: string,\n\townerId: string,\n): ThreadRecord | undefined {\n\tassertOwner(ownerId);\n\tconst record = s.threads.get(id);\n\treturn record?.thread.ownerId === ownerId ? record : undefined;\n}\n\nexport function ownOrThrow(\n\ts: State,\n\tid: string,\n\townerId: string,\n): ThreadRecord {\n\tconst record = own(s, id, ownerId);\n\tif (!record) {\n\t\tthrow notFound(id);\n\t}\n\treturn record;\n}\n",
7
+ "import type { Message } from '@nxgt/ai-core';\nimport { keepActions } from './actions';\nimport { MemoryError } from './errors';\nimport { messageSchema } from './schemas';\nimport { own, ownOrThrow, type State } from './state';\nimport type {\n\tLoadedThread,\n\tLoadOptions,\n\tStoredMessage,\n\tSummaryInput,\n\tThreadSummary,\n} from './types';\n\nexport function append(\n\ts: State,\n\tthreadId: string,\n\townerId: string,\n\tmessages: readonly Message[],\n): StoredMessage[] {\n\tconst record = ownOrThrow(s, threadId, ownerId);\n\t// Parsing strips unknown keys; schemas.types.spec.ts guards the cast.\n\tconst parsed = messages.map((m) => messageSchema.parse(m) as Message);\n\tconst at = s.now();\n\tconst stored = parsed.map(\n\t\t(message, i): StoredMessage => ({\n\t\t\tid: crypto.randomUUID(),\n\t\t\tthreadId,\n\t\t\tseq: record.messages.length + i + 1,\n\t\t\tmessage: structuredClone(message),\n\t\t\tcreatedAt: at,\n\t\t}),\n\t);\n\tfor (const m of stored) {\n\t\trecord.messages.push(m);\n\t\tkeepActions(s, record, m.message);\n\t}\n\trecord.thread.messageCount = record.messages.length;\n\trecord.thread.updatedAt = at;\n\treturn structuredClone(stored);\n}\n\nexport function load(\n\ts: State,\n\tthreadId: string,\n\townerId: string,\n\toptions: LoadOptions,\n): LoadedThread | undefined {\n\tconst record = own(s, threadId, ownerId);\n\tif (!record) return undefined;\n\tconst { summary } = record.thread;\n\tlet messages = record.messages;\n\tif (options.sinceSummary && summary) {\n\t\tconst through = messages.find((m) => m.id === summary.throughMessageId);\n\t\tmessages = messages.filter((m) => m.seq > (through?.seq ?? 0));\n\t}\n\treturn structuredClone({\n\t\tthread: record.thread,\n\t\t...(summary ? { summary } : {}),\n\t\tmessages,\n\t});\n}\n\nexport function setSummary(\n\ts: State,\n\tthreadId: string,\n\townerId: string,\n\tinput: SummaryInput,\n): ThreadSummary {\n\tconst record = ownOrThrow(s, threadId, ownerId);\n\tif (!record.messages.some((m) => m.id === input.throughMessageId)) {\n\t\tthrow new MemoryError(\n\t\t\t'MESSAGE_NOT_FOUND',\n\t\t\t`Message \"${input.throughMessageId}\" is not in thread \"${threadId}\".`,\n\t\t);\n\t}\n\tconst at = s.now();\n\trecord.thread.summary = { ...input, createdAt: at };\n\trecord.thread.updatedAt = at;\n\treturn structuredClone(record.thread.summary);\n}\n",
8
+ "import { dropActions } from './actions';\nimport { invalidCursor, pageLimit } from './paging';\nimport { assertOwner, own, type State } from './state';\nimport type { CreateThreadInput, Page, Thread, ThreadPage } from './types';\n\nfunction parseCursor(cursor: string): number {\n\tconst order = /^\\d+$/.test(cursor) ? Number(cursor) : Number.NaN;\n\tif (!Number.isSafeInteger(order)) {\n\t\tthrow invalidCursor(cursor);\n\t}\n\treturn order;\n}\n\nexport function createThread(s: State, input: CreateThreadInput): Thread {\n\tassertOwner(input.ownerId);\n\tconst at = s.now();\n\tconst thread: Thread = {\n\t\tid: crypto.randomUUID(),\n\t\townerId: input.ownerId,\n\t\t...(input.title === undefined ? {} : { title: input.title }),\n\t\t...(input.meta === undefined ? {} : { meta: structuredClone(input.meta) }),\n\t\tmessageCount: 0,\n\t\tcreatedAt: at,\n\t\tupdatedAt: at,\n\t};\n\ts.threads.set(thread.id, { thread, order: ++s.counter, messages: [] });\n\treturn structuredClone(thread);\n}\n\nexport function getThread(s: State, id: string, ownerId: string) {\n\tconst record = own(s, id, ownerId);\n\treturn record && structuredClone(record.thread);\n}\n\nexport function listThreads(s: State, ownerId: string, page: Page): ThreadPage {\n\tassertOwner(ownerId);\n\tconst limit = pageLimit(page);\n\tconst before =\n\t\tpage.cursor === undefined ? Infinity : parseCursor(page.cursor);\n\tconst mine = [...s.threads.values()]\n\t\t.filter((r) => r.thread.ownerId === ownerId && r.order < before)\n\t\t.sort((a, b) => b.order - a.order);\n\tconst items = mine.slice(0, limit);\n\tconst last = items[items.length - 1];\n\treturn {\n\t\titems: items.map((r) => structuredClone(r.thread)),\n\t\t...(mine.length > limit && last ? { nextCursor: String(last.order) } : {}),\n\t};\n}\n\nexport function deleteThread(s: State, id: string, ownerId: string): boolean {\n\tif (!own(s, id, ownerId)) return false;\n\ts.threads.delete(id);\n\tdropActions(s, id);\n\treturn true;\n}\n",
9
+ "import * as actions from './actions';\nimport * as messages from './messages';\nimport type { State } from './state';\nimport * as threads from './threads';\nimport type { MemoryStore } from './types';\n\nexport interface InProcessMemoryOptions {\n\t/** The clock, in epoch ms; default `Date.now`. */\n\tnow?: () => number;\n}\n\n/** A store that keeps everything in this process; for tests and small tools. */\nexport function inProcessMemory(\n\toptions: InProcessMemoryOptions = {},\n): MemoryStore {\n\tconst s: State = {\n\t\tnow: options.now ?? Date.now,\n\t\tthreads: new Map(),\n\t\tactions: new Map(),\n\t\tcounter: 0,\n\t};\n\treturn {\n\t\tcreateThread: async (input) => threads.createThread(s, input),\n\t\tgetThread: async (id, ownerId) => threads.getThread(s, id, ownerId),\n\t\tlistThreads: async (ownerId, page = {}) =>\n\t\t\tthreads.listThreads(s, ownerId, page),\n\t\tappend: async (id, ownerId, list) => messages.append(s, id, ownerId, list),\n\t\tload: async (id, ownerId, opts) => messages.load(s, id, ownerId, opts),\n\t\tsetSummary: async (id, ownerId, input) =>\n\t\t\tmessages.setSummary(s, id, ownerId, input),\n\t\tfindAction: async (id, ownerId) => actions.findAction(s, id, ownerId),\n\t\tupdateAction: async (id, ownerId, patch) =>\n\t\t\tactions.updateAction(s, id, ownerId, patch),\n\t\tdeleteThread: async (id, ownerId) => threads.deleteThread(s, id, ownerId),\n\t};\n}\n"
10
+ ],
11
+ "mappings": "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",
12
+ "debugId": "6B46FC5865C2FC0E64756E2164756E21",
13
+ "names": []
14
+ }
@@ -0,0 +1,119 @@
1
+ // src/errors.ts
2
+ var MEMORY_ERROR_CODES2 = [
3
+ "THREAD_NOT_FOUND",
4
+ "MESSAGE_NOT_FOUND",
5
+ "INVALID_CURSOR",
6
+ "INVALID_OWNER",
7
+ "EMPTY_SUMMARY"
8
+ ];
9
+
10
+ class MemoryError2 extends Error {
11
+ name = "MemoryError";
12
+ code;
13
+ constructor(code, message) {
14
+ super(message);
15
+ this.code = code;
16
+ }
17
+ }
18
+
19
+ // src/guards.ts
20
+ function assertOwner(ownerId) {
21
+ if (typeof ownerId !== "string" || ownerId === "") {
22
+ throw new MemoryError2("INVALID_OWNER", "ownerId must be a non-empty string.");
23
+ }
24
+ }
25
+ var notFound = (id) => new MemoryError2("THREAD_NOT_FOUND", `Thread "${id}" was not found.`);
26
+
27
+ // src/schemas.ts
28
+ import { z } from "zod";
29
+ var textPart = z.object({ type: z.literal("text"), text: z.string() });
30
+ var thinkingPart = z.object({
31
+ type: z.literal("thinking"),
32
+ text: z.string(),
33
+ signature: z.string().optional(),
34
+ redacted: z.string().optional()
35
+ });
36
+ var toolCallPart = z.object({
37
+ type: z.literal("tool-call"),
38
+ id: z.string(),
39
+ name: z.string(),
40
+ input: z.unknown()
41
+ });
42
+ var toolResultBase = {
43
+ type: z.literal("tool-result"),
44
+ callId: z.string(),
45
+ name: z.string()
46
+ };
47
+ var toolResultPart = z.discriminatedUnion("isError", [
48
+ z.object({
49
+ ...toolResultBase,
50
+ isError: z.literal(false),
51
+ output: z.unknown()
52
+ }),
53
+ z.object({
54
+ ...toolResultBase,
55
+ isError: z.literal(true),
56
+ error: z.object({
57
+ code: z.string(),
58
+ message: z.string(),
59
+ issues: z.array(z.object({ path: z.string(), message: z.string() })).readonly().optional()
60
+ })
61
+ })
62
+ ]);
63
+ var proposedActionPart = z.object({
64
+ type: z.literal("proposed-action"),
65
+ id: z.string(),
66
+ tool: z.string(),
67
+ input: z.unknown(),
68
+ summary: z.string().optional(),
69
+ expiresAt: z.number()
70
+ });
71
+ var messageSchema2 = z.object({
72
+ role: z.enum(["user", "assistant", "tool"]),
73
+ parts: z.array(z.union([
74
+ textPart,
75
+ thinkingPart,
76
+ toolCallPart,
77
+ toolResultPart,
78
+ proposedActionPart
79
+ ]))
80
+ });
81
+ var summarySchema2 = z.object({
82
+ text: z.string(),
83
+ throughMessageId: z.string(),
84
+ tokens: z.number().int().nonnegative(),
85
+ createdAt: z.number()
86
+ });
87
+ var threadSchema2 = z.object({
88
+ id: z.string(),
89
+ ownerId: z.string(),
90
+ title: z.string().optional(),
91
+ meta: z.record(z.string(), z.unknown()).optional(),
92
+ messageCount: z.number().int().nonnegative(),
93
+ summary: summarySchema2.optional(),
94
+ createdAt: z.number(),
95
+ updatedAt: z.number()
96
+ });
97
+ var storedMessageSchema2 = z.object({
98
+ id: z.string(),
99
+ threadId: z.string(),
100
+ seq: z.number().int().positive(),
101
+ message: messageSchema2,
102
+ createdAt: z.number()
103
+ });
104
+
105
+ // src/paging.ts
106
+ var DEFAULT_LIMIT = 20;
107
+ var MAX_LIMIT = 100;
108
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
109
+ var pageLimit = (page) => Math.min(Math.max(page.limit ?? DEFAULT_LIMIT, 1), MAX_LIMIT);
110
+ var invalidCursor = (cursor) => new MemoryError2("INVALID_CURSOR", `"${cursor}" is not a cursor this store gave.`);
111
+ function assertUuidCursor(cursor) {
112
+ if (cursor !== undefined && !UUID.test(cursor))
113
+ throw invalidCursor(cursor);
114
+ }
115
+
116
+ export { MEMORY_ERROR_CODES2, MemoryError2, assertOwner, notFound, messageSchema2, summarySchema2, threadSchema2, storedMessageSchema2, pageLimit, invalidCursor, assertUuidCursor };
117
+
118
+ //# debugId=18E2AC5E9488040164756E2164756E21
119
+ //# sourceMappingURL=index-vj24rcgc.js.map
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/errors.ts", "../src/guards.ts", "../src/schemas.ts", "../src/paging.ts"],
4
+ "sourcesContent": [
5
+ "/** Every code a memory store raises; later slices may add codes. */\nexport const MEMORY_ERROR_CODES = [\n\t'THREAD_NOT_FOUND',\n\t'MESSAGE_NOT_FOUND',\n\t'INVALID_CURSOR',\n\t'INVALID_OWNER',\n\t'EMPTY_SUMMARY',\n] as const;\n\n/** Adding a code is not a breaking change, so switch with a default branch. */\nexport type MemoryErrorCode = (typeof MEMORY_ERROR_CODES)[number];\n\n/** The error a `MemoryStore` raises on a write it cannot do. */\nexport class MemoryError extends Error {\n\toverride name = 'MemoryError';\n\treadonly code: MemoryErrorCode;\n\n\tconstructor(code: MemoryErrorCode, message: string) {\n\t\tsuper(message);\n\t\tthis.code = code;\n\t}\n}\n",
6
+ "import { MemoryError } from './errors';\n\n/** Every store call names its owner; an empty one is a bug, not a miss. */\nexport function assertOwner(ownerId: string): void {\n\tif (typeof ownerId !== 'string' || ownerId === '') {\n\t\tthrow new MemoryError(\n\t\t\t'INVALID_OWNER',\n\t\t\t'ownerId must be a non-empty string.',\n\t\t);\n\t}\n}\n\nexport const notFound = (id: string): MemoryError =>\n\tnew MemoryError('THREAD_NOT_FOUND', `Thread \"${id}\" was not found.`);\n",
7
+ "import { z } from 'zod';\n\nconst textPart = z.object({ type: z.literal('text'), text: z.string() });\n\nconst thinkingPart = z.object({\n\ttype: z.literal('thinking'),\n\ttext: z.string(),\n\tsignature: z.string().optional(),\n\tredacted: z.string().optional(),\n});\n\nconst toolCallPart = z.object({\n\ttype: z.literal('tool-call'),\n\tid: z.string(),\n\tname: z.string(),\n\tinput: z.unknown(),\n});\n\nconst toolResultBase = {\n\ttype: z.literal('tool-result'),\n\tcallId: z.string(),\n\tname: z.string(),\n};\n\nconst toolResultPart = z.discriminatedUnion('isError', [\n\tz.object({\n\t\t...toolResultBase,\n\t\tisError: z.literal(false),\n\t\toutput: z.unknown(),\n\t}),\n\tz.object({\n\t\t...toolResultBase,\n\t\tisError: z.literal(true),\n\t\terror: z.object({\n\t\t\tcode: z.string(),\n\t\t\tmessage: z.string(),\n\t\t\tissues: z\n\t\t\t\t.array(z.object({ path: z.string(), message: z.string() }))\n\t\t\t\t.readonly()\n\t\t\t\t.optional(),\n\t\t}),\n\t}),\n]);\n\nconst proposedActionPart = z.object({\n\ttype: z.literal('proposed-action'),\n\tid: z.string(),\n\ttool: z.string(),\n\tinput: z.unknown(),\n\tsummary: z.string().optional(),\n\texpiresAt: z.number(),\n});\n\n/** An `@nxgt/ai-core` `Message`; `append` parses every message with it. */\nexport const messageSchema = z.object({\n\trole: z.enum(['user', 'assistant', 'tool']),\n\tparts: z.array(\n\t\tz.union([\n\t\t\ttextPart,\n\t\t\tthinkingPart,\n\t\t\ttoolCallPart,\n\t\t\ttoolResultPart,\n\t\t\tproposedActionPart,\n\t\t]),\n\t),\n});\n\nexport const summarySchema = z.object({\n\ttext: z.string(),\n\t/** The last message the summary covers. */\n\tthroughMessageId: z.string(),\n\t/** Estimated tokens of `text`. */\n\ttokens: z.number().int().nonnegative(),\n\tcreatedAt: z.number(),\n});\n\nexport const threadSchema = z.object({\n\tid: z.string(),\n\townerId: z.string(),\n\ttitle: z.string().optional(),\n\tmeta: z.record(z.string(), z.unknown()).optional(),\n\tmessageCount: z.number().int().nonnegative(),\n\tsummary: summarySchema.optional(),\n\tcreatedAt: z.number(),\n\tupdatedAt: z.number(),\n});\n\nexport const storedMessageSchema = z.object({\n\tid: z.string(),\n\tthreadId: z.string(),\n\t/** 1-based position in the thread; ids sort by it, not by their text. */\n\tseq: z.number().int().positive(),\n\tmessage: messageSchema,\n\tcreatedAt: z.number(),\n});\n",
8
+ "import { MemoryError } from './errors';\nimport type { Page } from './types';\n\nconst DEFAULT_LIMIT = 20;\nconst MAX_LIMIT = 100;\n/** The id of a thread in the Mongo and Redis stores: a UUID, also their cursor. */\nconst UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;\n\n/** The page size, clamped to 1..100 and 20 by default. */\nexport const pageLimit = (page: Page): number =>\n\tMath.min(Math.max(page.limit ?? DEFAULT_LIMIT, 1), MAX_LIMIT);\n\nexport const invalidCursor = (cursor: string): MemoryError =>\n\tnew MemoryError(\n\t\t'INVALID_CURSOR',\n\t\t`\"${cursor}\" is not a cursor this store gave.`,\n\t);\n\n/** For the stores whose cursor is a thread id: throws on any other string. */\nexport function assertUuidCursor(cursor: string | undefined): void {\n\tif (cursor !== undefined && !UUID.test(cursor)) throw invalidCursor(cursor);\n}\n"
9
+ ],
10
+ "mappings": "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",
11
+ "debugId": "18E2AC5E9488040164756E2164756E21",
12
+ "names": []
13
+ }
@@ -0,0 +1,38 @@
1
+ import { type Message } from '@nxgt/ai-core';
2
+ import type { StoredMessage } from './types.js';
3
+ export interface CompactOptions {
4
+ /** The most tokens the summary and the messages may take together. */
5
+ budget: number;
6
+ /** The newest messages a fold never touches; default 6. */
7
+ keepLast?: number;
8
+ /** Longest serialised tool output kept, in characters; default 2000. */
9
+ truncateToolResultsTo?: number;
10
+ /** Writes the summary of the messages to fold; the previous one comes first. */
11
+ summarize: (messages: Message[]) => Promise<string>;
12
+ /** The summary already stored for the thread, folded into the next one. */
13
+ summary?: {
14
+ text: string;
15
+ };
16
+ /** Default: `estimateTokens` of `@nxgt/ai-core`. */
17
+ estimate?: (input: string | readonly Message[]) => number;
18
+ }
19
+ export interface CompactResult {
20
+ /** The new summary; absent when nothing was folded by this call. */
21
+ summary?: string;
22
+ /** The last message the new summary covers; store it with `setSummary`. */
23
+ throughMessageId?: string;
24
+ /** What to send to the model after the summary. */
25
+ messages: StoredMessage[];
26
+ /** Estimated tokens of the kept summary and `messages`. */
27
+ tokens: number;
28
+ }
29
+ /**
30
+ * Brings a thread under `budget`: first it cuts the tool results, oldest
31
+ * first; if that is not enough it folds the oldest messages into a rolling
32
+ * summary. A tool call and its result stay together, and the kept messages
33
+ * start on a user turn when the thread has one before the cut. Throws
34
+ * `MemoryError` `EMPTY_SUMMARY` when `summarize` returns blank text. `messages` are the
35
+ * thread's messages after its stored summary (`load` with `sinceSummary`).
36
+ */
37
+ export declare function compact(messages: readonly StoredMessage[], options: CompactOptions): Promise<CompactResult>;
38
+ //# sourceMappingURL=compact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compact.d.ts","sourceRoot":"","sources":["../src/compact.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AAI7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,MAAM,WAAW,cAAc;IAC9B,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,gFAAgF;IAChF,SAAS,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACpD,2EAA2E;IAC3E,OAAO,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3B,oDAAoD;IACpD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,OAAO,EAAE,KAAK,MAAM,CAAC;CAC1D;AAED,MAAM,WAAW,aAAa;IAC7B,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mDAAmD;IACnD,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;GAOG;AACH,wBAAsB,OAAO,CAC5B,QAAQ,EAAE,SAAS,aAAa,EAAE,EAClC,OAAO,EAAE,cAAc,GACrB,OAAO,CAAC,aAAa,CAAC,CAuCxB"}
@@ -0,0 +1,3 @@
1
+ import type { MemoryStore } from './types.js';
2
+ export declare function contractActions(make: () => MemoryStore | Promise<MemoryStore>): void;
3
+ //# sourceMappingURL=contract-actions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-actions.d.ts","sourceRoot":"","sources":["../src/contract-actions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,wBAAgB,eAAe,CAC9B,IAAI,EAAE,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,GAC5C,IAAI,CAgEN"}
@@ -0,0 +1,3 @@
1
+ import type { MemoryStore } from './types.js';
2
+ export declare function contractMessages(make: () => MemoryStore | Promise<MemoryStore>): void;
3
+ //# sourceMappingURL=contract-messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-messages.d.ts","sourceRoot":"","sources":["../src/contract-messages.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,wBAAgB,gBAAgB,CAC/B,IAAI,EAAE,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,GAC5C,IAAI,CAuDN"}
@@ -0,0 +1,3 @@
1
+ import type { MemoryStore } from './types.js';
2
+ export declare function contractOwner(make: () => MemoryStore | Promise<MemoryStore>): void;
3
+ //# sourceMappingURL=contract-owner.d.ts.map