@zhivex-ai/core 1.17.0 → 1.19.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.
Files changed (272) hide show
  1. package/README.md +4 -0
  2. package/dist/agent/approvals.d.ts +7 -0
  3. package/dist/agent/approvals.d.ts.map +1 -0
  4. package/dist/agent/approvals.js +97 -0
  5. package/dist/agent/approvals.js.map +1 -0
  6. package/dist/agent/cancellation.d.ts +4 -0
  7. package/dist/agent/cancellation.d.ts.map +1 -0
  8. package/dist/agent/cancellation.js +69 -0
  9. package/dist/agent/cancellation.js.map +1 -0
  10. package/dist/agent/common.d.ts +20 -0
  11. package/dist/agent/common.d.ts.map +1 -0
  12. package/dist/agent/common.js +150 -0
  13. package/dist/agent/common.js.map +1 -0
  14. package/dist/agent/compaction.d.ts +6 -0
  15. package/dist/agent/compaction.d.ts.map +1 -0
  16. package/dist/agent/compaction.js +156 -0
  17. package/dist/agent/compaction.js.map +1 -0
  18. package/dist/agent/context.d.ts +58 -0
  19. package/dist/agent/context.d.ts.map +1 -0
  20. package/dist/agent/context.js +267 -0
  21. package/dist/agent/context.js.map +1 -0
  22. package/dist/agent/definition.d.ts +4 -0
  23. package/dist/agent/definition.d.ts.map +1 -0
  24. package/dist/agent/definition.js +35 -0
  25. package/dist/agent/definition.js.map +1 -0
  26. package/dist/agent/execution.d.ts +14 -0
  27. package/dist/agent/execution.d.ts.map +1 -0
  28. package/dist/agent/execution.js +1062 -0
  29. package/dist/agent/execution.js.map +1 -0
  30. package/dist/agent/groups.d.ts +3 -0
  31. package/dist/agent/groups.d.ts.map +1 -0
  32. package/dist/agent/groups.js +130 -0
  33. package/dist/agent/groups.js.map +1 -0
  34. package/dist/agent/guardrails.d.ts +4 -0
  35. package/dist/agent/guardrails.d.ts.map +1 -0
  36. package/dist/agent/guardrails.js +29 -0
  37. package/dist/agent/guardrails.js.map +1 -0
  38. package/dist/agent/instance.d.ts +42 -0
  39. package/dist/agent/instance.d.ts.map +1 -0
  40. package/dist/agent/instance.js +82 -0
  41. package/dist/agent/instance.js.map +1 -0
  42. package/dist/agent/lifecycle.d.ts +31 -0
  43. package/dist/agent/lifecycle.d.ts.map +1 -0
  44. package/dist/agent/lifecycle.js +184 -0
  45. package/dist/agent/lifecycle.js.map +1 -0
  46. package/dist/agent/state.d.ts +6 -0
  47. package/dist/agent/state.d.ts.map +1 -0
  48. package/dist/agent/state.js +119 -0
  49. package/dist/agent/state.js.map +1 -0
  50. package/dist/agent/telemetry.d.ts +12 -0
  51. package/dist/agent/telemetry.d.ts.map +1 -0
  52. package/dist/agent/telemetry.js +176 -0
  53. package/dist/agent/telemetry.js.map +1 -0
  54. package/dist/agent/tool-execution.d.ts +6 -0
  55. package/dist/agent/tool-execution.d.ts.map +1 -0
  56. package/dist/agent/tool-execution.js +176 -0
  57. package/dist/agent/tool-execution.js.map +1 -0
  58. package/dist/agent-evaluation.d.ts.map +1 -1
  59. package/dist/agent-evaluation.js +2 -0
  60. package/dist/agent-evaluation.js.map +1 -1
  61. package/dist/agent-reconciliation.d.ts +12 -0
  62. package/dist/agent-reconciliation.d.ts.map +1 -0
  63. package/dist/agent-reconciliation.js +87 -0
  64. package/dist/agent-reconciliation.js.map +1 -0
  65. package/dist/agent-run-view.d.ts +9 -0
  66. package/dist/agent-run-view.d.ts.map +1 -0
  67. package/dist/agent-run-view.js +42 -0
  68. package/dist/agent-run-view.js.map +1 -0
  69. package/dist/agent-state.d.ts.map +1 -1
  70. package/dist/agent-state.js +34 -0
  71. package/dist/agent-state.js.map +1 -1
  72. package/dist/agent-store/file.d.ts +11 -0
  73. package/dist/agent-store/file.d.ts.map +1 -0
  74. package/dist/agent-store/file.js +534 -0
  75. package/dist/agent-store/file.js.map +1 -0
  76. package/dist/agent-store/memory.d.ts +9 -0
  77. package/dist/agent-store/memory.d.ts.map +1 -0
  78. package/dist/agent-store/memory.js +198 -0
  79. package/dist/agent-store/memory.js.map +1 -0
  80. package/dist/agent-store/postgres.d.ts +4 -0
  81. package/dist/agent-store/postgres.d.ts.map +1 -0
  82. package/dist/agent-store/postgres.js +374 -0
  83. package/dist/agent-store/postgres.js.map +1 -0
  84. package/dist/agent-store/shared.d.ts +19 -0
  85. package/dist/agent-store/shared.d.ts.map +1 -0
  86. package/dist/agent-store/shared.js +137 -0
  87. package/dist/agent-store/shared.js.map +1 -0
  88. package/dist/agent-store/sqlite.d.ts +4 -0
  89. package/dist/agent-store/sqlite.d.ts.map +1 -0
  90. package/dist/agent-store/sqlite.js +362 -0
  91. package/dist/agent-store/sqlite.js.map +1 -0
  92. package/dist/agent-store.d.ts +5 -21
  93. package/dist/agent-store.d.ts.map +1 -1
  94. package/dist/agent-store.js +5 -1540
  95. package/dist/agent-store.js.map +1 -1
  96. package/dist/agent.d.ts +6 -57
  97. package/dist/agent.d.ts.map +1 -1
  98. package/dist/agent.js +6 -2622
  99. package/dist/agent.js.map +1 -1
  100. package/dist/agents-entry.d.ts +19 -0
  101. package/dist/agents-entry.d.ts.map +1 -0
  102. package/dist/agents-entry.js +14 -0
  103. package/dist/agents-entry.js.map +1 -0
  104. package/dist/api-stability.d.ts.map +1 -1
  105. package/dist/api-stability.js +1 -0
  106. package/dist/api-stability.js.map +1 -1
  107. package/dist/artifact/file.d.ts +6 -0
  108. package/dist/artifact/file.d.ts.map +1 -0
  109. package/dist/artifact/file.js +380 -0
  110. package/dist/artifact/file.js.map +1 -0
  111. package/dist/artifact/memory.d.ts +3 -0
  112. package/dist/artifact/memory.d.ts.map +1 -0
  113. package/dist/artifact/memory.js +80 -0
  114. package/dist/artifact/memory.js.map +1 -0
  115. package/dist/artifact/postgres.d.ts +3 -0
  116. package/dist/artifact/postgres.d.ts.map +1 -0
  117. package/dist/artifact/postgres.js +283 -0
  118. package/dist/artifact/postgres.js.map +1 -0
  119. package/dist/artifact/shared.d.ts +208 -0
  120. package/dist/artifact/shared.d.ts.map +1 -0
  121. package/dist/artifact/shared.js +523 -0
  122. package/dist/artifact/shared.js.map +1 -0
  123. package/dist/artifact/sqlite.d.ts +3 -0
  124. package/dist/artifact/sqlite.d.ts.map +1 -0
  125. package/dist/artifact/sqlite.js +283 -0
  126. package/dist/artifact/sqlite.js.map +1 -0
  127. package/dist/artifact.d.ts +6 -186
  128. package/dist/artifact.d.ts.map +1 -1
  129. package/dist/artifact.js +6 -1536
  130. package/dist/artifact.js.map +1 -1
  131. package/dist/catalog-contracts.d.ts +52 -0
  132. package/dist/catalog-contracts.d.ts.map +1 -0
  133. package/dist/catalog-contracts.js +250 -0
  134. package/dist/catalog-contracts.js.map +1 -0
  135. package/dist/catalog.d.ts +3 -52
  136. package/dist/catalog.d.ts.map +1 -1
  137. package/dist/catalog.js +3 -249
  138. package/dist/catalog.js.map +1 -1
  139. package/dist/generate-cache-file.d.ts +11 -0
  140. package/dist/generate-cache-file.d.ts.map +1 -0
  141. package/dist/generate-cache-file.js +124 -0
  142. package/dist/generate-cache-file.js.map +1 -0
  143. package/dist/generate-text.d.ts +2 -5
  144. package/dist/generate-text.d.ts.map +1 -1
  145. package/dist/generate-text.js +3 -18
  146. package/dist/generate-text.js.map +1 -1
  147. package/dist/generation-entry.d.ts +6 -0
  148. package/dist/generation-entry.d.ts.map +1 -0
  149. package/dist/generation-entry.js +6 -0
  150. package/dist/generation-entry.js.map +1 -0
  151. package/dist/index.d.ts +3 -0
  152. package/dist/index.d.ts.map +1 -1
  153. package/dist/index.js +1 -0
  154. package/dist/index.js.map +1 -1
  155. package/dist/middleware-runtime.d.ts +35 -0
  156. package/dist/middleware-runtime.d.ts.map +1 -0
  157. package/dist/middleware-runtime.js +532 -0
  158. package/dist/middleware-runtime.js.map +1 -0
  159. package/dist/middleware.d.ts +3 -43
  160. package/dist/middleware.d.ts.map +1 -1
  161. package/dist/middleware.js +3 -653
  162. package/dist/middleware.js.map +1 -1
  163. package/dist/normalize-messages.d.ts +3 -0
  164. package/dist/normalize-messages.d.ts.map +1 -0
  165. package/dist/normalize-messages.js +16 -0
  166. package/dist/normalize-messages.js.map +1 -0
  167. package/dist/provider-entry.d.ts +17 -0
  168. package/dist/provider-entry.d.ts.map +1 -0
  169. package/dist/provider-entry.js +13 -0
  170. package/dist/provider-entry.js.map +1 -0
  171. package/dist/realtime.d.ts +2 -0
  172. package/dist/realtime.d.ts.map +1 -1
  173. package/dist/realtime.js +6 -0
  174. package/dist/realtime.js.map +1 -1
  175. package/dist/store-key.d.ts +3 -0
  176. package/dist/store-key.d.ts.map +1 -0
  177. package/dist/store-key.js +5 -0
  178. package/dist/store-key.js.map +1 -0
  179. package/dist/store-security.d.ts +1 -2
  180. package/dist/store-security.d.ts.map +1 -1
  181. package/dist/store-security.js +2 -4
  182. package/dist/store-security.js.map +1 -1
  183. package/dist/types/agent-state.d.ts +236 -0
  184. package/dist/types/agent-state.d.ts.map +1 -0
  185. package/dist/types/agent-state.js +2 -0
  186. package/dist/types/agent-state.js.map +1 -0
  187. package/dist/types/agents.d.ts +429 -0
  188. package/dist/types/agents.d.ts.map +1 -0
  189. package/dist/types/agents.js +2 -0
  190. package/dist/types/agents.js.map +1 -0
  191. package/dist/types/common.d.ts +179 -0
  192. package/dist/types/common.d.ts.map +1 -0
  193. package/dist/types/common.js +2 -0
  194. package/dist/types/common.js.map +1 -0
  195. package/dist/types/generation.d.ts +109 -0
  196. package/dist/types/generation.d.ts.map +1 -0
  197. package/dist/types/generation.js +2 -0
  198. package/dist/types/generation.js.map +1 -0
  199. package/dist/types/media-data.d.ts +65 -0
  200. package/dist/types/media-data.d.ts.map +1 -0
  201. package/dist/types/media-data.js +2 -0
  202. package/dist/types/media-data.js.map +1 -0
  203. package/dist/types/media.d.ts +141 -0
  204. package/dist/types/media.d.ts.map +1 -0
  205. package/dist/types/media.js +2 -0
  206. package/dist/types/media.js.map +1 -0
  207. package/dist/types/messages.d.ts +82 -0
  208. package/dist/types/messages.d.ts.map +1 -0
  209. package/dist/types/messages.js +2 -0
  210. package/dist/types/messages.js.map +1 -0
  211. package/dist/types/middleware.d.ts +130 -0
  212. package/dist/types/middleware.d.ts.map +1 -0
  213. package/dist/types/middleware.js +2 -0
  214. package/dist/types/middleware.js.map +1 -0
  215. package/dist/types/model-tools.d.ts +409 -0
  216. package/dist/types/model-tools.d.ts.map +1 -0
  217. package/dist/types/model-tools.js +2 -0
  218. package/dist/types/model-tools.js.map +1 -0
  219. package/dist/types/persistence.d.ts +168 -0
  220. package/dist/types/persistence.d.ts.map +1 -0
  221. package/dist/types/persistence.js +2 -0
  222. package/dist/types/persistence.js.map +1 -0
  223. package/dist/types/provider-resources.d.ts +336 -0
  224. package/dist/types/provider-resources.d.ts.map +1 -0
  225. package/dist/types/provider-resources.js +2 -0
  226. package/dist/types/provider-resources.js.map +1 -0
  227. package/dist/types/provider.d.ts +23 -0
  228. package/dist/types/provider.d.ts.map +1 -0
  229. package/dist/types/provider.js +2 -0
  230. package/dist/types/provider.js.map +1 -0
  231. package/dist/types/resource-options.d.ts +88 -0
  232. package/dist/types/resource-options.d.ts.map +1 -0
  233. package/dist/types/resource-options.js +2 -0
  234. package/dist/types/resource-options.js.map +1 -0
  235. package/dist/types/stream.d.ts +70 -0
  236. package/dist/types/stream.d.ts.map +1 -0
  237. package/dist/types/stream.js +2 -0
  238. package/dist/types/stream.js.map +1 -0
  239. package/dist/types/ui.d.ts +110 -0
  240. package/dist/types/ui.d.ts.map +1 -0
  241. package/dist/types/ui.js +2 -0
  242. package/dist/types/ui.js.map +1 -0
  243. package/dist/types.d.ts +16 -2442
  244. package/dist/types.d.ts.map +1 -1
  245. package/dist/ui.d.ts.map +1 -1
  246. package/dist/ui.js +2 -0
  247. package/dist/ui.js.map +1 -1
  248. package/dist/workflow-state-service/file.d.ts +5 -0
  249. package/dist/workflow-state-service/file.d.ts.map +1 -0
  250. package/dist/workflow-state-service/file.js +142 -0
  251. package/dist/workflow-state-service/file.js.map +1 -0
  252. package/dist/workflow-state-service/memory.d.ts +3 -0
  253. package/dist/workflow-state-service/memory.d.ts.map +1 -0
  254. package/dist/workflow-state-service/memory.js +27 -0
  255. package/dist/workflow-state-service/memory.js.map +1 -0
  256. package/dist/workflow-state-service/postgres.d.ts +4 -0
  257. package/dist/workflow-state-service/postgres.d.ts.map +1 -0
  258. package/dist/workflow-state-service/postgres.js +179 -0
  259. package/dist/workflow-state-service/postgres.js.map +1 -0
  260. package/dist/workflow-state-service/shared.d.ts +43 -0
  261. package/dist/workflow-state-service/shared.d.ts.map +1 -0
  262. package/dist/workflow-state-service/shared.js +107 -0
  263. package/dist/workflow-state-service/shared.js.map +1 -0
  264. package/dist/workflow-state-service/sqlite.d.ts +4 -0
  265. package/dist/workflow-state-service/sqlite.d.ts.map +1 -0
  266. package/dist/workflow-state-service/sqlite.js +183 -0
  267. package/dist/workflow-state-service/sqlite.js.map +1 -0
  268. package/dist/workflow-state-service.d.ts +6 -33
  269. package/dist/workflow-state-service.d.ts.map +1 -1
  270. package/dist/workflow-state-service.js +6 -626
  271. package/dist/workflow-state-service.js.map +1 -1
  272. package/package.json +17 -1
