@personaai/runtime 0.5.1 → 0.5.3

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.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { ChatMessageInput } from '@personaai/sdk';
1
+ import { ChatMessageInput, LogLevel, Logger } from '@personaai/sdk';
2
2
 
3
3
  type RuntimeMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
4
4
  /** A single uploaded file — present on `RuntimeRequest.file` for a multipart `POST /files`. */
@@ -220,6 +220,10 @@ interface CreateRuntimeOptions {
220
220
  maxTrackedRuns?: number;
221
221
  /** Opt-in switches for Project-level admin surface. See {@link RuntimeCapabilities} — everything defaults to off. */
222
222
  capabilities?: RuntimeCapabilities;
223
+ /** Log level for the runtime — off by default. Overrides the global level set via `setLogLevel()` from `@personaai/sdk`. */
224
+ logLevel?: LogLevel;
225
+ /** Custom logger instance — when provided, `logLevel` is ignored. */
226
+ logger?: Logger;
223
227
  }
224
228
  interface Runtime {
225
229
  handle(request: RuntimeRequest): Promise<RuntimeResponse>;
@@ -242,6 +246,6 @@ declare class RuntimeHttpError extends Error {
242
246
  constructor(status: number, code: string, message: string);
243
247
  }
244
248
 
245
- declare const RUNTIME_VERSION = "0.5.1";
249
+ declare const RUNTIME_VERSION = "0.5.3";
246
250
 
247
251
  export { type CreateRuntimeOptions, type ErrorContext, type FileUploadContext, type MemoryWriteContext, RUNTIME_VERSION, type ResolveUser, type RunContext, type RunResult, type Runtime, type RuntimeBinaryResponse, type RuntimeBufferedResponse, type RuntimeCapabilities, type RuntimeHooks, RuntimeHttpError, type RuntimeMethod, type RuntimeRequest, type RuntimeResponse, type RuntimeStreamResponse, type RuntimeUploadedFile, type ThreadCreateContext, type ToolCallContext, createRuntime };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ChatMessageInput } from '@personaai/sdk';
1
+ import { ChatMessageInput, LogLevel, Logger } from '@personaai/sdk';
2
2
 
3
3
  type RuntimeMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
4
4
  /** A single uploaded file — present on `RuntimeRequest.file` for a multipart `POST /files`. */
@@ -220,6 +220,10 @@ interface CreateRuntimeOptions {
220
220
  maxTrackedRuns?: number;
221
221
  /** Opt-in switches for Project-level admin surface. See {@link RuntimeCapabilities} — everything defaults to off. */
222
222
  capabilities?: RuntimeCapabilities;
223
+ /** Log level for the runtime — off by default. Overrides the global level set via `setLogLevel()` from `@personaai/sdk`. */
224
+ logLevel?: LogLevel;
225
+ /** Custom logger instance — when provided, `logLevel` is ignored. */
226
+ logger?: Logger;
223
227
  }
