@personaai/runtime 0.5.2 → 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/README.md +4 -4
- package/dist/index.cjs +231 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +231 -15
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
|
411
|
-
`@personaai/hono
|
|
412
|
-
|
|
413
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1388
|
-
|
|
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
|
|
1409
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|