@hexis-ai/engram-server 0.3.0 → 0.4.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,276 @@ 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
+ "/admin/v1/workspaces": tagged("Workspaces (admin)", {
169
235
  post: {
170
- summary: "Create a workspace (and, by default, an initial key).",
236
+ summary: "ワークスペースを作成する(デフォルトで初期キーも発行する)。",
171
237
  security: adminAuth,
172
238
  requestBody: jsonBody("CreateWorkspace"),
173
239
  responses: {
174
- "200": res("workspace, plus key unless issueKey=false"),
175
- "400": res("invalid body or workspace id"),
176
- "401": res("unauthorized"),
240
+ "200": res("ワークスペース(issueKey=false でない限りキーも含む)"),
241
+ "400": res("リクエストボディまたはワークスペース id が不正"),
242
+ "401": res("認証エラー"),
177
243
  },
178
244
  },
179
245
  get: {
180
- summary: "List all workspaces.",
246
+ summary: "全ワークスペースを一覧取得する。",
181
247
  security: adminAuth,
182
- responses: { "200": res("workspace list"), "401": res("unauthorized") },
248
+ responses: {
249
+ "200": res("ワークスペース一覧"),
250
+ "401": res("認証エラー"),
251
+ },
183
252
  },
184
- },
185
- "/admin/v1/workspaces/{id}": {
253
+ }),
254
+ "/admin/v1/workspaces/{id}": tagged("Workspaces (admin)", {
186
255
  get: {
187
- summary: "Fetch one workspace.",
256
+ summary: "単一のワークスペースを取得する。",
188
257
  security: adminAuth,
189
- parameters: [pathParam("id", "Workspace id.")],
258
+ parameters: [pathParam("id", "ワークスペース id")],
190
259
  responses: {
191
- "200": res("workspace"),
192
- "404": res("workspace not found"),
193
- "401": res("unauthorized"),
260
+ "200": res("ワークスペース"),
261
+ "404": res("ワークスペースが見つからない"),
262
+ "401": res("認証エラー"),
194
263
  },
195
264
  },
196
265
  delete: {
197
- summary: "Delete a workspace (cascades to keys, sessions, events).",
266
+ summary: "ワークスペースを削除する(キー・セッション・イベントにカスケードする)。",
198
267
  security: adminAuth,
199
- parameters: [pathParam("id", "Workspace id.")],
268
+ parameters: [pathParam("id", "ワークスペース id")],
200
269
  responses: {
201
- "204": res("deleted"),
202
- "404": res("workspace not found"),
203
- "401": res("unauthorized"),
270
+ "204": res("削除完了"),
271
+ "404": res("ワークスペースが見つからない"),
272
+ "401": res("認証エラー"),
204
273
  },
205
274
  },
206
- },
207
- "/admin/v1/workspaces/{id}/keys": {
275
+ }),
276
+ "/admin/v1/workspaces/{id}/keys": tagged("API Keys (admin)", {
208
277
  post: {
209
- summary: "Issue a new API key for a workspace.",
278
+ summary: "ワークスペースに新しい API キーを発行する。",
210
279
  security: adminAuth,
211
- parameters: [pathParam("id", "Workspace id.")],
280
+ parameters: [pathParam("id", "ワークスペース id")],
212
281
  requestBody: jsonBody("IssueKey", false),
213
282
  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"),
283
+ "200": res("発行されたキー(生のキーは一度のみ返却)"),
284
+ "400": res("リクエストボディが不正"),
285
+ "404": res("ワークスペースが見つからない"),
286
+ "401": res("認証エラー"),
218
287
  },
219
288
  },
220
289
  get: {
221
- summary: "List a workspace's API keys (hashes only).",
290
+ summary: "ワークスペースの API キー一覧を取得する(ハッシュのみ)。",
222
291
  security: adminAuth,
223
- parameters: [pathParam("id", "Workspace id.")],
292
+ parameters: [pathParam("id", "ワークスペース id")],
224
293
  responses: {
225
- "200": res("key list"),
226
- "404": res("workspace not found"),
227
- "401": res("unauthorized"),
294
+ "200": res("キー一覧"),
295
+ "404": res("ワークスペースが見つからない"),
296
+ "401": res("認証エラー"),
228
297
  },
229
298
  },
230
- },
231
- "/admin/v1/workspaces/{id}/keys/{keyId}": {
299
+ }),
300
+ "/admin/v1/workspaces/{id}/keys/{keyId}": tagged("API Keys (admin)", {
232
301
  delete: {
233
- summary: "Revoke an API key.",
302
+ summary: "API キーを無効化する。",
234
303
  security: adminAuth,
235
304
  parameters: [
236
- pathParam("id", "Workspace id."),
237
- pathParam("keyId", "Key id."),
305
+ pathParam("id", "ワークスペース id"),
306
+ pathParam("keyId", "キー id"),
238
307
  ],
239
308
  responses: {
240
- "204": res("revoked (idempotent)"),
241
- "404": res("key not found"),
242
- "401": res("unauthorized"),
309
+ "204": res("無効化完了(冪等)"),
310
+ "404": res("キーが見つからない"),
311
+ "401": res("認証エラー"),
243
312
  },
244
313
  },
245
- },
314
+ }),
246
315
  };