224
228
  interface Runtime {
225
229
  handle(request: RuntimeRequest): Promise<RuntimeResponse>;
@@ -242,6 +246,6 @@ declare class RuntimeHttpError extends Error {
242
246
  constructor(status: number, code: string, message: string);
243
247
  }
244
248
 
245
- declare const RUNTIME_VERSION = "0.5.1";
249
+ declare const RUNTIME_VERSION = "0.5.3";
246
250
 
247
251
  export { type CreateRuntimeOptions, type ErrorContext, type FileUploadContext, type MemoryWriteContext, RUNTIME_VERSION, type ResolveUser, type RunContext, type RunResult, type Runtime, type RuntimeBinaryResponse, type RuntimeBufferedResponse, type RuntimeCapabilities, type RuntimeHooks, RuntimeHttpError, type RuntimeMethod, type RuntimeRequest, type RuntimeResponse, type RuntimeStreamResponse, type RuntimeUploadedFile, type ThreadCreateContext, type ToolCallContext, createRuntime };
package/dist/index.js CHANGED
@@ -41,12 +41,14 @@ function stripMountPath(path, mountPath) {
41
41
 
42
42
  // src/client-factory.ts
43
43
  import { PersonaClient } from "@personaai/sdk";
44
- function createClientForRequest(options, userId) {
44
+ function createClientForRequest(options, userId, logger) {
45
+ const clientLogger = logger ? logger.child("sdk") : void 0;
45
46
  return new PersonaClient({
46
47
  baseUrl: options.baseUrl,
47
48
  credential: options.credential,
48
49
  externalUserId: userId ?? void 0,
49
- fetch: options.fetch
50
+ fetch: options.fetch,
51
+ logger: clientLogger
50
52
  });
51
53
  }
52
54
 
@@ -105,6 +107,9 @@ function errorToResponse(err, mode) {
105
107
  };
106
108
  }
107
109
 
110
+ // src/runtime.ts
111
+ import { createLogger } from "@personaai/sdk";
112
+
108
113
  // src/runRegistry.ts
109
114
  var DEFAULT_RUN_GRACE_MS = 5 * 60 * 1e3;
110
115
  var DEFAULT_MAX_TRACKED_RUNS = 1e3;
@@ -126,7 +131,7 @@ function evictStaleRuns(runs, now, graceMs = DEFAULT_RUN_GRACE_MS, maxTracked =
126
131
  }
127
132
 
128
133
  // src/version.ts
129
- var RUNTIME_VERSION = "0.5.1";
134
+ var RUNTIME_VERSION = "0.5.3";
130
135
 
131
136
  // src/routes/health.ts
132
137
  var alwaysOnCapabilities = {
@@ -448,7 +453,16 @@ function parseChatBody(body) {
448
453
  };
449
454
  }
450
455
  var chatRoute = async (request, ctx) => {
456
+ const logger = ctx.logger.child("chat");
457
+ logger.debug("chatRoute start", { userId: request.userId, hasBody: !!request.body });
451
458
  const body = parseChatBody(request.body);
459
+ logger.trace("chatRoute body", {
460
+ agentId: body.agentId,
461
+ threadId: body.threadId,
462
+ messageCount: body.messages?.length ?? 0,
463
+ hasResume: !!body.resume,
464
+ hasContextOverride: !!body.contextOverride
465
+ });
452
466
  const userId = request.userId;
453
467
  const runCtx = {
454
468
  userId,
@@ -457,7 +471,10 @@ var chatRoute = async (request, ctx) => {
457
471
  threadId: body.threadId,
458
472
  messages: body.messages
459
473
  };
474
+ logger.debug("beforeRun hook", { agentId: body.agentId, threadId: body.threadId });
460
475
  await ctx.hooks?.beforeRun?.(runCtx);
476
+ logger.trace("beforeRun completed", { agentId: body.agentId });
477
+ logger.debug("starting chat stream", { agentId: body.agentId, threadId: body.threadId });
461
478
  const stream = ctx.client.chat.stream(body.agentId, {
462
479
  messages: body.messages,
463
480
  threadId: body.threadId,
@@ -465,9 +482,28 @@ var chatRoute = async (request, ctx) => {
465
482
  contextOverride: body.contextOverride
466
483
  });
467
484
  const runId = crypto.randomUUID();
485
+ logger.info("chat run created", {
486
+ runId,
487
+ agentId: body.agentId,
488
+ threadId: body.threadId,
489
+ userId
490
+ });
468
491
  const driver = new RunDriver(runId, runCtx, stream, ctx.hooks, ctx.mode);
469
492
  ctx.runs.set(runId, driver);
470
- await driver.waitForFirstFrame();
493
+ logger.debug("run driver registered", { runId, trackedRuns: ctx.runs.size });
494
+ try {
495
+ await driver.waitForFirstFrame();
496
+ logger.debug("chat run first frame ready", { runId });
497
+ } catch (err) {
498
+ logger.warn("chat run failed before first frame", {
499
+ runId,
500
+ agentId: body.agentId,
501
+ error: err instanceof Error ? err.message : String(err)
502
+ });
503
+ ctx.runs.delete(runId);
504
+ throw err;
505
+ }
506
+ logger.info("chat stream ready", { runId, agentId: body.agentId });
471
507
  return {
472
508
  kind: "stream",
473
509
  status: 200,
@@ -500,22 +536,44 @@ function parseArchitectBody(body) {
500
536
  };
501
537
  }
502
538
  var architectRoute = async (request, ctx) => {
539
+ const logger = ctx.logger.child("architect");
540
+ logger.debug("architectRoute start", { userId: request.userId });
503
541
  const body = parseArchitectBody(request.body);
542
+ logger.trace("architectRoute body", {
543
+ messageCount: body.messages?.length ?? 0,
544
+ hasResume: !!body.resume
545
+ });
504
546
  const userId = request.userId;
505
547
  const runCtx = {
506
548
  userId,
507
549
  kind: "architect",
508
550
  messages: body.messages
509
551
  };
552
+ logger.debug("beforeRun hook (architect)", { userId });
510
553
  await ctx.hooks?.beforeRun?.(runCtx);
554
+ logger.trace("beforeRun completed (architect)");
555
+ logger.debug("starting architect stream");
511
556
  const stream = ctx.client.architect.stream({
512
557
  messages: body.messages,
513
558
  resume: body.resume
514
559
  });
515
560
  const runId = crypto.randomUUID();
561
+ logger.info("architect run created", { runId, userId });
516
562
  const driver = new RunDriver(runId, runCtx, stream, ctx.hooks, ctx.mode);
517
563
  ctx.runs.set(runId, driver);
518
- await driver.waitForFirstFrame();
564
+ logger.debug("architect driver registered", { runId, trackedRuns: ctx.runs.size });
565
+ try {
566
+ await driver.waitForFirstFrame();
567
+ logger.debug("architect first frame ready", { runId });
568
+ } catch (err) {
569
+ logger.warn("architect run failed before first frame", {
570
+ runId,
571
+ error: err instanceof Error ? err.message : String(err)
572
+ });
573
+ ctx.runs.delete(runId);
574
+ throw err;
575
+ }
576
+ logger.info("architect stream ready", { runId });
519
577
  return {
520
578
  kind: "stream",
521
579
  status: 200,
@@ -664,6 +722,13 @@ var listAgents = async (request, ctx) => {
664
722
  });
665
723
  return json(200, items);
666
724
  };
725
+ var getAgentMcpConnections = async (request, ctx) => {
726
+ const connections = await ctx.client.agents.getMcpConnections(
727
+ requireParam(ctx.params, "id"),
728
+ request.query.returnTo
729
+ );
730
+ return json(200, connections);
731
+ };
667
732
  var createAgent = async (request, ctx) => {
668
733
  const body = requireBodyObject(request.body);
669
734
  const input = {
@@ -1224,6 +1289,11 @@ function buildRoutes(capabilities) {
1224
1289
  method: "DELETE",
1225
1290
  pattern: ["mcps", ":id", "oauth", "owner", "connection"],
1226
1291
  handler: disconnectOwnerConnection
1292
+ },
1293
+ {
1294
+ method: "GET",
1295
+ pattern: ["agents", ":id", "mcp-connections"],
1296
+ handler: getAgentMcpConnections
1227
1297
  }
1228
1298
  ];
1229
1299
  if (capabilities.agentsWrite) {
@@ -1336,23 +1406,86 @@ function createRuntime(options) {
1336
1406
  if (!options.baseUrl) throw new Error('createRuntime: "baseUrl" is required');
1337
1407
  if (!options.credential) throw new Error('createRuntime: "credential" is required');
1338
1408
  if (!options.resolveUser) throw new Error('createRuntime: "resolveUser" is required');
1409
+ const logger = options.logger ?? createLogger(
1410
+ "runtime",
1411
+ options.logLevel !== void 0 ? { level: options.logLevel } : void 0
1412
+ );
1339
1413
  const capabilities = resolveCapabilities(options.capabilities);
1340
1414
  const routes = buildRoutes(capabilities);
1341
1415
  const mode = resolveMode(options);
1342
1416
  const heartbeatIntervalMs = options.heartbeatIntervalMs ?? 15e3;
1343
1417
  const runGraceMs = options.runGraceMs ?? DEFAULT_RUN_GRACE_MS;
1344
1418
  const maxTrackedRuns = options.maxTrackedRuns ?? DEFAULT_MAX_TRACKED_RUNS;
1419
+ logger.debug("runtime init", {
1420
+ mode,
1421
+ mountPath: options.mountPath ?? "",
1422
+ capabilities,
1423
+ heartbeatIntervalMs,
1424
+ runGraceMs,
1425
+ maxTrackedRuns
1426
+ });
1427
+ const enabledCaps = Object.entries(capabilities).filter(([, v]) => v).map(([k]) => k);
1428
+ logger.info("runtime created", {
1429
+ mode,
1430
+ mountPath: options.mountPath ?? "",
1431
+ enabledCapabilities: enabledCaps.length ? enabledCaps : ["(core only)"]
1432
+ });
1433
+ logger.trace("runtime config", {
1434
+ baseUrl: options.baseUrl,
1435
+ hasHooks: !!options.hooks,
1436
+ routeCount: routes.length
1437
+ });
1345
1438
  const runs = /* @__PURE__ */ new Map();
1346
- const evictionTimer = setInterval(
1347
- () => evictStaleRuns(runs, Date.now(), runGraceMs, maxTrackedRuns),
1348
- 6e4
1349
- );
1439
+ const evictionTimer = setInterval(() => {
1440
+ const before = runs.size;
1441
+ evictStaleRuns(runs, Date.now(), runGraceMs, maxTrackedRuns);
1442
+ if (runs.size !== before) {
1443
+ logger.debug("run eviction sweep", { before, after: runs.size, runGraceMs, maxTrackedRuns });
1444
+ } else {
1445
+ logger.trace("run eviction sweep \u2014 no evictions", { before });
1446
+ }
1447
+ }, 6e4);
1350
1448
  evictionTimer.unref?.();
1449
+ function getRequestPreview(req) {
1450
+ const preview = {
1451
+ method: req.method,
1452
+ path: req.path,
1453
+ hasQuery: req.query && Object.keys(req.query).length > 0,
1454
+ hasBody: req.body !== void 0,
1455
+ hasFile: !!req.file,
1456
+ fileCount: req.files?.length ?? 0,
1457
+ userId: req.userId ?? null
1458
+ };
1459
+ if (req.headers) {
1460
+ const redacted = {};
1461
+ for (const [k, v] of Object.entries(req.headers)) {
1462
+ if (v === void 0) continue;
1463
+ if (k.toLowerCase() === "authorization") redacted[k] = "***";
1464
+ else redacted[k] = v;
1465
+ }
1466
+ preview.headers = redacted;
1467
+ }
1468
+ if (req.query && Object.keys(req.query).length) preview.query = req.query;
1469
+ return preview;
1470
+ }
1351
1471
  async function handle(request) {
1472
+ const startMs = Date.now();
1473
+ logger.debug("handle start", { method: request.method, path: request.path });
1474
+ logger.trace("handle request details", getRequestPreview(request));
1352
1475
  try {
1353
1476
  const path = stripMountPath(request.path, options.mountPath);
1477
+ logger.trace("stripMountPath", {
1478
+ originalPath: request.path,
1479
+ mountPath: options.mountPath ?? "",
1480
+ strippedPath: path
1481
+ });
1354
1482
  const match = matchRoute(routes, request.method, path);
1355
1483
  if (match.kind === "not-found") {
1484
+ logger.warn("route not found", {
1485
+ method: request.method,
1486
+ path: request.path,
1487
+ strippedPath: path
1488
+ });
1356
1489
  throw new RuntimeHttpError(
1357
1490
  404,
1358
1491
  "NOT_FOUND",
@@ -1360,49 +1493,144 @@ function createRuntime(options) {
1360
1493
  );
1361
1494
  }
1362
1495
  if (match.kind === "method-not-allowed") {
1496
+ logger.warn("method not allowed", {
1497
+ method: request.method,
1498
+ path: request.path,
1499
+ strippedPath: path,
1500
+ allowed: match.allowed
1501
+ });
1363
1502
  const err = new RuntimeHttpError(
1364
1503
  405,
1365
1504
  "METHOD_NOT_ALLOWED",
1366
1505
  `${request.method} not allowed on ${request.path}. Allowed: ${match.allowed.join(", ")}.`
1367
1506
  );
1368
- const response = errorToResponse(err, mode);
1369
- return { ...response, headers: { ...response.headers, Allow: match.allowed.join(", ") } };
1507
+ const response2 = errorToResponse(err, mode);
1508
+ logger.debug("handle completed \u2014 405", {
1509
+ method: request.method,
1510
+ path: request.path,
1511
+ allowed: match.allowed,
1512
+ durationMs: Date.now() - startMs
1513
+ });
1514
+ return { ...response2, headers: { ...response2.headers, Allow: match.allowed.join(", ") } };
1370
1515
  }
1516
+ logger.info("route matched", {
1517
+ method: request.method,
1518
+ path: request.path,
1519
+ strippedPath: path,
1520
+ pattern: match.route.pattern.join("/"),
1521
+ params: match.params,
1522
+ requiresAuth: match.route.requiresAuth !== false
1523
+ });
1524
+ logger.debug("route matched", {
1525
+ method: request.method,
1526
+ path,
1527
+ pattern: match.route.pattern,
1528
+ params: match.params
1529
+ });
1530
+ logger.trace("route handler", {
1531
+ handler: match.route.handler.name || "anonymous",
1532
+ pattern: match.route.pattern
1533
+ });
1371
1534
  let userId = null;
1372
1535
  if (match.route.requiresAuth !== false) {
1536
+ logger.debug("resolving user", { path: request.path });
1373
1537
  try {
1374
1538
  const resolved = await options.resolveUser(request);
1375
1539
  userId = typeof resolved === "string" && resolved.length > 0 ? resolved : null;
1376
- } catch {
1540
+ logger.trace("resolveUser result", { userId: userId ?? null, hasResult: !!userId });
1541
+ } catch (err) {
1377
1542
  userId = null;
1543
+ logger.warn("resolveUser threw", {
1544
+ path: request.path,
1545
+ error: err instanceof Error ? err.message : String(err)
1546
+ });
1378
1547
  }
1379
1548
  if (userId === null) {
1549
+ logger.warn("unauthorized \u2014 could not resolve user", {
1550
+ method: request.method,
1551
+ path: request.path
1552
+ });
1380
1553
  throw new RuntimeHttpError(
1381
1554
  401,
1382
1555
  "UNAUTHORIZED",
1383
1556
  "Could not resolve an authenticated user for this request."
1384
1557
  );
1385
1558
  }
1559
+ logger.debug("user resolved", { userId });
1560
+ } else {
1561
+ logger.debug("skipping auth for public route", { path: request.path });
1386
1562
  }
1387
1563
  const resolvedRequest = { ...request, userId };
1388
- const client = createClientForRequest(options, userId);
1389
- return await match.route.handler(resolvedRequest, {
1564
+ const client = createClientForRequest(options, userId, logger);
1565
+ const routeLogger = logger.child("route");
1566
+ logger.debug("handler start", {
1567
+ method: request.method,
1568
+ path: request.path,
1569
+ handler: match.route.handler.name || "anonymous",
1570
+ userId
1571
+ });
1572
+ const response = await match.route.handler(resolvedRequest, {
1390
1573
  client,
1391
1574
  hooks: options.hooks,
1392
1575
  mode,
1393
1576
  params: match.params,
1394
1577
  heartbeatIntervalMs,
1395
1578
  runs,
1396
- capabilities
1579
+ capabilities,
1580
+ logger: routeLogger
1581
+ });
1582
+ const durationMs = Date.now() - startMs;
1583
+ logger.info("handle succeeded", {
1584
+ method: request.method,
1585
+ path: request.path,
1586
+ status: response.status,
1587
+ kind: response.kind,
1588
+ durationMs,
1589
+ userId
1590
+ });
1591
+ logger.debug("handle completed", {
1592
+ method: request.method,
1593
+ path: request.path,
1594
+ status: response.status,
1595
+ durationMs
1397
1596
  });
1597
+ logger.trace("handle response details", {
1598
+ method: request.method,
1599
+ path: request.path,
1600
+ status: response.status,
1601
+ headers: response.headers,
1602
+ kind: response.kind,
1603
+ durationMs
1604
+ });
1605
+ return response;
1398
1606
  } catch (err) {
1607
+ const durationMs = Date.now() - startMs;
1608
+ const isHttpError = err instanceof RuntimeHttpError;
1609
+ const meta = {
1610
+ method: request.method,
1611
+ path: request.path,
1612
+ durationMs,
1613
+ error: err instanceof Error ? err.message : String(err),
1614
+ code: isHttpError ? err.code : void 0,
1615
+ status: isHttpError ? err.status : void 0
1616
+ };
1617
+ if (isHttpError && err.status >= 500) {
1618
+ logger.error("handle failed \u2014 server error", meta);
1619
+ } else if (isHttpError && err.status >= 400) {
1620
+ logger.warn("handle failed \u2014 client error", meta);
1621
+ } else {
1622
+ logger.error("handle failed \u2014 unexpected error", meta);
1623
+ }
1624
+ logger.trace("handle error details", { error: err, durationMs });
1399
1625
  return errorToResponse(err, mode);
1400
1626
  }
1401
1627
  }
1402
1628
  return {
1403
1629
  handle,
1404
1630
  close() {
1631
+ logger.debug("runtime close", { trackedRuns: runs.size });
1405
1632
  clearInterval(evictionTimer);
1633
+ logger.info("runtime closed", { trackedRuns: runs.size });
1406
1634
  }
1407
1635
  };
1408
1636
  }