@eidentic/server 0.1.1 → 0.2.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/index.cjs CHANGED
@@ -2085,6 +2085,18 @@ function createServer2(opts) {
2085
2085
  });
2086
2086
  const trustProxy = opts.trustProxy ?? false;
2087
2087
  const getClientKey = opts.getClientKey ? opts.getClientKey : (c) => defaultGetClientKey(c, trustProxy);
2088
+ const onAuditEvent = opts.onAuditEvent;
2089
+ const emitAudit = (event) => {
2090
+ if (!onAuditEvent) return;
2091
+ try {
2092
+ onAuditEvent(event);
2093
+ } catch {
2094
+ }
2095
+ };
2096
+ const unauthorized = (c) => {
2097
+ emitAudit({ type: "auth.failure", at: Date.now(), route: c.req.path });
2098
+ return c.json({ error: "Unauthorized" }, 401);
2099
+ };
2088
2100
  let _draining = false;
2089
2101
  const app = new import_hono.Hono({ strict: true });
2090
2102
  const base = opts.basePath ?? "";
@@ -2113,6 +2125,7 @@ function createServer2(opts) {
2113
2125
  if (retryAfterSec !== void 0) {
2114
2126
  c.header("Retry-After", String(retryAfterSec));
2115
2127
  }
2128
+ emitAudit({ type: "ratelimit.exceeded", at: Date.now(), principalId: clientKey, route: c.req.path });
2116
2129
  return c.json({ error: "rate_limited", retryAfterMs: rl.retryAfterMs }, 429);
2117
2130
  }
2118
2131
  await next();