247
316
  }
248
317
  /**
@@ -256,12 +325,14 @@ export function buildOpenApiDocument() {
256
325
  info: {
257
326
  title: "engram-server",
258
327
  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.",
328
+ description: "AI エージェント向けのクロスセッション検索。リクエストボディは" +
329
+ "サーバーの Zod スキーマ(src/schemas.ts)から導出される。" +
330
+ "レスポンススキーマは現状ステータスコードのみの記述で、" +
331
+ "本格的な記述は今後対応予定。",
262
332
  },
333
+ tags: TAG_DEFS.map((t) => ({ ...t })),
263
334
  servers: [
264
- { url: "/", description: "relative to the deployed engram-server" },
335
+ { url: "/", description: "デプロイされた engram-server からの相対パス" },
265
336
  ],
266
337
  security: workspaceAuth,
267
338
  components: {
@@ -269,13 +340,14 @@ export function buildOpenApiDocument() {
269
340
  workspaceKey: {
270
341
  type: "http",
271
342
  scheme: "bearer",
272
- description: "Workspace API key (`eng_…`). Also accepted as the `X-Api-Key` header.",
343
+ description: "ワークスペース API キー(`eng_…`)。`X-Api-Key` ヘッダーでも受け付ける。",
273
344
  },
274
345
  adminToken: {
275
346
  type: "apiKey",
276
347
  in: "header",
277
348
  name: "X-Admin-Token",
278
- description: "Platform admin token for `/admin/v1/*`. Also accepted as `Authorization: Bearer`.",
349
+ description: "`/admin/v1/*` 用のプラットフォーム管理者トークン。" +
350
+ "`Authorization: Bearer` でも受け付ける。",
279
351
  },
280
352
  },
281
353
  schemas: {
@@ -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/server.js CHANGED
@@ -53,6 +53,7 @@ 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",
package/dist/storage.d.ts CHANGED
@@ -18,6 +18,14 @@ export interface StorageAdapter {
18
18
  appendEvents(sessionId: string, events: SessionEvent[]): Promise<void>;
19
19
  /** Materialize a session (events folded into Session shape). */
20
20
  getSession(sessionId: string): Promise<Session | null>;
21
+ /**
22
+ * Raw event log for a session, ordered by `seq`. Unlike `getSession`,
23
+ * this does not fold events — callers get per-event timestamps and the
24
+ * participant / title / end events as recorded. Returns `null` when the
25
+ * session doesn't exist (distinct from an existing session with no
26
+ * events, which returns `[]`).
27
+ */
28
+ getSessionEvents(sessionId: string): Promise<SessionEvent[] | null>;
21
29
  /** List recent sessions. */
22
30
  listSessions(opts: {
23
31
  limit: number;