@waynesutton/agent-memory 0.0.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 (201) hide show
  1. package/.claude/settings.json +9 -0
  2. package/.claude/settings.local.json +7 -0
  3. package/AGENTS.md +113 -0
  4. package/CLAUDE.md +79 -0
  5. package/README.md +1 -0
  6. package/dist/cli/index.d.ts +3 -0
  7. package/dist/cli/index.d.ts.map +1 -0
  8. package/dist/cli/index.js +192 -0
  9. package/dist/cli/index.js.map +1 -0
  10. package/dist/cli/parsers/claude-code.d.ts +3 -0
  11. package/dist/cli/parsers/claude-code.d.ts.map +1 -0
  12. package/dist/cli/parsers/claude-code.js +75 -0
  13. package/dist/cli/parsers/claude-code.js.map +1 -0
  14. package/dist/cli/parsers/codex.d.ts +3 -0
  15. package/dist/cli/parsers/codex.d.ts.map +1 -0
  16. package/dist/cli/parsers/codex.js +42 -0
  17. package/dist/cli/parsers/codex.js.map +1 -0
  18. package/dist/cli/parsers/conductor.d.ts +3 -0
  19. package/dist/cli/parsers/conductor.d.ts.map +1 -0
  20. package/dist/cli/parsers/conductor.js +43 -0
  21. package/dist/cli/parsers/conductor.js.map +1 -0
  22. package/dist/cli/parsers/cursor.d.ts +3 -0
  23. package/dist/cli/parsers/cursor.d.ts.map +1 -0
  24. package/dist/cli/parsers/cursor.js +50 -0
  25. package/dist/cli/parsers/cursor.js.map +1 -0
  26. package/dist/cli/parsers/index.d.ts +12 -0
  27. package/dist/cli/parsers/index.d.ts.map +1 -0
  28. package/dist/cli/parsers/index.js +27 -0
  29. package/dist/cli/parsers/index.js.map +1 -0
  30. package/dist/cli/parsers/opencode.d.ts +3 -0
  31. package/dist/cli/parsers/opencode.d.ts.map +1 -0
  32. package/dist/cli/parsers/opencode.js +72 -0
  33. package/dist/cli/parsers/opencode.js.map +1 -0
  34. package/dist/cli/parsers/parsers.test.d.ts +2 -0
  35. package/dist/cli/parsers/parsers.test.d.ts.map +1 -0
  36. package/dist/cli/parsers/parsers.test.js +151 -0
  37. package/dist/cli/parsers/parsers.test.js.map +1 -0
  38. package/dist/cli/parsers/pi.d.ts +3 -0
  39. package/dist/cli/parsers/pi.d.ts.map +1 -0
  40. package/dist/cli/parsers/pi.js +43 -0
  41. package/dist/cli/parsers/pi.js.map +1 -0
  42. package/dist/cli/parsers/types.d.ts +25 -0
  43. package/dist/cli/parsers/types.d.ts.map +1 -0
  44. package/dist/cli/parsers/types.js +2 -0
  45. package/dist/cli/parsers/types.js.map +1 -0
  46. package/dist/cli/parsers/vscode-copilot.d.ts +3 -0
  47. package/dist/cli/parsers/vscode-copilot.d.ts.map +1 -0
  48. package/dist/cli/parsers/vscode-copilot.js +69 -0
  49. package/dist/cli/parsers/vscode-copilot.js.map +1 -0
  50. package/dist/cli/parsers/zed.d.ts +3 -0
  51. package/dist/cli/parsers/zed.d.ts.map +1 -0
  52. package/dist/cli/parsers/zed.js +43 -0
  53. package/dist/cli/parsers/zed.js.map +1 -0
  54. package/dist/cli/sync.d.ts +21 -0
  55. package/dist/cli/sync.d.ts.map +1 -0
  56. package/dist/cli/sync.js +78 -0
  57. package/dist/cli/sync.js.map +1 -0
  58. package/dist/cli/type-extractor.d.ts +25 -0
  59. package/dist/cli/type-extractor.d.ts.map +1 -0
  60. package/dist/cli/type-extractor.js +254 -0
  61. package/dist/cli/type-extractor.js.map +1 -0
  62. package/dist/cli/type-extractor.test.d.ts +2 -0
  63. package/dist/cli/type-extractor.test.d.ts.map +1 -0
  64. package/dist/cli/type-extractor.test.js +173 -0
  65. package/dist/cli/type-extractor.test.js.map +1 -0
  66. package/dist/client/http.d.ts +44 -0
  67. package/dist/client/http.d.ts.map +1 -0
  68. package/dist/client/http.js +311 -0
  69. package/dist/client/http.js.map +1 -0
  70. package/dist/client/index.d.ts +158 -0
  71. package/dist/client/index.d.ts.map +1 -0
  72. package/dist/client/index.js +256 -0
  73. package/dist/client/index.js.map +1 -0
  74. package/dist/component/_generated/api.d.ts +12 -0
  75. package/dist/component/_generated/api.d.ts.map +1 -0
  76. package/dist/component/_generated/api.js +13 -0
  77. package/dist/component/_generated/api.js.map +1 -0
  78. package/dist/component/_generated/dataModel.d.ts +18 -0
  79. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  80. package/dist/component/_generated/dataModel.js +11 -0
  81. package/dist/component/_generated/dataModel.js.map +1 -0
  82. package/dist/component/_generated/server.d.ts +42 -0
  83. package/dist/component/_generated/server.d.ts.map +1 -0
  84. package/dist/component/_generated/server.js +39 -0
  85. package/dist/component/_generated/server.js.map +1 -0
  86. package/dist/component/actions.d.ts +42 -0
  87. package/dist/component/actions.d.ts.map +1 -0
  88. package/dist/component/actions.js +405 -0
  89. package/dist/component/actions.js.map +1 -0
  90. package/dist/component/apiKeyMutations.d.ts +29 -0
  91. package/dist/component/apiKeyMutations.d.ts.map +1 -0
  92. package/dist/component/apiKeyMutations.js +149 -0
  93. package/dist/component/apiKeyMutations.js.map +1 -0
  94. package/dist/component/apiKeyQueries.d.ts +37 -0
  95. package/dist/component/apiKeyQueries.d.ts.map +1 -0
  96. package/dist/component/apiKeyQueries.js +127 -0
  97. package/dist/component/apiKeyQueries.js.map +1 -0
  98. package/dist/component/checksum.d.ts +6 -0
  99. package/dist/component/checksum.d.ts.map +1 -0
  100. package/dist/component/checksum.js +14 -0
  101. package/dist/component/checksum.js.map +1 -0
  102. package/dist/component/checksum.test.d.ts +2 -0
  103. package/dist/component/checksum.test.d.ts.map +1 -0
  104. package/dist/component/checksum.test.js +27 -0
  105. package/dist/component/checksum.test.js.map +1 -0
  106. package/dist/component/convex.config.d.ts +3 -0
  107. package/dist/component/convex.config.d.ts.map +1 -0
  108. package/dist/component/convex.config.js +4 -0
  109. package/dist/component/convex.config.js.map +1 -0
  110. package/dist/component/cronActions.d.ts +3 -0
  111. package/dist/component/cronActions.d.ts.map +1 -0
  112. package/dist/component/cronActions.js +38 -0
  113. package/dist/component/cronActions.js.map +1 -0
  114. package/dist/component/cronQueries.d.ts +6 -0
  115. package/dist/component/cronQueries.d.ts.map +1 -0
  116. package/dist/component/cronQueries.js +38 -0
  117. package/dist/component/cronQueries.js.map +1 -0
  118. package/dist/component/crons.d.ts +3 -0
  119. package/dist/component/crons.d.ts.map +1 -0
  120. package/dist/component/crons.js +18 -0
  121. package/dist/component/crons.js.map +1 -0
  122. package/dist/component/format.d.ts +11 -0
  123. package/dist/component/format.d.ts.map +1 -0
  124. package/dist/component/format.js +175 -0
  125. package/dist/component/format.js.map +1 -0
  126. package/dist/component/format.test.d.ts +2 -0
  127. package/dist/component/format.test.d.ts.map +1 -0
  128. package/dist/component/format.test.js +118 -0
  129. package/dist/component/format.test.js.map +1 -0
  130. package/dist/component/mutations.d.ts +158 -0
  131. package/dist/component/mutations.d.ts.map +1 -0
  132. package/dist/component/mutations.js +745 -0
  133. package/dist/component/mutations.js.map +1 -0
  134. package/dist/component/queries.d.ts +94 -0
  135. package/dist/component/queries.d.ts.map +1 -0
  136. package/dist/component/queries.js +574 -0
  137. package/dist/component/queries.js.map +1 -0
  138. package/dist/component/schema.d.ts +278 -0
  139. package/dist/component/schema.d.ts.map +1 -0
  140. package/dist/component/schema.js +161 -0
  141. package/dist/component/schema.js.map +1 -0
  142. package/dist/mcp/server.d.ts +11 -0
  143. package/dist/mcp/server.d.ts.map +1 -0
  144. package/dist/mcp/server.js +571 -0
  145. package/dist/mcp/server.js.map +1 -0
  146. package/dist/shared.d.ts +126 -0
  147. package/dist/shared.d.ts.map +1 -0
  148. package/dist/shared.js +67 -0
  149. package/dist/shared.js.map +1 -0
  150. package/dist/test.d.ts +23 -0
  151. package/dist/test.d.ts.map +1 -0
  152. package/dist/test.js +21 -0
  153. package/dist/test.js.map +1 -0
  154. package/eslint.config.js +15 -0
  155. package/example/convex/convex.config.ts +7 -0
  156. package/example/convex/memory.ts +129 -0
  157. package/llms.md +175 -0
  158. package/llms.txt +126 -0
  159. package/package.json +72 -0
  160. package/prds/API-REFERENCE.md +935 -0
  161. package/prds/README.md +988 -0
  162. package/prds/SETUP.md +682 -0
  163. package/src/cli/index.ts +254 -0
  164. package/src/cli/parsers/claude-code.ts +80 -0
  165. package/src/cli/parsers/codex.ts +45 -0
  166. package/src/cli/parsers/conductor.ts +47 -0
  167. package/src/cli/parsers/cursor.ts +55 -0
  168. package/src/cli/parsers/index.ts +30 -0
  169. package/src/cli/parsers/opencode.ts +84 -0
  170. package/src/cli/parsers/parsers.test.ts +201 -0
  171. package/src/cli/parsers/pi.ts +47 -0
  172. package/src/cli/parsers/types.ts +26 -0
  173. package/src/cli/parsers/vscode-copilot.ts +78 -0
  174. package/src/cli/parsers/zed.ts +47 -0
  175. package/src/cli/sync.ts +110 -0
  176. package/src/cli/type-extractor.test.ts +241 -0
  177. package/src/cli/type-extractor.ts +331 -0
  178. package/src/client/http.ts +415 -0
  179. package/src/client/index.ts +519 -0
  180. package/src/component/_generated/api.ts +14 -0
  181. package/src/component/_generated/dataModel.ts +20 -0
  182. package/src/component/_generated/server.ts +64 -0
  183. package/src/component/actions.ts +558 -0
  184. package/src/component/apiKeyMutations.ts +175 -0
  185. package/src/component/apiKeyQueries.ts +156 -0
  186. package/src/component/checksum.test.ts +31 -0
  187. package/src/component/checksum.ts +13 -0
  188. package/src/component/convex.config.ts +5 -0
  189. package/src/component/cronActions.ts +52 -0
  190. package/src/component/cronQueries.ts +42 -0
  191. package/src/component/crons.ts +34 -0
  192. package/src/component/format.test.ts +133 -0
  193. package/src/component/format.ts +232 -0
  194. package/src/component/mutations.ts +824 -0
  195. package/src/component/queries.ts +684 -0
  196. package/src/component/schema.ts +207 -0
  197. package/src/mcp/server.ts +695 -0
  198. package/src/shared.ts +251 -0
  199. package/src/test.ts +32 -0
  200. package/tsconfig.json +21 -0
  201. package/vitest.config.ts +8 -0