@@ -2150,6 +2163,7 @@ function createServer2(opts) {
2150
2163
  streamQuotaKey = getQuotaKey(principal, agentId);
2151
2164
  const qc = await quota.check(streamQuotaKey);
2152
2165
  if (!qc.ok) {
2166
+ emitAudit({ type: "quota.exceeded", at: Date.now(), scopeKey: streamQuotaKey, ...qc.reason !== void 0 ? { reason: qc.reason } : {} });
2153
2167
  return c.json({ error: "quota_exceeded", reason: qc.reason, usage: qc.usage }, 402);
2154
2168
  }
2155
2169
  if (qc.warn) c.header("X-Eidentic-Quota-Warning", "soft-limit");
@@ -2256,6 +2270,7 @@ function createServer2(opts) {
2256
2270
  if (!rl.ok) {
2257
2271
  const retryAfterSec = rl.retryAfterMs !== void 0 ? Math.ceil(rl.retryAfterMs / 1e3) : void 0;
2258
2272
  if (retryAfterSec !== void 0) c.header("Retry-After", String(retryAfterSec));
2273
+ emitAudit({ type: "ratelimit.exceeded", at: Date.now(), principalId: rlKey, route: c.req.path });
2259
2274
  return c.json({ error: "rate_limited", retryAfterMs: rl.retryAfterMs }, 429);
2260
2275
  }
2261
2276
  return null;
@@ -2265,7 +2280,7 @@ function createServer2(opts) {
2265
2280
  (0, import_body_limit.bodyLimit)({ maxSize: BODY_LIMIT }),
2266
2281
  async (c) => {
2267
2282
  const principal = await runAuth(auth, c.req.raw);
2268
- if (principal === null) return c.json({ error: "Unauthorized" }, 401);
2283
+ if (principal === null) return unauthorized(c);
2269
2284
  const agentId = c.req.param("agentId");
2270
2285
  const rlErr = await checkPostAuthRateLimit(c, principal, agentId);
2271
2286
  if (rlErr) return rlErr;
@@ -2318,7 +2333,7 @@ function createServer2(opts) {
2318
2333
  (0, import_body_limit.bodyLimit)({ maxSize: BODY_LIMIT }),
2319
2334
  async (c) => {
2320
2335
  const principal = await runAuth(auth, c.req.raw);
2321
- if (principal === null) return c.json({ error: "Unauthorized" }, 401);
2336
+ if (principal === null) return unauthorized(c);
2322
2337
  const agentId = c.req.param("agentId");
2323
2338
  const rlErr = await checkPostAuthRateLimit(c, principal, agentId);
2324
2339
  if (rlErr) return rlErr;
@@ -2367,7 +2382,7 @@ function createServer2(opts) {
2367
2382
  (0, import_body_limit.bodyLimit)({ maxSize: BODY_LIMIT }),
2368
2383
  async (c) => {
2369
2384
  const principal = await runAuth(auth, c.req.raw);
2370
- if (principal === null) return c.json({ error: "Unauthorized" }, 401);
2385
+ if (principal === null) return unauthorized(c);
2371
2386
  const agentId = c.req.param("agentId");
2372
2387
  const rlErr = await checkPostAuthRateLimit(c, principal, agentId);
2373
2388
  if (rlErr) return rlErr;
@@ -2416,6 +2431,7 @@ function createServer2(opts) {
2416
2431
  asyncQuotaKey = getQuotaKey(principal, agentId);
2417
2432
  const qc = await quota.check(asyncQuotaKey);
2418
2433
  if (!qc.ok) {
2434
+ emitAudit({ type: "quota.exceeded", at: Date.now(), scopeKey: asyncQuotaKey, ...qc.reason !== void 0 ? { reason: qc.reason } : {} });
2419
2435
  return c.json({ error: "quota_exceeded", reason: qc.reason, usage: qc.usage }, 402);
2420
2436
  }
2421
2437
  asyncQuotaReservation = qc.reservation;
@@ -2507,7 +2523,7 @@ function createServer2(opts) {
2507
2523
  r.get("/v1/agents/:agentId/runs/:runId/status", async (c) => {
2508
2524
  const principal = await runAuth(auth, c.req.raw);
2509
2525
  if (principal === null) {
2510
- return c.json({ error: "Unauthorized" }, 401);
2526
+ return unauthorized(c);
2511
2527
  }
2512
2528
  const agentId = c.req.param("agentId");
2513
2529
  const agent = resolve(agentId);
@@ -2538,7 +2554,7 @@ function createServer2(opts) {
2538
2554
  r.get("/v1/agents/:agentId/sessions/:sessionId/events", async (c) => {
2539
2555
  const principal = await runAuth(auth, c.req.raw);
2540
2556
  if (principal === null) {
2541
- return c.json({ error: "Unauthorized" }, 401);
2557
+ return unauthorized(c);
2542
2558
  }
2543
2559
  const agentId = c.req.param("agentId");
2544
2560
  const agent = resolve(agentId);
@@ -2567,7 +2583,7 @@ function createServer2(opts) {
2567
2583
  r.get("/v1/workflows", async (c) => {
2568
2584
  const principal = await runAuth(auth, c.req.raw);
2569
2585
  if (principal === null) {
2570
- return c.json({ error: "Unauthorized" }, 401);
2586
+ return unauthorized(c);
2571
2587
  }
2572
2588
  const summaries = workflowRuns.list().filter((rec) => checkWorkflowOwnership(rec, principal)).map((rec) => ({
2573
2589
  id: rec.id,
@@ -2582,7 +2598,7 @@ function createServer2(opts) {
2582
2598
  r.get("/v1/workflows/:id", async (c) => {
2583
2599
  const principal = await runAuth(auth, c.req.raw);
2584
2600
  if (principal === null) {
2585
- return c.json({ error: "Unauthorized" }, 401);
2601
+ return unauthorized(c);
2586
2602
  }
2587
2603
  const id = c.req.param("id");
2588
2604
  const rec = workflowRuns.get(id);
package/dist/index.d.cts CHANGED
@@ -2,7 +2,7 @@ import * as hono from 'hono';
2
2
  import { Hono } from 'hono';
3
3
  import { cors } from 'hono/cors';
4
4
  import { Agent } from '@eidentic/core';
5
- import { RateLimiterPort, RateLimitResult, QuotaPort, QuotaLimits, QuotaCheck, StreamEvent, LoggerPort, Usage, CostBreakdown, AuthPrincipal, AuthPort } from '@eidentic/types';
5
+ import { RateLimiterPort, RateLimitResult, QuotaPort, QuotaLimits, QuotaCheck, StreamEvent, LoggerPort, Usage, CostBreakdown, AuthPrincipal, AuthPort, AuditSink } from '@eidentic/types';
6
6
  export { AuthPort, AuthPrincipal, AuthRequest, QuotaCheck, QuotaLimits, QuotaPort, QuotaUsage, RateLimiterPort } from '@eidentic/types';
7
7
  import { WorkflowResult, WorkflowRunOwner, RecordOptions, WorkflowRunError, WorkflowRunRegistry, StepTrace } from '@eidentic/workflow';
8
8
  export { RecordOptions, WorkflowRunError, WorkflowRunOwner, WorkflowRunRegistry } from '@eidentic/workflow';
@@ -868,6 +868,14 @@ interface ServerOptions {
868
868
  * `principal.apiKey ?? principal.userId ?? principal.orgId ?? "anonymous"`.
869
869
  */
870
870
  quotaKey?: (principal: AuthPrincipal, agentId: string) => string;
871
+ /**
872
+ * Best-effort audit sink (§10.4/§15). When set, the server emits structured `AuditEvent`s for
873
+ * security-relevant rejections at the HTTP edge: `auth.failure` (401), `quota.exceeded` (402),
874
+ * and `ratelimit.exceeded` (429, both pre-auth and post-auth). This is the same `AuditEvent`
875
+ * stream `Agent` emits (`tool.call` / `permission.denied` / `erasure`) — wire the same sink to
876
+ * both for one unified audit log. A throwing sink is swallowed, never affecting a request.
877
+ */
878
+ onAuditEvent?: AuditSink;
871
879
  /**
872
880
  * Maximum number of characters allowed in the `input` field of /query and /runs
873
881
  * requests, and in a string `decision` on /resume requests.
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import * as hono from 'hono';
2
2
  import { Hono } from 'hono';
3
3
  import { cors } from 'hono/cors';
4
4
  import { Agent } from '@eidentic/core';
5
- import { RateLimiterPort, RateLimitResult, QuotaPort, QuotaLimits, QuotaCheck, StreamEvent, LoggerPort, Usage, CostBreakdown, AuthPrincipal, AuthPort } from '@eidentic/types';
5
+ import { RateLimiterPort, RateLimitResult, QuotaPort, QuotaLimits, QuotaCheck, StreamEvent, LoggerPort, Usage, CostBreakdown, AuthPrincipal, AuthPort, AuditSink } from '@eidentic/types';
6
6
  export { AuthPort, AuthPrincipal, AuthRequest, QuotaCheck, QuotaLimits, QuotaPort, QuotaUsage, RateLimiterPort } from '@eidentic/types';
7
7
  import { WorkflowResult, WorkflowRunOwner, RecordOptions, WorkflowRunError, WorkflowRunRegistry, StepTrace } from '@eidentic/workflow';
8
8
  export { RecordOptions, WorkflowRunError, WorkflowRunOwner, WorkflowRunRegistry } from '@eidentic/workflow';
@@ -868,6 +868,14 @@ interface ServerOptions {
868
868
  * `principal.apiKey ?? principal.userId ?? principal.orgId ?? "anonymous"`.
869
869
  */
870
870
  quotaKey?: (principal: AuthPrincipal, agentId: string) => string;
871
+ /**
872
+ * Best-effort audit sink (§10.4/§15). When set, the server emits structured `AuditEvent`s for
873
+ * security-relevant rejections at the HTTP edge: `auth.failure` (401), `quota.exceeded` (402),
874
+ * and `ratelimit.exceeded` (429, both pre-auth and post-auth). This is the same `AuditEvent`
875
+ * stream `Agent` emits (`tool.call` / `permission.denied` / `erasure`) — wire the same sink to
876
+ * both for one unified audit log. A throwing sink is swallowed, never affecting a request.
877
+ */
878
+ onAuditEvent?: AuditSink;
871
879
  /**
872
880
  * Maximum number of characters allowed in the `input` field of /query and /runs
873
881
  * requests, and in a string `decision` on /resume requests.
package/dist/index.js CHANGED
@@ -1007,6 +1007,18 @@ function createServer(opts) {
1007
1007
  });
1008
1008
  const trustProxy = opts.trustProxy ?? false;
1009
1009
  const getClientKey = opts.getClientKey ? opts.getClientKey : (c) => defaultGetClientKey(c, trustProxy);
1010
+ const onAuditEvent = opts.onAuditEvent;
1011
+ const emitAudit = (event) => {
1012
+ if (!onAuditEvent) return;
1013
+ try {
1014
+ onAuditEvent(event);
1015
+ } catch {
1016
+ }
1017
+ };
1018
+ const unauthorized = (c) => {
1019
+ emitAudit({ type: "auth.failure", at: Date.now(), route: c.req.path });
1020
+ return c.json({ error: "Unauthorized" }, 401);
1021
+ };
1010
1022
  let _draining = false;
1011
1023
  const app = new Hono({ strict: true });
1012
1024
  const base = opts.basePath ?? "";
@@ -1035,6 +1047,7 @@ function createServer(opts) {
1035
1047
  if (retryAfterSec !== void 0) {
1036
1048
  c.header("Retry-After", String(retryAfterSec));
1037
1049
  }
1050
+ emitAudit({ type: "ratelimit.exceeded", at: Date.now(), principalId: clientKey, route: c.req.path });
1038
1051
  return c.json({ error: "rate_limited", retryAfterMs: rl.retryAfterMs }, 429);
1039
1052
  }
1040
1053
  await next();
@@ -1072,6 +1085,7 @@ function createServer(opts) {
1072
1085
  streamQuotaKey = getQuotaKey(principal, agentId);
1073
1086
  const qc = await quota.check(streamQuotaKey);
1074
1087
  if (!qc.ok) {
1088
+ emitAudit({ type: "quota.exceeded", at: Date.now(), scopeKey: streamQuotaKey, ...qc.reason !== void 0 ? { reason: qc.reason } : {} });
1075
1089
  return c.json({ error: "quota_exceeded", reason: qc.reason, usage: qc.usage }, 402);
1076
1090
  }
1077
1091
  if (qc.warn) c.header("X-Eidentic-Quota-Warning", "soft-limit");
@@ -1178,6 +1192,7 @@ function createServer(opts) {
1178
1192
  if (!rl.ok) {
1179
1193
  const retryAfterSec = rl.retryAfterMs !== void 0 ? Math.ceil(rl.retryAfterMs / 1e3) : void 0;
1180
1194
  if (retryAfterSec !== void 0) c.header("Retry-After", String(retryAfterSec));
1195
+ emitAudit({ type: "ratelimit.exceeded", at: Date.now(), principalId: rlKey, route: c.req.path });
1181
1196
  return c.json({ error: "rate_limited", retryAfterMs: rl.retryAfterMs }, 429);
1182
1197
  }
1183
1198
  return null;
@@ -1187,7 +1202,7 @@ function createServer(opts) {
1187
1202
  bodyLimit({ maxSize: BODY_LIMIT }),
1188
1203
  async (c) => {
1189
1204
  const principal = await runAuth(auth, c.req.raw);
1190
- if (principal === null) return c.json({ error: "Unauthorized" }, 401);
1205
+ if (principal === null) return unauthorized(c);
1191
1206
  const agentId = c.req.param("agentId");
1192
1207
  const rlErr = await checkPostAuthRateLimit(c, principal, agentId);
1193
1208
  if (rlErr) return rlErr;
@@ -1240,7 +1255,7 @@ function createServer(opts) {
1240
1255
  bodyLimit({ maxSize: BODY_LIMIT }),
1241
1256
  async (c) => {
1242
1257
  const principal = await runAuth(auth, c.req.raw);
1243
- if (principal === null) return c.json({ error: "Unauthorized" }, 401);
1258
+ if (principal === null) return unauthorized(c);
1244
1259
  const agentId = c.req.param("agentId");
1245
1260
  const rlErr = await checkPostAuthRateLimit(c, principal, agentId);
1246
1261
  if (rlErr) return rlErr;
@@ -1289,7 +1304,7 @@ function createServer(opts) {
1289
1304
  bodyLimit({ maxSize: BODY_LIMIT }),
1290
1305
  async (c) => {
1291
1306
  const principal = await runAuth(auth, c.req.raw);
1292
- if (principal === null) return c.json({ error: "Unauthorized" }, 401);
1307
+ if (principal === null) return unauthorized(c);
1293
1308
  const agentId = c.req.param("agentId");
1294
1309
  const rlErr = await checkPostAuthRateLimit(c, principal, agentId);
1295
1310
  if (rlErr) return rlErr;
@@ -1338,6 +1353,7 @@ function createServer(opts) {
1338
1353
  asyncQuotaKey = getQuotaKey(principal, agentId);
1339
1354
  const qc = await quota.check(asyncQuotaKey);
1340
1355
  if (!qc.ok) {
1356
+ emitAudit({ type: "quota.exceeded", at: Date.now(), scopeKey: asyncQuotaKey, ...qc.reason !== void 0 ? { reason: qc.reason } : {} });
1341
1357
  return c.json({ error: "quota_exceeded", reason: qc.reason, usage: qc.usage }, 402);
1342
1358
  }
1343
1359
  asyncQuotaReservation = qc.reservation;
@@ -1429,7 +1445,7 @@ function createServer(opts) {
1429
1445
  r.get("/v1/agents/:agentId/runs/:runId/status", async (c) => {
1430
1446
  const principal = await runAuth(auth, c.req.raw);
1431
1447
  if (principal === null) {
1432
- return c.json({ error: "Unauthorized" }, 401);
1448
+ return unauthorized(c);
1433
1449
  }
1434
1450
  const agentId = c.req.param("agentId");
1435
1451
  const agent = resolve(agentId);
@@ -1460,7 +1476,7 @@ function createServer(opts) {
1460
1476
  r.get("/v1/agents/:agentId/sessions/:sessionId/events", async (c) => {
1461
1477
  const principal = await runAuth(auth, c.req.raw);
1462
1478
  if (principal === null) {
1463
- return c.json({ error: "Unauthorized" }, 401);
1479
+ return unauthorized(c);
1464
1480
  }
1465
1481
  const agentId = c.req.param("agentId");
1466
1482
  const agent = resolve(agentId);
@@ -1489,7 +1505,7 @@ function createServer(opts) {
1489
1505
  r.get("/v1/workflows", async (c) => {
1490
1506
  const principal = await runAuth(auth, c.req.raw);
1491
1507
  if (principal === null) {
1492
- return c.json({ error: "Unauthorized" }, 401);
1508
+ return unauthorized(c);
1493
1509
  }
1494
1510
  const summaries = workflowRuns.list().filter((rec) => checkWorkflowOwnership(rec, principal)).map((rec) => ({
1495
1511
  id: rec.id,
@@ -1504,7 +1520,7 @@ function createServer(opts) {
1504
1520
  r.get("/v1/workflows/:id", async (c) => {
1505
1521
  const principal = await runAuth(auth, c.req.raw);
1506
1522
  if (principal === null) {
1507
- return c.json({ error: "Unauthorized" }, 401);
1523
+ return unauthorized(c);
1508
1524
  }
1509
1525
  const id = c.req.param("id");
1510
1526
  const rec = workflowRuns.get(id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eidentic/server",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {
@@ -31,9 +31,9 @@
31
31
  "ai": "^6.0.0",
32
32
  "cron-parser": "^5.5.0",
33
33
  "hono": "^4.12.0",
34
- "@eidentic/core": "0.1.1",
35
- "@eidentic/types": "0.1.1",
36
- "@eidentic/workflow": "0.1.1"
34
+ "@eidentic/workflow": "0.1.2",
35
+ "@eidentic/core": "0.2.0",
36
+ "@eidentic/types": "0.2.0"
37
37
  },
38
38
  "optionalDependencies": {
39
39
  "@hono/node-server": "^2.0.0"