@retinue/agentkit 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 (405) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +310 -0
  3. package/dist/adapters/bullmq/consumer.d.ts +33 -0
  4. package/dist/adapters/bullmq/consumer.js +41 -0
  5. package/dist/adapters/bullmq/dispatcher.d.ts +74 -0
  6. package/dist/adapters/bullmq/dispatcher.js +160 -0
  7. package/dist/adapters/bullmq/export.d.ts +31 -0
  8. package/dist/adapters/bullmq/export.js +53 -0
  9. package/dist/adapters/bullmq/extraction.d.ts +42 -0
  10. package/dist/adapters/bullmq/extraction.js +63 -0
  11. package/dist/adapters/bullmq/index.d.ts +13 -0
  12. package/dist/adapters/bullmq/index.js +13 -0
  13. package/dist/adapters/bullmq/lock.d.ts +77 -0
  14. package/dist/adapters/bullmq/lock.js +126 -0
  15. package/dist/adapters/bullmq/queue.d.ts +50 -0
  16. package/dist/adapters/bullmq/queue.js +81 -0
  17. package/dist/adapters/memory/artifact-exports.d.ts +11 -0
  18. package/dist/adapters/memory/artifact-exports.js +102 -0
  19. package/dist/adapters/memory/artifacts.d.ts +15 -0
  20. package/dist/adapters/memory/artifacts.js +134 -0
  21. package/dist/adapters/memory/blobs.d.ts +7 -0
  22. package/dist/adapters/memory/blobs.js +27 -0
  23. package/dist/adapters/memory/evaluation.d.ts +18 -0
  24. package/dist/adapters/memory/evaluation.js +148 -0
  25. package/dist/adapters/memory/files.d.ts +27 -0
  26. package/dist/adapters/memory/files.js +0 -0
  27. package/dist/adapters/memory/flows.d.ts +16 -0
  28. package/dist/adapters/memory/flows.js +117 -0
  29. package/dist/adapters/memory/hitl.d.ts +9 -0
  30. package/dist/adapters/memory/hitl.js +130 -0
  31. package/dist/adapters/memory/idempotency.d.ts +13 -0
  32. package/dist/adapters/memory/idempotency.js +32 -0
  33. package/dist/adapters/memory/index.d.ts +39 -0
  34. package/dist/adapters/memory/index.js +107 -0
  35. package/dist/adapters/memory/knowledge.d.ts +43 -0
  36. package/dist/adapters/memory/knowledge.js +248 -0
  37. package/dist/adapters/memory/mcp.d.ts +9 -0
  38. package/dist/adapters/memory/mcp.js +37 -0
  39. package/dist/adapters/memory/message-store.d.ts +17 -0
  40. package/dist/adapters/memory/message-store.js +70 -0
  41. package/dist/adapters/memory/principal-memory.d.ts +7 -0
  42. package/dist/adapters/memory/principal-memory.js +83 -0
  43. package/dist/adapters/memory/runtime.d.ts +29 -0
  44. package/dist/adapters/memory/runtime.js +0 -0
  45. package/dist/adapters/memory/sessions.d.ts +29 -0
  46. package/dist/adapters/memory/sessions.js +0 -0
  47. package/dist/adapters/memory/skills.d.ts +10 -0
  48. package/dist/adapters/memory/skills.js +41 -0
  49. package/dist/adapters/memory/thread-summary.d.ts +7 -0
  50. package/dist/adapters/memory/thread-summary.js +29 -0
  51. package/dist/adapters/memory/usage-limits.d.ts +13 -0
  52. package/dist/adapters/memory/usage-limits.js +72 -0
  53. package/dist/adapters/memory/usage.d.ts +16 -0
  54. package/dist/adapters/memory/usage.js +279 -0
  55. package/dist/adapters/otel/index.d.ts +111 -0
  56. package/dist/adapters/otel/index.js +133 -0
  57. package/dist/adapters/postgres/artifact-exports.d.ts +12 -0
  58. package/dist/adapters/postgres/artifact-exports.js +117 -0
  59. package/dist/adapters/postgres/artifacts.d.ts +16 -0
  60. package/dist/adapters/postgres/artifacts.js +172 -0
  61. package/dist/adapters/postgres/checkpoint-store.d.ts +16 -0
  62. package/dist/adapters/postgres/checkpoint-store.js +34 -0
  63. package/dist/adapters/postgres/config.d.ts +15 -0
  64. package/dist/adapters/postgres/config.js +187 -0
  65. package/dist/adapters/postgres/conversation-store.d.ts +4 -0
  66. package/dist/adapters/postgres/conversation-store.js +82 -0
  67. package/dist/adapters/postgres/evaluation.d.ts +17 -0
  68. package/dist/adapters/postgres/evaluation.js +193 -0
  69. package/dist/adapters/postgres/file-content.d.ts +30 -0
  70. package/dist/adapters/postgres/file-content.js +111 -0
  71. package/dist/adapters/postgres/files.d.ts +19 -0
  72. package/dist/adapters/postgres/files.js +209 -0
  73. package/dist/adapters/postgres/flows.d.ts +20 -0
  74. package/dist/adapters/postgres/flows.js +206 -0
  75. package/dist/adapters/postgres/hitl.d.ts +5 -0
  76. package/dist/adapters/postgres/hitl.js +247 -0
  77. package/dist/adapters/postgres/index.d.ts +35 -0
  78. package/dist/adapters/postgres/index.js +35 -0
  79. package/dist/adapters/postgres/knowledge.d.ts +48 -0
  80. package/dist/adapters/postgres/knowledge.js +255 -0
  81. package/dist/adapters/postgres/memory.d.ts +14 -0
  82. package/dist/adapters/postgres/memory.js +194 -0
  83. package/dist/adapters/postgres/message-store.d.ts +11 -0
  84. package/dist/adapters/postgres/message-store.js +145 -0
  85. package/dist/adapters/postgres/migrations.d.ts +69 -0
  86. package/dist/adapters/postgres/migrations.js +1594 -0
  87. package/dist/adapters/postgres/pg-executor.d.ts +19 -0
  88. package/dist/adapters/postgres/pg-executor.js +32 -0
  89. package/dist/adapters/postgres/retention.d.ts +26 -0
  90. package/dist/adapters/postgres/retention.js +59 -0
  91. package/dist/adapters/postgres/rollups.d.ts +17 -0
  92. package/dist/adapters/postgres/rollups.js +267 -0
  93. package/dist/adapters/postgres/run-coordinator.d.ts +5 -0
  94. package/dist/adapters/postgres/run-coordinator.js +98 -0
  95. package/dist/adapters/postgres/run-event-log.d.ts +26 -0
  96. package/dist/adapters/postgres/run-event-log.js +30 -0
  97. package/dist/adapters/postgres/run-store.d.ts +4 -0
  98. package/dist/adapters/postgres/run-store.js +199 -0
  99. package/dist/adapters/postgres/schema.d.ts +39 -0
  100. package/dist/adapters/postgres/schema.js +70 -0
  101. package/dist/adapters/postgres/session-state.d.ts +7 -0
  102. package/dist/adapters/postgres/session-state.js +99 -0
  103. package/dist/adapters/postgres/sql.d.ts +8 -0
  104. package/dist/adapters/postgres/sql.js +2 -0
  105. package/dist/adapters/postgres/transaction.d.ts +37 -0
  106. package/dist/adapters/postgres/transaction.js +93 -0
  107. package/dist/adapters/postgres/unit-of-work.d.ts +18 -0
  108. package/dist/adapters/postgres/unit-of-work.js +8 -0
  109. package/dist/adapters/postgres/usage-limits.d.ts +15 -0
  110. package/dist/adapters/postgres/usage-limits.js +136 -0
  111. package/dist/adapters/postgres/usage.d.ts +15 -0
  112. package/dist/adapters/postgres/usage.js +226 -0
  113. package/dist/adapters/redis/index.d.ts +9 -0
  114. package/dist/adapters/redis/index.js +9 -0
  115. package/dist/adapters/redis/realtime.d.ts +74 -0
  116. package/dist/adapters/redis/realtime.js +112 -0
  117. package/dist/adapters/supabase/index.d.ts +88 -0
  118. package/dist/adapters/supabase/index.js +84 -0
  119. package/dist/adapters/supabase/realtime.d.ts +30 -0
  120. package/dist/adapters/supabase/realtime.js +53 -0
  121. package/dist/adapters/supabase/rls.d.ts +99 -0
  122. package/dist/adapters/supabase/rls.js +216 -0
  123. package/dist/adapters/supabase/storage.d.ts +50 -0
  124. package/dist/adapters/supabase/storage.js +207 -0
  125. package/dist/agents/agent.d.ts +66 -0
  126. package/dist/agents/agent.js +209 -0
  127. package/dist/agents/define.d.ts +21 -0
  128. package/dist/agents/define.js +63 -0
  129. package/dist/agents/engine.d.ts +98 -0
  130. package/dist/agents/engine.js +462 -0
  131. package/dist/agents/index.d.ts +50 -0
  132. package/dist/agents/index.js +17 -0
  133. package/dist/artifacts/index.d.ts +114 -0
  134. package/dist/artifacts/index.js +219 -0
  135. package/dist/authorization/index.d.ts +76 -0
  136. package/dist/authorization/index.js +76 -0
  137. package/dist/capabilities/index.d.ts +120 -0
  138. package/dist/capabilities/index.js +167 -0
  139. package/dist/capabilities/runtime.d.ts +89 -0
  140. package/dist/capabilities/runtime.js +84 -0
  141. package/dist/citations/index.d.ts +161 -0
  142. package/dist/citations/index.js +182 -0
  143. package/dist/context/assembler.d.ts +82 -0
  144. package/dist/context/assembler.js +129 -0
  145. package/dist/context/compaction.d.ts +45 -0
  146. package/dist/context/compaction.js +55 -0
  147. package/dist/context/index.d.ts +75 -0
  148. package/dist/context/index.js +17 -0
  149. package/dist/core/content-parts.d.ts +194 -0
  150. package/dist/core/content-parts.js +23 -0
  151. package/dist/core/context.d.ts +51 -0
  152. package/dist/core/context.js +9 -0
  153. package/dist/core/env.d.ts +25 -0
  154. package/dist/core/env.js +41 -0
  155. package/dist/core/errors.d.ts +30 -0
  156. package/dist/core/errors.js +65 -0
  157. package/dist/core/events.d.ts +139 -0
  158. package/dist/core/events.js +99 -0
  159. package/dist/core/ids.d.ts +52 -0
  160. package/dist/core/ids.js +13 -0
  161. package/dist/core/index.d.ts +9 -0
  162. package/dist/core/index.js +9 -0
  163. package/dist/core/tokens.d.ts +22 -0
  164. package/dist/core/tokens.js +22 -0
  165. package/dist/core/validation.d.ts +34 -0
  166. package/dist/core/validation.js +176 -0
  167. package/dist/documents/extraction.d.ts +121 -0
  168. package/dist/documents/extraction.js +293 -0
  169. package/dist/documents/index.d.ts +199 -0
  170. package/dist/documents/index.js +65 -0
  171. package/dist/documents/parsers/pdf.d.ts +47 -0
  172. package/dist/documents/parsers/pdf.js +508 -0
  173. package/dist/documents/parsers/text.d.ts +59 -0
  174. package/dist/documents/parsers/text.js +325 -0
  175. package/dist/documents/read-tool.d.ts +52 -0
  176. package/dist/documents/read-tool.js +109 -0
  177. package/dist/documents/render.d.ts +29 -0
  178. package/dist/documents/render.js +59 -0
  179. package/dist/documents/vision.d.ts +159 -0
  180. package/dist/documents/vision.js +214 -0
  181. package/dist/entries/adapters-bullmq.d.ts +8 -0
  182. package/dist/entries/adapters-bullmq.js +8 -0
  183. package/dist/entries/adapters-otel.d.ts +13 -0
  184. package/dist/entries/adapters-otel.js +13 -0
  185. package/dist/entries/adapters-postgres.d.ts +10 -0
  186. package/dist/entries/adapters-postgres.js +10 -0
  187. package/dist/entries/adapters-redis.d.ts +3 -0
  188. package/dist/entries/adapters-redis.js +3 -0
  189. package/dist/entries/context.d.ts +20 -0
  190. package/dist/entries/context.js +20 -0
  191. package/dist/entries/flows.d.ts +15 -0
  192. package/dist/entries/flows.js +15 -0
  193. package/dist/entries/hitl.d.ts +10 -0
  194. package/dist/entries/hitl.js +10 -0
  195. package/dist/entries/knowledge.d.ts +18 -0
  196. package/dist/entries/knowledge.js +19 -0
  197. package/dist/entries/mcp.d.ts +10 -0
  198. package/dist/entries/mcp.js +10 -0
  199. package/dist/entries/observability.d.ts +14 -0
  200. package/dist/entries/observability.js +16 -0
  201. package/dist/entries/persistence.d.ts +11 -0
  202. package/dist/entries/persistence.js +11 -0
  203. package/dist/entries/providers.d.ts +14 -0
  204. package/dist/entries/providers.js +14 -0
  205. package/dist/entries/runtime.d.ts +13 -0
  206. package/dist/entries/runtime.js +13 -0
  207. package/dist/entries/server.d.ts +24 -0
  208. package/dist/entries/server.js +24 -0
  209. package/dist/entries/tools.d.ts +21 -0
  210. package/dist/entries/tools.js +21 -0
  211. package/dist/entries/usage.d.ts +10 -0
  212. package/dist/entries/usage.js +10 -0
  213. package/dist/evaluation/gate.d.ts +168 -0
  214. package/dist/evaluation/gate.js +180 -0
  215. package/dist/evaluation/graders.d.ts +125 -0
  216. package/dist/evaluation/graders.js +203 -0
  217. package/dist/evaluation/index.d.ts +120 -0
  218. package/dist/evaluation/index.js +183 -0
  219. package/dist/evaluation/judge.d.ts +75 -0
  220. package/dist/evaluation/judge.js +111 -0
  221. package/dist/export/index.d.ts +162 -0
  222. package/dist/export/index.js +363 -0
  223. package/dist/export/markdown.d.ts +19 -0
  224. package/dist/export/markdown.js +29 -0
  225. package/dist/export/pdf.d.ts +73 -0
  226. package/dist/export/pdf.js +407 -0
  227. package/dist/files/context.d.ts +97 -0
  228. package/dist/files/context.js +185 -0
  229. package/dist/files/index.d.ts +210 -0
  230. package/dist/files/index.js +338 -0
  231. package/dist/files/read-tool.d.ts +81 -0
  232. package/dist/files/read-tool.js +163 -0
  233. package/dist/files/turn-parts.d.ts +96 -0
  234. package/dist/files/turn-parts.js +171 -0
  235. package/dist/flows/index.d.ts +270 -0
  236. package/dist/flows/index.js +62 -0
  237. package/dist/flows/interpreter.d.ts +146 -0
  238. package/dist/flows/interpreter.js +426 -0
  239. package/dist/flows/runner.d.ts +145 -0
  240. package/dist/flows/runner.js +270 -0
  241. package/dist/graphql/index.d.ts +8 -0
  242. package/dist/graphql/index.js +8 -0
  243. package/dist/graphql/resolvers.d.ts +237 -0
  244. package/dist/graphql/resolvers.js +253 -0
  245. package/dist/graphql/schema.d.ts +11 -0
  246. package/dist/graphql/schema.js +258 -0
  247. package/dist/graphql/sse.d.ts +77 -0
  248. package/dist/graphql/sse.js +100 -0
  249. package/dist/hitl/approved-execution.d.ts +127 -0
  250. package/dist/hitl/approved-execution.js +177 -0
  251. package/dist/hitl/index.d.ts +79 -0
  252. package/dist/hitl/index.js +12 -0
  253. package/dist/hitl/service.d.ts +221 -0
  254. package/dist/hitl/service.js +268 -0
  255. package/dist/idempotency/index.d.ts +70 -0
  256. package/dist/idempotency/index.js +59 -0
  257. package/dist/index.d.ts +103 -0
  258. package/dist/index.js +60 -0
  259. package/dist/knowledge/chunking.d.ts +57 -0
  260. package/dist/knowledge/chunking.js +158 -0
  261. package/dist/knowledge/index.d.ts +119 -0
  262. package/dist/knowledge/index.js +166 -0
  263. package/dist/knowledge/retrieval.d.ts +146 -0
  264. package/dist/knowledge/retrieval.js +170 -0
  265. package/dist/loadtest/harness.d.ts +168 -0
  266. package/dist/loadtest/harness.js +507 -0
  267. package/dist/loadtest/index.d.ts +13 -0
  268. package/dist/loadtest/index.js +13 -0
  269. package/dist/loadtest/injection.d.ts +89 -0
  270. package/dist/loadtest/injection.js +147 -0
  271. package/dist/loadtest/metrics.d.ts +197 -0
  272. package/dist/loadtest/metrics.js +160 -0
  273. package/dist/loadtest/runbooks.d.ts +28 -0
  274. package/dist/loadtest/runbooks.js +159 -0
  275. package/dist/loadtest/scenario.d.ts +104 -0
  276. package/dist/loadtest/scenario.js +208 -0
  277. package/dist/mcp/egress.d.ts +53 -0
  278. package/dist/mcp/egress.js +115 -0
  279. package/dist/mcp/index.d.ts +93 -0
  280. package/dist/mcp/index.js +33 -0
  281. package/dist/mcp/provider.d.ts +62 -0
  282. package/dist/mcp/provider.js +0 -0
  283. package/dist/models/index.d.ts +98 -0
  284. package/dist/models/index.js +74 -0
  285. package/dist/models/pricing.d.ts +24 -0
  286. package/dist/models/pricing.js +37 -0
  287. package/dist/models/provider-factory.d.ts +31 -0
  288. package/dist/models/provider-factory.js +67 -0
  289. package/dist/models/streaming.d.ts +145 -0
  290. package/dist/models/streaming.js +272 -0
  291. package/dist/models/vision.d.ts +38 -0
  292. package/dist/models/vision.js +62 -0
  293. package/dist/persistence/index.d.ts +1654 -0
  294. package/dist/persistence/index.js +226 -0
  295. package/dist/principal-memory/index.d.ts +106 -0
  296. package/dist/principal-memory/index.js +89 -0
  297. package/dist/retention/index.d.ts +89 -0
  298. package/dist/retention/index.js +70 -0
  299. package/dist/runtime/checkpoint.d.ts +37 -0
  300. package/dist/runtime/checkpoint.js +22 -0
  301. package/dist/runtime/index.d.ts +118 -0
  302. package/dist/runtime/index.js +69 -0
  303. package/dist/runtime/retry.d.ts +95 -0
  304. package/dist/runtime/retry.js +126 -0
  305. package/dist/runtime/serialization.d.ts +85 -0
  306. package/dist/runtime/serialization.js +95 -0
  307. package/dist/runtime/streaming.d.ts +54 -0
  308. package/dist/runtime/streaming.js +115 -0
  309. package/dist/runtime/worker.d.ts +130 -0
  310. package/dist/runtime/worker.js +405 -0
  311. package/dist/security/checklist.d.ts +53 -0
  312. package/dist/security/checklist.js +204 -0
  313. package/dist/security/findings.d.ts +56 -0
  314. package/dist/security/findings.js +168 -0
  315. package/dist/security/index.d.ts +14 -0
  316. package/dist/security/index.js +14 -0
  317. package/dist/security/prompt-safety.d.ts +100 -0
  318. package/dist/security/prompt-safety.js +133 -0
  319. package/dist/server/boot.d.ts +32 -0
  320. package/dist/server/boot.js +36 -0
  321. package/dist/server/cli-worker.d.ts +37 -0
  322. package/dist/server/cli-worker.js +151 -0
  323. package/dist/server/cli.d.ts +27 -0
  324. package/dist/server/cli.js +74 -0
  325. package/dist/server/config.d.ts +42 -0
  326. package/dist/server/config.js +127 -0
  327. package/dist/server/health.d.ts +59 -0
  328. package/dist/server/health.js +90 -0
  329. package/dist/server/host.d.ts +39 -0
  330. package/dist/server/host.js +124 -0
  331. package/dist/server/index.d.ts +15 -0
  332. package/dist/server/index.js +15 -0
  333. package/dist/server/main.d.ts +16 -0
  334. package/dist/server/main.js +31 -0
  335. package/dist/server/sse-route.d.ts +21 -0
  336. package/dist/server/sse-route.js +282 -0
  337. package/dist/skills/index.d.ts +67 -0
  338. package/dist/skills/index.js +31 -0
  339. package/dist/skills/resolver.d.ts +54 -0
  340. package/dist/skills/resolver.js +121 -0
  341. package/dist/teams/index.d.ts +93 -0
  342. package/dist/teams/index.js +207 -0
  343. package/dist/telemetry/index.d.ts +157 -0
  344. package/dist/telemetry/index.js +71 -0
  345. package/dist/telemetry/instrument.d.ts +108 -0
  346. package/dist/telemetry/instrument.js +232 -0
  347. package/dist/telemetry/log-events.d.ts +17 -0
  348. package/dist/telemetry/log-events.js +58 -0
  349. package/dist/telemetry/metrics.d.ts +123 -0
  350. package/dist/telemetry/metrics.js +135 -0
  351. package/dist/telemetry/noop.d.ts +39 -0
  352. package/dist/telemetry/noop.js +143 -0
  353. package/dist/telemetry/redaction.d.ts +64 -0
  354. package/dist/telemetry/redaction.js +153 -0
  355. package/dist/telemetry/spans.d.ts +56 -0
  356. package/dist/telemetry/spans.js +78 -0
  357. package/dist/telemetry/trace-context.d.ts +55 -0
  358. package/dist/telemetry/trace-context.js +60 -0
  359. package/dist/toolkit/compute.d.ts +53 -0
  360. package/dist/toolkit/compute.js +152 -0
  361. package/dist/toolkit/data.d.ts +98 -0
  362. package/dist/toolkit/data.js +235 -0
  363. package/dist/toolkit/http.d.ts +113 -0
  364. package/dist/toolkit/http.js +205 -0
  365. package/dist/toolkit/index.d.ts +21 -0
  366. package/dist/toolkit/index.js +17 -0
  367. package/dist/toolkit/web.d.ts +107 -0
  368. package/dist/toolkit/web.js +147 -0
  369. package/dist/tools/define.d.ts +25 -0
  370. package/dist/tools/define.js +45 -0
  371. package/dist/tools/delegating.d.ts +132 -0
  372. package/dist/tools/delegating.js +211 -0
  373. package/dist/tools/index.d.ts +129 -0
  374. package/dist/tools/index.js +33 -0
  375. package/dist/tools/library/compute.d.ts +11 -0
  376. package/dist/tools/library/compute.js +46 -0
  377. package/dist/tools/library/data.d.ts +16 -0
  378. package/dist/tools/library/data.js +92 -0
  379. package/dist/tools/library/http.d.ts +28 -0
  380. package/dist/tools/library/http.js +71 -0
  381. package/dist/tools/library/index.d.ts +97 -0
  382. package/dist/tools/library/index.js +134 -0
  383. package/dist/tools/library/knowledge.d.ts +39 -0
  384. package/dist/tools/library/knowledge.js +58 -0
  385. package/dist/tools/library/web.d.ts +19 -0
  386. package/dist/tools/library/web.js +65 -0
  387. package/dist/tools/meta-tools.d.ts +19 -0
  388. package/dist/tools/meta-tools.js +36 -0
  389. package/dist/tools/registry.d.ts +146 -0
  390. package/dist/tools/registry.js +291 -0
  391. package/dist/usage/index.d.ts +105 -0
  392. package/dist/usage/index.js +20 -0
  393. package/dist/usage/quota.d.ts +258 -0
  394. package/dist/usage/quota.js +510 -0
  395. package/dist/usage/recorder.d.ts +29 -0
  396. package/dist/usage/recorder.js +96 -0
  397. package/dist/usage/rollups.d.ts +121 -0
  398. package/dist/usage/rollups.js +157 -0
  399. package/dist/worker/export.d.ts +57 -0
  400. package/dist/worker/export.js +81 -0
  401. package/dist/worker/extraction.d.ts +57 -0
  402. package/dist/worker/extraction.js +84 -0
  403. package/dist/worker/main.d.ts +103 -0
  404. package/dist/worker/main.js +159 -0
  405. package/package.json +187 -0