@@ -0,0 +1,415 @@
1
+ import { httpActionGeneric, type HttpRouter } from "convex/server";
2
+ import type { api } from "../component/_generated/api.js";
3
+
4
+ // Use the generic httpAction since this is a component (no app-specific data model)
5
+ const httpAction = httpActionGeneric;
6
+
7
+ // ── Types ───────────────────────────────────────────────────────────
8
+
9
+ type ComponentApi = typeof api;
10
+
11
+ export interface MemoryHttpApiConfig {
12
+ /** Allowed CORS origins. Defaults to ["*"]. */
13
+ corsOrigins?: string[];
14
+ }
15
+
16
+ interface ValidKey {
17
+ valid: true;
18
+ keyHash: string;
19
+ projectId: string;
20
+ permissions: string[];
21
+ rateLimit: { requestsPerWindow: number; windowMs: number };
22
+ }
23
+
24
+ // ── Helpers ─────────────────────────────────────────────────────────
25
+
26
+ function corsHeaders(origins: string[]): Record<string, string> {
27
+ return {
28
+ "Access-Control-Allow-Origin": origins.includes("*") ? "*" : origins[0],
29
+ "Access-Control-Allow-Methods": "GET, OPTIONS",
30
+ "Access-Control-Allow-Headers": "Authorization, Content-Type",
31
+ "Access-Control-Max-Age": "86400",
32
+ };
33
+ }
34
+
35
+ function jsonResponse(
36
+ data: unknown,
37
+ status: number,
38
+ cors: Record<string, string>,
39
+ ): Response {
40
+ return new Response(JSON.stringify(data), {
41
+ status,
42
+ headers: { "Content-Type": "application/json", ...cors },
43
+ });
44
+ }
45
+
46
+ function errorResponse(
47
+ message: string,
48
+ status: number,
49
+ cors: Record<string, string>,
50
+ extra?: Record<string, unknown>,
51
+ ): Response {
52
+ return jsonResponse({ error: message, ...extra }, status, cors);
53
+ }
54
+
55
+ // ── MemoryHttpApi ───────────────────────────────────────────────────
56
+
57
+ /**
58
+ * Generates read-only HTTP endpoint handlers that a Convex app mounts
59
+ * on its own `httpRouter`. Each request is authenticated via an API key
60
+ * (Bearer token) and rate-limited using the component's built-in
61
+ * fixed-window token bucket.
62
+ *
63
+ * Usage in the consuming app's `convex/http.ts`:
64
+ *
65
+ * ```typescript
66
+ * import { httpRouter } from "convex/server";
67
+ * import { MemoryHttpApi } from "@waynesutton/agent-memory/http";
68
+ * import { components } from "./_generated/api";
69
+ *
70
+ * const http = httpRouter();
71
+ * const memoryApi = new MemoryHttpApi(components.agentMemory);
72
+ * memoryApi.mount(http, "/api/memory");
73
+ * export default http;
74
+ * ```
75
+ */
76
+ export class MemoryHttpApi {
77
+ private component: ComponentApi;
78
+ private cors: Record<string, string>;
79
+
80
+ constructor(component: ComponentApi, config?: MemoryHttpApiConfig) {
81
+ this.component = component;
82
+ this.cors = corsHeaders(config?.corsOrigins ?? ["*"]);
83
+ }
84
+
85
+ // ── Auth + rate-limit middleware ─────────────────────────────────
86
+
87
+ /**
88
+ * Validates the Bearer token, checks rate limits, and returns the
89
+ * validated key info. Returns an error Response if auth or rate
90
+ * limiting fails.
91
+ */
92
+ private async authenticate(
93
+ ctx: { runQuery: any; runMutation: any },
94
+ request: Request,
95
+ requiredPermission: string,
96
+ ): Promise<ValidKey | Response> {
97
+ const authHeader = request.headers.get("Authorization");
98
+ if (!authHeader?.startsWith("Bearer ")) {
99
+ return errorResponse(
100
+ "Missing or invalid Authorization header. Expected: Bearer am_<key>",
101
+ 401,
102
+ this.cors,
103
+ );
104
+ }
105
+
106
+ const key = authHeader.slice(7);
107
+ const validation = await ctx.runQuery(
108
+ this.component.apiKeyQueries.validateApiKey,
109
+ { key },
110
+ );
111
+
112
+ if (!validation.valid) {
113
+ return errorResponse(validation.reason, 401, this.cors);
114
+ }
115
+
116
+ // Check permission
117
+ if (!validation.permissions.includes(requiredPermission)) {
118
+ return errorResponse(
119
+ `API key does not have "${requiredPermission}" permission`,
120
+ 403,
121
+ this.cors,
122
+ );
123
+ }
124
+
125
+ // Consume rate limit
126
+ const rateResult = await ctx.runMutation(
127
+ this.component.apiKeyMutations.consumeRateLimit,
128
+ {
129
+ keyHash: validation.keyHash,
130
+ requestsPerWindow: validation.rateLimit.requestsPerWindow,
131
+ windowMs: validation.rateLimit.windowMs,
132
+ },
133
+ );
134
+
135
+ if (!rateResult.allowed) {
136
+ return errorResponse("Rate limit exceeded", 429, this.cors, {
137
+ retryAfterMs: rateResult.retryAfterMs,
138
+ "Retry-After": String(Math.ceil(rateResult.retryAfterMs / 1000)),
139
+ });
140
+ }
141
+
142
+ // Fire-and-forget: update lastUsedAt
143
+ ctx
144
+ .runMutation(this.component.apiKeyQueries.updateKeyLastUsed, {
145
+ keyHash: validation.keyHash,
146
+ })
147
+ .catch(() => {});
148
+
149
+ return validation as ValidKey;
150
+ }
151
+
152
+ // ── Route mounting ──────────────────────────────────────────────
153
+
154
+ /**
155
+ * Registers all read-only memory API routes on the given httpRouter
156
+ * under the specified prefix (e.g. "/api/memory").
157
+ */
158
+ mount(http: HttpRouter, prefix: string): void {
159
+ const p = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
160
+
161
+ // CORS preflight for all routes
162
+ const optionsHandler = httpAction(async () => {
163
+ return new Response(null, { status: 204, headers: this.cors });
164
+ });
165
+
166
+ // ── GET /list ────────────────────────────────────────────────
167
+ http.route({
168
+ path: `${p}/list`,
169
+ method: "GET",
170
+ handler: httpAction(async (ctx, request) => {
171
+ const auth = await this.authenticate(ctx, request, "list");
172
+ if (auth instanceof Response) return auth;
173
+
174
+ const url = new URL(request.url);
175
+ const result = await ctx.runQuery(this.component.queries.list, {
176
+ projectId: auth.projectId,
177
+ memoryType: url.searchParams.get("memoryType") ?? undefined,
178
+ scope: (url.searchParams.get("scope") as any) ?? undefined,
179
+ agentId: url.searchParams.get("agentId") ?? undefined,
180
+ sessionId: url.searchParams.get("sessionId") ?? undefined,
181
+ source: url.searchParams.get("source") ?? undefined,
182
+ tags: url.searchParams.get("tags")
183
+ ? url.searchParams.get("tags")!.split(",")
184
+ : undefined,
185
+ archived: url.searchParams.get("archived") === "true"
186
+ ? true
187
+ : undefined,
188
+ minPriority: url.searchParams.get("minPriority")
189
+ ? Number(url.searchParams.get("minPriority"))
190
+ : undefined,
191
+ limit: url.searchParams.get("limit")
192
+ ? Number(url.searchParams.get("limit"))
193
+ : undefined,
194
+ });
195
+
196
+ return jsonResponse(result, 200, this.cors);
197
+ }),
198
+ });
199
+
200
+ http.route({
201
+ path: `${p}/list`,
202
+ method: "OPTIONS",
203
+ handler: optionsHandler,
204
+ });
205
+
206
+ // ── GET /get?id=<memoryId> ──────────────────────────────────
207
+ http.route({
208
+ path: `${p}/get`,
209
+ method: "GET",
210
+ handler: httpAction(async (ctx, request) => {
211
+ const auth = await this.authenticate(ctx, request, "get");
212
+ if (auth instanceof Response) return auth;
213
+
214
+ const url = new URL(request.url);
215
+ const memoryId = url.searchParams.get("id");
216
+ if (!memoryId) {
217
+ return errorResponse("Missing required query param: id", 400, this.cors);
218
+ }
219
+
220
+ const result = await ctx.runQuery(this.component.queries.get, {
221
+ memoryId,
222
+ });
223
+
224
+ if (!result) {
225
+ return errorResponse("Memory not found", 404, this.cors);
226
+ }
227
+
228
+ return jsonResponse(result, 200, this.cors);
229
+ }),
230
+ });
231
+
232
+ http.route({
233
+ path: `${p}/get`,
234
+ method: "OPTIONS",
235
+ handler: optionsHandler,
236
+ });
237
+
238
+ // ── GET /search?q=<query> ───────────────────────────────────
239
+ http.route({
240
+ path: `${p}/search`,
241
+ method: "GET",
242
+ handler: httpAction(async (ctx, request) => {
243
+ const auth = await this.authenticate(ctx, request, "search");
244
+ if (auth instanceof Response) return auth;
245
+
246
+ const url = new URL(request.url);
247
+ const query = url.searchParams.get("q");
248
+ if (!query) {
249
+ return errorResponse("Missing required query param: q", 400, this.cors);
250
+ }
251
+
252
+ const result = await ctx.runQuery(this.component.queries.search, {
253
+ projectId: auth.projectId,
254
+ query,
255
+ memoryType: url.searchParams.get("memoryType") ?? undefined,
256
+ scope: (url.searchParams.get("scope") as any) ?? undefined,
257
+ limit: url.searchParams.get("limit")
258
+ ? Number(url.searchParams.get("limit"))
259
+ : undefined,
260
+ });
261
+
262
+ return jsonResponse(result, 200, this.cors);
263
+ }),
264
+ });
265
+
266
+ http.route({
267
+ path: `${p}/search`,
268
+ method: "OPTIONS",
269
+ handler: optionsHandler,
270
+ });
271
+
272
+ // ── GET /context ────────────────────────────────────────────
273
+ http.route({
274
+ path: `${p}/context`,
275
+ method: "GET",
276
+ handler: httpAction(async (ctx, request) => {
277
+ const auth = await this.authenticate(ctx, request, "context");
278
+ if (auth instanceof Response) return auth;
279
+
280
+ const url = new URL(request.url);
281
+ const result = await ctx.runQuery(
282
+ this.component.queries.getContextBundle,
283
+ {
284
+ projectId: auth.projectId,
285
+ scope: (url.searchParams.get("scope") as any) ?? "project",
286
+ userId: url.searchParams.get("userId") ?? undefined,
287
+ agentId: url.searchParams.get("agentId") ?? undefined,
288
+ activePaths: url.searchParams.get("activePaths")
289
+ ? url.searchParams.get("activePaths")!.split(",")
290
+ : undefined,
291
+ maxTokens: url.searchParams.get("maxTokens")
292
+ ? Number(url.searchParams.get("maxTokens"))
293
+ : undefined,
294
+ },
295
+ );
296
+
297
+ return jsonResponse(result, 200, this.cors);
298
+ }),
299
+ });
300
+
301
+ http.route({
302
+ path: `${p}/context`,
303
+ method: "OPTIONS",
304
+ handler: optionsHandler,
305
+ });
306
+
307
+ // ── GET /export?format=<format> ─────────────────────────────
308
+ http.route({
309
+ path: `${p}/export`,
310
+ method: "GET",
311
+ handler: httpAction(async (ctx, request) => {
312
+ const auth = await this.authenticate(ctx, request, "export");
313
+ if (auth instanceof Response) return auth;
314
+
315
+ const url = new URL(request.url);
316
+ const format = url.searchParams.get("format");
317
+ if (!format) {
318
+ return errorResponse(
319
+ "Missing required query param: format",
320
+ 400,
321
+ this.cors,
322
+ );
323
+ }
324
+
325
+ const result = await ctx.runQuery(
326
+ this.component.queries.exportForTool,
327
+ {
328
+ projectId: auth.projectId,
329
+ format,
330
+ scope: (url.searchParams.get("scope") as any) ?? undefined,
331
+ userId: url.searchParams.get("userId") ?? undefined,
332
+ since: url.searchParams.get("since")
333
+ ? Number(url.searchParams.get("since"))
334
+ : undefined,
335
+ },
336
+ );
337
+
338
+ return jsonResponse(result, 200, this.cors);
339
+ }),
340
+ });
341
+
342
+ http.route({
343
+ path: `${p}/export`,
344
+ method: "OPTIONS",
345
+ handler: optionsHandler,
346
+ });
347
+
348
+ // ── GET /history?id=<memoryId> ──────────────────────────────
349
+ http.route({
350
+ path: `${p}/history`,
351
+ method: "GET",
352
+ handler: httpAction(async (ctx, request) => {
353
+ const auth = await this.authenticate(ctx, request, "history");
354
+ if (auth instanceof Response) return auth;
355
+
356
+ const url = new URL(request.url);
357
+ const memoryId = url.searchParams.get("id");
358
+ if (!memoryId) {
359
+ return errorResponse("Missing required query param: id", 400, this.cors);
360
+ }
361
+
362
+ const result = await ctx.runQuery(this.component.queries.history, {
363
+ memoryId,
364
+ limit: url.searchParams.get("limit")
365
+ ? Number(url.searchParams.get("limit"))
366
+ : undefined,
367
+ });
368
+
369
+ return jsonResponse(result, 200, this.cors);
370
+ }),
371
+ });
372
+
373
+ http.route({
374
+ path: `${p}/history`,
375
+ method: "OPTIONS",
376
+ handler: optionsHandler,
377
+ });
378
+
379
+ // ── GET /relations?id=<memoryId> ────────────────────────────
380
+ http.route({
381
+ path: `${p}/relations`,
382
+ method: "GET",
383
+ handler: httpAction(async (ctx, request) => {
384
+ const auth = await this.authenticate(ctx, request, "relations");
385
+ if (auth instanceof Response) return auth;
386
+
387
+ const url = new URL(request.url);
388
+ const memoryId = url.searchParams.get("id");
389
+ if (!memoryId) {
390
+ return errorResponse("Missing required query param: id", 400, this.cors);
391
+ }
392
+
393
+ const result = await ctx.runQuery(
394
+ this.component.queries.getRelations,
395
+ {
396
+ memoryId,
397
+ direction: (url.searchParams.get("direction") as any) ?? undefined,
398
+ relationship: url.searchParams.get("relationship") ?? undefined,
399
+ limit: url.searchParams.get("limit")
400
+ ? Number(url.searchParams.get("limit"))
401
+ : undefined,
402
+ },
403
+ );
404
+
405
+ return jsonResponse(result, 200, this.cors);
406
+ }),
407
+ });
408
+
409
+ http.route({
410
+ path: `${p}/relations`,
411
+ method: "OPTIONS",
412
+ handler: optionsHandler,
413
+ });
414
+ }
415
+ }