@hexis-ai/engram-server 0.3.0 → 0.5.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.
package/dist/openapi.js CHANGED
@@ -11,6 +11,11 @@
11
11
  *
12
12
  * `info.version` is the API version (the `/v1` surface), not the npm package
13
13
  * version — it changes only when the wire contract changes.
14
+ *
15
+ * The human-readable strings (`summary`, `description`, response and
16
+ * parameter descriptions) are written in Japanese — the API reference is
17
+ * read by the Hexis team. Identifiers, paths, schema names and status
18
+ * codes stay as-is.
14
19
  */
15
20
  import { z } from "zod";
16
21
  import { createWorkspaceSchema, eventBatchSchema, issueKeySchema, personCreateSchema, personUpdateSchema, searchRequestSchema, sessionInitSchema, } from "./schemas";
@@ -37,212 +42,302 @@ const res = (description) => ({ description });
37
42
  /** Default security: a workspace API key. `/admin/v1` paths override this. */
38
43
  const workspaceAuth = [{ workspaceKey: [] }];
39
44
  const adminAuth = [{ adminToken: [] }];
40
- const limitParam = queryParam("limit", "Page size; clamped server-side.", {
45
+ // ---------------------------------------------------------------------
46
+ // Tags — group the rendered reference (Scalar, on monet-web /docs/engram)
47
+ // into a per-domain sidebar instead of one flat list of 19 operations.
48
+ //
49
+ // The tag *definitions* (sidebar order + Japanese descriptions) live
50
+ // in TAG_DEFS; `tagged()` stamps a tag onto every operation in a
51
+ // path-item, so each path in buildPaths() declares its tag once,
52
+ // inline. test/openapi.test.ts guards that no operation slips through
53
+ // untagged and no operation references an undefined tag.
54
+ // ---------------------------------------------------------------------
55
+ /** Every tag, in sidebar order, with a Japanese description. */
56
+ const TAG_DEFS = [
57
+ { name: "Me", description: "識別プローブ" },
58
+ { name: "Sessions", description: "セッションの作成・取得・イベント追加" },
59
+ { name: "Search", description: "ワークスペースコーパスへのスコアリング検索" },
60
+ { name: "Persons", description: "person の作成・更新・検索" },
61
+ {
62
+ name: "Workspaces (admin)",
63
+ description: "ワークスペースの管理(管理者トークン必須)",
64
+ },
65
+ {
66
+ name: "API Keys (admin)",
67
+ description: "ワークスペース API キーの発行・無効化(管理者トークン必須)",
68
+ },
69
+ ];
70
+ /**
71
+ * Stamp `tag` onto every operation in a path-item. The path declares its
72
+ * tag once — `tagged("Sessions", { post: {…}, get: {…} })` — rather than
73
+ * repeating `tags` on each method.
74
+ */
75
+ function tagged(tag, pathItem) {
76
+ const out = {};
77
+ for (const [method, op] of Object.entries(pathItem)) {
78
+ out[method] = { ...op, tags: [tag] };
79
+ }
80
+ return out;
81
+ }
82
+ const limitParam = queryParam("limit", "ページサイズ。サーバー側で上限が適用される。", {
41
83
  type: "integer",
42
84
  minimum: 1,
43
85
  });
44
- const channelParam = queryParam("channel", "Filter by session channel.");
86
+ const channelParam = queryParam("channel", "セッションのチャンネルで絞り込む。");
45
87
  function buildPaths() {
46
88
  return {
47
- "/v1/me": {
89
+ "/v1/me": tagged("Me", {
48
90
  get: {
49
- summary: "Identity probe the workspace the caller's key resolves to.",
50
- responses: { "200": res("workspace id"), "401": res("unauthorized") },
91
+ summary: "識別プローブ呼び出し元のキーが解決するワークスペースを返す。",
92
+ responses: {
93
+ "200": res("ワークスペース id"),
94
+ "401": res("認証エラー"),
95
+ },
51
96
  },
52
- },
53
- "/v1/sessions": {
97
+ }),
98
+ "/v1/sessions": tagged("Sessions", {
54
99
  post: {
55
- summary: "Create a session.",
100
+ summary: "セッションを作成する。",
56
101
  requestBody: jsonBody("SessionInit"),
57
102
  responses: {
58
- "200": res("created session id"),
59
- "400": res("invalid body"),
60
- "401": res("unauthorized"),
103
+ "200": res("作成されたセッション id"),
104
+ "400": res("リクエストボディが不正"),
105
+ "401": res("認証エラー"),
61
106
  },
62
107
  },
63
108
  get: {
64
- summary: "List recent sessions plus their persons map.",
109
+ summary: "最近のセッション一覧と、それに紐づく persons マップを取得する。",
65
110
  parameters: [limitParam, channelParam],
66
- responses: { "200": res("session list envelope"), "401": res("unauthorized") },
111
+ responses: {
112
+ "200": res("セッション一覧のエンベロープ"),
113
+ "401": res("認証エラー"),
114
+ },
67
115
  },
68
- },
69
- "/v1/sessions/{id}": {
116
+ }),
117
+ "/v1/sessions/{id}": tagged("Sessions", {
70
118
  get: {
71
- summary: "Fetch one session plus its persons map.",
72
- parameters: [pathParam("id", "Session id.")],
119
+ summary: "単一セッションと、それに紐づく persons マップを取得する。",
120
+ parameters: [pathParam("id", "セッション id")],
73
121
  responses: {
74
- "200": res("session envelope"),
75
- "404": res("session not found"),
76
- "401": res("unauthorized"),
122
+ "200": res("セッションのエンベロープ"),
123
+ "404": res("セッションが見つからない"),
124
+ "401": res("認証エラー"),
77
125
  },
78
126
  },
79
- },
80
- "/v1/sessions/{id}/events": {
127
+ }),
128
+ "/v1/sessions/{id}/events": tagged("Sessions", {
81
129
  post: {
82
- summary: "Append events to a session.",
83
- parameters: [pathParam("id", "Session id.")],
130
+ summary: "セッションにイベントを追加する。",
131
+ parameters: [pathParam("id", "セッション id")],
84
132
  requestBody: jsonBody("EventBatch"),
85
133
  responses: {
86
- "204": res("appended"),
87
- "400": res("invalid body"),
88
- "404": res("session not found"),
89
- "401": res("unauthorized"),
134
+ "204": res("追加完了"),
135
+ "400": res("リクエストボディが不正"),
136
+ "404": res("セッションが見つからない"),
137
+ "401": res("認証エラー"),
90
138
  },
91
139
  },
92
- },
93
- "/v1/search": {
140
+ get: {
141
+ summary: "セッションの生イベントログを seq 順で取得する(fold せず、各イベントの時刻つき)。",
142
+ parameters: [pathParam("id", "セッション id。")],
143
+ responses: {
144
+ "200": res("イベントログ"),
145
+ "404": res("セッションが見つからない"),
146
+ "401": res("認証エラー"),
147
+ },
148
+ },
149
+ }),
150
+ "/v1/search": tagged("Search", {
94
151
  post: {
95
- summary: "Score the workspace corpus against a query session.",
152
+ summary: "クエリセッションに対してワークスペースのコーパスをスコアリングする。",
96
153
  requestBody: jsonBody("SearchRequest"),
97
154
  responses: {
98
- "200": res("scored results plus persons map"),
99
- "400": res("invalid body"),
100
- "404": res("query session not found"),
101
- "401": res("unauthorized"),
155
+ "200": res("スコアリング結果と persons マップ"),
156
+ "400": res("リクエストボディが不正"),
157
+ "404": res("クエリセッションが見つからない"),
158
+ "401": res("認証エラー"),
102
159
  },
103
160
  },
104
- },
105
- "/v1/persons": {
161
+ }),
162
+ "/v1/persons": tagged("Persons", {
106
163
  post: {
107
- summary: "Create a person (server allocates the id).",
164
+ summary: "person を作成する(id はサーバーが採番する)。",
108
165
  requestBody: jsonBody("PersonCreate"),
109
166
  responses: {
110
- "201": res("created person"),
111
- "400": res("invalid body"),
112
- "401": res("unauthorized"),
167
+ "201": res("作成された person"),
168
+ "400": res("リクエストボディが不正"),
169
+ "401": res("認証エラー"),
113
170
  },
114
171
  },
115
172
  get: {
116
- summary: "List or free-text search persons.",
117
- parameters: [limitParam, queryParam("q", "Free-text query over id + display_name.")],
118
- responses: { "200": res("person list"), "401": res("unauthorized") },
173
+ summary: "person の一覧取得、またはフリーテキスト検索を行う。",
174
+ parameters: [
175
+ limitParam,
176
+ queryParam("q", "id と display_name に対するフリーテキストクエリ。"),
177
+ ],
178
+ responses: {
179
+ "200": res("person 一覧"),
180
+ "401": res("認証エラー"),
181
+ },
119
182
  },
120
- },
121
- "/v1/persons/{id}": {
183
+ }),
184
+ "/v1/persons/{id}": tagged("Persons", {
122
185
  put: {
123
- summary: "Upsert a person at a caller-supplied id.",
124
- parameters: [pathParam("id", "Person id.")],
186
+ summary: "呼び出し元が指定した id person upsert する。",
187
+ parameters: [pathParam("id", "person id")],
125
188
  requestBody: jsonBody("PersonCreate"),
126
189
  responses: {
127
- "200": res("upserted person"),
128
- "400": res("invalid body"),
129
- "401": res("unauthorized"),
190
+ "200": res("upsert された person"),
191
+ "400": res("リクエストボディが不正"),
192
+ "401": res("認証エラー"),
130
193
  },
131
194
  },
132
195
  patch: {
133
- summary: "Patch a person's profile fields.",
134
- parameters: [pathParam("id", "Person id.")],
196
+ summary: "person のプロフィール項目を部分更新する。",
197
+ parameters: [pathParam("id", "person id")],
135
198
  requestBody: jsonBody("PersonUpdate"),
136
199
  responses: {
137
- "200": res("updated person"),
138
- "400": res("invalid body"),
139
- "404": res("person not found"),
140
- "401": res("unauthorized"),
200
+ "200": res("更新された person"),
201
+ "400": res("リクエストボディが不正"),
202
+ "404": res("person が見つからない"),
203
+ "401": res("認証エラー"),
141
204
  },
142
205
  },
143
206
  get: {
144
- summary: "Fetch one person.",
145
- parameters: [pathParam("id", "Person id.")],
207
+ summary: "単一の person を取得する。",
208
+ parameters: [pathParam("id", "person id")],
146
209
  responses: {
147
210
  "200": res("person"),
148
- "404": res("person not found"),
149
- "401": res("unauthorized"),
211
+ "404": res("person が見つからない"),
212
+ "401": res("認証エラー"),
150
213
  },
151
214
  },
152
- },
153
- "/v1/persons/{id}/sessions": {
215
+ }),
216
+ "/v1/persons/{id}/sessions": tagged("Persons", {
154
217
  get: {
155
- summary: "Sessions this person participates in (or can view).",
218
+ summary: "この person が参加している(または閲覧可能な)セッション一覧。",
156
219
  parameters: [
157
- pathParam("id", "Person id."),
220
+ pathParam("id", "person id"),
158
221
  limitParam,
159
222
  channelParam,
160
- queryParam("scope", "`participant` (default) or `viewable`.", {
223
+ queryParam("scope", "`participant`(デフォルト)または `viewable`。", {
161
224
  type: "string",
162
225
  enum: ["participant", "viewable"],
163
226
  }),
164
227
  ],
165
- responses: { "200": res("session list envelope"), "401": res("unauthorized") },
228
+ responses: {
229
+ "200": res("セッション一覧のエンベロープ"),
230
+ "401": res("認証エラー"),
231
+ },
166
232
  },
167
- },
168
- "/admin/v1/workspaces": {
233
+ }),
234
+ "/v1/persons/{id}/aliases": tagged("Persons", {
235
+ get: {
236
+ summary: "この person の alias 一覧を取得する(直近使用が先頭)。",
237
+ parameters: [pathParam("id", "person id。")],
238
+ responses: {
239
+ "200": res("alias 一覧"),
240
+ "401": res("認証エラー"),
241
+ },
242
+ },
243
+ }),
244
+ "/v1/persons/{id}/aliases/{name}": tagged("Persons", {
245
+ put: {
246
+ summary: "person に alias を upsert する。name は case-insensitive で比較される。",
247
+ parameters: [
248
+ pathParam("id", "person id。"),
249
+ pathParam("name", "alias の名前(URL-encoded)。"),
250
+ ],
251
+ requestBody: jsonBody("AliasUpsert"),
252
+ responses: {
253
+ "200": res("upsert された alias"),
254
+ "400": res("リクエストボディが不正"),
255
+ "404": res("person が見つからない"),
256
+ "401": res("認証エラー"),
257
+ },
258
+ },
259
+ }),
260
+ "/admin/v1/workspaces": tagged("Workspaces (admin)", {
169
261
  post: {
170
- summary: "Create a workspace (and, by default, an initial key).",
262
+ summary: "ワークスペースを作成する(デフォルトで初期キーも発行する)。",
171
263
  security: adminAuth,
172
264
  requestBody: jsonBody("CreateWorkspace"),
173
265
  responses: {
174
- "200": res("workspace, plus key unless issueKey=false"),
175
- "400": res("invalid body or workspace id"),
176
- "401": res("unauthorized"),
266
+ "200": res("ワークスペース(issueKey=false でない限りキーも含む)"),
267
+ "400": res("リクエストボディまたはワークスペース id が不正"),
268
+ "401": res("認証エラー"),
177
269
  },
178
270
  },
179
271
  get: {
180
- summary: "List all workspaces.",
272
+ summary: "全ワークスペースを一覧取得する。",
181
273
  security: adminAuth,
182
- responses: { "200": res("workspace list"), "401": res("unauthorized") },
274
+ responses: {
275
+ "200": res("ワークスペース一覧"),
276
+ "401": res("認証エラー"),
277
+ },
183
278
  },
184
- },
185
- "/admin/v1/workspaces/{id}": {
279
+ }),
280
+ "/admin/v1/workspaces/{id}": tagged("Workspaces (admin)", {
186
281
  get: {
187
- summary: "Fetch one workspace.",
282
+ summary: "単一のワークスペースを取得する。",
188
283
  security: adminAuth,
189
- parameters: [pathParam("id", "Workspace id.")],
284
+ parameters: [pathParam("id", "ワークスペース id")],
190
285
  responses: {
191
- "200": res("workspace"),
192
- "404": res("workspace not found"),
193
- "401": res("unauthorized"),
286
+ "200": res("ワークスペース"),
287
+ "404": res("ワークスペースが見つからない"),
288
+ "401": res("認証エラー"),
194
289
  },
195
290
  },
196
291
  delete: {
197
- summary: "Delete a workspace (cascades to keys, sessions, events).",
292
+ summary: "ワークスペースを削除する(キー・セッション・イベントにカスケードする)。",
198
293
  security: adminAuth,
199
- parameters: [pathParam("id", "Workspace id.")],
294
+ parameters: [pathParam("id", "ワークスペース id")],
200
295
  responses: {
201
- "204": res("deleted"),
202
- "404": res("workspace not found"),
203
- "401": res("unauthorized"),
296
+ "204": res("削除完了"),
297
+ "404": res("ワークスペースが見つからない"),
298
+ "401": res("認証エラー"),
204
299
  },
205
300
  },
206
- },
207
- "/admin/v1/workspaces/{id}/keys": {
301
+ }),
302
+ "/admin/v1/workspaces/{id}/keys": tagged("API Keys (admin)", {
208
303
  post: {
209
- summary: "Issue a new API key for a workspace.",
304
+ summary: "ワークスペースに新しい API キーを発行する。",
210
305
  security: adminAuth,
211
- parameters: [pathParam("id", "Workspace id.")],
306
+ parameters: [pathParam("id", "ワークスペース id")],
212
307
  requestBody: jsonBody("IssueKey", false),
213
308
  responses: {
214
- "200": res("issued key (raw key returned once)"),
215
- "400": res("invalid body"),
216
- "404": res("workspace not found"),
217
- "401": res("unauthorized"),
309
+ "200": res("発行されたキー(生のキーは一度のみ返却)"),
310
+ "400": res("リクエストボディが不正"),
311
+ "404": res("ワークスペースが見つからない"),
312
+ "401": res("認証エラー"),
218
313
  },
219
314
  },
220
315
  get: {
221
- summary: "List a workspace's API keys (hashes only).",
316
+ summary: "ワークスペースの API キー一覧を取得する(ハッシュのみ)。",
222
317
  security: adminAuth,
223
- parameters: [pathParam("id", "Workspace id.")],
318
+ parameters: [pathParam("id", "ワークスペース id")],
224
319
  responses: {
225
- "200": res("key list"),
226
- "404": res("workspace not found"),
227
- "401": res("unauthorized"),
320
+ "200": res("キー一覧"),
321
+ "404": res("ワークスペースが見つからない"),
322
+ "401": res("認証エラー"),
228
323
  },
229
324
  },
230
- },
231
- "/admin/v1/workspaces/{id}/keys/{keyId}": {
325
+ }),
326
+ "/admin/v1/workspaces/{id}/keys/{keyId}": tagged("API Keys (admin)", {
232
327
  delete: {
233
- summary: "Revoke an API key.",
328
+ summary: "API キーを無効化する。",
234
329
  security: adminAuth,
235
330
  parameters: [
236
- pathParam("id", "Workspace id."),
237
- pathParam("keyId", "Key id."),
331
+ pathParam("id", "ワークスペース id"),
332
+ pathParam("keyId", "キー id"),
238
333
  ],
239
334
  responses: {
240
- "204": res("revoked (idempotent)"),
241
- "404": res("key not found"),
242
- "401": res("unauthorized"),
335
+ "204": res("無効化完了(冪等)"),
336
+ "404": res("キーが見つからない"),
337
+ "401": res("認証エラー"),
243
338
  },
244
339
  },
245
- },
340
+ }),
246
341
  };
247
342
  }
248
343
  /**
@@ -256,12 +351,14 @@ export function buildOpenApiDocument() {
256
351
  info: {
257
352
  title: "engram-server",
258
353
  version: "1.0",
259
- description: "Cross-session retrieval for AI agents. Request bodies are derived " +
260
- "from the server's Zod schemas (src/schemas.ts); response schemas " +
261
- "are described by status only for now and are a planned follow-up.",
354
+ description: "AI エージェント向けのクロスセッション検索。リクエストボディは" +
355
+ "サーバーの Zod スキーマ(src/schemas.ts)から導出される。" +
356
+ "レスポンススキーマは現状ステータスコードのみの記述で、" +
357
+ "本格的な記述は今後対応予定。",
262
358
  },
359
+ tags: TAG_DEFS.map((t) => ({ ...t })),
263
360
  servers: [
264
- { url: "/", description: "relative to the deployed engram-server" },
361
+ { url: "/", description: "デプロイされた engram-server からの相対パス" },
265
362
  ],
266
363
  security: workspaceAuth,
267
364
  components: {
@@ -269,13 +366,14 @@ export function buildOpenApiDocument() {
269
366
  workspaceKey: {
270
367
  type: "http",
271
368
  scheme: "bearer",
272
- description: "Workspace API key (`eng_…`). Also accepted as the `X-Api-Key` header.",
369
+ description: "ワークスペース API キー(`eng_…`)。`X-Api-Key` ヘッダーでも受け付ける。",
273
370
  },
274
371
  adminToken: {
275
372
  type: "apiKey",
276
373
  in: "header",
277
374
  name: "X-Admin-Token",
278
- description: "Platform admin token for `/admin/v1/*`. Also accepted as `Authorization: Bearer`.",
375
+ description: "`/admin/v1/*` 用のプラットフォーム管理者トークン。" +
376
+ "`Authorization: Bearer` でも受け付ける。",
279
377
  },
280
378
  },
281
379
  schemas: {
@@ -3,11 +3,13 @@ import type { Env } from "../context";
3
3
  import { type RouteConfig } from "./helpers";
4
4
  /**
5
5
  * Person routes. Mount under `/v1`:
6
- * POST /v1/persons create a person (server allocates the id)
7
- * PUT /v1/persons/:id upsert at a host-supplied id
8
- * PATCH /v1/persons/:id patch profile fields
9
- * GET /v1/persons/:id fetch one person
10
- * GET /v1/persons list / free-text search persons
11
- * GET /v1/persons/:id/sessions sessions this person participates in / can view
6
+ * POST /v1/persons create a person (server allocates the id)
7
+ * PUT /v1/persons/:id upsert at a host-supplied id
8
+ * PATCH /v1/persons/:id patch profile fields
9
+ * GET /v1/persons/:id fetch one person
10
+ * GET /v1/persons list / free-text search persons
11
+ * GET /v1/persons/:id/sessions sessions this person participates in / can view
12
+ * PUT /v1/persons/:id/aliases/:name upsert an alias for this person
13
+ * GET /v1/persons/:id/aliases list aliases for this person (newest-used-first)
12
14
  */
13
15
  export declare function personsRoutes(cfg: RouteConfig): Hono<Env>;
@@ -1,14 +1,16 @@
1
1
  import { Hono } from "hono";
2
- import { parseJsonBody, personCreateSchema, personUpdateSchema } from "../schemas";
2
+ import { aliasUpsertSchema, parseJsonBody, personCreateSchema, personUpdateSchema, } from "../schemas";
3
3
  import { clampLimit, resolvePersonMap } from "./helpers";
4
4
  /**
5
5
  * Person routes. Mount under `/v1`:
6
- * POST /v1/persons create a person (server allocates the id)
7
- * PUT /v1/persons/:id upsert at a host-supplied id
8
- * PATCH /v1/persons/:id patch profile fields
9
- * GET /v1/persons/:id fetch one person
10
- * GET /v1/persons list / free-text search persons
11
- * GET /v1/persons/:id/sessions sessions this person participates in / can view
6
+ * POST /v1/persons create a person (server allocates the id)
7
+ * PUT /v1/persons/:id upsert at a host-supplied id
8
+ * PATCH /v1/persons/:id patch profile fields
9
+ * GET /v1/persons/:id fetch one person
10
+ * GET /v1/persons list / free-text search persons
11
+ * GET /v1/persons/:id/sessions sessions this person participates in / can view
12
+ * PUT /v1/persons/:id/aliases/:name upsert an alias for this person
13
+ * GET /v1/persons/:id/aliases list aliases for this person (newest-used-first)
12
14
  */
13
15
  export function personsRoutes(cfg) {
14
16
  const app = new Hono();
@@ -50,6 +52,22 @@ export function personsRoutes(cfg) {
50
52
  const persons = await c.var.ctx.storage.listPersons({ limit, q });
51
53
  return c.json({ persons });
52
54
  });
55
+ app.put("/persons/:id/aliases/:name", async (c) => {
56
+ const id = c.req.param("id");
57
+ const name = c.req.param("name");
58
+ const body = await parseJsonBody(c, aliasUpsertSchema);
59
+ if (body instanceof Response)
60
+ return body;
61
+ const alias = await c.var.ctx.storage.upsertAlias(id, { name, ...body });
62
+ if (!alias)
63
+ return c.json({ error: "person_not_found" }, 404);
64
+ return c.json(alias);
65
+ });
66
+ app.get("/persons/:id/aliases", async (c) => {
67
+ const id = c.req.param("id");
68
+ const aliases = await c.var.ctx.storage.listAliases(id);
69
+ return c.json({ aliases });
70
+ });
53
71
  app.get("/persons/:id/sessions", async (c) => {
54
72
  const id = c.req.param("id");
55
73
  const limit = clampLimit(c, cfg.defaultListLimit, cfg.maxListLimit);
@@ -3,9 +3,10 @@ import type { Env } from "../context";
3
3
  import { type RouteConfig } from "./helpers";
4
4
  /**
5
5
  * Session routes. Mount under `/v1`:
6
- * POST /v1/sessions create a session
7
- * POST /v1/sessions/:id/events append events
8
- * GET /v1/sessions/:id fetch one session + its persons map
9
- * GET /v1/sessions list recent sessions + persons map
6
+ * POST /v1/sessions create a session
7
+ * POST /v1/sessions/:id/events append events
8
+ * GET /v1/sessions/:id fetch one session + its persons map
9
+ * GET /v1/sessions/:id/events fetch the raw, ordered event log
10
+ * GET /v1/sessions list recent sessions + persons map
10
11
  */
11
12
  export declare function sessionsRoutes(cfg: RouteConfig): Hono<Env>;
@@ -3,10 +3,11 @@ import { eventBatchSchema, parseJsonBody, sessionInitSchema } from "../schemas";
3
3
  import { clampLimit, resolvePersonMap } from "./helpers";
4
4
  /**
5
5
  * Session routes. Mount under `/v1`:
6
- * POST /v1/sessions create a session
7
- * POST /v1/sessions/:id/events append events
8
- * GET /v1/sessions/:id fetch one session + its persons map
9
- * GET /v1/sessions list recent sessions + persons map
6
+ * POST /v1/sessions create a session
7
+ * POST /v1/sessions/:id/events append events
8
+ * GET /v1/sessions/:id fetch one session + its persons map
9
+ * GET /v1/sessions/:id/events fetch the raw, ordered event log
10
+ * GET /v1/sessions list recent sessions + persons map
10
11
  */
11
12
  export function sessionsRoutes(cfg) {
12
13
  const app = new Hono();
@@ -40,6 +41,13 @@ export function sessionsRoutes(cfg) {
40
41
  const persons = await resolvePersonMap(c.var.ctx.storage, [s]);
41
42
  return c.json({ session: s, persons });
42
43
  });
44
+ app.get("/sessions/:id/events", async (c) => {
45
+ const id = c.req.param("id");
46
+ const events = await c.var.ctx.storage.getSessionEvents(id);
47
+ if (events === null)
48
+ return c.json({ error: "session_not_found" }, 404);
49
+ return c.json({ events });
50
+ });
43
51
  app.get("/sessions", async (c) => {
44
52
  const limit = clampLimit(c, cfg.defaultListLimit, cfg.maxListLimit);
45
53
  const channel = c.req.query("channel") || undefined;
package/dist/schemas.d.ts CHANGED
@@ -66,6 +66,11 @@ export declare const personCreateSchema: z.ZodObject<{
66
66
  export declare const personUpdateSchema: z.ZodObject<{
67
67
  display_name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
68
68
  }, z.core.$strip>;
69
+ export declare const aliasUpsertSchema: z.ZodObject<{
70
+ caller: z.ZodString;
71
+ last_used: z.ZodString;
72
+ increment: z.ZodOptional<z.ZodBoolean>;
73
+ }, z.core.$strip>;
69
74
  export declare const searchRequestSchema: z.ZodObject<{
70
75
  query: z.ZodUnion<readonly [z.ZodObject<{
71
76
  sessionId: z.ZodString;
package/dist/schemas.js CHANGED
@@ -56,6 +56,16 @@ export const personCreateSchema = z.object({
56
56
  export const personUpdateSchema = z.object({
57
57
  display_name: z.string().nullable().optional(),
58
58
  });
59
+ // --- Aliases ----------------------------------------------------------
60
+ export const aliasUpsertSchema = z.object({
61
+ caller: z.string().min(1),
62
+ // YYYY-MM-DD; loose-validate so callers can also pass an ISO timestamp
63
+ // and the server will accept the date prefix.
64
+ last_used: z
65
+ .string()
66
+ .regex(/^\d{4}-\d{2}-\d{2}/, "expected YYYY-MM-DD"),
67
+ increment: z.boolean().optional(),
68
+ });
59
69
  // --- Search ----------------------------------------------------------
60
70
  const searchQuerySchema = z.union([
61
71
  z.object({ sessionId: z.string().min(1) }),
package/dist/server.js CHANGED
@@ -53,10 +53,13 @@ export function createServer(opts) {
53
53
  sessions: "POST/GET /v1/sessions",
54
54
  sessionById: "GET /v1/sessions/:id",
55
55
  events: "POST /v1/sessions/:id/events",
56
+ sessionEvents: "GET /v1/sessions/:id/events",
56
57
  search: "POST /v1/search",
57
58
  persons: "POST/GET /v1/persons",
58
59
  personById: "GET/PUT/PATCH /v1/persons/:id",
59
60
  personSessions: "GET /v1/persons/:id/sessions",
61
+ personAliases: "GET /v1/persons/:id/aliases",
62
+ upsertAlias: "PUT /v1/persons/:id/aliases/:name",
60
63
  },
61
64
  }));
62
65
  app.get("/healthz", (c) => c.json({ ok: true }));
@@ -76,7 +79,8 @@ export function createServer(opts) {
76
79
  await next();
77
80
  });
78
81
  // Identity probe — echoes the workspace the caller's key resolves to.
79
- // Used by clients (e.g. engram-web) to label which tenant they're viewing.
82
+ // Used by host clients (e.g. monet's `/v1/engram/*` proxy) to label
83
+ // which tenant they're viewing.
80
84
  app.get("/v1/me", (c) => c.json({ workspaceId: c.var.ctx.workspaceId }));
81
85
  app.route("/v1", sessionsRoutes(cfg));
82
86
  app.route("/v1", personsRoutes(cfg));