@@ -0,0 +1,255 @@
1
+ /**
2
+ * pgvector `KnowledgeStore` and `VectorIndex` (#135).
3
+ *
4
+ * One table backs both ports, which is the point of splitting them: a deployment on pgvector answers both from
5
+ * `knowledge_chunks`, and a deployment on a dedicated vector database answers them from two systems, with
6
+ * nothing above changing.
7
+ *
8
+ * **AC-3 is a `WHERE` clause, and it is the reason this adapter exists rather than a filter above it.** The
9
+ * permission predicate is in the same statement as the nearest-neighbour scan, so an unauthorised chunk is
10
+ * never a candidate. Filtering afterwards leaks through result counts — ask for ten, get three, and you have
11
+ * learned that seven exist you may not see. `EXPLAIN` shows the predicate inside the scan, which is what the
12
+ * test asserts.
13
+ *
14
+ * **`replaceSource` is one transaction-shaped pair of statements.** Delete-then-insert, in that order, so a
15
+ * changed document's old chunks are never searchable alongside its new ones. Appending would leave a stale
16
+ * chunk that cites text no longer in the document.
17
+ */
18
+ import { AgentPlatformError } from "../../core/errors.js";
19
+ import { stripStopwords } from "../../persistence/index.js";
20
+ import { VECTOR_DIMENSIONS } from "./migrations.js";
21
+ const iso = (v) => (v instanceof Date ? v.toISOString() : v);
22
+ const toChunk = (r) => ({
23
+ id: r.id,
24
+ sourceType: r.source_type,
25
+ sourceId: r.source_id,
26
+ chunkIndex: Number(r.chunk_index),
27
+ content: r.content,
28
+ tokenCount: Number(r.token_count),
29
+ authSubject: r.auth_subject,
30
+ embeddingModel: {
31
+ modelId: r.embedding_model,
32
+ version: r.embedding_version,
33
+ dimensions: Number(r.embedding_dims),
34
+ },
35
+ ...(r.locator === null ? {} : { locator: r.locator }),
36
+ createdAt: iso(r.created_at),
37
+ });
38
+ const COLUMNS = `id, source_type, source_id, chunk_index, content, token_count, auth_subject,
39
+ embedding_model, embedding_version, embedding_dims, locator, created_at`;
40
+ /**
41
+ * pgvector's literal form: `[0.1,0.2,...]`.
42
+ *
43
+ * Built as a string rather than passed as an array, because node-postgres has no vector type and would send an
44
+ * array as `{...}` — which pgvector rejects. `Number.isFinite` is checked because a `NaN` in a vector makes
45
+ * every distance `NaN`, and the symptom is a search that returns nothing rather than an error.
46
+ */
47
+ export const toVectorLiteral = (embedding) => {
48
+ if (embedding.length !== VECTOR_DIMENSIONS)
49
+ throw new AgentPlatformError({
50
+ code: "invalid_input",
51
+ message: `embedding has ${embedding.length} dimensions but the schema declares ${VECTOR_DIMENSIONS}`,
52
+ retryable: false,
53
+ });
54
+ for (const value of embedding)
55
+ if (!Number.isFinite(value))
56
+ throw new AgentPlatformError({
57
+ code: "invalid_input",
58
+ message: "embedding contains a non-finite value",
59
+ retryable: false,
60
+ });
61
+ return `[${embedding.join(",")}]`;
62
+ };
63
+ export const createPostgresKnowledgeStore = (sql) => ({
64
+ async replaceSource({ tenantId, sourceType, sourceId, chunks }) {
65
+ for (const chunk of chunks) {
66
+ if (chunk.embedding.length !== chunk.embeddingModel.dimensions)
67
+ // Caught before the write: a vector of the wrong length would score against whatever prefix overlapped,
68
+ // which looks like bad relevance rather than a bug.
69
+ throw new AgentPlatformError({
70
+ code: "invalid_input",
71
+ message: `chunk ${chunk.id} has ${chunk.embedding.length} dimensions but its model declares ${chunk.embeddingModel.dimensions}`,
72
+ retryable: false,
73
+ });
74
+ }
75
+ // Delete first. Old chunks must never be searchable next to new ones: a stale chunk cites text that is no
76
+ // longer in the document, which is worse than the document being briefly absent.
77
+ const removedRows = await sql.query(`DELETE FROM knowledge_chunks
78
+ WHERE tenant_id = $1 AND source_type = $2 AND source_id = $3
79
+ RETURNING id`, [tenantId, sourceType, sourceId]);
80
+ for (const chunk of chunks) {
81
+ await sql.query(`INSERT INTO knowledge_chunks (tenant_id, id, source_type, source_id, chunk_index, content,
82
+ token_count, auth_subject, embedding, embedding_model,
83
+ embedding_version, embedding_dims, locator, created_at)
84
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::vector,$10,$11,$12,$13,$14)
85
+ -- A re-run of the same batch overwrites its own rows rather than failing: an interrupted re-index is
86
+ -- resumed by running it again, and that has to be safe (AC-5).
87
+ ON CONFLICT (tenant_id, id) DO UPDATE SET
88
+ chunk_index = EXCLUDED.chunk_index,
89
+ content = EXCLUDED.content,
90
+ token_count = EXCLUDED.token_count,
91
+ auth_subject = EXCLUDED.auth_subject,
92
+ embedding = EXCLUDED.embedding,
93
+ embedding_model = EXCLUDED.embedding_model,
94
+ embedding_version = EXCLUDED.embedding_version,
95
+ embedding_dims = EXCLUDED.embedding_dims,
96
+ locator = EXCLUDED.locator`, [
97
+ tenantId,
98
+ chunk.id,
99
+ sourceType,
100
+ sourceId,
101
+ chunk.chunkIndex,
102
+ chunk.content,
103
+ chunk.tokenCount,
104
+ chunk.authSubject,
105
+ toVectorLiteral(chunk.embedding),
106
+ chunk.embeddingModel.modelId,
107
+ chunk.embeddingModel.version,
108
+ chunk.embeddingModel.dimensions,
109
+ chunk.locator ?? null,
110
+ chunk.createdAt,
111
+ ]);
112
+ }
113
+ return { written: chunks.length, removed: removedRows.length };
114
+ },
115
+ async listBySource({ tenantId, sourceType, sourceId, limit, cursor }) {
116
+ // Cursored on `chunk_index`, which is contiguous within a source and therefore exact -- and "the chunks of
117
+ // this document" is an ordered thing, so the order must be the document's.
118
+ const after = cursor === undefined ? -1 : Number.parseInt(cursor, 10);
119
+ const from = Number.isSafeInteger(after) ? after : -1;
120
+ const rows = await sql.query(`SELECT ${COLUMNS} FROM knowledge_chunks
121
+ WHERE tenant_id = $1 AND source_type = $2 AND source_id = $3 AND chunk_index > $4
122
+ ORDER BY chunk_index
123
+ LIMIT $5`, [tenantId, sourceType, sourceId, from, limit + 1]);
124
+ const items = rows.slice(0, limit).map(toChunk);
125
+ const last = items[items.length - 1];
126
+ return rows.length > limit && last !== undefined
127
+ ? { items, nextCursor: String(last.chunkIndex) }
128
+ : { items };
129
+ },
130
+ async get({ tenantId, id }) {
131
+ const rows = await sql.query(`SELECT ${COLUMNS} FROM knowledge_chunks WHERE tenant_id = $1 AND id = $2`, [tenantId, id]);
132
+ // A foreign id yields no row, so it is null without a comparison anyone could get wrong.
133
+ return rows[0] === undefined ? null : toChunk(rows[0]);
134
+ },
135
+ async deleteSource({ tenantId, sourceType, sourceId }) {
136
+ const rows = await sql.query(`DELETE FROM knowledge_chunks
137
+ WHERE tenant_id = $1 AND source_type = $2 AND source_id = $3
138
+ RETURNING id`, [tenantId, sourceType, sourceId]);
139
+ return { removed: rows.length };
140
+ },
141
+ async listStaleSources({ tenantId, current, limit, cursor }) {
142
+ const rows = await sql.query(`SELECT source_type, source_id, COUNT(*) AS chunk_count
143
+ FROM knowledge_chunks
144
+ WHERE tenant_id = $1
145
+ -- Anything not embedded by exactly this model. Derived from what is *stored*, which is what makes an
146
+ -- interrupted re-index resumable: it asks again rather than remembering where it was.
147
+ AND NOT (embedding_model = $2 AND embedding_version = $3 AND embedding_dims = $4)
148
+ AND ($5::text IS NULL OR (source_type || ' ' || source_id) > $5::text)
149
+ GROUP BY source_type, source_id
150
+ -- Stable order, so a resumed re-index does not revisit sources it finished and skip ones it did not.
151
+ ORDER BY source_type, source_id
152
+ LIMIT $6`, [tenantId, current.modelId, current.version, current.dimensions, cursor ?? null, limit + 1]);
153
+ const items = rows.slice(0, limit).map((r) => ({
154
+ sourceType: r.source_type,
155
+ sourceId: r.source_id,
156
+ chunkCount: Number(r.chunk_count),
157
+ }));
158
+ const last = items[items.length - 1];
159
+ return rows.length > limit && last !== undefined
160
+ ? { items, nextCursor: `${last.sourceType} ${last.sourceId}` }
161
+ : { items };
162
+ },
163
+ });
164
+ export const createPostgresVectorIndex = (sql) => ({
165
+ async search({ tenantId, embedding, authSubjects, limit, minScore, sourceTypes }) {
166
+ // An empty subject list means "no subjects", which correctly returns nothing. Short-circuited so the
167
+ // database is not asked a question whose answer is already known -- and so `= ANY('{}')` semantics are
168
+ // never relied on.
169
+ if (authSubjects.length === 0)
170
+ return [];
171
+ const rows = await sql.query(`SELECT ${COLUMNS},
172
+ -- Cosine *distance* is 0 (identical) to 2 (opposite); the ports normalise to a 0-1 score where
173
+ -- higher is closer, so a caller never has to know the metric.
174
+ 1 - (embedding <=> $2::vector) / 2 AS score
175
+ FROM knowledge_chunks
176
+ WHERE tenant_id = $1
177
+ -- AC-3: the permission predicate is in the same statement as the scan, so an unauthorised chunk is
178
+ -- never a candidate. EXPLAIN shows it inside the scan, which is what the test asserts.
179
+ AND auth_subject = ANY($3::text[])
180
+ AND ($4::text[] IS NULL OR source_type = ANY($4::text[]))
181
+ AND ($5::float8 IS NULL OR 1 - (embedding <=> $2::vector) / 2 >= $5::float8)
182
+ ORDER BY embedding <=> $2::vector, id
183
+ LIMIT $6`, [
184
+ tenantId,
185
+ toVectorLiteral(embedding),
186
+ authSubjects,
187
+ sourceTypes === undefined ? null : [...sourceTypes],
188
+ minScore ?? null,
189
+ limit,
190
+ ]);
191
+ return rows.map((r) => ({ chunk: toChunk(r), score: Number(r.score) }));
192
+ },
193
+ });
194
+ /** Re-exported so a caller comparing a stored chunk's model to the current one does it the same way. */
195
+ export const sameEmbeddingModel = (a, b) => a.modelId === b.modelId && a.version === b.version && a.dimensions === b.dimensions;
196
+ /**
197
+ * Postgres full-text `KeywordIndex` (#136).
198
+ *
199
+ * Over `knowledge_chunks.content_tsv`, the same rows the vector index searches, so a chunk cannot be visible to
200
+ * one signal and invisible to the other.
201
+ *
202
+ * **`websearch_to_tsquery`, not `to_tsquery`.** The latter throws on ordinary user input — an unbalanced quote,
203
+ * a bare `&`, a trailing operator — and a search box that errors on a stray character is a search box that
204
+ * errors. `websearch_to_tsquery` accepts anything and interprets quotes and `or` the way a person expects.
205
+ *
206
+ * **The score is normalised against the best hit in the result set**, not against an absolute. `ts_rank_cd`
207
+ * has no upper bound and its scale depends on the corpus, so an absolute threshold would mean different things
208
+ * in different tenants — and fusing an unbounded score with a bounded cosine similarity would let the
209
+ * unbounded one dominate whenever it happened to be larger.
210
+ */
211
+ export const createPostgresKeywordIndex = (sql) => ({
212
+ async search({ tenantId, query, authSubjects, limit, minScore, sourceTypes }) {
213
+ // "No subjects" means nothing, not everything. Short-circuited so the database is never asked a question
214
+ // whose answer is already known.
215
+ if (authSubjects.length === 0)
216
+ return [];
217
+ // Stopwords come off the query, not the index. `simple` is chosen over `english` so identifiers survive
218
+ // (`english` stems, which destroys `ERR-4021`), and the price of `simple` is that it keeps stopwords —
219
+ // paid here rather than by matching every document that says "the".
220
+ const terms = stripStopwords(query);
221
+ if (terms === "")
222
+ return [];
223
+ const rows = await sql.query(`WITH q AS (SELECT websearch_to_tsquery('simple', $2) AS tsq),
224
+ scored AS (
225
+ SELECT ${COLUMNS},
226
+ -- cd = cover density: it rewards query terms appearing close together, which is what
227
+ -- distinguishes a chunk *about* the terms from one that mentions them separately.
228
+ ts_rank_cd(content_tsv, q.tsq) AS rank
229
+ FROM knowledge_chunks, q
230
+ WHERE tenant_id = $1
231
+ -- AC-3, for the keyword signal too: the permission predicate is in the same statement as the
232
+ -- match, so an excluded chunk is never a candidate and never influences the ranking of the
233
+ -- ones that are.
234
+ AND auth_subject = ANY($3::text[])
235
+ AND ($4::text[] IS NULL OR source_type = ANY($4::text[]))
236
+ AND content_tsv @@ q.tsq
237
+ )
238
+ SELECT *, rank / NULLIF(MAX(rank) OVER (), 0) AS normalised
239
+ FROM scored
240
+ ORDER BY rank DESC, id
241
+ LIMIT $5`, [tenantId, terms, authSubjects, sourceTypes === undefined ? null : [...sourceTypes], limit]);
242
+ return rows
243
+ .map((r) => {
244
+ const normalised = r.normalised;
245
+ return {
246
+ chunk: toChunk(r),
247
+ // `NULLIF(...,0)` yields null when every rank is zero, which cannot happen for a matching row but is
248
+ // cheaper to handle than to prove impossible.
249
+ score: normalised === null ? 0 : Number(normalised),
250
+ };
251
+ })
252
+ .filter((h) => minScore === undefined || h.score >= minScore);
253
+ },
254
+ });
255
+ //# sourceMappingURL=knowledge.js.map
@@ -0,0 +1,14 @@
1
+ import type { BlobStore } from "../../persistence/index.js";
2
+ import type { PrincipalMemoryStore } from "../../principal-memory/index.js";
3
+ import type { SqlExecutor } from "./sql.js";
4
+ export declare const createPostgresPrincipalMemoryStore: (sql: SqlExecutor, options?: {
5
+ readonly clock?: () => string;
6
+ }) => PrincipalMemoryStore;
7
+ /**
8
+ * The tool-output offload. Stores the **value**, because that is what the port does: `put(value)`
9
+ * returns a ref and `get(ref)` must hand the value back. The `blob_refs` metadata-and-pointer table
10
+ * the SPEC described cannot implement this — there is nothing in the port to fetch bytes with — and
11
+ * belongs to `FileMetadataStore` (#129) or `ArtifactStore` (#133) instead.
12
+ */
13
+ export declare const createPostgresBlobStore: (sql: SqlExecutor) => BlobStore;
14
+ //# sourceMappingURL=memory.d.ts.map
@@ -0,0 +1,194 @@
1
+ /**
2
+ * PostgreSQL `PrincipalMemoryStore` and `BlobStore` (#102) — per-user memory and the tool-output
3
+ * offload. Completes the Postgres column at 19/19.
4
+ *
5
+ * Per-user memory is the feature most visibly broken by memory-only storage: a user says "remember
6
+ * this" and it is gone after the next deploy. Two properties carry the weight, and both are enforced
7
+ * in the statement rather than by convention:
8
+ *
9
+ * - **Cross-principal isolation is structural.** Every method takes `principalId` and every statement
10
+ * filters on it as part of the primary key. There is no method that *could* read across principals,
11
+ * which is a stronger guarantee than a policy that each query remembers to apply.
12
+ * - **A disabled entry is never a retrieval candidate.** `retrieve` filters `disabled_at IS NULL`, and
13
+ * the index it reads is partial on the same predicate — an index cannot return what it does not
14
+ * contain. A user switching a memory off and having it still shape answers is the failure this
15
+ * prevents, and it is a privacy failure, not a correctness nicety.
16
+ */
17
+ import { AgentPlatformError } from "../../core/errors.js";
18
+ import { asId } from "../../core/ids.js";
19
+ const iso = (v) => (v instanceof Date ? v.toISOString() : new Date(v).toISOString());
20
+ const notFound = (id) => new AgentPlatformError({ code: "not_found", message: `Memory ${id} not found`, retryable: false });
21
+ const conflict = (m) => new AgentPlatformError({ code: "conflict", message: m, retryable: false });
22
+ const json = (value) => (typeof value === "string" ? JSON.parse(value) : value);
23
+ const toEntry = (r) => ({
24
+ id: r.id,
25
+ tenantId: r.tenant_id,
26
+ principalId: r.principal_id,
27
+ text: r.text,
28
+ tags: json(r.tags) ?? [],
29
+ salience: Number(r.salience),
30
+ version: Number(r.version),
31
+ createdAt: iso(r.created_at),
32
+ updatedAt: iso(r.updated_at),
33
+ ...(r.disabled_at === null ? {} : { disabledAt: iso(r.disabled_at) }),
34
+ });
35
+ const MEMORY_COLUMNS = `tenant_id, principal_id, id, text, tags, salience, version,
36
+ created_at, updated_at, disabled_at`;
37
+ export const createPostgresPrincipalMemoryStore = (sql, options = {}) => {
38
+ const clock = options.clock ?? (() => new Date().toISOString());
39
+ return {
40
+ async put({ tenantId, principalId, id, text, tags, salience }) {
41
+ const now = clock();
42
+ // Version incremented and created_at preserved server-side, matching the reference adapter.
43
+ // Doing it in the statement means two concurrent puts on one id cannot both compute version 2.
44
+ const rows = await sql.query(`INSERT INTO principal_memory
45
+ (tenant_id, principal_id, id, text, tags, salience, version, created_at, updated_at)
46
+ VALUES ($1, $2, COALESCE($3, 'mem-' || nextval('blob_ref_seq')), $4, $5::jsonb, $6, 1,
47
+ $7::timestamptz, $7::timestamptz)
48
+ ON CONFLICT (tenant_id, principal_id, id) DO UPDATE
49
+ SET text = excluded.text,
50
+ tags = excluded.tags,
51
+ salience = excluded.salience,
52
+ version = principal_memory.version + 1,
53
+ updated_at = excluded.updated_at
54
+ RETURNING ${MEMORY_COLUMNS}`, [
55
+ tenantId,
56
+ principalId,
57
+ id ?? null,
58
+ text,
59
+ JSON.stringify(tags ?? []),
60
+ salience ?? 1,
61
+ now,
62
+ ]);
63
+ const row = rows[0];
64
+ // Unreachable: the upsert always returns a row. Throwing rather than returning a fabricated
65
+ // entry, because a caller that thinks a memory was stored when it was not is worse than an error.
66
+ if (!row)
67
+ throw conflict(`Could not store memory for ${principalId}`);
68
+ return toEntry(row);
69
+ },
70
+ async get({ tenantId, principalId, id }) {
71
+ const rows = await sql.query(`SELECT ${MEMORY_COLUMNS} FROM principal_memory
72
+ WHERE tenant_id = $1 AND principal_id = $2 AND id = $3`, [tenantId, principalId, id]);
73
+ const row = rows[0];
74
+ return row ? toEntry(row) : null;
75
+ },
76
+ async list({ tenantId, principalId, limit, cursor }) {
77
+ // Keyset on (created_at, id): the cursor names a row, and paging resumes strictly after it, so
78
+ // a concurrent put cannot shift a page boundary the way an OFFSET would.
79
+ const rows = await sql.query(`WITH anchor AS (
80
+ SELECT created_at, id FROM principal_memory
81
+ WHERE tenant_id = $1 AND principal_id = $2 AND id = $3
82
+ )
83
+ SELECT ${MEMORY_COLUMNS} FROM principal_memory
84
+ WHERE tenant_id = $1 AND principal_id = $2
85
+ AND ($3::text IS NULL
86
+ OR (created_at, id) > ((SELECT created_at FROM anchor), (SELECT id FROM anchor)))
87
+ ORDER BY created_at, id
88
+ LIMIT $4`, [tenantId, principalId, cursor ?? null, limit + 1]);
89
+ const hasMore = rows.length > limit;
90
+ const items = (hasMore ? rows.slice(0, limit) : rows).map(toEntry);
91
+ const last = items[items.length - 1];
92
+ const page = hasMore && last ? { items, nextCursor: last.id } : { items };
93
+ return page;
94
+ },
95
+ async update({ tenantId, principalId, id, expectedVersion, patch }) {
96
+ // The version guard is in the WHERE clause, so a stale write matches no row rather than
97
+ // overwriting a newer one. COALESCE leaves an omitted patch field untouched.
98
+ //
99
+ // `disabled` is three-valued on the way in: undefined leaves it alone, true stamps now, false
100
+ // clears it. Expressed as a CASE because COALESCE cannot distinguish "set to null" from "leave".
101
+ const rows = await sql.query(`UPDATE principal_memory
102
+ SET text = COALESCE($5, text),
103
+ tags = COALESCE($6::jsonb, tags),
104
+ salience = COALESCE($7, salience),
105
+ disabled_at = CASE
106
+ WHEN $8::boolean IS NULL THEN disabled_at
107
+ WHEN $8::boolean THEN $9::timestamptz
108
+ ELSE NULL
109
+ END,
110
+ version = version + 1,
111
+ updated_at = $9::timestamptz
112
+ WHERE tenant_id = $1 AND principal_id = $2 AND id = $3 AND version = $4
113
+ RETURNING ${MEMORY_COLUMNS}`, [
114
+ tenantId,
115
+ principalId,
116
+ id,
117
+ expectedVersion,
118
+ patch.text ?? null,
119
+ patch.tags === undefined ? null : JSON.stringify(patch.tags),
120
+ patch.salience ?? null,
121
+ patch.disabled ?? null,
122
+ clock(),
123
+ ]);
124
+ const row = rows[0];
125
+ if (row)
126
+ return toEntry(row);
127
+ // No row: either it does not exist, or the version was stale. The distinction is the caller's
128
+ // next move — retry with a fresh read, versus stop.
129
+ const current = await this.get({ tenantId, principalId, id });
130
+ if (!current)
131
+ throw notFound(id);
132
+ throw conflict(`Memory ${id} version ${expectedVersion} is stale (current ${current.version})`);
133
+ },
134
+ async delete({ tenantId, principalId, id }) {
135
+ // Hard delete, as the port documents: a deleted entry cannot resurface in a later prompt. A
136
+ // soft delete would leave the text on disk after a user asked for it to be gone.
137
+ await sql.query(`DELETE FROM principal_memory WHERE tenant_id = $1 AND principal_id = $2 AND id = $3`, [tenantId, principalId, id]);
138
+ },
139
+ async retrieve({ tenantId, principalId, query, limit }) {
140
+ const q = query?.trim();
141
+ // Active only, matched against text or any tag, most salient first.
142
+ //
143
+ // `strpos` on lowercased text, deliberately **not** `ILIKE '%' || $3 || '%'`. Binding the query
144
+ // as a parameter stops SQL injection but does nothing about `LIKE` metacharacters: a query of
145
+ // "%" would still be a wildcard and return every memory the principal has. The reference
146
+ // adapter uses `String.includes`, which is literal, so ILIKE also made the two adapters
147
+ // disagree. `strpos` is a literal substring search and matches `includes` exactly.
148
+ //
149
+ // Neither form can use an index for a leading-wildcard search, so nothing is lost by it — see
150
+ // the open question on #102 about whether substring matching is the intended semantics at all.
151
+ const rows = await sql.query(`SELECT ${MEMORY_COLUMNS} FROM principal_memory
152
+ WHERE tenant_id = $1
153
+ AND principal_id = $2
154
+ AND disabled_at IS NULL
155
+ AND ($3::text IS NULL
156
+ OR strpos(lower(text), lower($3::text)) > 0
157
+ OR EXISTS (
158
+ SELECT 1 FROM jsonb_array_elements_text(tags) AS t(tag)
159
+ WHERE strpos(lower(tag), lower($3::text)) > 0
160
+ ))
161
+ ORDER BY salience DESC, created_at, id
162
+ LIMIT $4`, [tenantId, principalId, q === undefined || q === "" ? null : q, limit]);
163
+ return rows.map(toEntry);
164
+ },
165
+ };
166
+ };
167
+ /**
168
+ * The tool-output offload. Stores the **value**, because that is what the port does: `put(value)`
169
+ * returns a ref and `get(ref)` must hand the value back. The `blob_refs` metadata-and-pointer table
170
+ * the SPEC described cannot implement this — there is nothing in the port to fetch bytes with — and
171
+ * belongs to `FileMetadataStore` (#129) or `ArtifactStore` (#133) instead.
172
+ */
173
+ export const createPostgresBlobStore = (sql) => ({
174
+ async put({ tenantId, value }) {
175
+ // The ref is generated in-statement from a sequence, so it is durable and unique without a
176
+ // read-then-write. The reference adapter's process-local counter would, after a restart, hand out
177
+ // a ref that already belongs to someone else's value.
178
+ const rows = await sql.query(`INSERT INTO blobs (tenant_id, ref, value)
179
+ VALUES ($1, 'blob:' || $1 || ':' || nextval('blob_ref_seq'), $2::jsonb)
180
+ RETURNING ref`, [tenantId, JSON.stringify(value ?? null)]);
181
+ const row = rows[0];
182
+ if (!row)
183
+ throw conflict(`Could not store a blob for ${tenantId}`);
184
+ return asId(row.ref);
185
+ },
186
+ async get({ tenantId, ref }) {
187
+ // Tenant-scoped by primary key, so a ref from one tenant cannot resolve another's value even if
188
+ // it is guessed or leaked — the port's docstring calls that out specifically.
189
+ const rows = await sql.query(`SELECT value FROM blobs WHERE tenant_id = $1 AND ref = $2`, [tenantId, ref]);
190
+ const row = rows[0];
191
+ return row === undefined ? null : json(row.value);
192
+ },
193
+ });
194
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1,11 @@
1
+ import type { AgentManifest } from "../../agents/index.js";
2
+ import type { MessageId, RunId, TenantId } from "../../core/ids.js";
3
+ import type { AgentStore, ConversationBindingStore, MessageStore } from "../../persistence/index.js";
4
+ import type { SqlExecutor } from "./sql.js";
5
+ export declare const createPostgresMessageStore: (sql: SqlExecutor) => MessageStore;
6
+ export declare const createPostgresAgentStore: (sql: SqlExecutor) => AgentStore & {
7
+ put(tenantId: string, manifest: AgentManifest): Promise<void>;
8
+ };
9
+ export declare const createPostgresConversationBindingStore: (sql: SqlExecutor) => ConversationBindingStore;
10
+ export type { MessageId, RunId, TenantId };
11
+ //# sourceMappingURL=message-store.d.ts.map
@@ -0,0 +1,145 @@
1
+ /**
2
+ * PostgreSQL `MessageStore`, `AgentStore` and `ConversationBindingStore` (#96) — the stores that make
3
+ * a conversation durable rather than a thing that exists until the next deploy.
4
+ *
5
+ * Paging uses the same `(created_at, id)` composite cursor as `createPostgresConversationStore`. That
6
+ * pairing is the whole of AC-2: a timestamp alone lets a concurrently-inserted row sharing the same
7
+ * `created_at` slip between pages, and `OFFSET` shifts every subsequent page when a row lands early.
8
+ * The `id` tiebreak plus a keyset comparison makes the sequence stable while the conversation is
9
+ * still being written to.
10
+ */
11
+ import { AgentPlatformError } from "../../core/errors.js";
12
+ import { parseMessagePart } from "../../core/validation.js";
13
+ const iso = (v) => (v instanceof Date ? v.toISOString() : new Date(v).toISOString());
14
+ const invalid = (message) => new AgentPlatformError({ code: "invalid_input", message, retryable: false });
15
+ /**
16
+ * Validate on read. A hand-edited or half-migrated `parts` column otherwise flows into a client's
17
+ * stream, where the failure surfaces far from its cause — so the error names the message that carries
18
+ * the bad payload (AC-5).
19
+ */
20
+ const toMessage = (r) => {
21
+ const stored = typeof r.parts === "string" ? JSON.parse(r.parts) : r.parts;
22
+ if (!Array.isArray(stored))
23
+ throw invalid(`Message ${r.id} has a non-array parts column`);
24
+ // There is no whole-message validator, so each part goes through `parseMessagePart`. Wrapping the
25
+ // failure keeps the message id in the error — otherwise a bad row reports only "invalid part" and
26
+ // leaves you grepping a table for it (AC-5).
27
+ let parts;
28
+ try {
29
+ parts = stored.map(parseMessagePart);
30
+ }
31
+ catch (cause) {
32
+ throw invalid(`Message ${r.id} has an invalid stored part: ${cause.message}`);
33
+ }
34
+ return {
35
+ id: r.id,
36
+ conversationId: r.conversation_id,
37
+ ...(r.run_id === null ? {} : { runId: r.run_id }),
38
+ role: r.role,
39
+ parts,
40
+ createdAt: iso(r.created_at),
41
+ };
42
+ };
43
+ const encodeCursor = (r) => Buffer.from(JSON.stringify({ c: iso(r.created_at), i: r.id })).toString("base64");
44
+ const decodeCursor = (cursor) => JSON.parse(Buffer.from(cursor, "base64").toString("utf8"));
45
+ export const createPostgresMessageStore = (sql) => ({
46
+ /**
47
+ * On the port as of #157, no longer a "test-only affordance".
48
+ *
49
+ * The signature changed from `(tenantId, message)` to the object form every other port method uses — a
50
+ * positional pair was the shape a test-only helper could get away with, and it is the wrong shape for
51
+ * something callers depend on.
52
+ *
53
+ * Messages stay immutable: `DO NOTHING` on conflict, no update, no delete. Editing one would rewrite history a
54
+ * client has already streamed and a model has already been shown, and a repeat of the same id is a retry
55
+ * rather than an error.
56
+ */
57
+ async append({ tenantId, message }) {
58
+ await sql.query(`INSERT INTO messages (tenant_id, id, conversation_id, run_id, role, parts, created_at)
59
+ VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7::timestamptz)
60
+ ON CONFLICT (tenant_id, id) DO NOTHING`, [
61
+ tenantId,
62
+ message.id,
63
+ message.conversationId,
64
+ message.runId ?? null,
65
+ message.role,
66
+ JSON.stringify(message.parts),
67
+ message.createdAt,
68
+ ]);
69
+ },
70
+ async findById({ tenantId, id }) {
71
+ const rows = await sql.query(`SELECT * FROM messages WHERE tenant_id = $1 AND id = $2`, [tenantId, id]);
72
+ const row = rows[0];
73
+ return row ? toMessage(row) : null;
74
+ },
75
+ async listByConversation({ tenantId, conversationId, limit, cursor, newestFirst }) {
76
+ const params = [tenantId, conversationId, limit + 1];
77
+ let where = `tenant_id = $1 AND conversation_id = $2`;
78
+ // The comparator and the sort flip together — #167. Split apart they produce a page that scans one way and
79
+ // pages the other, which returns rows before the cursor and looks like duplicates.
80
+ const descending = newestFirst === true;
81
+ if (cursor) {
82
+ const { c, i } = decodeCursor(cursor);
83
+ // Keyset, not OFFSET: strictly past (created_at, id) in the direction being read, so an insert landing
84
+ // earlier in the ordering cannot shift this page or duplicate a row into the next one.
85
+ where += descending
86
+ ? ` AND (created_at, id) < ($4::timestamptz, $5)`
87
+ : ` AND (created_at, id) > ($4::timestamptz, $5)`;
88
+ params.push(c, i);
89
+ }
90
+ const rows = await sql.query(`SELECT * FROM messages WHERE ${where}
91
+ ORDER BY created_at ${descending ? "DESC" : "ASC"}, id ${descending ? "DESC" : "ASC"}
92
+ LIMIT $3`, params);
93
+ const hasMore = rows.length > limit;
94
+ const items = (hasMore ? rows.slice(0, limit) : rows).map(toMessage);
95
+ const last = hasMore ? rows[limit - 1] : undefined;
96
+ const page = last ? { items, nextCursor: encodeCursor(last) } : { items };
97
+ return page;
98
+ },
99
+ });
100
+ export const createPostgresAgentStore = (sql) => ({
101
+ async put(tenantId, manifest) {
102
+ await sql.query(`INSERT INTO agents (tenant_id, id, version, manifest, created_at, updated_at)
103
+ VALUES ($1, $2, $3, $4::jsonb, now(), now())
104
+ ON CONFLICT (tenant_id, id, version) DO UPDATE
105
+ SET manifest = excluded.manifest, updated_at = now()`, [tenantId, manifest.id, manifest.version, JSON.stringify(manifest)]);
106
+ },
107
+ async findByVersion({ tenantId, agentId, version }) {
108
+ // Tenant-scoped, unlike the in-memory adapter before #91 fixed it: a manifest is owned by the
109
+ // tenant that registered it, and the key leads with tenant_id so a cross-tenant read is a miss.
110
+ const rows = await sql.query(`SELECT manifest FROM agents WHERE tenant_id = $1 AND id = $2 AND version = $3`, [tenantId, agentId, version]);
111
+ const row = rows[0];
112
+ if (!row)
113
+ return null;
114
+ return (typeof row.manifest === "string" ? JSON.parse(row.manifest) : row.manifest);
115
+ },
116
+ });
117
+ const toBinding = (r) => ({
118
+ conversationId: r.conversation_id,
119
+ agentId: r.agent_id,
120
+ agentVersionPolicy: r.agent_version_policy,
121
+ // Absent rather than null for a `latest` binding: the type makes it optional, and a null here
122
+ // would round-trip as a present-but-empty version.
123
+ ...(r.agent_version === null ? {} : { agentVersion: r.agent_version }),
124
+ });
125
+ export const createPostgresConversationBindingStore = (sql) => ({
126
+ async bind({ tenantId, conversationId, agentId, agentVersionPolicy, agentVersion }) {
127
+ // Idempotent by conversation: re-binding is a legitimate operation (an agent upgrade), not a
128
+ // conflict, so the last write wins rather than erroring.
129
+ await sql.query(`INSERT INTO conversation_bindings
130
+ (tenant_id, conversation_id, agent_id, agent_version_policy, agent_version, bound_at)
131
+ VALUES ($1, $2, $3, $4, $5, now())
132
+ ON CONFLICT (tenant_id, conversation_id) DO UPDATE
133
+ SET agent_id = excluded.agent_id,
134
+ agent_version_policy = excluded.agent_version_policy,
135
+ agent_version = excluded.agent_version,
136
+ bound_at = excluded.bound_at`, [tenantId, conversationId, agentId, agentVersionPolicy, agentVersion ?? null]);
137
+ },
138
+ async get({ tenantId, conversationId }) {
139
+ const rows = await sql.query(`SELECT conversation_id, agent_id, agent_version_policy, agent_version
140
+ FROM conversation_bindings WHERE tenant_id = $1 AND conversation_id = $2`, [tenantId, conversationId]);
141
+ const row = rows[0];
142
+ return row ? toBinding(row) : null;
143
+ },
144
+ });
145
+ //# sourceMappingURL=message-store.js.map