@hexis-ai/engram-server 0.1.2 → 0.1.4

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.
@@ -57,6 +57,10 @@ export class PostgresAdapter {
57
57
  if (events.length === 0)
58
58
  return;
59
59
  for (const ev of events) {
60
+ // Pass the event object directly: postgres serializes JS objects as
61
+ // JSON for jsonb columns. Doing JSON.stringify ourselves then casting
62
+ // ::jsonb produced a doubly-encoded string value (jsonb containing
63
+ // a string instead of an object).
60
64
  await this.sql `
61
65
  INSERT INTO engram_events (workspace_id, session_id, seq, type, at, payload)
62
66
  VALUES (
@@ -65,7 +69,7 @@ export class PostgresAdapter {
65
69
  ${ev.seq},
66
70
  ${ev.type},
67
71
  ${ev.at},
68
- ${JSON.stringify(ev)}::jsonb
72
+ ${ev}
69
73
  )
70
74
  ON CONFLICT (workspace_id, session_id, seq) DO NOTHING
71
75
  `;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Structured logging for engram-server.
3
+ *
4
+ * Emits one JSON object per line so Cloud Logging picks it up natively:
5
+ * {severity, message, ...fields}. Severity matches Google's "LogSeverity"
6
+ * enum names (DEBUG, INFO, WARNING, ERROR) so log explorer auto-classifies.
7
+ *
8
+ * Keep this dependency-free; structured logging is a small enough surface
9
+ * that pulling pino/winston isn't worth the dep weight.
10
+ */
11
+ export interface LogFields {
12
+ request_id?: string;
13
+ [key: string]: unknown;
14
+ }
15
+ export declare const log: {
16
+ debug: (message: string, fields?: LogFields) => void;
17
+ info: (message: string, fields?: LogFields) => void;
18
+ warn: (message: string, fields?: LogFields) => void;
19
+ error: (message: string, fields?: LogFields) => void;
20
+ };
21
+ /**
22
+ * Generate a short request id when no upstream `x-request-id` is supplied.
23
+ * Not cryptographically strong — just enough to correlate one container's
24
+ * log lines for a single request.
25
+ */
26
+ export declare function newRequestId(): string;
package/dist/logger.js ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Structured logging for engram-server.
3
+ *
4
+ * Emits one JSON object per line so Cloud Logging picks it up natively:
5
+ * {severity, message, ...fields}. Severity matches Google's "LogSeverity"
6
+ * enum names (DEBUG, INFO, WARNING, ERROR) so log explorer auto-classifies.
7
+ *
8
+ * Keep this dependency-free; structured logging is a small enough surface
9
+ * that pulling pino/winston isn't worth the dep weight.
10
+ */
11
+ function emit(severity, message, fields) {
12
+ const line = JSON.stringify({ severity, message, ...fields });
13
+ if (severity === "ERROR")
14
+ console.error(line);
15
+ else if (severity === "WARNING")
16
+ console.warn(line);
17
+ else
18
+ console.log(line);
19
+ }
20
+ export const log = {
21
+ debug: (message, fields) => emit("DEBUG", message, fields),
22
+ info: (message, fields) => emit("INFO", message, fields),
23
+ warn: (message, fields) => emit("WARNING", message, fields),
24
+ error: (message, fields) => emit("ERROR", message, fields),
25
+ };
26
+ /**
27
+ * Generate a short request id when no upstream `x-request-id` is supplied.
28
+ * Not cryptographically strong — just enough to correlate one container's
29
+ * log lines for a single request.
30
+ */
31
+ export function newRequestId() {
32
+ return Math.random().toString(36).slice(2, 12);
33
+ }
package/dist/server.d.ts CHANGED
@@ -29,6 +29,7 @@ export interface CreateServerOptions {
29
29
  interface Env {
30
30
  Variables: {
31
31
  ctx: WorkspaceContext;
32
+ request_id: string;
32
33
  };
33
34
  }
34
35
  export declare function createServer(opts: CreateServerOptions): Hono<Env>;
package/dist/server.js CHANGED
@@ -1,10 +1,41 @@
1
1
  import { Hono } from "hono";
2
2
  import { buildIndex, search, } from "@hexis-ai/engram-core";
3
+ import { log, newRequestId } from "./logger";
3
4
  export function createServer(opts) {
4
5
  const newId = opts.newSessionId ?? (() => crypto.randomUUID());
5
6
  const defaultLimit = opts.defaultListLimit ?? 100;
6
7
  const maxLimit = opts.maxListLimit ?? 500;
7
8
  const app = new Hono();
9
+ // Request id + access log middleware. Runs for every route.
10
+ app.use("*", async (c, next) => {
11
+ const rid = c.req.header("x-request-id") ?? newRequestId();
12
+ c.set("request_id", rid);
13
+ c.header("x-request-id", rid);
14
+ const start = Date.now();
15
+ let status = 500;
16
+ try {
17
+ await next();
18
+ status = c.res.status;
19
+ }
20
+ finally {
21
+ log.info("request", {
22
+ request_id: rid,
23
+ method: c.req.method,
24
+ path: c.req.path,
25
+ status,
26
+ duration_ms: Date.now() - start,
27
+ });
28
+ }
29
+ });
30
+ app.onError((err, c) => {
31
+ log.error("unhandled", {
32
+ request_id: c.var.request_id,
33
+ path: c.req.path,
34
+ error: err instanceof Error ? err.message : String(err),
35
+ stack: err instanceof Error ? err.stack : undefined,
36
+ });
37
+ return c.json({ error: "internal_error" }, 500);
38
+ });
8
39
  // Unauthenticated service info. Useful for browser sanity checks and
9
40
  // health probes (Cloud Run, k8s, uptime monitors).
10
41
  app.get("/", (c) => c.json({
@@ -19,11 +50,15 @@ export function createServer(opts) {
19
50
  }));
20
51
  app.get("/healthz", (c) => c.json({ ok: true }));
21
52
  app.use("/v1/*", async (c, next) => {
22
- const auth = c.req.header("authorization");
23
- const m = auth?.match(/^Bearer\s+(.+)$/i);
24
- if (!m)
53
+ // Prefer X-Api-Key so callers can use the Authorization header for
54
+ // platform-level auth (Cloud Run IAM with an ID token, for example)
55
+ // without collision. Falls back to Authorization: Bearer <key> for
56
+ // existing clients.
57
+ const apiKey = c.req.header("x-api-key") ??
58
+ c.req.header("authorization")?.match(/^Bearer\s+(.+)$/i)?.[1];
59
+ if (!apiKey)
25
60
  return c.json({ error: "unauthorized" }, 401);
26
- const ctx = await opts.auth(m[1]);
61
+ const ctx = await opts.auth(apiKey);
27
62
  if (!ctx)
28
63
  return c.json({ error: "unauthorized" }, 401);
29
64
  c.set("ctx", ctx);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hexis-ai/engram-server",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Engram server: ingest agent session events, persist via a pluggable adapter, expose search.",
5
5
  "keywords": ["engram", "agents", "search", "hono", "postgres", "server"],
6
6
  "homepage": "https://github.com/hexis-ltd/engram#readme",
@@ -41,8 +41,8 @@
41
41
  "dev": "bun --hot src/dev.ts"
42
42
  },
43
43
  "dependencies": {
44
- "@hexis-ai/engram-core": "^0.1.2",
45
- "@hexis-ai/engram-sdk": "^0.1.2",
44
+ "@hexis-ai/engram-core": "^0.1.4",
45
+ "@hexis-ai/engram-sdk": "^0.1.4",
46
46
  "hono": "^4.6.0"
47
47
  },
48
48
  "peerDependencies": {