@@ -1,1541 +1,6 @@
1
- import { constants, promises as fs } from "node:fs";
2
- import { createHash, randomUUID } from "node:crypto";
3
- import path from "node:path";
4
- import { normalizeAgentRunState } from "./agent-state.js";
5
- import { ConflictError, ValidationError } from "./errors.js";
6
- import { assertPostgresClient } from "./postgres-client.js";
7
- import { ensurePrivateDirectory, writePrivateFile } from "./store-security.js";
8
- const cloneState = (state) => JSON.parse(JSON.stringify(normalizeAgentRunState(state)));
9
- const cloneMessages = (messages) => JSON.parse(JSON.stringify(messages));
10
- const cloneJournalEntry = (entry) => JSON.parse(JSON.stringify(entry));
11
- const scopePrefix = (scope) => scope
12
- ? `${encodeURIComponent(scope.namespace ?? "default")}:${encodeURIComponent(scope.tenantId)}:${encodeURIComponent(scope.userId ?? "*")}:`
13
- : "";
14
- const scopedKey = (scope, value) => `${scopePrefix(scope)}${value}`;
15
- const defaultMemoryKey = (context) => context.scope
16
- ? scopedKey(context.scope, context.agentId ?? context.runId)
17
- : context.runId;
18
- const sameScope = (left, right) => left.tenantId === right.tenantId && left.userId === right.userId && left.namespace === right.namespace;
19
- const validateScope = (value) => {
20
- if (!value)
21
- return undefined;
22
- if (typeof value.tenantId !== "string" || value.tenantId.length === 0) {
23
- throw new ValidationError('Agent store scope "tenantId" must be a non-empty string.');
24
- }
25
- for (const field of ["userId", "namespace"]) {
26
- if (value[field] !== undefined && (typeof value[field] !== "string" || value[field].length === 0)) {
27
- throw new ValidationError(`Agent store scope "${field}" must be a non-empty string when provided.`);
28
- }
29
- }
30
- return value;
31
- };
32
- const resolveScope = (configured, operation) => {
33
- configured = validateScope(configured);
34
- operation = validateScope(operation);
35
- if (configured && operation && !sameScope(configured, operation)) {
36
- throw new ValidationError("The operation scope does not match the store scope.");
37
- }
38
- return operation ?? configured;
39
- };
40
- const fileNameForAgentStoreKey = (key) => `${encodeURIComponent(key)}.json`;
41
- const fileNameForIdempotencyKey = (key) => `.idempotency-${createHash("sha256").update(key).digest("hex")}.json`;
42
- const identifierPattern = /^[A-Za-z_][A-Za-z0-9_]*$/;
43
- const FILE_RUN_LOCK_TIMEOUT_MS = 2_000;
44
- const FILE_RUN_LOCK_RETRY_MS = 10;
45
- const FILE_RUN_LOCK_STALE_MS = 30_000;
46
- const waitForFileRunLock = () => new Promise((resolve) => {
47
- setTimeout(resolve, FILE_RUN_LOCK_RETRY_MS);
48
- });
49
- const parseFileRunStoreLock = (value) => {
50
- try {
51
- const lock = JSON.parse(value);
52
- return typeof lock.ownerId === "string" &&
53
- Number.isSafeInteger(lock.pid) &&
54
- lock.pid > 0 &&
55
- Number.isFinite(lock.createdAt)
56
- ? lock
57
- : undefined;
58
- }
59
- catch {
60
- return undefined;
61
- }
62
- };
63
- const isProcessAlive = (pid) => {
64
- try {
65
- process.kill(pid, 0);
66
- return true;
67
- }
68
- catch (error) {
69
- return error.code !== "ESRCH";
70
- }
71
- };
72
- const readFileRunStoreLock = async (lockPath) => {
73
- let handle;
74
- try {
75
- handle = await fs.open(lockPath, constants.O_RDONLY | constants.O_NOFOLLOW);
76
- const stats = await handle.stat();
77
- return {
78
- lock: parseFileRunStoreLock(await handle.readFile("utf8")),
79
- modifiedAt: stats.mtimeMs
80
- };
81
- }
82
- catch (error) {
83
- if (error.code === "ENOENT") {
84
- return undefined;
85
- }
86
- if (error.code === "ELOOP") {
87
- throw new ValidationError("Refusing to follow a file AgentRunStore lock symlink.");
88
- }
89
- throw error;
90
- }
91
- finally {
92
- await handle?.close().catch(() => undefined);
93
- }
94
- };
95
- const removeFileRunStoreLock = async (lockPath, ownerId) => {
96
- const current = await readFileRunStoreLock(lockPath);
97
- if (current?.lock?.ownerId !== ownerId) {
98
- return false;
99
- }
100
- try {
101
- await fs.unlink(lockPath);
102
- return true;
103
- }
104
- catch (error) {
105
- if (error.code === "ENOENT") {
106
- return false;
107
- }
108
- throw error;
109
- }
110
- };
111
- const recoverFileRunStoreLock = async (lockPath) => {
112
- const current = await readFileRunStoreLock(lockPath);
113
- if (!current) {
114
- return true;
115
- }
116
- const malformedAndStale = !current.lock && Date.now() - current.modifiedAt >= FILE_RUN_LOCK_STALE_MS;
117
- const ownerExited = current.lock ? !isProcessAlive(current.lock.pid) : false;
118
- // Never steal an old lock from a live owner: a large local store can make a
119
- // legitimate CAS operation exceed the stale threshold. The age fallback is
120
- // reserved for malformed locks that have no verifiable owner.
121
- if (!malformedAndStale && !ownerExited) {
122
- return false;
123
- }
124
- const recoveryId = createHash("sha256")
125
- .update(current.lock?.ownerId ?? "malformed")
126
- .digest("hex");
127
- const recoveryPath = `${lockPath}.${recoveryId}.recovery`;
128
- try {
129
- await fs.link(lockPath, recoveryPath);
130
- }
131
- catch (error) {
132
- if (error.code === "ENOENT") {
133
- return true;
134
- }
135
- if (error.code === "EEXIST") {
136
- return false;
137
- }
138
- throw error;
139
- }
140
- try {
141
- const [lockStats, recoveryStats] = await Promise.all([
142
- fs.lstat(lockPath),
143
- fs.lstat(recoveryPath)
144
- ]);
145
- if (lockStats.dev !== recoveryStats.dev || lockStats.ino !== recoveryStats.ino) {
146
- return false;
147
- }
148
- const verified = await readFileRunStoreLock(lockPath);
149
- if (current.lock?.ownerId !== verified?.lock?.ownerId ||
150
- (!current.lock && verified?.lock)) {
151
- return false;
152
- }
153
- await fs.unlink(lockPath);
154
- return true;
155
- }
156
- catch (error) {
157
- if (error.code === "ENOENT") {
158
- return true;
159
- }
160
- throw error;
161
- }
162
- finally {
163
- await fs.unlink(recoveryPath).catch((error) => {
164
- if (error.code !== "ENOENT")
165
- throw error;
166
- });
167
- }
168
- };
169
- const acquireFileRunStoreLock = async (lockPath) => {
170
- const ownerId = randomUUID();
171
- const startedAt = Date.now();
172
- const lock = { ownerId, pid: process.pid, createdAt: startedAt };
173
- while (true) {
174
- try {
175
- await writePrivateFile(lockPath, JSON.stringify(lock), { flag: "wx" });
176
- return async () => {
177
- await removeFileRunStoreLock(lockPath, ownerId);
178
- };
179
- }
180
- catch (error) {
181
- if (error.code !== "EEXIST") {
182
- throw error;
183
- }
184
- }
185
- if (await recoverFileRunStoreLock(lockPath)) {
186
- continue;
187
- }
188
- if (Date.now() - startedAt >= FILE_RUN_LOCK_TIMEOUT_MS) {
189
- throw new ConflictError("Timed out acquiring the file AgentRunStore revision lock.");
190
- }
191
- await waitForFileRunLock();
192
- }
193
- };
194
- const normalizeLimit = (value, fallback = 50) => {
195
- if (value === undefined)
196
- return fallback;
197
- if (!Number.isSafeInteger(value) || value < 1 || value > 1_000) {
198
- throw new ValidationError('The "limit" option must be an integer between 1 and 1000.');
199
- }
200
- return value;
201
- };
202
- const validateLeaseOptions = (options) => {
203
- if (!options.ownerId.trim())
204
- throw new ValidationError('The lease "ownerId" must not be empty.');
205
- if (!Number.isSafeInteger(options.ttlMs) || options.ttlMs < 1 || options.ttlMs > 86_400_000) {
206
- throw new ValidationError('The lease "ttlMs" must be an integer between 1 and 86400000.');
207
- }
208
- if (options.now !== undefined && (!Number.isSafeInteger(options.now) || options.now < 0)) {
209
- throw new ValidationError('The lease "now" value must be a non-negative integer.');
210
- }
211
- };
212
- const encodeCursor = (state) => Buffer.from(JSON.stringify([state.updatedAt ?? state.startedAt ?? 0, state.runId]), "utf8").toString("base64url");
213
- const decodeCursor = (cursor) => {
214
- if (!cursor)
215
- return undefined;
216
- try {
217
- const value = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
218
- if (!Array.isArray(value) || value.length !== 2 || typeof value[0] !== "number" || typeof value[1] !== "string") {
219
- throw new Error("invalid");
220
- }
221
- return [value[0], value[1]];
222
- }
223
- catch {
224
- throw new ValidationError('The "cursor" option is invalid.');
225
- }
226
- };
227
- const listStates = (states, options = {}) => {
228
- const limit = normalizeLimit(options.limit);
229
- const cursor = decodeCursor(options.cursor);
230
- const filtered = [...states]
231
- .filter((state) => options.agentId === undefined || state.agentId === options.agentId)
232
- .filter((state) => options.parentRunId === undefined || state.parentRunId === options.parentRunId)
233
- .filter((state) => !options.statuses?.length || options.statuses.includes(state.status))
234
- .filter((state) => options.updatedAfter === undefined || (state.updatedAt ?? 0) > options.updatedAfter)
235
- .filter((state) => options.updatedBefore === undefined || (state.updatedAt ?? 0) < options.updatedBefore)
236
- .sort((left, right) => (right.updatedAt ?? right.startedAt ?? 0) - (left.updatedAt ?? left.startedAt ?? 0) || right.runId.localeCompare(left.runId))
237
- .filter((state) => !cursor || (state.updatedAt ?? state.startedAt ?? 0) < cursor[0] || ((state.updatedAt ?? state.startedAt ?? 0) === cursor[0] && state.runId < cursor[1]));
238
- const page = filtered.slice(0, limit);
239
- return {
240
- items: page.map(cloneState),
241
- ...(filtered.length > limit && page.at(-1) ? { nextCursor: encodeCursor(page.at(-1)) } : {})
242
- };
243
- };
244
- const assertJournalRevision = (current, expectedRevision) => {
245
- if (expectedRevision !== undefined && (!current || current.revision !== expectedRevision)) {
246
- throw new ConflictError("Agent tool-call journal revision conflict.");
247
- }
248
- };
249
- const nextJournalEntry = (entry, options) => {
250
- if (!entry.runId || !entry.toolCallId || !entry.toolName || !entry.idempotencyKey) {
251
- throw new ValidationError("Tool-call journal entries require runId, toolCallId, toolName, and idempotencyKey.");
252
- }
253
- return cloneJournalEntry({
254
- ...entry,
255
- revision: options?.expectedRevision === undefined ? entry.revision : options.expectedRevision + 1
256
- });
257
- };
258
- const defaultMemoryMessages = (state) => {
259
- const lastAssistantMessage = [...state.messages].reverse().find((message) => message.role === "assistant");
260
- return lastAssistantMessage ? [lastAssistantMessage] : [];
261
- };
262
- const validateIdentifier = (value, fieldName) => {
263
- if (!identifierPattern.test(value)) {
264
- throw new ValidationError(`The "${fieldName}" option must match the SQL identifier pattern [A-Za-z_][A-Za-z0-9_]*.`);
265
- }
266
- return value;
267
- };
268
- const getRecordField = (value, candidates) => {
269
- if (!value || typeof value !== "object") {
270
- return undefined;
271
- }
272
- const record = value;
273
- for (const candidate of candidates) {
274
- if (candidate in record) {
275
- return record[candidate];
276
- }
277
- }
278
- return undefined;
279
- };
280
- const prepareSqliteStatement = (db, sql) => {
281
- if (typeof db.prepare === "function") {
282
- return db.prepare(sql);
283
- }
284
- if (typeof db.query === "function") {
285
- return db.query(sql);
286
- }
287
- throw new ValidationError('The "db" option must expose either a "prepare()" or "query()" method.');
288
- };
289
- const initializeSqliteTable = (db, sql) => {
290
- db.exec(sql);
291
- };
292
- const assertExpectedRevision = (current, expectedRevision) => {
293
- if (expectedRevision !== undefined && (current?.revision ?? 0) !== expectedRevision) {
294
- throw new ConflictError("AgentRunState revision conflict.");
295
- }
296
- };
297
- const nextStoredState = (state, options) => {
298
- const normalized = normalizeAgentRunState(state);
299
- return options?.expectedRevision === undefined
300
- ? normalized
301
- : { ...normalized, revision: options.expectedRevision + 1 };
302
- };
303
- const isConcurrentPostgresTableCreationConflict = (error) => {
304
- if (!error || typeof error !== "object")
305
- return false;
306
- const record = error;
307
- return record.code === "23505" &&
308
- (record.constraint === "pg_type_typname_nsp_index" ||
309
- record.constraint_name === "pg_type_typname_nsp_index");
310
- };
311
- const ensurePostgresTable = (() => {
312
- const initializedTables = new WeakMap();
313
- return async (client, tableName, createSql) => {
314
- let tables = initializedTables.get(client);
315
- if (!tables) {
316
- tables = new Map();
317
- initializedTables.set(client, tables);
318
- }
319
- let initialization = tables.get(tableName);
320
- if (!initialization) {
321
- initialization = (async () => {
322
- for (let attempt = 0;; attempt += 1) {
323
- try {
324
- await client.query(createSql, []);
325
- return;
326
- }
327
- catch (error) {
328
- if (!isConcurrentPostgresTableCreationConflict(error) || attempt >= 2) {
329
- throw error;
330
- }
331
- await new Promise((resolve) => setTimeout(resolve, 10 * (attempt + 1)));
332
- }
333
- }
334
- })();
335
- tables.set(tableName, initialization);
336
- }
337
- try {
338
- await initialization;
339
- }
340
- catch (error) {
341
- if (tables.get(tableName) === initialization) {
342
- tables.delete(tableName);
343
- }
344
- throw error;
345
- }
346
- };
347
- })();
348
- export const createInMemoryAgentRunStore = (options = {}) => {
349
- const states = new Map();
350
- const idempotencyKeys = new Map();
351
- const parentRunIds = new Map();
352
- const leases = new Map();
353
- const journal = new Map();
354
- const runKey = (runId, scope) => scopedKey(resolveScope(options.scope, scope), runId);
355
- const idempotencyKey = (key, scope) => scopedKey(resolveScope(options.scope, scope), key);
356
- const journalKey = (runId, toolCallId, scope) => `${runKey(runId, scope)}:${toolCallId}`;
357
- const removeParentIndex = (state) => {
358
- if (!state?.parentRunId) {
359
- return;
360
- }
361
- const parentKey = scopedKey(resolveScope(options.scope, state.scope), state.parentRunId);
362
- const children = parentRunIds.get(parentKey);
363
- children?.delete(runKey(state.runId, state.scope));
364
- if (children?.size === 0) {
365
- parentRunIds.delete(parentKey);
366
- }
367
- };
368
- return {
369
- load(runId, scope) {
370
- const state = states.get(runKey(runId, scope));
371
- return state ? cloneState(normalizeAgentRunState(state)) : undefined;
372
- },
373
- findByIdempotencyKey(idempotencyKeyValue, scope) {
374
- const runId = idempotencyKeys.get(idempotencyKey(idempotencyKeyValue, scope));
375
- if (!runId) {
376
- return undefined;
377
- }
378
- const state = states.get(runId);
379
- return state ? cloneState(normalizeAgentRunState(state)) : undefined;
380
- },
381
- findByParentRunId(parentRunId, scope) {
382
- return [...(parentRunIds.get(scopedKey(resolveScope(options.scope, scope), parentRunId)) ?? [])].flatMap((runId) => {
383
- const state = states.get(runId);
384
- return state ? [cloneState(normalizeAgentRunState(state))] : [];
385
- });
386
- },
387
- claimIdempotencyKey(state) {
388
- const scope = resolveScope(options.scope, state.scope);
389
- const existingRunId = idempotencyKeys.get(idempotencyKey(state.idempotencyKey, scope));
390
- const existing = existingRunId ? states.get(existingRunId) : undefined;
391
- if (existing) {
392
- return { claimed: false, state: cloneState(existing) };
393
- }
394
- const normalized = normalizeAgentRunState(state);
395
- removeParentIndex(states.get(runKey(normalized.runId, scope)));
396
- const stored = cloneState({ ...normalized, ...(scope ? { scope } : {}) });
397
- states.set(runKey(normalized.runId, scope), stored);
398
- idempotencyKeys.set(idempotencyKey(state.idempotencyKey, scope), runKey(normalized.runId, scope));
399
- if (normalized.parentRunId) {
400
- const parentKey = scopedKey(scope, normalized.parentRunId);
401
- const children = parentRunIds.get(parentKey) ?? new Set();
402
- children.add(runKey(normalized.runId, scope));
403
- parentRunIds.set(parentKey, children);
404
- }
405
- return { claimed: true, state: cloneState(stored) };
406
- },
407
- save(state, saveOptions) {
408
- const scope = resolveScope(options.scope, state.scope);
409
- const current = states.get(runKey(state.runId, scope));
410
- assertExpectedRevision(current, saveOptions?.expectedRevision);
411
- const normalized = nextStoredState(state, saveOptions);
412
- removeParentIndex(current);
413
- states.set(runKey(normalized.runId, scope), cloneState({ ...normalized, ...(scope ? { scope } : {}) }));
414
- if (normalized.idempotencyKey) {
415
- const owner = idempotencyKeys.get(idempotencyKey(normalized.idempotencyKey, scope));
416
- if (owner && owner !== runKey(normalized.runId, scope)) {
417
- throw new ConflictError("AgentRunState idempotency key conflict.");
418
- }
419
- idempotencyKeys.set(idempotencyKey(normalized.idempotencyKey, scope), runKey(normalized.runId, scope));
420
- }
421
- if (normalized.parentRunId) {
422
- const parentKey = scopedKey(scope, normalized.parentRunId);
423
- const children = parentRunIds.get(parentKey) ?? new Set();
424
- children.add(runKey(normalized.runId, scope));
425
- parentRunIds.set(parentKey, children);
426
- }
427
- },
428
- delete(runId, scope) {
429
- const key = runKey(runId, scope);
430
- const state = states.get(key);
431
- if (state?.idempotencyKey) {
432
- idempotencyKeys.delete(idempotencyKey(state.idempotencyKey, state.scope));
433
- }
434
- removeParentIndex(state);
435
- states.delete(key);
436
- leases.delete(key);
437
- for (const journalEntryKey of journal.keys()) {
438
- if (journalEntryKey.startsWith(`${key}:`))
439
- journal.delete(journalEntryKey);
440
- }
441
- },
442
- list(listOptions, scope) {
443
- const prefix = scopePrefix(resolveScope(options.scope, scope));
444
- return listStates([...states.entries()].filter(([key]) => key.startsWith(prefix)).map(([, state]) => state), listOptions);
445
- },
446
- deleteExpired(retention, scope) {
447
- const prefix = scopePrefix(resolveScope(options.scope, scope));
448
- const candidates = listStates([...states.entries()].filter(([key]) => key.startsWith(prefix)).map(([, state]) => state), {
449
- statuses: retention.statuses,
450
- updatedBefore: retention.before,
451
- limit: retention.limit ?? 1_000
452
- }).items;
453
- for (const state of candidates)
454
- this.delete?.(state.runId, state.scope);
455
- return candidates.length;
456
- },
457
- acquireLease(runId, leaseOptions, scope) {
458
- validateLeaseOptions(leaseOptions);
459
- const key = runKey(runId, scope);
460
- if (!states.has(key))
461
- return undefined;
462
- const now = leaseOptions.now ?? Date.now();
463
- const current = leases.get(key);
464
- if (current && current.expiresAt > now && current.ownerId !== leaseOptions.ownerId)
465
- return undefined;
466
- const lease = { runId, ownerId: leaseOptions.ownerId, expiresAt: now + leaseOptions.ttlMs };
467
- leases.set(key, lease);
468
- return { ...lease };
469
- },
470
- renewLease(runId, leaseOptions, scope) {
471
- validateLeaseOptions(leaseOptions);
472
- const key = runKey(runId, scope);
473
- const now = leaseOptions.now ?? Date.now();
474
- const current = leases.get(key);
475
- if (!current || current.ownerId !== leaseOptions.ownerId || current.expiresAt <= now)
476
- return undefined;
477
- const lease = { runId, ownerId: leaseOptions.ownerId, expiresAt: now + leaseOptions.ttlMs };
478
- leases.set(key, lease);
479
- return { ...lease };
480
- },
481
- releaseLease(runId, ownerId, scope) {
482
- const key = runKey(runId, scope);
483
- if (leases.get(key)?.ownerId !== ownerId)
484
- return false;
485
- return leases.delete(key);
486
- },
487
- loadToolCall(runId, toolCallId, scope) {
488
- const entry = journal.get(journalKey(runId, toolCallId, scope));
489
- return entry ? cloneJournalEntry(entry) : undefined;
490
- },
491
- loadToolExecution(runId, toolCallId, scope) {
492
- return this.loadToolCall?.(runId, toolCallId, scope);
493
- },
494
- listToolCalls(runId, scope) {
495
- const prefix = `${runKey(runId, scope)}:`;
496
- return [...journal.entries()]
497
- .filter(([key]) => key.startsWith(prefix))
498
- .map(([, entry]) => cloneJournalEntry(entry))
499
- .sort((left, right) => left.updatedAt - right.updatedAt || left.toolCallId.localeCompare(right.toolCallId));
500
- },
501
- saveToolCall(entry, journalOptions) {
502
- const scope = resolveScope(options.scope, entry.scope);
503
- if (!states.has(runKey(entry.runId, scope)))
504
- throw new ValidationError("Cannot journal a tool call for an unknown run.");
505
- const key = journalKey(entry.runId, entry.toolCallId, scope);
506
- const current = journal.get(key);
507
- assertJournalRevision(current, journalOptions?.expectedRevision);
508
- const next = nextJournalEntry(entry, journalOptions);
509
- journal.set(key, next);
510
- return cloneJournalEntry(next);
511
- },
512
- claimToolExecution(entry) {
513
- const key = journalKey(entry.runId, entry.toolCallId, entry.scope);
514
- const existing = journal.get(key);
515
- if (existing)
516
- return { claimed: false, entry: cloneJournalEntry(existing) };
517
- const claimed = this.saveToolCall?.({ ...entry, status: "running", revision: 0 });
518
- return { claimed: true, entry: claimed };
519
- },
520
- completeToolExecution(entry, journalOptions) {
521
- return this.saveToolCall?.({ ...entry, status: entry.status === "failed" ? "failed" : "completed" }, journalOptions);
522
- }
523
- };
524
- };
525
- export const createFileAgentRunStore = (options) => {
526
- const effectiveScope = (scope) => resolveScope(options.scope, scope);
527
- const runPath = (runId, scope) => path.join(options.directory, fileNameForAgentStoreKey(scopedKey(effectiveScope(scope), runId)));
528
- const idempotencyPath = (key, scope) => path.join(options.directory, fileNameForIdempotencyKey(scopedKey(effectiveScope(scope), key)));
529
- const leasePath = (runId, scope) => path.join(options.directory, `.lease-${createHash("sha256").update(scopedKey(effectiveScope(scope), runId)).digest("hex")}.json`);
530
- const toolPath = (runId, toolCallId, scope) => path.join(options.directory, `.tool-${createHash("sha256").update(`${scopedKey(effectiveScope(scope), runId)}:${toolCallId}`).digest("hex")}.json`);
531
- const revisionLockPath = (runId, scope) => path.join(options.directory, `.run-lock-${createHash("sha256").update(scopedKey(effectiveScope(scope), runId)).digest("hex")}.lock`);
532
- const load = async (runId, scope) => {
533
- try {
534
- const content = await fs.readFile(runPath(runId, scope), "utf8");
535
- return normalizeAgentRunState(JSON.parse(content));
536
- }
537
- catch (error) {
538
- if (error.code === "ENOENT") {
539
- return undefined;
540
- }
541
- throw error;
542
- }
543
- };
544
- const findByIdempotencyKey = async (idempotencyKey, scope) => {
545
- const targetScope = effectiveScope(scope);
546
- try {
547
- const marker = await fs.readFile(idempotencyPath(idempotencyKey, scope), "utf8");
548
- const claimed = normalizeAgentRunState(JSON.parse(marker));
549
- return (await load(claimed.runId, scope)) ?? claimed;
550
- }
551
- catch (error) {
552
- if (error.code !== "ENOENT") {
553
- throw error;
554
- }
555
- }
556
- let entries;
557
- try {
558
- entries = await fs.readdir(options.directory);
559
- }
560
- catch (error) {
561
- return error.code === "ENOENT" ? undefined : Promise.reject(error);
562
- }
563
- for (const entry of entries) {
564
- if (!entry.endsWith(".json") || entry.startsWith(".")) {
565
- continue;
566
- }
567
- const content = await fs.readFile(path.join(options.directory, entry), "utf8");
568
- const state = normalizeAgentRunState(JSON.parse(content));
569
- if (state.idempotencyKey === idempotencyKey && (!targetScope || (state.scope && sameScope(state.scope, targetScope)))) {
570
- return state;
571
- }
572
- }
573
- return undefined;
574
- };
575
- return {
576
- load,
577
- findByIdempotencyKey,
578
- async findByParentRunId(parentRunId, scope) {
579
- const targetScope = effectiveScope(scope);
580
- let entries;
581
- try {
582
- entries = await fs.readdir(options.directory);
583
- }
584
- catch (error) {
585
- if (error.code === "ENOENT") {
586
- return [];
587
- }
588
- throw error;
589
- }
590
- const states = [];
591
- for (const entry of entries) {
592
- if (!entry.endsWith(".json") || entry.startsWith(".")) {
593
- continue;
594
- }
595
- const content = await fs.readFile(path.join(options.directory, entry), "utf8");
596
- const state = normalizeAgentRunState(JSON.parse(content));
597
- if (state.parentRunId === parentRunId && (!targetScope || (state.scope && sameScope(state.scope, targetScope)))) {
598
- states.push(state);
599
- }
600
- }
601
- return states;
602
- },
603
- async claimIdempotencyKey(state) {
604
- await ensurePrivateDirectory(options.directory);
605
- const scope = effectiveScope(state.scope);
606
- const normalized = normalizeAgentRunState({ ...state, ...(scope ? { scope } : {}) });
607
- try {
608
- await writePrivateFile(idempotencyPath(state.idempotencyKey, scope), JSON.stringify(normalized, null, 2), { flag: "wx" });
609
- await writePrivateFile(runPath(normalized.runId, scope), JSON.stringify(normalized, null, 2));
610
- return { claimed: true, state: normalized };
611
- }
612
- catch (error) {
613
- if (error.code !== "EEXIST") {
614
- throw error;
615
- }
616
- const existing = await findByIdempotencyKey(state.idempotencyKey, scope);
617
- if (!existing) {
618
- throw new ConflictError("AgentRunState idempotency claim could not be loaded.");
619
- }
620
- return { claimed: false, state: existing };
621
- }
622
- },
623
- async save(state, saveOptions) {
624
- await ensurePrivateDirectory(options.directory);
625
- const scope = effectiveScope(state.scope);
626
- const releaseLock = await acquireFileRunStoreLock(revisionLockPath(state.runId, scope));
627
- try {
628
- const current = await load(state.runId, scope);
629
- assertExpectedRevision(current, saveOptions?.expectedRevision);
630
- const normalized = nextStoredState(state, saveOptions);
631
- if (normalized.idempotencyKey) {
632
- const owner = await findByIdempotencyKey(normalized.idempotencyKey, scope);
633
- if (owner && owner.runId !== normalized.runId) {
634
- throw new ConflictError("AgentRunState idempotency key conflict.");
635
- }
636
- }
637
- const stored = { ...normalized, ...(scope ? { scope } : {}) };
638
- await writePrivateFile(runPath(normalized.runId, scope), JSON.stringify(stored, null, 2));
639
- if (normalized.idempotencyKey) {
640
- await writePrivateFile(idempotencyPath(normalized.idempotencyKey, scope), JSON.stringify(stored, null, 2));
641
- }
642
- }
643
- finally {
644
- await releaseLock();
645
- }
646
- },
647
- async delete(runId, scope) {
648
- const current = await load(runId, scope);
649
- const targetScope = effectiveScope(scope);
650
- try {
651
- await fs.unlink(runPath(runId, scope));
652
- }
653
- catch (error) {
654
- if (error.code !== "ENOENT") {
655
- throw error;
656
- }
657
- }
658
- if (current?.idempotencyKey) {
659
- try {
660
- await fs.unlink(idempotencyPath(current.idempotencyKey, scope));
661
- }
662
- catch (error) {
663
- if (error.code !== "ENOENT") {
664
- throw error;
665
- }
666
- }
667
- }
668
- await fs.unlink(leasePath(runId, scope)).catch((error) => {
669
- if (error.code !== "ENOENT")
670
- throw error;
671
- });
672
- const entries = await fs.readdir(options.directory).catch((error) => {
673
- if (error.code === "ENOENT")
674
- return [];
675
- throw error;
676
- });
677
- for (const entryName of entries.filter((name) => name.startsWith(".tool-") && name.endsWith(".json"))) {
678
- const entryPath = path.join(options.directory, entryName);
679
- try {
680
- const journalEntry = JSON.parse(await fs.readFile(entryPath, "utf8"));
681
- const sameEntryScope = targetScope
682
- ? Boolean(journalEntry.scope && sameScope(journalEntry.scope, targetScope))
683
- : journalEntry.scope === undefined;
684
- if (journalEntry.runId === runId && sameEntryScope) {
685
- await fs.unlink(entryPath);
686
- }
687
- }
688
- catch (error) {
689
- if (error.code !== "ENOENT")
690
- throw error;
691
- }
692
- }
693
- },
694
- async list(listOptions, scope) {
695
- const targetScope = effectiveScope(scope);
696
- const entries = await fs.readdir(options.directory).catch((error) => {
697
- if (error.code === "ENOENT")
698
- return [];
699
- throw error;
700
- });
701
- const states = [];
702
- for (const entry of entries) {
703
- if (!entry.endsWith(".json") || entry.startsWith("."))
704
- continue;
705
- const state = normalizeAgentRunState(JSON.parse(await fs.readFile(path.join(options.directory, entry), "utf8")));
706
- if (!targetScope || (state.scope && sameScope(state.scope, targetScope)))
707
- states.push(state);
708
- }
709
- return listStates(states, listOptions);
710
- },
711
- async deleteExpired(retention, scope) {
712
- const page = await this.list?.({ statuses: retention.statuses, updatedBefore: retention.before, limit: retention.limit ?? 1_000 }, scope);
713
- const items = page.items;
714
- for (const state of items)
715
- await this.delete?.(state.runId, state.scope);
716
- return items.length;
717
- },
718
- async acquireLease(runId, leaseOptions, scope) {
719
- validateLeaseOptions(leaseOptions);
720
- if (!await load(runId, scope))
721
- return undefined;
722
- await ensurePrivateDirectory(options.directory);
723
- const file = leasePath(runId, scope);
724
- const now = leaseOptions.now ?? Date.now();
725
- const lease = { runId, ownerId: leaseOptions.ownerId, expiresAt: now + leaseOptions.ttlMs };
726
- for (let attempt = 0; attempt < 2; attempt += 1) {
727
- try {
728
- await writePrivateFile(file, JSON.stringify(lease), { flag: "wx" });
729
- return lease;
730
- }
731
- catch (error) {
732
- if (error.code !== "EEXIST")
733
- throw error;
734
- const current = JSON.parse(await fs.readFile(file, "utf8"));
735
- if (current.ownerId === leaseOptions.ownerId) {
736
- await writePrivateFile(file, JSON.stringify(lease));
737
- return lease;
738
- }
739
- if (current.expiresAt > now)
740
- return undefined;
741
- await fs.unlink(file).catch(() => undefined);
742
- }
743
- }
744
- return undefined;
745
- },
746
- async renewLease(runId, leaseOptions, scope) {
747
- validateLeaseOptions(leaseOptions);
748
- const file = leasePath(runId, scope);
749
- const now = leaseOptions.now ?? Date.now();
750
- try {
751
- const current = JSON.parse(await fs.readFile(file, "utf8"));
752
- if (current.ownerId !== leaseOptions.ownerId || current.expiresAt <= now)
753
- return undefined;
754
- const lease = { runId, ownerId: leaseOptions.ownerId, expiresAt: now + leaseOptions.ttlMs };
755
- await writePrivateFile(file, JSON.stringify(lease));
756
- return lease;
757
- }
758
- catch (error) {
759
- if (error.code === "ENOENT")
760
- return undefined;
761
- throw error;
762
- }
763
- },
764
- async releaseLease(runId, ownerId, scope) {
765
- const file = leasePath(runId, scope);
766
- try {
767
- const current = JSON.parse(await fs.readFile(file, "utf8"));
768
- if (current.ownerId !== ownerId)
769
- return false;
770
- await fs.unlink(file);
771
- return true;
772
- }
773
- catch (error) {
774
- if (error.code === "ENOENT")
775
- return false;
776
- throw error;
777
- }
778
- },
779
- async loadToolCall(runId, toolCallId, scope) {
780
- try {
781
- return JSON.parse(await fs.readFile(toolPath(runId, toolCallId, scope), "utf8"));
782
- }
783
- catch (error) {
784
- if (error.code === "ENOENT")
785
- return undefined;
786
- throw error;
787
- }
788
- },
789
- async loadToolExecution(runId, toolCallId, scope) {
790
- return this.loadToolCall?.(runId, toolCallId, scope);
791
- },
792
- async listToolCalls(runId, scope) {
793
- const targetScope = effectiveScope(scope);
794
- const entries = await fs.readdir(options.directory).catch(() => []);
795
- const results = [];
796
- for (const entry of entries) {
797
- if (!entry.startsWith(".tool-") || !entry.endsWith(".json"))
798
- continue;
799
- const value = JSON.parse(await fs.readFile(path.join(options.directory, entry), "utf8"));
800
- if (value.runId === runId && (!targetScope || (value.scope && sameScope(value.scope, targetScope))))
801
- results.push(value);
802
- }
803
- return results.sort((a, b) => a.updatedAt - b.updatedAt || a.toolCallId.localeCompare(b.toolCallId));
804
- },
805
- async saveToolCall(entry, journalOptions) {
806
- const file = toolPath(entry.runId, entry.toolCallId, entry.scope);
807
- const current = await this.loadToolCall?.(entry.runId, entry.toolCallId, entry.scope);
808
- assertJournalRevision(current, journalOptions?.expectedRevision);
809
- const next = nextJournalEntry(entry, journalOptions);
810
- await writePrivateFile(file, JSON.stringify(next, null, 2));
811
- return next;
812
- },
813
- async claimToolExecution(entry) {
814
- await ensurePrivateDirectory(options.directory);
815
- if (!await load(entry.runId, entry.scope))
816
- throw new ValidationError("Cannot journal a tool call for an unknown run.");
817
- const next = nextJournalEntry({ ...entry, status: "running", revision: 0 });
818
- try {
819
- await writePrivateFile(toolPath(entry.runId, entry.toolCallId, entry.scope), JSON.stringify(next, null, 2), { flag: "wx" });
820
- return { claimed: true, entry: next };
821
- }
822
- catch (error) {
823
- if (error.code !== "EEXIST")
824
- throw error;
825
- const existing = await this.loadToolCall?.(entry.runId, entry.toolCallId, entry.scope);
826
- if (!existing)
827
- throw new ConflictError("Tool execution claim could not be loaded.");
828
- return { claimed: false, entry: existing };
829
- }
830
- },
831
- async completeToolExecution(entry, journalOptions) {
832
- return this.saveToolCall?.({ ...entry, status: entry.status === "failed" ? "failed" : "completed" }, journalOptions);
833
- }
834
- };
835
- };
836
- export const createInMemoryAgentMemoryStore = (options = {}) => {
837
- const keyFor = (context) => (options.key ?? defaultMemoryKey)({ ...context, scope: resolveScope(options.scope, context.scope) });
838
- const selectMessages = options.selectMessages ?? defaultMemoryMessages;
839
- const memories = new Map(Object.entries(options.initialMessages ?? {}).map(([key, messages]) => [key, cloneMessages(messages)]));
840
- return {
841
- load(context) {
842
- return cloneMessages(memories.get(keyFor(context)) ?? (context.agentId ? options.initialMessages?.[context.agentId] : undefined) ?? []);
843
- },
844
- save(context) {
845
- memories.set(keyFor(context), cloneMessages(selectMessages(context.state)));
846
- }
847
- };
848
- };
849
- export const createFileAgentMemoryStore = (options) => {
850
- const keyFor = (context) => (options.key ?? defaultMemoryKey)({ ...context, scope: resolveScope(options.scope, context.scope) });
851
- const selectMessages = options.selectMessages ?? defaultMemoryMessages;
852
- return {
853
- async load(context) {
854
- try {
855
- const file = await fs.readFile(path.join(options.directory, fileNameForAgentStoreKey(keyFor(context))), "utf8");
856
- return JSON.parse(file);
857
- }
858
- catch (error) {
859
- if (error.code === "ENOENT") {
860
- return [];
861
- }
862
- throw error;
863
- }
864
- },
865
- async save(context) {
866
- await ensurePrivateDirectory(options.directory);
867
- await writePrivateFile(path.join(options.directory, fileNameForAgentStoreKey(keyFor(context))), JSON.stringify(selectMessages(context.state), null, 2));
868
- }
869
- };
870
- };
871
- export const createSqliteAgentRunStore = (options) => {
872
- const tableName = validateIdentifier(options.tableName ?? "zhivex_agent_runs", "tableName");
873
- const idempotencyTableName = `${tableName}_idempotency`;
874
- const parentTableName = `${tableName}_parents`;
875
- const leaseTableName = `${tableName}_leases`;
876
- const journalTableName = `${tableName}_tool_journal`;
877
- const dbKey = (value, scope) => scopedKey(resolveScope(options.scope, scope), value);
878
- initializeSqliteTable(options.db, `CREATE TABLE IF NOT EXISTS ${tableName} (
879
- run_id TEXT PRIMARY KEY,
880
- state_json TEXT NOT NULL,
881
- updated_at_ms INTEGER NOT NULL
882
- )`);
883
- initializeSqliteTable(options.db, `CREATE INDEX IF NOT EXISTS ${tableName}_updated_idx ON ${tableName} (updated_at_ms, run_id)`);
884
- initializeSqliteTable(options.db, `CREATE TABLE IF NOT EXISTS ${leaseTableName} (
885
- run_key TEXT PRIMARY KEY,
886
- run_id TEXT NOT NULL,
887
- owner_id TEXT NOT NULL,
888
- expires_at_ms INTEGER NOT NULL
889
- )`);
890
- initializeSqliteTable(options.db, `CREATE INDEX IF NOT EXISTS ${leaseTableName}_expiry_idx ON ${leaseTableName} (expires_at_ms)`);
891
- initializeSqliteTable(options.db, `CREATE TABLE IF NOT EXISTS ${journalTableName} (
892
- run_key TEXT NOT NULL,
893
- tool_call_id TEXT NOT NULL,
894
- entry_json TEXT NOT NULL,
895
- revision INTEGER NOT NULL,
896
- updated_at_ms INTEGER NOT NULL,
897
- PRIMARY KEY (run_key, tool_call_id)
898
- )`);
899
- initializeSqliteTable(options.db, `CREATE INDEX IF NOT EXISTS ${journalTableName}_run_idx ON ${journalTableName} (run_key, updated_at_ms)`);
900
- initializeSqliteTable(options.db, `CREATE TABLE IF NOT EXISTS ${idempotencyTableName} (
901
- idempotency_key TEXT PRIMARY KEY,
902
- run_id TEXT NOT NULL,
903
- updated_at_ms INTEGER NOT NULL
904
- )`);
905
- initializeSqliteTable(options.db, `CREATE TABLE IF NOT EXISTS ${parentTableName} (
906
- run_id TEXT PRIMARY KEY,
907
- parent_run_id TEXT NOT NULL,
908
- updated_at_ms INTEGER NOT NULL
909
- )`);
910
- initializeSqliteTable(options.db, `CREATE INDEX IF NOT EXISTS ${parentTableName}_parent_idx ON ${parentTableName} (parent_run_id, updated_at_ms)`);
911
- const loadStatement = prepareSqliteStatement(options.db, `SELECT state_json FROM ${tableName} WHERE run_id = ?`);
912
- const saveStatement = prepareSqliteStatement(options.db, `
913
- INSERT INTO ${tableName} (run_id, state_json, updated_at_ms)
914
- VALUES (?, ?, ?)
915
- ON CONFLICT(run_id) DO UPDATE SET
916
- state_json = excluded.state_json,
917
- updated_at_ms = excluded.updated_at_ms
918
- `);
919
- const deleteStatement = prepareSqliteStatement(options.db, `DELETE FROM ${tableName} WHERE run_id = ?`);
920
- const findIdempotencyStatement = prepareSqliteStatement(options.db, `SELECT runs.state_json
921
- FROM ${tableName} runs
922
- INNER JOIN ${idempotencyTableName} keys ON keys.run_id = runs.run_id
923
- WHERE keys.idempotency_key = ?`);
924
- const saveIdempotencyStatement = prepareSqliteStatement(options.db, `
925
- INSERT INTO ${idempotencyTableName} (idempotency_key, run_id, updated_at_ms)
926
- VALUES (?, ?, ?)
927
- ON CONFLICT(idempotency_key) DO NOTHING
928
- `);
929
- const deleteIdempotencyStatement = prepareSqliteStatement(options.db, `DELETE FROM ${idempotencyTableName} WHERE run_id = ?`);
930
- const findParentStatement = prepareSqliteStatement(options.db, `SELECT runs.state_json
931
- FROM ${tableName} runs
932
- INNER JOIN ${parentTableName} parents ON parents.run_id = runs.run_id
933
- WHERE parents.parent_run_id = ?`);
934
- const saveParentStatement = prepareSqliteStatement(options.db, `
935
- INSERT INTO ${parentTableName} (run_id, parent_run_id, updated_at_ms)
936
- VALUES (?, ?, ?)
937
- ON CONFLICT(run_id) DO UPDATE SET
938
- parent_run_id = excluded.parent_run_id,
939
- updated_at_ms = excluded.updated_at_ms
940
- `);
941
- const deleteParentStatement = prepareSqliteStatement(options.db, `DELETE FROM ${parentTableName} WHERE run_id = ?`);
942
- const listStatement = prepareSqliteStatement(options.db, `SELECT state_json FROM ${tableName}`);
943
- const loadLeaseStatement = prepareSqliteStatement(options.db, `SELECT owner_id, expires_at_ms FROM ${leaseTableName} WHERE run_key = ?`);
944
- const saveLeaseStatement = prepareSqliteStatement(options.db, `INSERT INTO ${leaseTableName} (run_key, run_id, owner_id, expires_at_ms) VALUES (?, ?, ?, ?) ON CONFLICT(run_key) DO UPDATE SET owner_id = excluded.owner_id, expires_at_ms = excluded.expires_at_ms`);
945
- const deleteLeaseStatement = prepareSqliteStatement(options.db, `DELETE FROM ${leaseTableName} WHERE run_key = ? AND owner_id = ?`);
946
- const deleteRunLeaseStatement = prepareSqliteStatement(options.db, `DELETE FROM ${leaseTableName} WHERE run_key = ?`);
947
- const deleteRunJournalStatement = prepareSqliteStatement(options.db, `DELETE FROM ${journalTableName} WHERE run_key = ?`);
948
- const loadJournalStatement = prepareSqliteStatement(options.db, `SELECT entry_json FROM ${journalTableName} WHERE run_key = ? AND tool_call_id = ?`);
949
- const listJournalStatement = prepareSqliteStatement(options.db, `SELECT entry_json FROM ${journalTableName} WHERE run_key = ? ORDER BY updated_at_ms, tool_call_id`);
950
- const insertJournalStatement = prepareSqliteStatement(options.db, `INSERT INTO ${journalTableName} (run_key, tool_call_id, entry_json, revision, updated_at_ms) VALUES (?, ?, ?, ?, ?) ON CONFLICT(run_key, tool_call_id) DO NOTHING`);
951
- const saveJournalStatement = prepareSqliteStatement(options.db, `INSERT INTO ${journalTableName} (run_key, tool_call_id, entry_json, revision, updated_at_ms) VALUES (?, ?, ?, ?, ?) ON CONFLICT(run_key, tool_call_id) DO UPDATE SET entry_json = excluded.entry_json, revision = excluded.revision, updated_at_ms = excluded.updated_at_ms`);
952
- return {
953
- load(runId, scope) {
954
- const row = loadStatement.get([dbKey(runId, scope)]);
955
- const stateJson = getRecordField(row, ["state_json", "stateJson"]);
956
- return typeof stateJson === "string" ? normalizeAgentRunState(JSON.parse(stateJson)) : undefined;
957
- },
958
- findByIdempotencyKey(idempotencyKey, scope) {
959
- const row = findIdempotencyStatement.get([dbKey(idempotencyKey, scope)]);
960
- const stateJson = getRecordField(row, ["state_json", "stateJson"]);
961
- return typeof stateJson === "string" ? normalizeAgentRunState(JSON.parse(stateJson)) : undefined;
962
- },
963
- findByParentRunId(parentRunId, scope) {
964
- const rows = findParentStatement.all?.([dbKey(parentRunId, scope)]);
965
- if (Array.isArray(rows)) {
966
- return rows.flatMap((row) => {
967
- const stateJson = getRecordField(row, ["state_json", "stateJson"]);
968
- return typeof stateJson === "string" ? [normalizeAgentRunState(JSON.parse(stateJson))] : [];
969
- });
970
- }
971
- const row = findParentStatement.get([dbKey(parentRunId, scope)]);
972
- const stateJson = getRecordField(row, ["state_json", "stateJson"]);
973
- return typeof stateJson === "string" ? [normalizeAgentRunState(JSON.parse(stateJson))] : [];
974
- },
975
- claimIdempotencyKey(state) {
976
- options.db.exec("BEGIN IMMEDIATE");
977
- try {
978
- const scope = resolveScope(options.scope, state.scope);
979
- const existingRow = findIdempotencyStatement.get([dbKey(state.idempotencyKey, scope)]);
980
- const existingJson = getRecordField(existingRow, ["state_json", "stateJson"]);
981
- if (typeof existingJson === "string") {
982
- options.db.exec("COMMIT");
983
- return { claimed: false, state: normalizeAgentRunState(JSON.parse(existingJson)) };
984
- }
985
- const normalized = normalizeAgentRunState({ ...state, ...(scope ? { scope } : {}) });
986
- const updatedAt = Date.now();
987
- saveStatement.run([dbKey(normalized.runId, scope), JSON.stringify(normalized), updatedAt]);
988
- saveIdempotencyStatement.run([dbKey(state.idempotencyKey, scope), dbKey(normalized.runId, scope), updatedAt]);
989
- if (normalized.parentRunId) {
990
- saveParentStatement.run([dbKey(normalized.runId, scope), dbKey(normalized.parentRunId, scope), updatedAt]);
991
- }
992
- options.db.exec("COMMIT");
993
- return { claimed: true, state: normalized };
994
- }
995
- catch (error) {
996
- options.db.exec("ROLLBACK");
997
- throw error;
998
- }
999
- },
1000
- save(state, saveOptions) {
1001
- options.db.exec("BEGIN IMMEDIATE");
1002
- try {
1003
- const scope = resolveScope(options.scope, state.scope);
1004
- const currentRow = loadStatement.get([dbKey(state.runId, scope)]);
1005
- const currentJson = getRecordField(currentRow, ["state_json", "stateJson"]);
1006
- const current = typeof currentJson === "string"
1007
- ? normalizeAgentRunState(JSON.parse(currentJson))
1008
- : undefined;
1009
- assertExpectedRevision(current, saveOptions?.expectedRevision);
1010
- const normalized = nextStoredState(state, saveOptions);
1011
- if (normalized.idempotencyKey) {
1012
- const ownerRow = findIdempotencyStatement.get([dbKey(normalized.idempotencyKey, scope)]);
1013
- const ownerJson = getRecordField(ownerRow, ["state_json", "stateJson"]);
1014
- const owner = typeof ownerJson === "string"
1015
- ? normalizeAgentRunState(JSON.parse(ownerJson))
1016
- : undefined;
1017
- if (owner && owner.runId !== normalized.runId) {
1018
- throw new ConflictError("AgentRunState idempotency key conflict.");
1019
- }
1020
- }
1021
- const updatedAt = Date.now();
1022
- const stored = { ...normalized, ...(scope ? { scope } : {}) };
1023
- saveStatement.run([dbKey(normalized.runId, scope), JSON.stringify(stored), updatedAt]);
1024
- if (normalized.idempotencyKey) {
1025
- saveIdempotencyStatement.run([dbKey(normalized.idempotencyKey, scope), dbKey(normalized.runId, scope), updatedAt]);
1026
- }
1027
- deleteParentStatement.run([dbKey(normalized.runId, scope)]);
1028
- if (normalized.parentRunId) {
1029
- saveParentStatement.run([dbKey(normalized.runId, scope), dbKey(normalized.parentRunId, scope), updatedAt]);
1030
- }
1031
- options.db.exec("COMMIT");
1032
- }
1033
- catch (error) {
1034
- options.db.exec("ROLLBACK");
1035
- throw error;
1036
- }
1037
- },
1038
- delete(runId, scope) {
1039
- const key = dbKey(runId, scope);
1040
- options.db.exec("BEGIN IMMEDIATE");
1041
- try {
1042
- deleteStatement.run([key]);
1043
- deleteIdempotencyStatement.run([key]);
1044
- deleteParentStatement.run([key]);
1045
- deleteRunLeaseStatement.run([key]);
1046
- deleteRunJournalStatement.run([key]);
1047
- options.db.exec("COMMIT");
1048
- }
1049
- catch (error) {
1050
- options.db.exec("ROLLBACK");
1051
- throw error;
1052
- }
1053
- },
1054
- list(listOptions, scope) {
1055
- const rows = listStatement.all?.([]) ?? [];
1056
- const prefix = scopePrefix(resolveScope(options.scope, scope));
1057
- const states = rows.flatMap((row) => {
1058
- const value = getRecordField(row, ["state_json", "stateJson"]);
1059
- if (typeof value !== "string")
1060
- return [];
1061
- const state = normalizeAgentRunState(JSON.parse(value));
1062
- return scopedKey(state.scope, state.runId).startsWith(prefix) ? [state] : [];
1063
- });
1064
- return listStates(states, listOptions);
1065
- },
1066
- deleteExpired(retention, scope) {
1067
- const page = this.list?.({ statuses: retention.statuses, updatedBefore: retention.before, limit: retention.limit ?? 1_000 }, scope);
1068
- for (const state of page.items)
1069
- this.delete?.(state.runId, state.scope);
1070
- return page.items.length;
1071
- },
1072
- acquireLease(runId, leaseOptions, scope) {
1073
- validateLeaseOptions(leaseOptions);
1074
- const now = leaseOptions.now ?? Date.now();
1075
- const key = dbKey(runId, scope);
1076
- options.db.exec("BEGIN IMMEDIATE");
1077
- try {
1078
- const row = loadLeaseStatement.get([key]);
1079
- const owner = getRecordField(row, ["owner_id", "ownerId"]);
1080
- const expiry = getRecordField(row, ["expires_at_ms", "expiresAtMs"]);
1081
- if (typeof owner === "string" && owner !== leaseOptions.ownerId && typeof expiry === "number" && expiry > now) {
1082
- options.db.exec("COMMIT");
1083
- return undefined;
1084
- }
1085
- const lease = { runId, ownerId: leaseOptions.ownerId, expiresAt: now + leaseOptions.ttlMs };
1086
- saveLeaseStatement.run([key, runId, lease.ownerId, lease.expiresAt]);
1087
- options.db.exec("COMMIT");
1088
- return lease;
1089
- }
1090
- catch (error) {
1091
- options.db.exec("ROLLBACK");
1092
- throw error;
1093
- }
1094
- },
1095
- renewLease(runId, leaseOptions, scope) {
1096
- validateLeaseOptions(leaseOptions);
1097
- const now = leaseOptions.now ?? Date.now();
1098
- const key = dbKey(runId, scope);
1099
- options.db.exec("BEGIN IMMEDIATE");
1100
- try {
1101
- const row = loadLeaseStatement.get([key]);
1102
- const owner = getRecordField(row, ["owner_id", "ownerId"]);
1103
- const expiry = getRecordField(row, ["expires_at_ms", "expiresAtMs"]);
1104
- if (owner !== leaseOptions.ownerId || typeof expiry !== "number" || expiry <= now) {
1105
- options.db.exec("COMMIT");
1106
- return undefined;
1107
- }
1108
- const lease = { runId, ownerId: leaseOptions.ownerId, expiresAt: now + leaseOptions.ttlMs };
1109
- saveLeaseStatement.run([key, runId, lease.ownerId, lease.expiresAt]);
1110
- options.db.exec("COMMIT");
1111
- return lease;
1112
- }
1113
- catch (error) {
1114
- options.db.exec("ROLLBACK");
1115
- throw error;
1116
- }
1117
- },
1118
- releaseLease(runId, ownerId, scope) {
1119
- const key = dbKey(runId, scope);
1120
- options.db.exec("BEGIN IMMEDIATE");
1121
- try {
1122
- const row = loadLeaseStatement.get([key]);
1123
- if (getRecordField(row, ["owner_id", "ownerId"]) !== ownerId) {
1124
- options.db.exec("COMMIT");
1125
- return false;
1126
- }
1127
- deleteLeaseStatement.run([key, ownerId]);
1128
- options.db.exec("COMMIT");
1129
- return true;
1130
- }
1131
- catch (error) {
1132
- options.db.exec("ROLLBACK");
1133
- throw error;
1134
- }
1135
- },
1136
- loadToolCall(runId, toolCallId, scope) {
1137
- const row = loadJournalStatement.get([dbKey(runId, scope), toolCallId]);
1138
- const value = getRecordField(row, ["entry_json", "entryJson"]);
1139
- return typeof value === "string" ? JSON.parse(value) : undefined;
1140
- },
1141
- loadToolExecution(runId, toolCallId, scope) {
1142
- return this.loadToolCall?.(runId, toolCallId, scope);
1143
- },
1144
- listToolCalls(runId, scope) {
1145
- const rows = listJournalStatement.all?.([dbKey(runId, scope)]) ?? [];
1146
- return rows.flatMap((row) => {
1147
- const value = getRecordField(row, ["entry_json", "entryJson"]);
1148
- return typeof value === "string" ? [JSON.parse(value)] : [];
1149
- });
1150
- },
1151
- saveToolCall(entry, journalOptions) {
1152
- options.db.exec("BEGIN IMMEDIATE");
1153
- try {
1154
- const current = this.loadToolCall?.(entry.runId, entry.toolCallId, entry.scope);
1155
- assertJournalRevision(current, journalOptions?.expectedRevision);
1156
- const next = nextJournalEntry(entry, journalOptions);
1157
- saveJournalStatement.run([dbKey(entry.runId, entry.scope), entry.toolCallId, JSON.stringify(next), next.revision, next.updatedAt]);
1158
- options.db.exec("COMMIT");
1159
- return next;
1160
- }
1161
- catch (error) {
1162
- options.db.exec("ROLLBACK");
1163
- throw error;
1164
- }
1165
- },
1166
- claimToolExecution(entry) {
1167
- options.db.exec("BEGIN IMMEDIATE");
1168
- try {
1169
- const current = this.loadToolCall?.(entry.runId, entry.toolCallId, entry.scope);
1170
- if (current) {
1171
- options.db.exec("COMMIT");
1172
- return { claimed: false, entry: current };
1173
- }
1174
- const next = nextJournalEntry({ ...entry, status: "running", revision: 0 });
1175
- insertJournalStatement.run([dbKey(entry.runId, entry.scope), entry.toolCallId, JSON.stringify(next), 0, next.updatedAt]);
1176
- options.db.exec("COMMIT");
1177
- return { claimed: true, entry: next };
1178
- }
1179
- catch (error) {
1180
- options.db.exec("ROLLBACK");
1181
- throw error;
1182
- }
1183
- },
1184
- completeToolExecution(entry, journalOptions) {
1185
- return this.saveToolCall?.({ ...entry, status: entry.status === "failed" ? "failed" : "completed" }, journalOptions);
1186
- }
1187
- };
1188
- };
1189
- export const createSqliteAgentMemoryStore = (options) => {
1190
- const tableName = validateIdentifier(options.tableName ?? "zhivex_agent_memory", "tableName");
1191
- const keyFor = (context) => (options.key ?? defaultMemoryKey)({ ...context, scope: resolveScope(options.scope, context.scope) });
1192
- const selectMessages = options.selectMessages ?? defaultMemoryMessages;
1193
- initializeSqliteTable(options.db, `CREATE TABLE IF NOT EXISTS ${tableName} (
1194
- memory_key TEXT PRIMARY KEY,
1195
- messages_json TEXT NOT NULL,
1196
- updated_at_ms INTEGER NOT NULL
1197
- )`);
1198
- const loadStatement = prepareSqliteStatement(options.db, `SELECT messages_json FROM ${tableName} WHERE memory_key = ?`);
1199
- const saveStatement = prepareSqliteStatement(options.db, `
1200
- INSERT INTO ${tableName} (memory_key, messages_json, updated_at_ms)
1201
- VALUES (?, ?, ?)
1202
- ON CONFLICT(memory_key) DO UPDATE SET
1203
- messages_json = excluded.messages_json,
1204
- updated_at_ms = excluded.updated_at_ms
1205
- `);
1206
- return {
1207
- load(context) {
1208
- const row = loadStatement.get([keyFor(context)]);
1209
- const messagesJson = getRecordField(row, ["messages_json", "messagesJson"]);
1210
- return typeof messagesJson === "string" ? JSON.parse(messagesJson) : [];
1211
- },
1212
- save(context) {
1213
- saveStatement.run([keyFor(context), JSON.stringify(selectMessages(context.state)), Date.now()]);
1214
- }
1215
- };
1216
- };
1217
- export const createPostgresAgentRunStore = (options) => {
1218
- assertPostgresClient(options.client);
1219
- const tableName = validateIdentifier(options.tableName ?? "zhivex_agent_runs", "tableName");
1220
- const idempotencyTableName = `${tableName}_idempotency`;
1221
- const parentTableName = `${tableName}_parents`;
1222
- const leaseTableName = `${tableName}_leases`;
1223
- const journalTableName = `${tableName}_tool_journal`;
1224
- const dbKey = (value, scope) => scopedKey(resolveScope(options.scope, scope), value);
1225
- const createSql = `
1226
- CREATE TABLE IF NOT EXISTS ${tableName} (
1227
- run_id TEXT PRIMARY KEY,
1228
- state_json JSONB NOT NULL,
1229
- updated_at_ms BIGINT NOT NULL
1230
- )
1231
- `;
1232
- const createIdempotencySql = `
1233
- CREATE TABLE IF NOT EXISTS ${idempotencyTableName} (
1234
- idempotency_key TEXT PRIMARY KEY,
1235
- run_id TEXT NOT NULL,
1236
- updated_at_ms BIGINT NOT NULL
1237
- )
1238
- `;
1239
- const createParentSql = `
1240
- CREATE TABLE IF NOT EXISTS ${parentTableName} (
1241
- run_id TEXT PRIMARY KEY,
1242
- parent_run_id TEXT NOT NULL,
1243
- updated_at_ms BIGINT NOT NULL
1244
- )
1245
- `;
1246
- const createLeaseSql = `CREATE TABLE IF NOT EXISTS ${leaseTableName} (
1247
- run_key TEXT PRIMARY KEY,
1248
- run_id TEXT NOT NULL,
1249
- owner_id TEXT NOT NULL,
1250
- expires_at_ms BIGINT NOT NULL
1251
- )`;
1252
- const createJournalSql = `CREATE TABLE IF NOT EXISTS ${journalTableName} (
1253
- run_key TEXT NOT NULL,
1254
- tool_call_id TEXT NOT NULL,
1255
- entry_json JSONB NOT NULL,
1256
- revision BIGINT NOT NULL,
1257
- updated_at_ms BIGINT NOT NULL,
1258
- PRIMARY KEY (run_key, tool_call_id)
1259
- )`;
1260
- const createIndexesSql = `
1261
- CREATE INDEX IF NOT EXISTS ${tableName}_updated_idx ON ${tableName} (updated_at_ms DESC, run_id);
1262
- CREATE INDEX IF NOT EXISTS ${parentTableName}_parent_idx ON ${parentTableName} (parent_run_id, updated_at_ms DESC);
1263
- CREATE INDEX IF NOT EXISTS ${leaseTableName}_expiry_idx ON ${leaseTableName} (expires_at_ms);
1264
- CREATE INDEX IF NOT EXISTS ${journalTableName}_run_idx ON ${journalTableName} (run_key, updated_at_ms, tool_call_id)
1265
- `;
1266
- const ensureAllTables = async () => {
1267
- await ensurePostgresTable(options.client, tableName, createSql);
1268
- await ensurePostgresTable(options.client, idempotencyTableName, createIdempotencySql);
1269
- await ensurePostgresTable(options.client, parentTableName, createParentSql);
1270
- await ensurePostgresTable(options.client, leaseTableName, createLeaseSql);
1271
- await ensurePostgresTable(options.client, journalTableName, createJournalSql);
1272
- await ensurePostgresTable(options.client, `${tableName}:indexes`, createIndexesSql);
1273
- };
1274
- return {
1275
- async load(runId, scope) {
1276
- await ensureAllTables();
1277
- const result = await options.client.query(`SELECT state_json FROM ${tableName} WHERE run_id = $1`, [dbKey(runId, scope)]);
1278
- const state = result.rows[0] ? (getRecordField(result.rows[0], ["state_json", "stateJson"]) ?? undefined) : undefined;
1279
- return state ? normalizeAgentRunState(state) : undefined;
1280
- },
1281
- async findByIdempotencyKey(idempotencyKey, scope) {
1282
- await ensureAllTables();
1283
- const result = await options.client.query(`SELECT runs.state_json
1284
- FROM ${tableName} runs
1285
- INNER JOIN ${idempotencyTableName} keys ON keys.run_id = runs.run_id
1286
- WHERE keys.idempotency_key = $1`, [dbKey(idempotencyKey, scope)]);
1287
- const state = result.rows[0] ? (getRecordField(result.rows[0], ["state_json", "stateJson"]) ?? undefined) : undefined;
1288
- return state ? normalizeAgentRunState(state) : undefined;
1289
- },
1290
- async findByParentRunId(parentRunId, scope) {
1291
- await ensureAllTables();
1292
- const result = await options.client.query(`SELECT runs.state_json
1293
- FROM ${tableName} runs
1294
- INNER JOIN ${parentTableName} parents ON parents.run_id = runs.run_id
1295
- WHERE parents.parent_run_id = $1`, [dbKey(parentRunId, scope)]);
1296
- return result.rows.flatMap((row) => {
1297
- const state = getRecordField(row, ["state_json", "stateJson"]) ?? undefined;
1298
- return state ? [normalizeAgentRunState(state)] : [];
1299
- });
1300
- },
1301
- async claimIdempotencyKey(state) {
1302
- await ensureAllTables();
1303
- const scope = resolveScope(options.scope, state.scope);
1304
- const normalized = normalizeAgentRunState({ ...state, ...(scope ? { scope } : {}) });
1305
- const updatedAt = Date.now();
1306
- await options.client.query(`INSERT INTO ${tableName} (run_id, state_json, updated_at_ms)
1307
- VALUES ($1, $2::jsonb, $3)
1308
- ON CONFLICT(run_id) DO NOTHING`, [dbKey(normalized.runId, scope), normalized, updatedAt]);
1309
- const claim = await options.client.query(`INSERT INTO ${idempotencyTableName} (idempotency_key, run_id, updated_at_ms)
1310
- VALUES ($1, $2, $3)
1311
- ON CONFLICT(idempotency_key) DO NOTHING
1312
- RETURNING run_id`, [dbKey(state.idempotencyKey, scope), dbKey(normalized.runId, scope), updatedAt]);
1313
- const claimedRunId = getRecordField(claim.rows[0], ["run_id", "runId"]);
1314
- if (claimedRunId === dbKey(normalized.runId, scope)) {
1315
- if (normalized.parentRunId) {
1316
- await options.client.query(`INSERT INTO ${parentTableName} (run_id, parent_run_id, updated_at_ms)
1317
- VALUES ($1, $2, $3)
1318
- ON CONFLICT(run_id) DO UPDATE SET
1319
- parent_run_id = EXCLUDED.parent_run_id,
1320
- updated_at_ms = EXCLUDED.updated_at_ms`, [dbKey(normalized.runId, scope), dbKey(normalized.parentRunId, scope), updatedAt]);
1321
- }
1322
- return { claimed: true, state: normalized };
1323
- }
1324
- const existing = await options.client.query(`SELECT runs.state_json
1325
- FROM ${tableName} runs
1326
- INNER JOIN ${idempotencyTableName} keys ON keys.run_id = runs.run_id
1327
- WHERE keys.idempotency_key = $1`, [dbKey(state.idempotencyKey, scope)]);
1328
- const existingState = existing.rows[0]
1329
- ? getRecordField(existing.rows[0], ["state_json", "stateJson"])
1330
- : undefined;
1331
- if (!existingState) {
1332
- throw new ConflictError("AgentRunState idempotency claim could not be loaded.");
1333
- }
1334
- if (existingState.runId !== normalized.runId) {
1335
- await options.client.query(`DELETE FROM ${tableName} WHERE run_id = $1`, [dbKey(normalized.runId, scope)]);
1336
- }
1337
- return { claimed: false, state: normalizeAgentRunState(existingState) };
1338
- },
1339
- async save(state, saveOptions) {
1340
- await ensureAllTables();
1341
- const scope = resolveScope(options.scope, state.scope);
1342
- const normalized = nextStoredState(state, saveOptions);
1343
- const stored = { ...normalized, ...(scope ? { scope } : {}) };
1344
- const updatedAt = Date.now();
1345
- if (normalized.idempotencyKey) {
1346
- const owner = await options.client.query(`SELECT run_id FROM ${idempotencyTableName} WHERE idempotency_key = $1`, [dbKey(normalized.idempotencyKey, scope)]);
1347
- const ownerRunId = getRecordField(owner.rows[0], ["run_id", "runId"]);
1348
- if (typeof ownerRunId === "string" && ownerRunId !== dbKey(normalized.runId, scope)) {
1349
- throw new ConflictError("AgentRunState idempotency key conflict.");
1350
- }
1351
- }
1352
- if (saveOptions?.expectedRevision === undefined) {
1353
- await options.client.query(`INSERT INTO ${tableName} (run_id, state_json, updated_at_ms)
1354
- VALUES ($1, $2::jsonb, $3)
1355
- ON CONFLICT(run_id) DO UPDATE SET
1356
- state_json = EXCLUDED.state_json,
1357
- updated_at_ms = EXCLUDED.updated_at_ms`, [dbKey(normalized.runId, scope), stored, updatedAt]);
1358
- }
1359
- else {
1360
- const saved = await options.client.query(`INSERT INTO ${tableName} (run_id, state_json, updated_at_ms)
1361
- VALUES ($1, $2::jsonb, $3)
1362
- ON CONFLICT(run_id) DO UPDATE SET
1363
- state_json = EXCLUDED.state_json,
1364
- updated_at_ms = EXCLUDED.updated_at_ms
1365
- WHERE COALESCE((${tableName}.state_json->>'revision')::bigint, 0) = $4
1366
- RETURNING run_id`, [dbKey(normalized.runId, scope), stored, updatedAt, saveOptions.expectedRevision]);
1367
- if (getRecordField(saved.rows[0], ["run_id", "runId"]) !== dbKey(normalized.runId, scope)) {
1368
- throw new ConflictError("AgentRunState revision conflict.");
1369
- }
1370
- }
1371
- if (normalized.idempotencyKey) {
1372
- await options.client.query(`INSERT INTO ${idempotencyTableName} (idempotency_key, run_id, updated_at_ms)
1373
- VALUES ($1, $2, $3)
1374
- ON CONFLICT(idempotency_key) DO NOTHING`, [dbKey(normalized.idempotencyKey, scope), dbKey(normalized.runId, scope), updatedAt]);
1375
- }
1376
- await options.client.query(`DELETE FROM ${parentTableName} WHERE run_id = $1`, [dbKey(normalized.runId, scope)]);
1377
- if (normalized.parentRunId) {
1378
- await options.client.query(`INSERT INTO ${parentTableName} (run_id, parent_run_id, updated_at_ms)
1379
- VALUES ($1, $2, $3)
1380
- ON CONFLICT(run_id) DO UPDATE SET
1381
- parent_run_id = EXCLUDED.parent_run_id,
1382
- updated_at_ms = EXCLUDED.updated_at_ms`, [dbKey(normalized.runId, scope), dbKey(normalized.parentRunId, scope), updatedAt]);
1383
- }
1384
- },
1385
- async delete(runId, scope) {
1386
- await ensureAllTables();
1387
- const key = dbKey(runId, scope);
1388
- await options.client.query(`WITH deleted_run AS (
1389
- DELETE FROM ${tableName} WHERE run_id = $1 RETURNING run_id
1390
- ), deleted_idempotency AS (
1391
- DELETE FROM ${idempotencyTableName} WHERE run_id = $1 RETURNING run_id
1392
- ), deleted_parent AS (
1393
- DELETE FROM ${parentTableName} WHERE run_id = $1 RETURNING run_id
1394
- ), deleted_lease AS (
1395
- DELETE FROM ${leaseTableName} WHERE run_key = $1 RETURNING run_key
1396
- )
1397
- DELETE FROM ${journalTableName} WHERE run_key = $1`, [key]);
1398
- },
1399
- async list(listOptions, scope) {
1400
- await ensureAllTables();
1401
- const prefix = scopePrefix(resolveScope(options.scope, scope));
1402
- const result = await options.client.query(`SELECT state_json FROM ${tableName} WHERE run_id >= $1 AND run_id < $2`, [prefix, `${prefix}\uffff`]);
1403
- const states = result.rows.flatMap((row) => {
1404
- const state = getRecordField(row, ["state_json", "stateJson"]);
1405
- return state ? [normalizeAgentRunState(state)] : [];
1406
- });
1407
- return listStates(states, listOptions);
1408
- },
1409
- async deleteExpired(retention, scope) {
1410
- const page = await this.list?.({ statuses: retention.statuses, updatedBefore: retention.before, limit: retention.limit ?? 1_000 }, scope);
1411
- for (const state of page.items)
1412
- await this.delete?.(state.runId, state.scope);
1413
- return page.items.length;
1414
- },
1415
- async acquireLease(runId, leaseOptions, scope) {
1416
- validateLeaseOptions(leaseOptions);
1417
- await ensureAllTables();
1418
- const now = leaseOptions.now ?? Date.now();
1419
- const key = dbKey(runId, scope);
1420
- const expiresAt = now + leaseOptions.ttlMs;
1421
- const result = await options.client.query(`INSERT INTO ${leaseTableName} (run_key, run_id, owner_id, expires_at_ms)
1422
- SELECT $1, $2, $3, $4
1423
- WHERE EXISTS (SELECT 1 FROM ${tableName} WHERE run_id = $1)
1424
- ON CONFLICT(run_key) DO UPDATE SET
1425
- owner_id = EXCLUDED.owner_id,
1426
- expires_at_ms = EXCLUDED.expires_at_ms
1427
- WHERE ${leaseTableName}.owner_id = EXCLUDED.owner_id OR ${leaseTableName}.expires_at_ms <= $5
1428
- RETURNING owner_id, expires_at_ms`, [key, runId, leaseOptions.ownerId, expiresAt, now]);
1429
- const owner = getRecordField(result.rows[0], ["owner_id", "ownerId"]);
1430
- return owner === leaseOptions.ownerId ? { runId, ownerId: leaseOptions.ownerId, expiresAt } : undefined;
1431
- },
1432
- async renewLease(runId, leaseOptions, scope) {
1433
- validateLeaseOptions(leaseOptions);
1434
- await ensureAllTables();
1435
- const now = leaseOptions.now ?? Date.now();
1436
- const expiresAt = now + leaseOptions.ttlMs;
1437
- const result = await options.client.query(`UPDATE ${leaseTableName}
1438
- SET expires_at_ms = $3
1439
- WHERE run_key = $1 AND owner_id = $2 AND expires_at_ms > $4
1440
- RETURNING owner_id`, [dbKey(runId, scope), leaseOptions.ownerId, expiresAt, now]);
1441
- return getRecordField(result.rows[0], ["owner_id", "ownerId"]) === leaseOptions.ownerId
1442
- ? { runId, ownerId: leaseOptions.ownerId, expiresAt }
1443
- : undefined;
1444
- },
1445
- async releaseLease(runId, ownerId, scope) {
1446
- await ensureAllTables();
1447
- const result = await options.client.query(`DELETE FROM ${leaseTableName} WHERE run_key = $1 AND owner_id = $2 RETURNING owner_id`, [dbKey(runId, scope), ownerId]);
1448
- return getRecordField(result.rows[0], ["owner_id", "ownerId"]) === ownerId;
1449
- },
1450
- async loadToolCall(runId, toolCallId, scope) {
1451
- await ensureAllTables();
1452
- const result = await options.client.query(`SELECT entry_json FROM ${journalTableName} WHERE run_key = $1 AND tool_call_id = $2`, [dbKey(runId, scope), toolCallId]);
1453
- const entry = getRecordField(result.rows[0], ["entry_json", "entryJson"]);
1454
- return entry && typeof entry === "object" ? cloneJournalEntry(entry) : undefined;
1455
- },
1456
- async loadToolExecution(runId, toolCallId, scope) {
1457
- return this.loadToolCall?.(runId, toolCallId, scope);
1458
- },
1459
- async listToolCalls(runId, scope) {
1460
- await ensureAllTables();
1461
- const result = await options.client.query(`SELECT entry_json FROM ${journalTableName} WHERE run_key = $1 ORDER BY updated_at_ms, tool_call_id`, [dbKey(runId, scope)]);
1462
- return result.rows.flatMap((row) => {
1463
- const entry = getRecordField(row, ["entry_json", "entryJson"]);
1464
- return entry && typeof entry === "object" ? [cloneJournalEntry(entry)] : [];
1465
- });
1466
- },
1467
- async saveToolCall(entry, journalOptions) {
1468
- await ensureAllTables();
1469
- const next = nextJournalEntry(entry, journalOptions);
1470
- const key = dbKey(entry.runId, entry.scope);
1471
- const result = journalOptions?.expectedRevision === undefined
1472
- ? await options.client.query(`INSERT INTO ${journalTableName} (run_key, tool_call_id, entry_json, revision, updated_at_ms)
1473
- VALUES ($1, $2, $3::jsonb, $4, $5)
1474
- ON CONFLICT(run_key, tool_call_id) DO UPDATE SET
1475
- entry_json = EXCLUDED.entry_json,
1476
- revision = EXCLUDED.revision,
1477
- updated_at_ms = EXCLUDED.updated_at_ms
1478
- RETURNING revision`, [key, entry.toolCallId, next, next.revision, next.updatedAt])
1479
- : await options.client.query(`UPDATE ${journalTableName}
1480
- SET entry_json = $3::jsonb,
1481
- revision = $4,
1482
- updated_at_ms = $5
1483
- WHERE run_key = $1 AND tool_call_id = $2 AND revision = $6
1484
- RETURNING revision`, [key, entry.toolCallId, next, next.revision, next.updatedAt, journalOptions.expectedRevision]);
1485
- if (getRecordField(result.rows[0], ["revision"]) === undefined) {
1486
- throw new ConflictError("Agent tool-call journal revision conflict.");
1487
- }
1488
- return next;
1489
- },
1490
- async claimToolExecution(entry) {
1491
- await ensureAllTables();
1492
- const next = nextJournalEntry({ ...entry, status: "running", revision: 0 });
1493
- const result = await options.client.query(`INSERT INTO ${journalTableName} (run_key, tool_call_id, entry_json, revision, updated_at_ms)
1494
- SELECT $1, $2, $3::jsonb, 0, $4
1495
- WHERE EXISTS (SELECT 1 FROM ${tableName} WHERE run_id = $1)
1496
- ON CONFLICT(run_key, tool_call_id) DO NOTHING
1497
- RETURNING entry_json`, [dbKey(entry.runId, entry.scope), entry.toolCallId, next, next.updatedAt]);
1498
- const claimed = getRecordField(result.rows[0], ["entry_json", "entryJson"]);
1499
- if (claimed && typeof claimed === "object")
1500
- return { claimed: true, entry: next };
1501
- const existing = await this.loadToolCall?.(entry.runId, entry.toolCallId, entry.scope);
1502
- if (!existing)
1503
- throw new ValidationError("Cannot journal a tool call for an unknown run.");
1504
- return { claimed: false, entry: existing };
1505
- },
1506
- async completeToolExecution(entry, journalOptions) {
1507
- return this.saveToolCall?.({ ...entry, status: entry.status === "failed" ? "failed" : "completed" }, journalOptions);
1508
- }
1509
- };
1510
- };
1511
- export const createPostgresAgentMemoryStore = (options) => {
1512
- assertPostgresClient(options.client);
1513
- const tableName = validateIdentifier(options.tableName ?? "zhivex_agent_memory", "tableName");
1514
- const keyFor = (context) => (options.key ?? defaultMemoryKey)({ ...context, scope: resolveScope(options.scope, context.scope) });
1515
- const selectMessages = options.selectMessages ?? defaultMemoryMessages;
1516
- const createSql = `
1517
- CREATE TABLE IF NOT EXISTS ${tableName} (
1518
- memory_key TEXT PRIMARY KEY,
1519
- messages_json JSONB NOT NULL,
1520
- updated_at_ms BIGINT NOT NULL
1521
- )
1522
- `;
1523
- return {
1524
- async load(context) {
1525
- await ensurePostgresTable(options.client, tableName, createSql);
1526
- const result = await options.client.query(`SELECT messages_json FROM ${tableName} WHERE memory_key = $1`, [keyFor(context)]);
1527
- return result.rows[0]
1528
- ? (getRecordField(result.rows[0], ["messages_json", "messagesJson"]) ?? [])
1529
- : [];
1530
- },
1531
- async save(context) {
1532
- await ensurePostgresTable(options.client, tableName, createSql);
1533
- await options.client.query(`INSERT INTO ${tableName} (memory_key, messages_json, updated_at_ms)
1534
- VALUES ($1, $2::jsonb, $3)
1535
- ON CONFLICT(memory_key) DO UPDATE SET
1536
- messages_json = EXCLUDED.messages_json,
1537
- updated_at_ms = EXCLUDED.updated_at_ms`, [keyFor(context), selectMessages(context.state), Date.now()]);
1538
- }
1539
- };
1540
- };
1
+ /** Compatibility facade. Backend implementations live in dedicated internal modules. */
2
+ export { createInMemoryAgentRunStore, createInMemoryAgentMemoryStore } from "./agent-store/memory.js";
3
+ export { createFileAgentRunStore, createFileAgentMemoryStore } from "./agent-store/file.js";
4
+ export { createSqliteAgentRunStore, createSqliteAgentMemoryStore } from "./agent-store/sqlite.js";
5
+ export { createPostgresAgentRunStore, createPostgresAgentMemoryStore } from "./agent-store/postgres.js";
1541
6
  //# sourceMappingURL=agent-store.js.map