@personaai/runtime 0.5.2 → 0.5.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.
package/README.md CHANGED
@@ -407,10 +407,10 @@ genuine unclosed gap or an intentional package boundary, not an oversight:
407
407
  - **Fine-grained (per-user, per-action) permissions within an enabled capability** — **by
408
408
  design**, not a gap: see [Capabilities](#capabilities--admin-surface). A capability is on or off
409
409
  per mount; anything finer belongs in `resolveUser` or a hook, not the runtime.
410
- - Framework adapters beyond Express (`@personaai/nextjs`, `@personaai/node`, `@personaai/fastify`,
411
- `@personaai/hono`, `@personaai/nestjs`) — **by design**, not a gap: this package is the
412
- foundation they're meant to wrap, not a replacement for them. `@personaai/express` (Wave 3)
413
- has shipped as the first.
410
+ - Framework adapters beyond the shipped ones (`@personaai/node`, `@personaai/fastify`,
411
+ `@personaai/hono`) — **by design**, not a gap: this package is the foundation they're meant to
412
+ wrap, not a replacement for them. `@personaai/nextjs` (Wave 2), `@personaai/express` and
413
+ `@personaai/nestjs` (Wave 3) have shipped.
414
414
 
415
415
  ## Roadmap
416
416
 
package/dist/index.cjs CHANGED
@@ -69,12 +69,14 @@ function stripMountPath(path, mountPath) {
69
69
 
70
70
  // src/client-factory.ts
71
71
  var import_sdk = require("@personaai/sdk");
72
- function createClientForRequest(options, userId) {
72
+ function createClientForRequest(options, userId, logger) {
73
+ const clientLogger = logger ? logger.child("sdk") : void 0;
73
74
  return new import_sdk.PersonaClient({
74
75
  baseUrl: options.baseUrl,
75
76
  credential: options.credential,
76
77
  externalUserId: userId ?? void 0,
77
- fetch: options.fetch
78
+ fetch: options.fetch,
79
+ logger: clientLogger
78
80
  });
79
81
  }
80
82
 
@@ -133,6 +135,9 @@ function errorToResponse(err, mode) {
133
135
  };
134
136
  }
135
137
 
138
+ // src/runtime.ts
139
+ var import_sdk4 = require("@personaai/sdk");
140
+
136
141
  // src/runRegistry.ts
137
142
  var DEFAULT_RUN_GRACE_MS = 5 * 60 * 1e3;
138
143
  var DEFAULT_MAX_TRACKED_RUNS = 1e3;
@@ -154,7 +159,7 @@ function evictStaleRuns(runs, now, graceMs = DEFAULT_RUN_GRACE_MS, maxTracked =
154
159
  }
155
160
 
156
161
  // src/version.ts
157
- var RUNTIME_VERSION = "0.5.2";
162
+ var RUNTIME_VERSION = "0.5.3";
158
163
 
159
164
  // src/routes/health.ts
160
165
  var alwaysOnCapabilities = {
@@ -476,7 +481,16 @@ function parseChatBody(body) {
476
481
  };
477
482
  }
478
483
  var chatRoute = async (request, ctx) => {
484
+ const logger = ctx.logger.child("chat");
485
+ logger.debug("chatRoute start", { userId: request.userId, hasBody: !!request.body });
479
486
  const body = parseChatBody(request.body);
487
+ logger.trace("chatRoute body", {
488
+ agentId: body.agentId,
489
+ threadId: body.threadId,
490
+ messageCount: body.messages?.length ?? 0,
491
+ hasResume: !!body.resume,
492
+ hasContextOverride: !!body.contextOverride
493
+ });
480
494
  const userId = request.userId;
481
495
  const runCtx = {
482
496
  userId,
@@ -485,7 +499,10 @@ var chatRoute = async (request, ctx) => {
485
499
  threadId: body.threadId,
486
500
  messages: body.messages
487
501
  };
502
+ logger.debug("beforeRun hook", { agentId: body.agentId, threadId: body.threadId });
488
503
  await ctx.hooks?.beforeRun?.(runCtx);
504
+ logger.trace("beforeRun completed", { agentId: body.agentId });
505
+ logger.debug("starting chat stream", { agentId: body.agentId, threadId: body.threadId });
489
506
  const stream = ctx.client.chat.stream(body.agentId, {
490
507
  messages: body.messages,
491
508
  threadId: body.threadId,
@@ -493,9 +510,28 @@ var chatRoute = async (request, ctx) => {
493
510
  contextOverride: body.contextOverride
494
511
  });
495
512
  const runId = crypto.randomUUID();
513
+ logger.info("chat run created", {
514
+ runId,
515
+ agentId: body.agentId,
516
+ threadId: body.threadId,
517
+ userId
518
+ });
496
519
  const driver = new RunDriver(runId, runCtx, stream, ctx.hooks, ctx.mode);
497
520
  ctx.runs.set(runId, driver);
498
- await driver.waitForFirstFrame();
521
+ logger.debug("run driver registered", { runId, trackedRuns: ctx.runs.size });
522
+ try {
523
+ await driver.waitForFirstFrame();
524
+ logger.debug("chat run first frame ready", { runId });
525
+ } catch (err) {
526
+ logger.warn("chat run failed before first frame", {
527
+ runId,
528
+ agentId: body.agentId,
529
+ error: err instanceof Error ? err.message : String(err)
530
+ });
531
+ ctx.runs.delete(runId);
532
+ throw err;
533
+ }
534
+ logger.info("chat stream ready", { runId, agentId: body.agentId });
499
535
  return {
500
536
  kind: "stream",
501
537
  status: 200,
@@ -528,22 +564,44 @@ function parseArchitectBody(body) {
528
564
  };
529
565
  }
530
566
  var architectRoute = async (request, ctx) => {
567
+ const logger = ctx.logger.child("architect");
568
+ logger.debug("architectRoute start", { userId: request.userId });
531
569
  const body = parseArchitectBody(request.body);
570
+ logger.trace("architectRoute body", {
571
+ messageCount: body.messages?.length ?? 0,
572
+ hasResume: !!body.resume
573
+ });
532
574
  const userId = request.userId;
533
575
  const runCtx = {
534
576
  userId,
535
577
  kind: "architect",
536
578
  messages: body.messages
537
579
  };
580
+ logger.debug("beforeRun hook (architect)", { userId });
538
581
  await ctx.hooks?.beforeRun?.(runCtx);
582
+ logger.trace("beforeRun completed (architect)");
583
+ logger.debug("starting architect stream");
539
584
  const stream = ctx.client.architect.stream({
540
585
  messages: body.messages,
541
586
  resume: body.resume
542
587
  });
543
588
  const runId = crypto.randomUUID();
589
+ logger.info("architect run created", { runId, userId });
544
590
  const driver = new RunDriver(runId, runCtx, stream, ctx.hooks, ctx.mode);
545
591
  ctx.runs.set(runId, driver);
546
- await driver.waitForFirstFrame();
592
+ logger.debug("architect driver registered", { runId, trackedRuns: ctx.runs.size });
593
+ try {
594
+ await driver.waitForFirstFrame();
595
+ logger.debug("architect first frame ready", { runId });
596
+ } catch (err) {
597
+ logger.warn("architect run failed before first frame", {
598
+ runId,
599
+ error: err instanceof Error ? err.message : String(err)
600
+ });
601
+ ctx.runs.delete(runId);
602
+ throw err;
603
+ }
604
+ logger.info("architect stream ready", { runId });
547
605
  return {
548
606
  kind: "stream",
549
607
  status: 200,
@@ -1376,23 +1434,86 @@ function createRuntime(options) {
1376
1434
  if (!options.baseUrl) throw new Error('createRuntime: "baseUrl" is required');
1377
1435
  if (!options.credential) throw new Error('createRuntime: "credential" is required');
1378
1436
  if (!options.resolveUser) throw new Error('createRuntime: "resolveUser" is required');
1437
+ const logger = options.logger ?? (0, import_sdk4.createLogger)(
1438
+ "runtime",
1439
+ options.logLevel !== void 0 ? { level: options.logLevel } : void 0
1440
+ );
1379
1441
  const capabilities = resolveCapabilities(options.capabilities);
1380
1442
  const routes = buildRoutes(capabilities);
1381
1443
  const mode = resolveMode(options);
1382
1444
  const heartbeatIntervalMs = options.heartbeatIntervalMs ?? 15e3;
1383
1445
  const runGraceMs = options.runGraceMs ?? DEFAULT_RUN_GRACE_MS;
1384
1446
  const maxTrackedRuns = options.maxTrackedRuns ?? DEFAULT_MAX_TRACKED_RUNS;
1447
+ logger.debug("runtime init", {
1448
+ mode,
1449
+ mountPath: options.mountPath ?? "",
1450
+ capabilities,
1451
+ heartbeatIntervalMs,
1452
+ runGraceMs,
1453
+ maxTrackedRuns
1454
+ });
1455
+ const enabledCaps = Object.entries(capabilities).filter(([, v]) => v).map(([k]) => k);
1456
+ logger.info("runtime created", {
1457
+ mode,
1458
+ mountPath: options.mountPath ?? "",
1459
+ enabledCapabilities: enabledCaps.length ? enabledCaps : ["(core only)"]
1460
+ });
1461
+ logger.trace("runtime config", {
1462
+ baseUrl: options.baseUrl,
1463
+ hasHooks: !!options.hooks,
1464
+ routeCount: routes.length
1465
+ });
1385
1466
  const runs = /* @__PURE__ */ new Map();
1386
- const evictionTimer = setInterval(
1387
- () => evictStaleRuns(runs, Date.now(), runGraceMs, maxTrackedRuns),
1388
- 6e4
1389
- );
1467
+ const evictionTimer = setInterval(() => {
1468
+ const before = runs.size;
1469
+ evictStaleRuns(runs, Date.now(), runGraceMs, maxTrackedRuns);
1470
+ if (runs.size !== before) {
1471
+ logger.debug("run eviction sweep", { before, after: runs.size, runGraceMs, maxTrackedRuns });
1472
+ } else {
1473
+ logger.trace("run eviction sweep \u2014 no evictions", { before });
1474
+ }
1475
+ }, 6e4);
1390
1476
  evictionTimer.unref?.();
1477
+ function getRequestPreview(req) {
1478
+ const preview = {
1479
+ method: req.method,
1480
+ path: req.path,
1481
+ hasQuery: req.query && Object.keys(req.query).length > 0,
1482
+ hasBody: req.body !== void 0,
1483
+ hasFile: !!req.file,
1484
+ fileCount: req.files?.length ?? 0,
1485
+ userId: req.userId ?? null
1486
+ };
1487
+ if (req.headers) {
1488
+ const redacted = {};
1489
+ for (const [k, v] of Object.entries(req.headers)) {
1490
+ if (v === void 0) continue;
1491
+ if (k.toLowerCase() === "authorization") redacted[k] = "***";
1492
+ else redacted[k] = v;
1493
+ }
1494
+ preview.headers = redacted;
1495
+ }
1496
+ if (req.query && Object.keys(req.query).length) preview.query = req.query;
1497
+ return preview;
1498
+ }
1391
1499
  async function handle(request) {
1500
+ const startMs = Date.now();
1501
+ logger.debug("handle start", { method: request.method, path: request.path });
1502
+ logger.trace("handle request details", getRequestPreview(request));
1392
1503
  try {
1393
1504
  const path = stripMountPath(request.path, options.mountPath);
1505
+ logger.trace("stripMountPath", {
1506
+ originalPath: request.path,
1507
+ mountPath: options.mountPath ?? "",
1508
+ strippedPath: path
1509
+ });
1394
1510
  const match = matchRoute(routes, request.method, path);
1395
1511
  if (match.kind === "not-found") {
1512
+ logger.warn("route not found", {
1513
+ method: request.method,
1514
+ path: request.path,
1515
+ strippedPath: path
1516
+ });
1396
1517
  throw new RuntimeHttpError(
1397
1518
  404,
1398
1519
  "NOT_FOUND",
@@ -1400,49 +1521,144 @@ function createRuntime(options) {
1400
1521
  );
1401
1522
  }
1402
1523
  if (match.kind === "method-not-allowed") {
1524
+ logger.warn("method not allowed", {
1525
+ method: request.method,
1526
+ path: request.path,
1527
+ strippedPath: path,
1528
+ allowed: match.allowed
1529
+ });
1403
1530
  const err = new RuntimeHttpError(
1404
1531
  405,
1405
1532
  "METHOD_NOT_ALLOWED",
1406
1533
  `${request.method} not allowed on ${request.path}. Allowed: ${match.allowed.join(", ")}.`
1407
1534
  );
1408
- const response = errorToResponse(err, mode);
1409
- return { ...response, headers: { ...response.headers, Allow: match.allowed.join(", ") } };
1535
+ const response2 = errorToResponse(err, mode);
1536
+ logger.debug("handle completed \u2014 405", {
1537
+ method: request.method,
1538
+ path: request.path,
1539
+ allowed: match.allowed,
1540
+ durationMs: Date.now() - startMs
1541
+ });
1542
+ return { ...response2, headers: { ...response2.headers, Allow: match.allowed.join(", ") } };
1410
1543
  }
1544
+ logger.info("route matched", {
1545
+ method: request.method,
1546
+ path: request.path,
1547
+ strippedPath: path,
1548
+ pattern: match.route.pattern.join("/"),
1549
+ params: match.params,
1550
+ requiresAuth: match.route.requiresAuth !== false
1551
+ });
1552
+ logger.debug("route matched", {
1553
+ method: request.method,
1554
+ path,
1555
+ pattern: match.route.pattern,
1556
+ params: match.params
1557
+ });
1558
+ logger.trace("route handler", {
1559
+ handler: match.route.handler.name || "anonymous",
1560
+ pattern: match.route.pattern
1561
+ });
1411
1562
  let userId = null;
1412
1563
  if (match.route.requiresAuth !== false) {
1564
+ logger.debug("resolving user", { path: request.path });
1413
1565
  try {
1414
1566
  const resolved = await options.resolveUser(request);
1415
1567
  userId = typeof resolved === "string" && resolved.length > 0 ? resolved : null;
1416
- } catch {
1568
+ logger.trace("resolveUser result", { userId: userId ?? null, hasResult: !!userId });
1569
+ } catch (err) {
1417
1570
  userId = null;
1571
+ logger.warn("resolveUser threw", {
1572
+ path: request.path,
1573
+ error: err instanceof Error ? err.message : String(err)
1574
+ });
1418
1575
  }
1419
1576
  if (userId === null) {
1577
+ logger.warn("unauthorized \u2014 could not resolve user", {
1578
+ method: request.method,
1579
+ path: request.path
1580
+ });
1420
1581
  throw new RuntimeHttpError(
1421
1582
  401,
1422
1583
  "UNAUTHORIZED",
1423
1584
  "Could not resolve an authenticated user for this request."
1424
1585
  );
1425
1586
  }
1587
+ logger.debug("user resolved", { userId });
1588
+ } else {
1589
+ logger.debug("skipping auth for public route", { path: request.path });
1426
1590
  }
1427
1591
  const resolvedRequest = { ...request, userId };
1428
- const client = createClientForRequest(options, userId);
1429
- return await match.route.handler(resolvedRequest, {
1592
+ const client = createClientForRequest(options, userId, logger);
1593
+ const routeLogger = logger.child("route");
1594
+ logger.debug("handler start", {
1595
+ method: request.method,
1596
+ path: request.path,
1597
+ handler: match.route.handler.name || "anonymous",
1598
+ userId
1599
+ });
1600
+ const response = await match.route.handler(resolvedRequest, {
1430
1601
  client,
1431
1602
  hooks: options.hooks,
1432
1603
  mode,
1433
1604
  params: match.params,
1434
1605
  heartbeatIntervalMs,
1435
1606
  runs,
1436
- capabilities
1607
+ capabilities,
1608
+ logger: routeLogger
1437
1609
  });
1610
+ const durationMs = Date.now() - startMs;
1611
+ logger.info("handle succeeded", {
1612
+ method: request.method,
1613
+ path: request.path,
1614
+ status: response.status,
1615
+ kind: response.kind,
1616
+ durationMs,
1617
+ userId
1618
+ });
1619
+ logger.debug("handle completed", {
1620
+ method: request.method,
1621
+ path: request.path,
1622
+ status: response.status,
1623
+ durationMs
1624
+ });
1625
+ logger.trace("handle response details", {
1626
+ method: request.method,
1627
+ path: request.path,
1628
+ status: response.status,
1629
+ headers: response.headers,
1630
+ kind: response.kind,
1631
+ durationMs
1632
+ });
1633
+ return response;
1438
1634
  } catch (err) {
1635
+ const durationMs = Date.now() - startMs;
1636
+ const isHttpError = err instanceof RuntimeHttpError;
1637
+ const meta = {
1638
+ method: request.method,
1639
+ path: request.path,
1640
+ durationMs,
1641
+ error: err instanceof Error ? err.message : String(err),
1642
+ code: isHttpError ? err.code : void 0,
1643
+ status: isHttpError ? err.status : void 0
1644
+ };
1645
+ if (isHttpError && err.status >= 500) {
1646
+ logger.error("handle failed \u2014 server error", meta);
1647
+ } else if (isHttpError && err.status >= 400) {
1648
+ logger.warn("handle failed \u2014 client error", meta);
1649
+ } else {
1650
+ logger.error("handle failed \u2014 unexpected error", meta);
1651
+ }
1652
+ logger.trace("handle error details", { error: err, durationMs });
1439
1653
  return errorToResponse(err, mode);
1440
1654
  }
1441
1655
  }
1442
1656
  return {
1443
1657
  handle,
1444
1658
  close() {
1659
+ logger.debug("runtime close", { trackedRuns: runs.size });
1445
1660
  clearInterval(evictionTimer);
1661
+ logger.info("runtime closed", { trackedRuns: runs.size });
1446
1662
  }
1447
1663
  };
1448
1664
  }