@anvia/studio 0.1.2 → 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.js CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  Agent,
4
4
  createHook as createHook3,
5
5
  Message,
6
+ Pipeline,
6
7
  resolveMemoryOptions
7
8
  } from "@anvia/core";
8
9
  import { serve } from "@hono/node-server";
@@ -692,12 +693,15 @@ var SqliteSessionStore = class {
692
693
  db;
693
694
  listSessions(options) {
694
695
  const db = this.database();
695
- const agentClause = options.agentId === void 0 ? "" : "WHERE agent_id = $agentId";
696
+ const agentClause = options.agentId === void 0 ? "" : "WHERE s.agent_id = $agentId";
696
697
  const rows = db.prepare(
697
- `SELECT id, agent_id, title, metadata_json, messages_json, transcript_json, created_at, updated_at
698
- FROM runner_sessions
698
+ `SELECT s.id, s.agent_id, s.title, s.metadata_json, s.created_at, s.updated_at,
699
+ COUNT(m.message_index) AS message_count
700
+ FROM runner_sessions s
701
+ LEFT JOIN runner_session_messages m ON m.session_id = s.id
699
702
  ${agentClause}
700
- ORDER BY updated_at DESC
703
+ GROUP BY s.id, s.agent_id, s.title, s.metadata_json, s.created_at, s.updated_at
704
+ ORDER BY s.updated_at DESC
701
705
  LIMIT $limit`
702
706
  ).all({
703
707
  $agentId: options.agentId ?? null,
@@ -714,11 +718,9 @@ var SqliteSessionStore = class {
714
718
  agent_id,
715
719
  title,
716
720
  metadata_json,
717
- messages_json,
718
- transcript_json,
719
721
  created_at,
720
722
  updated_at
721
- ) VALUES ($id, $agentId, $title, $metadata, '[]', '[]', $now, $now)`
723
+ ) VALUES ($id, $agentId, $title, $metadata, $now, $now)`
722
724
  ).run({
723
725
  $id: input.id,
724
726
  $agentId: input.agentId,
@@ -738,7 +740,7 @@ var SqliteSessionStore = class {
738
740
  }
739
741
  getSession(id) {
740
742
  const row = this.getSessionRow(id);
741
- return row === void 0 ? void 0 : toSession(row, this.listSessionRunRows(id));
743
+ return row === void 0 ? void 0 : toSession(row, this.listSessionMessages(id), this.listSessionRunRows(id));
742
744
  }
743
745
  load(context) {
744
746
  const session = this.getSession(context.sessionId);
@@ -749,7 +751,7 @@ var SqliteSessionStore = class {
749
751
  try {
750
752
  db.exec("BEGIN IMMEDIATE");
751
753
  const row = db.prepare(
752
- `SELECT id, agent_id, title, metadata_json, messages_json, transcript_json, created_at, updated_at
754
+ `SELECT id, agent_id, title, metadata_json, created_at, updated_at
753
755
  FROM runner_sessions
754
756
  WHERE id = $id`
755
757
  ).get({ $id: input.context.sessionId });
@@ -757,17 +759,15 @@ var SqliteSessionStore = class {
757
759
  db.exec("ROLLBACK");
758
760
  return Promise.resolve();
759
761
  }
760
- const current = toSession(row);
761
- const messages = [...current.messages, ...input.messages];
762
762
  const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
763
+ const nextIndex = this.nextMessageIndex(input.context.sessionId);
764
+ this.insertMessages(input.context.sessionId, input.messages, nextIndex, updatedAt);
763
765
  db.prepare(
764
766
  `UPDATE runner_sessions
765
- SET messages_json = $messages,
766
- updated_at = $updatedAt
767
+ SET updated_at = $updatedAt
767
768
  WHERE id = $id`
768
769
  ).run({
769
770
  $id: input.context.sessionId,
770
- $messages: JSON.stringify(messages),
771
771
  $updatedAt: updatedAt
772
772
  });
773
773
  db.exec("COMMIT");
@@ -786,14 +786,15 @@ var SqliteSessionStore = class {
786
786
  db.exec("BEGIN IMMEDIATE");
787
787
  db.prepare(
788
788
  `UPDATE runner_sessions
789
- SET messages_json = '[]',
790
- transcript_json = '[]',
791
- updated_at = $updatedAt
789
+ SET updated_at = $updatedAt
792
790
  WHERE id = $id`
793
791
  ).run({
794
792
  $id: context.sessionId,
795
793
  $updatedAt: updatedAt
796
794
  });
795
+ db.prepare("DELETE FROM runner_session_messages WHERE session_id = $id").run({
796
+ $id: context.sessionId
797
+ });
797
798
  db.prepare("DELETE FROM runner_session_runs WHERE session_id = $id").run({
798
799
  $id: context.sessionId
799
800
  });
@@ -828,7 +829,11 @@ var SqliteSessionStore = class {
828
829
  db.exec("ROLLBACK");
829
830
  return void 0;
830
831
  }
831
- const current = toSession(row, this.listSessionRunRows(input.id));
832
+ const current = toSession(
833
+ row,
834
+ this.listSessionMessages(input.id),
835
+ this.listSessionRunRows(input.id)
836
+ );
832
837
  const title = current.title ?? input.title;
833
838
  db.prepare(
834
839
  `INSERT INTO runner_session_runs (
@@ -885,12 +890,179 @@ var SqliteSessionStore = class {
885
890
  throw error;
886
891
  }
887
892
  }
893
+ appendSessionLog(input) {
894
+ const db = this.database();
895
+ const now = (/* @__PURE__ */ new Date()).toISOString();
896
+ try {
897
+ db.exec("BEGIN IMMEDIATE");
898
+ const row = this.getSessionRow(input.sessionId);
899
+ if (row === void 0) {
900
+ throw new Error("Session not found");
901
+ }
902
+ const sequence = this.nextSessionLogSequence(input.sessionId);
903
+ const entry = {
904
+ id: globalThis.crypto.randomUUID(),
905
+ sessionId: input.sessionId,
906
+ ...input.runId === void 0 ? {} : { runId: input.runId },
907
+ sequence,
908
+ timestamp: now,
909
+ level: input.level,
910
+ category: input.category,
911
+ event: input.event,
912
+ message: input.message,
913
+ ...input.metadata === void 0 ? {} : { metadata: input.metadata }
914
+ };
915
+ db.prepare(
916
+ `INSERT INTO runner_session_logs (
917
+ id,
918
+ session_id,
919
+ run_id,
920
+ sequence,
921
+ timestamp,
922
+ level,
923
+ category,
924
+ event,
925
+ message,
926
+ metadata_json
927
+ ) VALUES (
928
+ $id,
929
+ $sessionId,
930
+ $runId,
931
+ $sequence,
932
+ $timestamp,
933
+ $level,
934
+ $category,
935
+ $event,
936
+ $message,
937
+ $metadata
938
+ )`
939
+ ).run({
940
+ $id: entry.id,
941
+ $sessionId: entry.sessionId,
942
+ $runId: entry.runId ?? null,
943
+ $sequence: entry.sequence,
944
+ $timestamp: entry.timestamp,
945
+ $level: entry.level,
946
+ $category: entry.category,
947
+ $event: entry.event,
948
+ $message: entry.message,
949
+ $metadata: entry.metadata === void 0 ? null : JSON.stringify(entry.metadata)
950
+ });
951
+ db.exec("COMMIT");
952
+ return entry;
953
+ } catch (error) {
954
+ if (db.isTransaction) {
955
+ db.exec("ROLLBACK");
956
+ }
957
+ throw error;
958
+ }
959
+ }
960
+ listSessionLogs(options) {
961
+ const db = this.database();
962
+ const afterClause = options.after === void 0 ? "" : "AND sequence > $after";
963
+ const rows = db.prepare(
964
+ `SELECT id, session_id, run_id, sequence, timestamp, level, category, event, message,
965
+ metadata_json
966
+ FROM runner_session_logs
967
+ WHERE session_id = $sessionId
968
+ ${afterClause}
969
+ ORDER BY sequence ASC
970
+ LIMIT $limit`
971
+ ).all({
972
+ $sessionId: options.sessionId,
973
+ $after: options.after ?? null,
974
+ $limit: options.limit
975
+ });
976
+ return rows.map(toSessionLog);
977
+ }
978
+ appendPipelineLog(input) {
979
+ const db = this.database();
980
+ const now = (/* @__PURE__ */ new Date()).toISOString();
981
+ try {
982
+ db.exec("BEGIN IMMEDIATE");
983
+ const sequence = this.nextPipelineLogSequence(input.pipelineId);
984
+ const entry = {
985
+ id: globalThis.crypto.randomUUID(),
986
+ pipelineId: input.pipelineId,
987
+ ...input.runId === void 0 ? {} : { runId: input.runId },
988
+ sequence,
989
+ timestamp: now,
990
+ level: input.level,
991
+ category: input.category,
992
+ event: input.event,
993
+ message: input.message,
994
+ ...input.metadata === void 0 ? {} : { metadata: input.metadata }
995
+ };
996
+ db.prepare(
997
+ `INSERT INTO runner_pipeline_logs (
998
+ id,
999
+ pipeline_id,
1000
+ run_id,
1001
+ sequence,
1002
+ timestamp,
1003
+ level,
1004
+ category,
1005
+ event,
1006
+ message,
1007
+ metadata_json
1008
+ ) VALUES (
1009
+ $id,
1010
+ $pipelineId,
1011
+ $runId,
1012
+ $sequence,
1013
+ $timestamp,
1014
+ $level,
1015
+ $category,
1016
+ $event,
1017
+ $message,
1018
+ $metadata
1019
+ )`
1020
+ ).run({
1021
+ $id: entry.id,
1022
+ $pipelineId: entry.pipelineId,
1023
+ $runId: entry.runId ?? null,
1024
+ $sequence: entry.sequence,
1025
+ $timestamp: entry.timestamp,
1026
+ $level: entry.level,
1027
+ $category: entry.category,
1028
+ $event: entry.event,
1029
+ $message: entry.message,
1030
+ $metadata: entry.metadata === void 0 ? null : JSON.stringify(entry.metadata)
1031
+ });
1032
+ db.exec("COMMIT");
1033
+ return entry;
1034
+ } catch (error) {
1035
+ if (db.isTransaction) {
1036
+ db.exec("ROLLBACK");
1037
+ }
1038
+ throw error;
1039
+ }
1040
+ }
1041
+ listPipelineLogs(options) {
1042
+ const db = this.database();
1043
+ const afterClause = options.after === void 0 ? "" : "AND sequence > $after";
1044
+ const rows = db.prepare(
1045
+ `SELECT id, pipeline_id, run_id, sequence, timestamp, level, category, event, message,
1046
+ metadata_json
1047
+ FROM runner_pipeline_logs
1048
+ WHERE pipeline_id = $pipelineId
1049
+ ${afterClause}
1050
+ ORDER BY sequence ASC
1051
+ LIMIT $limit`
1052
+ ).all({
1053
+ $pipelineId: options.pipelineId,
1054
+ $after: options.after ?? null,
1055
+ $limit: options.limit
1056
+ });
1057
+ return rows.map(toPipelineLog);
1058
+ }
888
1059
  deleteSession(id) {
889
1060
  const db = this.database();
890
1061
  try {
891
1062
  db.exec("BEGIN IMMEDIATE");
892
1063
  db.prepare("DELETE FROM runner_traces WHERE session_id = $id").run({ $id: id });
893
1064
  db.prepare("DELETE FROM runner_session_runs WHERE session_id = $id").run({ $id: id });
1065
+ db.prepare("DELETE FROM runner_session_logs WHERE session_id = $id").run({ $id: id });
894
1066
  const result = db.prepare("DELETE FROM runner_sessions WHERE id = $id").run({ $id: id });
895
1067
  db.exec("COMMIT");
896
1068
  return Number(result.changes) > 0;
@@ -1039,18 +1211,41 @@ var SqliteSessionStore = class {
1039
1211
  db.exec(`
1040
1212
  PRAGMA journal_mode = WAL;
1041
1213
  PRAGMA foreign_keys = ON;
1214
+ `);
1215
+ guardAgainstLegacySessionSchema(db);
1216
+ db.exec(`
1042
1217
  CREATE TABLE IF NOT EXISTS runner_sessions (
1043
1218
  id TEXT PRIMARY KEY,
1044
1219
  agent_id TEXT NOT NULL,
1045
1220
  title TEXT,
1046
1221
  metadata_json TEXT,
1047
- messages_json TEXT NOT NULL,
1048
- transcript_json TEXT NOT NULL,
1049
1222
  created_at TEXT NOT NULL,
1050
1223
  updated_at TEXT NOT NULL
1051
1224
  ) STRICT;
1052
1225
  CREATE INDEX IF NOT EXISTS runner_sessions_agent_updated_idx
1053
1226
  ON runner_sessions(agent_id, updated_at DESC);
1227
+ CREATE TABLE IF NOT EXISTS runner_session_messages (
1228
+ session_id TEXT NOT NULL,
1229
+ message_index INTEGER NOT NULL,
1230
+ role TEXT NOT NULL,
1231
+ message_id TEXT,
1232
+ created_at TEXT NOT NULL,
1233
+ PRIMARY KEY(session_id, message_index),
1234
+ FOREIGN KEY(session_id) REFERENCES runner_sessions(id) ON DELETE CASCADE
1235
+ ) STRICT;
1236
+ CREATE INDEX IF NOT EXISTS runner_session_messages_session_idx
1237
+ ON runner_session_messages(session_id, message_index ASC);
1238
+ CREATE TABLE IF NOT EXISTS runner_session_message_parts (
1239
+ session_id TEXT NOT NULL,
1240
+ message_index INTEGER NOT NULL,
1241
+ part_index INTEGER NOT NULL,
1242
+ type TEXT NOT NULL,
1243
+ part_json TEXT NOT NULL,
1244
+ PRIMARY KEY(session_id, message_index, part_index),
1245
+ FOREIGN KEY(session_id, message_index)
1246
+ REFERENCES runner_session_messages(session_id, message_index)
1247
+ ON DELETE CASCADE
1248
+ ) STRICT;
1054
1249
  CREATE TABLE IF NOT EXISTS runner_session_runs (
1055
1250
  run_id TEXT PRIMARY KEY,
1056
1251
  session_id TEXT NOT NULL,
@@ -1064,6 +1259,37 @@ var SqliteSessionStore = class {
1064
1259
  ) STRICT;
1065
1260
  CREATE INDEX IF NOT EXISTS runner_session_runs_session_created_idx
1066
1261
  ON runner_session_runs(session_id, created_at ASC);
1262
+ CREATE TABLE IF NOT EXISTS runner_session_logs (
1263
+ id TEXT PRIMARY KEY,
1264
+ session_id TEXT NOT NULL,
1265
+ run_id TEXT,
1266
+ sequence INTEGER NOT NULL,
1267
+ timestamp TEXT NOT NULL,
1268
+ level TEXT NOT NULL,
1269
+ category TEXT NOT NULL,
1270
+ event TEXT NOT NULL,
1271
+ message TEXT NOT NULL,
1272
+ metadata_json TEXT,
1273
+ UNIQUE(session_id, sequence),
1274
+ FOREIGN KEY(session_id) REFERENCES runner_sessions(id) ON DELETE CASCADE
1275
+ ) STRICT;
1276
+ CREATE INDEX IF NOT EXISTS runner_session_logs_session_sequence_idx
1277
+ ON runner_session_logs(session_id, sequence ASC);
1278
+ CREATE TABLE IF NOT EXISTS runner_pipeline_logs (
1279
+ id TEXT PRIMARY KEY,
1280
+ pipeline_id TEXT NOT NULL,
1281
+ run_id TEXT,
1282
+ sequence INTEGER NOT NULL,
1283
+ timestamp TEXT NOT NULL,
1284
+ level TEXT NOT NULL,
1285
+ category TEXT NOT NULL,
1286
+ event TEXT NOT NULL,
1287
+ message TEXT NOT NULL,
1288
+ metadata_json TEXT,
1289
+ UNIQUE(pipeline_id, sequence)
1290
+ ) STRICT;
1291
+ CREATE INDEX IF NOT EXISTS runner_pipeline_logs_pipeline_sequence_idx
1292
+ ON runner_pipeline_logs(pipeline_id, sequence ASC);
1067
1293
  CREATE TABLE IF NOT EXISTS runner_traces (
1068
1294
  id TEXT PRIMARY KEY,
1069
1295
  session_id TEXT NOT NULL,
@@ -1088,7 +1314,7 @@ var SqliteSessionStore = class {
1088
1314
  }
1089
1315
  getSessionRow(id) {
1090
1316
  return this.database().prepare(
1091
- `SELECT id, agent_id, title, metadata_json, messages_json, transcript_json, created_at, updated_at
1317
+ `SELECT id, agent_id, title, metadata_json, created_at, updated_at
1092
1318
  FROM runner_sessions
1093
1319
  WHERE id = $id`
1094
1320
  ).get({ $id: id });
@@ -1108,21 +1334,110 @@ var SqliteSessionStore = class {
1108
1334
  ORDER BY created_at ASC`
1109
1335
  ).all({ $sessionId: sessionId });
1110
1336
  }
1337
+ nextSessionLogSequence(sessionId) {
1338
+ const row = this.database().prepare(
1339
+ `SELECT COALESCE(MAX(sequence) + 1, 0) AS next_sequence
1340
+ FROM runner_session_logs
1341
+ WHERE session_id = $sessionId`
1342
+ ).get({ $sessionId: sessionId });
1343
+ return row.next_sequence;
1344
+ }
1345
+ nextPipelineLogSequence(pipelineId) {
1346
+ const row = this.database().prepare(
1347
+ `SELECT COALESCE(MAX(sequence) + 1, 0) AS next_sequence
1348
+ FROM runner_pipeline_logs
1349
+ WHERE pipeline_id = $pipelineId`
1350
+ ).get({ $pipelineId: pipelineId });
1351
+ return row.next_sequence;
1352
+ }
1353
+ listSessionMessages(sessionId) {
1354
+ const db = this.database();
1355
+ const messageRows = db.prepare(
1356
+ `SELECT session_id, message_index, role, message_id, created_at
1357
+ FROM runner_session_messages
1358
+ WHERE session_id = $sessionId
1359
+ ORDER BY message_index ASC`
1360
+ ).all({ $sessionId: sessionId });
1361
+ if (messageRows.length === 0) {
1362
+ return [];
1363
+ }
1364
+ const partRows = db.prepare(
1365
+ `SELECT session_id, message_index, part_index, type, part_json
1366
+ FROM runner_session_message_parts
1367
+ WHERE session_id = $sessionId
1368
+ ORDER BY message_index ASC, part_index ASC`
1369
+ ).all({ $sessionId: sessionId });
1370
+ const partsByMessage = /* @__PURE__ */ new Map();
1371
+ for (const partRow of partRows) {
1372
+ const parts = partsByMessage.get(partRow.message_index) ?? [];
1373
+ parts.push(partRow);
1374
+ partsByMessage.set(partRow.message_index, parts);
1375
+ }
1376
+ return messageRows.map(
1377
+ (row) => messageFromRows(row, partsByMessage.get(row.message_index) ?? [])
1378
+ );
1379
+ }
1380
+ nextMessageIndex(sessionId) {
1381
+ const row = this.database().prepare(
1382
+ `SELECT COALESCE(MAX(message_index) + 1, 0) AS next_index
1383
+ FROM runner_session_messages
1384
+ WHERE session_id = $sessionId`
1385
+ ).get({ $sessionId: sessionId });
1386
+ return row.next_index;
1387
+ }
1388
+ insertMessages(sessionId, messages, startIndex, createdAt) {
1389
+ const db = this.database();
1390
+ const insertMessage = db.prepare(
1391
+ `INSERT INTO runner_session_messages (
1392
+ session_id,
1393
+ message_index,
1394
+ role,
1395
+ message_id,
1396
+ created_at
1397
+ ) VALUES ($sessionId, $messageIndex, $role, $messageId, $createdAt)`
1398
+ );
1399
+ const insertPart = db.prepare(
1400
+ `INSERT INTO runner_session_message_parts (
1401
+ session_id,
1402
+ message_index,
1403
+ part_index,
1404
+ type,
1405
+ part_json
1406
+ ) VALUES ($sessionId, $messageIndex, $partIndex, $type, $partJson)`
1407
+ );
1408
+ messages.forEach((message, messageOffset) => {
1409
+ const messageIndex = startIndex + messageOffset;
1410
+ insertMessage.run({
1411
+ $sessionId: sessionId,
1412
+ $messageIndex: messageIndex,
1413
+ $role: message.role,
1414
+ $messageId: message.role === "assistant" ? message.id ?? null : null,
1415
+ $createdAt: createdAt
1416
+ });
1417
+ messageParts(message).forEach((part, partIndex) => {
1418
+ insertPart.run({
1419
+ $sessionId: sessionId,
1420
+ $messageIndex: messageIndex,
1421
+ $partIndex: partIndex,
1422
+ $type: part.type,
1423
+ $partJson: JSON.stringify(part.value)
1424
+ });
1425
+ });
1426
+ });
1427
+ }
1111
1428
  };
1112
- function toSession(row, runRows = []) {
1113
- const summary = toSessionSummary(row);
1114
- const legacyTranscript = parseJsonArray(row.transcript_json);
1429
+ function toSession(row, messages, runRows = []) {
1430
+ const summary = toSessionSummary({ ...row, message_count: messages.length });
1115
1431
  const runTranscript = runRows.flatMap(
1116
1432
  (runRow) => parseJsonArray(runRow.transcript_json)
1117
1433
  );
1118
1434
  return {
1119
1435
  ...summary,
1120
- messages: parseJsonArray(row.messages_json),
1121
- transcript: renumberTranscript([...legacyTranscript, ...runTranscript])
1436
+ messages,
1437
+ transcript: renumberTranscript(runTranscript)
1122
1438
  };
1123
1439
  }
1124
1440
  function toSessionSummary(row) {
1125
- const messages = parseJsonArray(row.messages_json);
1126
1441
  const metadata = parseJsonValue(row.metadata_json);
1127
1442
  return {
1128
1443
  id: row.id,
@@ -1130,10 +1445,90 @@ function toSessionSummary(row) {
1130
1445
  ...row.title === null ? {} : { title: row.title },
1131
1446
  createdAt: row.created_at,
1132
1447
  updatedAt: row.updated_at,
1133
- messageCount: messages.length,
1448
+ messageCount: row.message_count,
1449
+ ...metadata === void 0 ? {} : { metadata }
1450
+ };
1451
+ }
1452
+ function toSessionLog(row) {
1453
+ const metadata = parseJsonValue(row.metadata_json);
1454
+ return {
1455
+ id: row.id,
1456
+ sessionId: row.session_id,
1457
+ ...row.run_id === null ? {} : { runId: row.run_id },
1458
+ sequence: row.sequence,
1459
+ timestamp: row.timestamp,
1460
+ level: row.level,
1461
+ category: row.category,
1462
+ event: row.event,
1463
+ message: row.message,
1464
+ ...metadata === void 0 ? {} : { metadata }
1465
+ };
1466
+ }
1467
+ function toPipelineLog(row) {
1468
+ const metadata = parseJsonValue(row.metadata_json);
1469
+ return {
1470
+ id: row.id,
1471
+ pipelineId: row.pipeline_id,
1472
+ ...row.run_id === null ? {} : { runId: row.run_id },
1473
+ sequence: row.sequence,
1474
+ timestamp: row.timestamp,
1475
+ level: row.level,
1476
+ category: row.category,
1477
+ event: row.event,
1478
+ message: row.message,
1134
1479
  ...metadata === void 0 ? {} : { metadata }
1135
1480
  };
1136
1481
  }
1482
+ function messageParts(message) {
1483
+ if (message.role === "system") {
1484
+ return [{ type: "text", value: { type: "text", text: message.content } }];
1485
+ }
1486
+ return message.content.map((content) => ({
1487
+ type: content.type,
1488
+ value: content
1489
+ }));
1490
+ }
1491
+ function messageFromRows(row, partRows) {
1492
+ const parts = partRows.map((partRow) => JSON.parse(partRow.part_json));
1493
+ if (row.role === "system") {
1494
+ return { role: "system", content: systemContentFromParts(parts) };
1495
+ }
1496
+ if (row.role === "user") {
1497
+ return {
1498
+ role: "user",
1499
+ content: parts
1500
+ };
1501
+ }
1502
+ if (row.role === "assistant") {
1503
+ return {
1504
+ role: "assistant",
1505
+ ...row.message_id === null ? {} : { id: row.message_id },
1506
+ content: parts
1507
+ };
1508
+ }
1509
+ if (row.role === "tool") {
1510
+ return {
1511
+ role: "tool",
1512
+ content: parts
1513
+ };
1514
+ }
1515
+ throw new Error(`Unsupported stored message role: ${row.role}`);
1516
+ }
1517
+ function systemContentFromParts(parts) {
1518
+ const first = parts[0];
1519
+ if (typeof first === "object" && first !== null && "type" in first && first.type === "text" && "text" in first && typeof first.text === "string") {
1520
+ return first.text;
1521
+ }
1522
+ return "";
1523
+ }
1524
+ function guardAgainstLegacySessionSchema(db) {
1525
+ const columns = db.prepare("PRAGMA table_info('runner_sessions')").all();
1526
+ if (columns.some((column) => column.name === "messages_json")) {
1527
+ throw new Error(
1528
+ "Existing Studio SQLite DB uses the legacy messages_json schema. Delete or recreate the Studio SQLite DB to use normalized session messages."
1529
+ );
1530
+ }
1531
+ }
1137
1532
  function toTrace(row) {
1138
1533
  const trace = parseJsonValue(row.trace_json);
1139
1534
  const input = parseJsonValue(row.input_json);
@@ -1268,15 +1663,56 @@ function formatJson(value) {
1268
1663
  }
1269
1664
  }
1270
1665
 
1666
+ // src/runtime/tool-metadata.ts
1667
+ import { ToolSet } from "@anvia/core";
1668
+ var MCP_TOOL_METADATA_KEY = /* @__PURE__ */ Symbol.for("anvia.mcp.tool.metadata");
1669
+ function agentToolItems(agent) {
1670
+ return [
1671
+ ...agent.agent.toolSet.values().map((tool) => ({ tool, source: "static" })),
1672
+ ...agent.agent.dynamicTools.flatMap((registration) => {
1673
+ const maybeToolSet = registration.index.toolSet;
1674
+ if (!(maybeToolSet instanceof ToolSet)) {
1675
+ return [];
1676
+ }
1677
+ return maybeToolSet.values().map((tool) => ({ tool, source: "dynamic" }));
1678
+ })
1679
+ ];
1680
+ }
1681
+ function approvalMetadata(tool) {
1682
+ const approval = tool.approval;
1683
+ if (approval === void 0 || typeof approval !== "object" || approval === null) {
1684
+ return { required: false };
1685
+ }
1686
+ const policy = approval;
1687
+ return {
1688
+ required: true,
1689
+ ...typeof policy.reason === "string" ? { reason: policy.reason } : {},
1690
+ ...typeof policy.rejectMessage === "string" ? { rejectMessage: policy.rejectMessage } : {}
1691
+ };
1692
+ }
1693
+ function mcpServerName(tool) {
1694
+ const metadata = tool[MCP_TOOL_METADATA_KEY];
1695
+ if (typeof metadata !== "object" || metadata === null) {
1696
+ return void 0;
1697
+ }
1698
+ const serverName = metadata.serverName;
1699
+ return typeof serverName === "string" && serverName.length > 0 ? serverName : void 0;
1700
+ }
1701
+ function agentHasMcpTools(agent) {
1702
+ return agentToolItems(agent).some(({ tool }) => mcpServerName(tool) !== void 0);
1703
+ }
1704
+
1271
1705
  // src/runtime/shared.ts
1272
1706
  function resolveStores(options) {
1273
1707
  const defaultPath = process.env.ANVIA_STUDIO_DB ?? process.env.AION_STUDIO_DB ?? join(process.cwd(), ".anvia-studio", `${safeFileName(runnerId(options))}.sqlite`);
1274
1708
  const defaultStore = createSqliteSessionStore({ path: defaultPath });
1275
1709
  const sessions = resolveSessionStore(options, defaultStore);
1276
1710
  const traces = resolveTraceStore(options, sessions, defaultStore);
1711
+ const pipelineLogs = resolvePipelineLogStore(options, sessions, defaultStore);
1277
1712
  return {
1278
1713
  ...sessions === void 0 ? {} : { sessions },
1279
- ...traces === void 0 ? {} : { traces }
1714
+ ...traces === void 0 ? {} : { traces },
1715
+ ...pipelineLogs === void 0 ? {} : { pipelineLogs }
1280
1716
  };
1281
1717
  }
1282
1718
  function resolveSessionStore(options, defaultStore) {
@@ -1300,10 +1736,26 @@ function resolveTraceStore(options, sessionStore, defaultStore) {
1300
1736
  }
1301
1737
  return defaultStore;
1302
1738
  }
1739
+ function resolvePipelineLogStore(options, sessionStore, defaultStore) {
1740
+ if (options.stores?.pipelineLogs === false) {
1741
+ return void 0;
1742
+ }
1743
+ if (options.stores?.pipelineLogs !== void 0) {
1744
+ return options.stores.pipelineLogs;
1745
+ }
1746
+ if (sessionStore !== void 0 && isPipelineLogStore(sessionStore)) {
1747
+ return sessionStore;
1748
+ }
1749
+ return defaultStore;
1750
+ }
1303
1751
  function isTraceStore(store) {
1304
1752
  const candidate = store;
1305
1753
  return typeof candidate.listSessionTraces === "function" && typeof candidate.getTrace === "function" && typeof candidate.saveTrace === "function";
1306
1754
  }
1755
+ function isPipelineLogStore(store) {
1756
+ const candidate = store;
1757
+ return typeof candidate.appendPipelineLog === "function" && typeof candidate.listPipelineLogs === "function";
1758
+ }
1307
1759
  function unsupportedCapabilities(stores) {
1308
1760
  return [
1309
1761
  ...stores.sessions === void 0 ? ["sessions"] : [],
@@ -1324,17 +1776,32 @@ function normalizeAgents(agents) {
1324
1776
  return { ...agent, id };
1325
1777
  });
1326
1778
  }
1327
- function buildConfig(options, agents, stores) {
1779
+ function normalizePipelines(pipelines) {
1780
+ const ids = /* @__PURE__ */ new Set();
1781
+ return pipelines.map((pipeline) => {
1782
+ const id = pipeline.id.trim();
1783
+ if (id.length === 0) {
1784
+ throw new Error("Studio pipeline id cannot be empty");
1785
+ }
1786
+ if (ids.has(id)) {
1787
+ throw new Error(`Duplicate Studio pipeline id: ${id}`);
1788
+ }
1789
+ ids.add(id);
1790
+ return { ...pipeline, id };
1791
+ });
1792
+ }
1793
+ function buildConfig(options, agents, pipelines, stores) {
1328
1794
  return {
1329
1795
  id: runnerId(options),
1330
1796
  ...options.name === void 0 ? {} : { name: options.name },
1331
1797
  ...options.description === void 0 ? {} : { description: options.description },
1332
1798
  ...options.version === void 0 ? {} : { version: options.version },
1333
1799
  agents: agents.map(agentConfig),
1800
+ pipelines: pipelines.map(pipelineConfig),
1334
1801
  chat: {
1335
1802
  quickPrompts: Object.fromEntries(agents.map((agent) => [agent.id, agent.quickPrompts ?? []]))
1336
1803
  },
1337
- capabilities: capabilityConfig(options, agents, stores),
1804
+ capabilities: capabilityConfig(options, agents, pipelines, stores),
1338
1805
  unsupportedCapabilities: unsupportedCapabilities(stores)
1339
1806
  };
1340
1807
  }
@@ -1352,7 +1819,22 @@ function agentConfig(agent) {
1352
1819
  ...agent.metadata === void 0 ? {} : { metadata: agent.metadata }
1353
1820
  };
1354
1821
  }
1355
- function capabilityConfig(_options, agents, stores) {
1822
+ function pipelineConfig(pipeline) {
1823
+ const graph = pipeline.pipeline.graph();
1824
+ const stageNodes = graph.nodes.filter((node) => node.kind !== "input" && node.kind !== "output");
1825
+ return {
1826
+ id: pipeline.id,
1827
+ ...pipeline.name === void 0 ? {} : { name: pipeline.name },
1828
+ ...pipeline.description === void 0 ? {} : { description: pipeline.description },
1829
+ ...pipeline.metadata === void 0 ? {} : { metadata: pipeline.metadata },
1830
+ stageCount: stageNodes.length,
1831
+ edgeCount: graph.edges.length,
1832
+ hasParallelStages: graph.nodes.some((node) => node.kind === "parallel"),
1833
+ agentCount: graph.nodes.filter((node) => node.kind === "agent").length,
1834
+ extractorCount: graph.nodes.filter((node) => node.kind === "extractor").length
1835
+ };
1836
+ }
1837
+ function capabilityConfig(_options, agents, pipelines, stores) {
1356
1838
  const capabilities = {
1357
1839
  agents: { enabled: true }
1358
1840
  };
@@ -1362,10 +1844,23 @@ function capabilityConfig(_options, agents, stores) {
1362
1844
  if (stores.traces !== void 0) {
1363
1845
  capabilities.traces = { enabled: true };
1364
1846
  }
1847
+ if (pipelines.length > 0) {
1848
+ capabilities.pipelines = { enabled: true };
1849
+ }
1850
+ if (agents.some(
1851
+ (agent) => agent.agent.toolSet.values().length > 0 || agent.agent.dynamicTools.length > 0
1852
+ )) {
1853
+ capabilities.tools = { enabled: true };
1854
+ }
1855
+ if (agents.some(agentHasMcpTools)) {
1856
+ capabilities.mcps = { enabled: true };
1857
+ }
1365
1858
  if (agents.some((agent) => agent.agent.observers.length > 0)) {
1366
1859
  capabilities.observability = { enabled: true };
1367
1860
  }
1368
- if (agents.some((agent) => agent.agent.toolSet.values().some((tool) => tool.approval))) {
1861
+ if (agents.some(
1862
+ (agent) => agent.agent.hook !== void 0 || agent.agent.toolSet.values().some((tool) => tool.approval)
1863
+ )) {
1369
1864
  capabilities.approvals = { enabled: true };
1370
1865
  }
1371
1866
  if (agents.some(
@@ -1560,45 +2055,62 @@ function createApprovalRuntime() {
1560
2055
  return {
1561
2056
  approvals,
1562
2057
  createHook(context) {
1563
- return createHook({
1564
- async onToolCall({ toolName, toolCallId, internalCallId, args, tool: control }) {
1565
- const registeredTool = context.getTool(toolName);
1566
- if (registeredTool?.approval === void 0) {
1567
- return control.run();
1568
- }
1569
- const approval = registeredTool.approval;
1570
- const rawParsedArgs = parseToolArgs(args);
1571
- const parsedArgs = registeredTool.parseApprovalArgs?.(rawParsedArgs) ?? rawParsedArgs;
1572
- const approvalContext = {
1573
- toolName,
1574
- args: parsedArgs,
1575
- rawArgs: args,
1576
- ...toolCallId === void 0 ? {} : { toolCallId },
1577
- internalCallId,
1578
- run: {
1579
- agentId: context.agentId,
1580
- runId: context.runId,
1581
- ...context.sessionId === void 0 ? {} : { sessionId: context.sessionId },
1582
- ...context.metadata === void 0 ? {} : { metadata: context.metadata }
2058
+ const handleApprovalRequest = async ({ toolName, toolCallId, internalCallId, args, tool: control }, request) => {
2059
+ const decision = await requestApproval(approvals, context, {
2060
+ toolName,
2061
+ ...toolCallId === void 0 ? {} : { toolCallId },
2062
+ internalCallId,
2063
+ args,
2064
+ ...request.reason === void 0 ? {} : { reason: request.reason },
2065
+ ...request.rejectMessage === void 0 ? {} : { rejectMessage: request.rejectMessage }
2066
+ });
2067
+ return decision.approved ? control.run() : control.skip(decision.reason ?? request.rejectMessage ?? "Rejected in Anvia Studio.");
2068
+ };
2069
+ return {
2070
+ ...createHook({
2071
+ async onToolCall({ toolName, toolCallId, internalCallId, args, tool: control }) {
2072
+ const registeredTool = context.getTool(toolName);
2073
+ if (registeredTool?.approval === void 0) {
2074
+ return control.run();
1583
2075
  }
1584
- };
1585
- const required = await approval.when(approvalContext);
1586
- if (!required) {
1587
- return control.run();
2076
+ const approval = registeredTool.approval;
2077
+ const rawParsedArgs = parseToolArgs(args);
2078
+ const parsedArgs = registeredTool.parseApprovalArgs?.(rawParsedArgs) ?? rawParsedArgs;
2079
+ const approvalContext = {
2080
+ toolName,
2081
+ args: parsedArgs,
2082
+ rawArgs: args,
2083
+ ...toolCallId === void 0 ? {} : { toolCallId },
2084
+ internalCallId,
2085
+ run: {
2086
+ agentId: context.agentId,
2087
+ runId: context.runId,
2088
+ ...context.sessionId === void 0 ? {} : { sessionId: context.sessionId },
2089
+ ...context.metadata === void 0 ? {} : { metadata: context.metadata }
2090
+ }
2091
+ };
2092
+ const required = await approval.when(approvalContext);
2093
+ if (!required) {
2094
+ return control.run();
2095
+ }
2096
+ const reason = await resolveApprovalText(approval.reason, approvalContext);
2097
+ const rejectMessage = await resolveApprovalText(
2098
+ approval.rejectMessage,
2099
+ approvalContext
2100
+ );
2101
+ const decision = await requestApproval(approvals, context, {
2102
+ toolName,
2103
+ ...toolCallId === void 0 ? {} : { toolCallId },
2104
+ internalCallId,
2105
+ args,
2106
+ ...reason === void 0 ? {} : { reason },
2107
+ ...rejectMessage === void 0 ? {} : { rejectMessage }
2108
+ });
2109
+ return decision.approved ? control.run() : control.skip(decision.reason ?? rejectMessage ?? "Rejected in Anvia Studio.");
1588
2110
  }
1589
- const reason = await resolveApprovalText(approval.reason, approvalContext);
1590
- const rejectMessage = await resolveApprovalText(approval.rejectMessage, approvalContext);
1591
- const decision = await requestApproval(approvals, context, {
1592
- toolName,
1593
- ...toolCallId === void 0 ? {} : { toolCallId },
1594
- internalCallId,
1595
- args,
1596
- ...reason === void 0 ? {} : { reason },
1597
- ...rejectMessage === void 0 ? {} : { rejectMessage }
1598
- });
1599
- return decision.approved ? control.run() : control.skip(decision.reason ?? rejectMessage ?? "Rejected in Anvia Studio.");
1600
- }
1601
- });
2111
+ }),
2112
+ handleApprovalRequest
2113
+ };
1602
2114
  },
1603
2115
  list(options) {
1604
2116
  return [...approvals.values()].filter((approval) => {
@@ -1894,243 +2406,207 @@ function isRecord2(value) {
1894
2406
  return typeof value === "object" && value !== null && !Array.isArray(value);
1895
2407
  }
1896
2408
 
1897
- // src/runtime/questions.ts
1898
- import { createHook as createHook2, parseToolArgs as parseToolArgs2 } from "@anvia/core";
1899
- function registerQuestionRoutes(app, questions) {
1900
- app.get("/questions", (c) => {
1901
- const status = parseQuestionStatus(c.req.query("status"));
1902
- if (status === false) {
1903
- return errorResponse(c, 400, "bad_request", "status must be pending or resolved");
1904
- }
1905
- const options = {};
1906
- const runId = optionalQueryString(c.req.query("runId"));
1907
- const agentId = optionalQueryString(c.req.query("agentId"));
1908
- const sessionId = optionalQueryString(c.req.query("sessionId"));
1909
- if (status !== void 0) {
1910
- options.status = status;
1911
- }
1912
- if (runId !== void 0) {
1913
- options.runId = runId;
1914
- }
1915
- if (agentId !== void 0) {
1916
- options.agentId = agentId;
1917
- }
1918
- if (sessionId !== void 0) {
1919
- options.sessionId = sessionId;
2409
+ // src/runtime/mcps.ts
2410
+ function registerMcpRoutes(app, props) {
2411
+ app.get("/agents/:agentId/mcps", async (c) => {
2412
+ const agentId = c.req.param("agentId");
2413
+ const agent = props.agentMap.get(agentId);
2414
+ if (agent === void 0) {
2415
+ return errorResponse(c, 404, "not_found", "Agent not found");
1920
2416
  }
1921
- return c.json({ questions: questions.list(options) });
2417
+ return c.json({
2418
+ agentId,
2419
+ servers: await agentMcpMetadata(agent)
2420
+ });
1922
2421
  });
1923
- app.post("/questions/:questionId/answer", async (c) => {
1924
- const body = await parseQuestionAnswerRequest(c);
1925
- if ("error" in body) {
1926
- return body.error;
1927
- }
1928
- const result = questions.answer(c.req.param("questionId"), body.answers);
1929
- if (result === "missing") {
1930
- return errorResponse(c, 404, "not_found", "Question not found");
2422
+ }
2423
+ async function agentMcpMetadata(agent) {
2424
+ const servers = /* @__PURE__ */ new Map();
2425
+ const seen = /* @__PURE__ */ new Set();
2426
+ for (const { tool, source } of agentToolItems(agent)) {
2427
+ const serverName = mcpServerName(tool);
2428
+ if (serverName === void 0) {
2429
+ continue;
1931
2430
  }
1932
- if (result === "resolved") {
1933
- return errorResponse(c, 409, "conflict", "Question is already answered");
2431
+ const definition = await tool.definition("");
2432
+ const key = `${serverName}:${source}:${definition.name}`;
2433
+ if (seen.has(key)) {
2434
+ continue;
1934
2435
  }
1935
- return c.json(result);
1936
- });
1937
- }
1938
- function parseQuestionStatus(value) {
1939
- const status = optionalQueryString(value);
1940
- if (status === void 0) {
1941
- return void 0;
2436
+ seen.add(key);
2437
+ const tools = servers.get(serverName) ?? [];
2438
+ tools.push({
2439
+ name: definition.name,
2440
+ description: definition.description,
2441
+ parameters: definition.parameters,
2442
+ source
2443
+ });
2444
+ servers.set(serverName, tools);
1942
2445
  }
1943
- return status === "pending" || status === "resolved" ? status : false;
2446
+ return [...servers.entries()].map(([name, tools]) => {
2447
+ const sortedTools = tools.sort((left, right) => {
2448
+ if (left.source !== right.source) {
2449
+ return left.source === "static" ? -1 : 1;
2450
+ }
2451
+ return left.name.localeCompare(right.name);
2452
+ });
2453
+ return {
2454
+ agentId: agent.id,
2455
+ name,
2456
+ toolCount: sortedTools.length,
2457
+ tools: sortedTools
2458
+ };
2459
+ }).sort((left, right) => left.name.localeCompare(right.name));
1944
2460
  }
1945
- async function parseQuestionAnswerRequest(c) {
1946
- let body;
1947
- try {
1948
- body = await c.req.json();
1949
- } catch {
1950
- return { error: errorResponse(c, 400, "bad_request", "Request body must be JSON") };
1951
- }
1952
- if (!isObject(body)) {
1953
- return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
1954
- }
1955
- if (!Array.isArray(body.answers)) {
1956
- return { error: errorResponse(c, 400, "bad_request", "answers must be an array") };
1957
- }
1958
- const answers = [];
1959
- for (const answer of body.answers) {
1960
- if (!isObject(answer)) {
1961
- return { error: errorResponse(c, 400, "bad_request", "answers must contain objects") };
1962
- }
1963
- if (typeof answer.questionId !== "string" || answer.questionId.trim().length === 0) {
1964
- return { error: errorResponse(c, 400, "bad_request", "questionId must be a string") };
1965
- }
1966
- if (typeof answer.answer !== "string" || answer.answer.trim().length === 0) {
1967
- return { error: errorResponse(c, 400, "bad_request", "answer must be a string") };
1968
- }
1969
- if ("choice" in answer && typeof answer.choice !== "string") {
1970
- return { error: errorResponse(c, 400, "bad_request", "choice must be a string") };
1971
- }
1972
- if ("custom" in answer && typeof answer.custom !== "boolean") {
1973
- return { error: errorResponse(c, 400, "bad_request", "custom must be a boolean") };
1974
- }
1975
- answers.push({
1976
- questionId: answer.questionId.trim(),
1977
- answer: answer.answer.trim(),
1978
- ...typeof answer.choice === "string" ? { choice: answer.choice } : {},
1979
- ...typeof answer.custom === "boolean" ? { custom: answer.custom } : {}
1980
- });
2461
+
2462
+ // src/runtime/pipelines.ts
2463
+ import { stream as streamResponse2 } from "hono/streaming";
2464
+
2465
+ // src/runtime/pipeline-logs.ts
2466
+ async function appendPipelineLog(store, input) {
2467
+ return store?.appendPipelineLog(input);
2468
+ }
2469
+ async function* emitPipelineLog(store, input) {
2470
+ const log = await appendPipelineLog(store, input);
2471
+ if (log !== void 0) {
2472
+ yield { type: "pipeline_log", log };
1981
2473
  }
1982
- return { answers };
1983
2474
  }
1984
- function createQuestionRuntime() {
1985
- const questions = /* @__PURE__ */ new Map();
2475
+ function pipelineRunReceivedLog(props) {
1986
2476
  return {
1987
- questions,
1988
- createHook(context) {
1989
- return createHook2({
1990
- async onToolCall({ toolName, toolCallId, internalCallId, args, tool: control }) {
1991
- if (toolName !== "ask_question") {
1992
- return control.run();
1993
- }
1994
- const prompts = normalizeQuestionPrompts(parseToolArgs2(args));
1995
- if ("error" in prompts) {
1996
- return control.skip(prompts.error);
1997
- }
1998
- const answers = await requestQuestion(questions, context, {
1999
- toolName,
2000
- ...toolCallId === void 0 ? {} : { toolCallId },
2001
- internalCallId,
2002
- args,
2003
- questions: prompts.questions
2004
- });
2005
- return control.skip(JSON.stringify({ answers }));
2006
- }
2007
- });
2008
- },
2009
- list(options) {
2010
- return [...questions.values()].filter((question) => {
2011
- if (options.status === "pending" && question.status !== "pending") {
2012
- return false;
2013
- }
2014
- if (options.status === "resolved" && question.status === "pending") {
2015
- return false;
2016
- }
2017
- if (options.runId !== void 0 && question.runId !== options.runId) {
2018
- return false;
2019
- }
2020
- if (options.agentId !== void 0 && question.agentId !== options.agentId) {
2021
- return false;
2022
- }
2023
- if (options.sessionId !== void 0 && question.sessionId !== options.sessionId) {
2024
- return false;
2025
- }
2026
- return true;
2027
- }).map(publicQuestion);
2028
- },
2029
- answer(id, answers) {
2030
- const question = questions.get(id);
2031
- if (question === void 0) {
2032
- return "missing";
2033
- }
2034
- if (!isPendingQuestion(question)) {
2035
- return "resolved";
2036
- }
2037
- const resolved = resolveQuestion(question, answers);
2038
- questions.set(id, resolved);
2039
- question.emit?.({ type: "tool_question_result", question: resolved });
2040
- question.resolve(answers);
2041
- return publicQuestion(resolved);
2042
- }
2477
+ pipelineId: props.pipeline.id,
2478
+ runId: props.runId,
2479
+ level: "info",
2480
+ category: "api",
2481
+ event: "pipeline.run_received",
2482
+ message: "Pipeline run request received",
2483
+ metadata: cleanMetadata({
2484
+ stream: props.stream,
2485
+ inputBytes: byteLength(formatUnknown(props.input)),
2486
+ metadataKeys: Object.keys(props.metadata ?? {})
2487
+ })
2043
2488
  };
2044
2489
  }
2045
- async function requestQuestion(questions, context, request) {
2046
- const id = globalThis.crypto.randomUUID();
2047
- const question = {
2048
- id,
2049
- runId: context.runId,
2050
- agentId: context.agentId,
2051
- ...context.sessionId === void 0 ? {} : { sessionId: context.sessionId },
2052
- toolName: request.toolName,
2053
- ...request.toolCallId === void 0 ? {} : { callId: request.toolCallId },
2054
- internalCallId: request.internalCallId,
2055
- args: request.args,
2056
- questions: request.questions,
2057
- status: "pending",
2058
- requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
2059
- ...context.emit === void 0 ? {} : { emit: context.emit },
2060
- resolve: () => {
2061
- }
2490
+ function pipelineRunStartedLog(pipeline, runId) {
2491
+ const graph = pipeline.pipeline.graph();
2492
+ return {
2493
+ pipelineId: pipeline.id,
2494
+ runId,
2495
+ level: "info",
2496
+ category: "run",
2497
+ event: "pipeline.run_started",
2498
+ message: "Pipeline run started",
2499
+ metadata: cleanMetadata({
2500
+ stageCount: graph.nodes.filter((node) => node.kind !== "input" && node.kind !== "output").length,
2501
+ edgeCount: graph.edges.length
2502
+ })
2062
2503
  };
2063
- const answer = new Promise((resolve2) => {
2064
- question.resolve = (answers) => {
2065
- resolve2(answers);
2066
- };
2067
- });
2068
- questions.set(id, question);
2069
- context.emit?.({ type: "tool_question_request", question: publicQuestion(question) });
2070
- return answer;
2071
2504
  }
2072
- function normalizeQuestionPrompts(args) {
2073
- if (!isObject(args)) {
2074
- return { error: "ask_question requires a JSON object with questions." };
2075
- }
2076
- const rawQuestions = Array.isArray(args.questions) ? args.questions : [args];
2077
- if (rawQuestions.length === 0) {
2078
- return { error: "ask_question requires at least one question." };
2079
- }
2080
- const questions = [];
2081
- for (const [index, question] of rawQuestions.entries()) {
2082
- const normalized = normalizeQuestionPrompt(question, index);
2083
- if (normalized === void 0) {
2084
- return {
2085
- error: "ask_question requires every question to include text and at least one choice."
2086
- };
2087
- }
2088
- questions.push(normalized);
2089
- }
2090
- return { questions };
2505
+ function pipelineRunCompletedLog(props) {
2506
+ return {
2507
+ pipelineId: props.pipelineId,
2508
+ runId: props.runId,
2509
+ level: "info",
2510
+ category: "run",
2511
+ event: "pipeline.run_completed",
2512
+ message: "Pipeline run completed",
2513
+ metadata: cleanMetadata({
2514
+ durationMs: props.durationMs,
2515
+ outputBytes: byteLength(formatUnknown(props.output))
2516
+ })
2517
+ };
2091
2518
  }
2092
- function normalizeQuestionPrompt(value, index) {
2093
- if (!isObject(value) || typeof value.question !== "string" || value.question.trim().length === 0) {
2094
- return void 0;
2095
- }
2096
- const choices = Array.isArray(value.choices) ? value.choices.map(normalizeQuestionChoice).filter((choice) => choice !== void 0) : [];
2097
- if (choices.length === 0) {
2098
- return void 0;
2099
- }
2519
+ function pipelineRunFailedLog(pipelineId, runId, error, startedAt) {
2100
2520
  return {
2101
- id: typeof value.id === "string" && value.id.trim().length > 0 ? value.id.trim() : `question_${index + 1}`,
2102
- question: value.question.trim(),
2103
- choices
2521
+ pipelineId,
2522
+ runId,
2523
+ level: "error",
2524
+ category: "run",
2525
+ event: "pipeline.run_failed",
2526
+ message: "Pipeline run failed",
2527
+ metadata: cleanMetadata({
2528
+ durationMs: Date.now() - startedAt,
2529
+ error: serializeError2(error)
2530
+ })
2104
2531
  };
2105
2532
  }
2106
- function normalizeQuestionChoice(value) {
2107
- if (typeof value === "string" && value.trim().length > 0) {
2108
- return { label: value.trim(), value: value.trim() };
2533
+ function pipelineStageLog(pipelineId, runId, event) {
2534
+ const category = stageCategory(event.node);
2535
+ if (event.type === "stage_started") {
2536
+ return {
2537
+ pipelineId,
2538
+ runId,
2539
+ level: "debug",
2540
+ category,
2541
+ event: `${event.node.kind}.started`,
2542
+ message: `${event.node.label} started`,
2543
+ metadata: nodeMetadata(event.node)
2544
+ };
2109
2545
  }
2110
- if (!isObject(value) || typeof value.label !== "string" || value.label.trim().length === 0) {
2111
- return void 0;
2546
+ if (event.type === "stage_completed") {
2547
+ return {
2548
+ pipelineId,
2549
+ runId,
2550
+ level: "debug",
2551
+ category,
2552
+ event: `${event.node.kind}.completed`,
2553
+ message: `${event.node.label} completed`,
2554
+ metadata: cleanMetadata({
2555
+ ...nodeMetadata(event.node),
2556
+ durationMs: event.durationMs
2557
+ })
2558
+ };
2112
2559
  }
2113
2560
  return {
2114
- label: value.label.trim(),
2115
- value: typeof value.value === "string" && value.value.trim().length > 0 ? value.value.trim() : value.label.trim()
2561
+ pipelineId,
2562
+ runId,
2563
+ level: "error",
2564
+ category,
2565
+ event: `${event.node.kind}.failed`,
2566
+ message: `${event.node.label} failed`,
2567
+ metadata: cleanMetadata({
2568
+ ...nodeMetadata(event.node),
2569
+ durationMs: event.durationMs,
2570
+ error: serializeError2(event.error)
2571
+ })
2116
2572
  };
2117
2573
  }
2118
- function isPendingQuestion(question) {
2119
- return question !== void 0 && question.status === "pending" && "resolve" in question;
2574
+ function stageCategory(node) {
2575
+ if (node.kind === "parallel" || node.kind === "branch") {
2576
+ return "parallel";
2577
+ }
2578
+ if (node.kind === "agent") {
2579
+ return "agent";
2580
+ }
2581
+ if (node.kind === "extractor") {
2582
+ return "extractor";
2583
+ }
2584
+ return "stage";
2120
2585
  }
2121
- function resolveQuestion(question, answers) {
2122
- return publicQuestion({
2123
- ...question,
2124
- status: "answered",
2125
- answeredAt: (/* @__PURE__ */ new Date()).toISOString(),
2126
- answers
2586
+ function nodeMetadata(node) {
2587
+ return cleanMetadata({
2588
+ nodeId: node.id,
2589
+ kind: node.kind,
2590
+ label: node.label,
2591
+ agentId: node.agentId,
2592
+ pipelineId: node.pipelineId,
2593
+ branchKey: node.branchKey
2127
2594
  });
2128
2595
  }
2129
- function publicQuestion(question) {
2130
- const { emit, resolve: resolve2, ...rest } = question;
2131
- void emit;
2132
- void resolve2;
2133
- return rest;
2596
+ function cleanMetadata(value) {
2597
+ return Object.fromEntries(
2598
+ Object.entries(value).filter(([, item]) => item !== void 0)
2599
+ );
2600
+ }
2601
+ function byteLength(value) {
2602
+ return value === void 0 ? void 0 : new TextEncoder().encode(value).length;
2603
+ }
2604
+ function formatUnknown(value) {
2605
+ try {
2606
+ return JSON.stringify(value);
2607
+ } catch {
2608
+ return void 0;
2609
+ }
2134
2610
  }
2135
2611
 
2136
2612
  // src/runtime/runs.ts
@@ -2719,69 +3195,1102 @@ async function parseRunRequest(c) {
2719
3195
  return request;
2720
3196
  }
2721
3197
 
2722
- // src/runtime/sessions.ts
2723
- function registerSessionRoutes(app, props) {
2724
- app.get("/sessions", async (c) => {
2725
- const agentId = optionalQueryString(c.req.query("agentId"));
2726
- if (agentId !== void 0 && !props.agentMap.has(agentId)) {
2727
- return errorResponse(c, 404, "not_found", "Agent not found");
2728
- }
2729
- const limit = parseLimit(c.req.query("limit"));
2730
- if (limit === void 0) {
2731
- return errorResponse(c, 400, "bad_request", "limit must be a positive integer");
2732
- }
2733
- const sessions = await props.sessionStore.listSessions({
2734
- ...agentId === void 0 ? {} : { agentId },
2735
- limit
2736
- });
2737
- return c.json({ sessions });
2738
- });
2739
- app.post("/sessions", async (c) => {
2740
- const body = await parseCreateSessionRequest(c);
2741
- if ("error" in body) {
2742
- return body.error;
2743
- }
2744
- if (!props.agentMap.has(body.agentId)) {
2745
- return errorResponse(c, 404, "not_found", "Agent not found");
3198
+ // src/runtime/pipelines.ts
3199
+ function registerPipelineRoutes(app, props) {
3200
+ app.get(
3201
+ "/pipelines",
3202
+ (c) => c.json({
3203
+ pipelines: props.pipelines.map(pipelineConfig)
3204
+ })
3205
+ );
3206
+ app.get("/pipelines/:pipelineId", (c) => {
3207
+ const pipeline = props.pipelineMap.get(c.req.param("pipelineId"));
3208
+ if (pipeline === void 0) {
3209
+ return errorResponse(c, 404, "not_found", "Pipeline not found");
2746
3210
  }
2747
- const session = await props.sessionStore.createSession({
2748
- id: globalThis.crypto.randomUUID(),
2749
- agentId: body.agentId,
2750
- ...body.title === void 0 ? {} : { title: body.title },
2751
- ...body.metadata === void 0 ? {} : { metadata: body.metadata }
2752
- });
2753
- return c.json(session, 201);
3211
+ return c.json(pipelineDetail(pipeline));
2754
3212
  });
2755
- app.get("/sessions/:sessionId", async (c) => {
2756
- const session = await props.sessionStore.getSession(c.req.param("sessionId"));
2757
- if (session === void 0) {
2758
- return errorResponse(c, 404, "not_found", "Session not found");
3213
+ app.get("/pipelines/:pipelineId/logs", async (c) => {
3214
+ const pipelineId = c.req.param("pipelineId");
3215
+ if (!props.pipelineMap.has(pipelineId)) {
3216
+ return errorResponse(c, 404, "not_found", "Pipeline not found");
2759
3217
  }
2760
- return c.json(session);
2761
- });
2762
- app.delete("/sessions/:sessionId", async (c) => {
2763
- if (props.sessionStore.deleteSession === void 0) {
3218
+ if (props.store === void 0) {
2764
3219
  return errorResponse(
2765
3220
  c,
2766
3221
  501,
2767
3222
  "unsupported_capability",
2768
- 'Capability "sessions.delete" is not implemented by this runner',
2769
- { capability: "sessions", operation: "delete" }
3223
+ 'Capability "pipelines.logs" is not implemented by this runner',
3224
+ { capability: "pipelines", operation: "logs" }
2770
3225
  );
2771
3226
  }
2772
- const deleted = await props.sessionStore.deleteSession(c.req.param("sessionId"));
2773
- if (!deleted) {
2774
- return errorResponse(c, 404, "not_found", "Session not found");
3227
+ const limit = parsePipelineLogLimit(c.req.query("limit"));
3228
+ if (limit === void 0) {
3229
+ return errorResponse(c, 400, "bad_request", "limit must be a positive integer");
2775
3230
  }
2776
- return c.body(null, 204);
2777
- });
2778
- app.get("/sessions/:sessionId/traces", async (c) => {
2779
- if (props.traceStore === void 0) {
2780
- return unsupportedCapability(c, "traces");
3231
+ const after = parsePipelineLogAfter(c.req.query("after"));
3232
+ if (after === false) {
3233
+ return errorResponse(c, 400, "bad_request", "after must be a non-negative integer");
2781
3234
  }
2782
- const sessionId = c.req.param("sessionId");
2783
- const session = await props.sessionStore.getSession(sessionId);
2784
- if (session === void 0) {
3235
+ const logs = await props.store.listPipelineLogs({
3236
+ pipelineId,
3237
+ limit,
3238
+ ...after === void 0 ? {} : { after }
3239
+ });
3240
+ const last = logs.at(-1);
3241
+ return c.json({
3242
+ logs,
3243
+ ...logs.length === limit && last !== void 0 ? { nextCursor: last.sequence } : {}
3244
+ });
3245
+ });
3246
+ app.post("/pipelines/:pipelineId/runs", async (c) => {
3247
+ const pipeline = props.pipelineMap.get(c.req.param("pipelineId"));
3248
+ if (pipeline === void 0) {
3249
+ return errorResponse(c, 404, "not_found", "Pipeline not found");
3250
+ }
3251
+ const body = await parsePipelineRunRequest(c);
3252
+ if ("error" in body) {
3253
+ return body.error;
3254
+ }
3255
+ const runId = globalThis.crypto.randomUUID();
3256
+ const startedAt = Date.now();
3257
+ await appendPipelineLog(
3258
+ props.store,
3259
+ pipelineRunReceivedLog({
3260
+ pipeline,
3261
+ runId,
3262
+ stream: body.stream === true,
3263
+ input: body.input,
3264
+ ...body.metadata === void 0 ? {} : { metadata: body.metadata }
3265
+ })
3266
+ );
3267
+ if (body.stream === true) {
3268
+ return streamPipelineRun(c, {
3269
+ pipeline,
3270
+ runId,
3271
+ input: body.input,
3272
+ startedAt,
3273
+ ...props.store === void 0 ? {} : { store: props.store }
3274
+ });
3275
+ }
3276
+ try {
3277
+ await appendPipelineLog(props.store, pipelineRunStartedLog(pipeline, runId));
3278
+ const output = await pipeline.pipeline.run(body.input, {
3279
+ observer: {
3280
+ async onEvent(event) {
3281
+ await appendPipelineLog(props.store, pipelineStageLog(pipeline.id, runId, event));
3282
+ }
3283
+ }
3284
+ });
3285
+ const jsonOutput = toJsonValue3(output);
3286
+ await appendPipelineLog(
3287
+ props.store,
3288
+ pipelineRunCompletedLog({
3289
+ pipelineId: pipeline.id,
3290
+ runId,
3291
+ durationMs: Date.now() - startedAt,
3292
+ output: jsonOutput
3293
+ })
3294
+ );
3295
+ const response = {
3296
+ runId,
3297
+ pipelineId: pipeline.id,
3298
+ output: jsonOutput
3299
+ };
3300
+ return c.json(response);
3301
+ } catch (error) {
3302
+ await appendPipelineLog(
3303
+ props.store,
3304
+ pipelineRunFailedLog(pipeline.id, runId, error, startedAt)
3305
+ );
3306
+ return errorResponse(c, 500, "internal_error", "Pipeline run failed", serializeError2(error));
3307
+ }
3308
+ });
3309
+ }
3310
+ function pipelineDetail(pipeline) {
3311
+ const graph = pipeline.pipeline.graph();
3312
+ graph.id = pipeline.id;
3313
+ return {
3314
+ ...pipelineConfig(pipeline),
3315
+ graph
3316
+ };
3317
+ }
3318
+ function streamPipelineRun(c, props) {
3319
+ c.header("content-type", "application/x-ndjson; charset=utf-8");
3320
+ c.header("cache-control", "no-cache, no-transform");
3321
+ c.header("connection", "keep-alive");
3322
+ c.header("transfer-encoding", "chunked");
3323
+ c.header("x-accel-buffering", "no");
3324
+ return streamResponse2(
3325
+ c,
3326
+ async (stream) => {
3327
+ for await (const event of pipelineRunEvents(props)) {
3328
+ await stream.write(`${JSON.stringify(event)}
3329
+ `);
3330
+ }
3331
+ },
3332
+ async (error, stream) => {
3333
+ await stream.write(`${JSON.stringify({ type: "error", error: serializeError2(error) })}
3334
+ `);
3335
+ }
3336
+ );
3337
+ }
3338
+ async function* pipelineRunEvents(props) {
3339
+ yield* emitPipelineLog(props.store, pipelineRunStartedLog(props.pipeline, props.runId));
3340
+ const events = new AsyncEventQueue();
3341
+ const run = props.pipeline.pipeline.run(props.input, {
3342
+ observer: {
3343
+ async onEvent(event) {
3344
+ const log = await appendPipelineLog(
3345
+ props.store,
3346
+ pipelineStageLog(props.pipeline.id, props.runId, event)
3347
+ );
3348
+ if (log !== void 0) {
3349
+ events.push({ type: "pipeline_log", log });
3350
+ }
3351
+ }
3352
+ }
3353
+ }).then(async (output) => {
3354
+ const jsonOutput = toJsonValue3(output);
3355
+ const log = await appendPipelineLog(
3356
+ props.store,
3357
+ pipelineRunCompletedLog({
3358
+ pipelineId: props.pipeline.id,
3359
+ runId: props.runId,
3360
+ durationMs: Date.now() - props.startedAt,
3361
+ output: jsonOutput
3362
+ })
3363
+ );
3364
+ if (log !== void 0) {
3365
+ events.push({ type: "pipeline_log", log });
3366
+ }
3367
+ events.push({
3368
+ type: "pipeline_final",
3369
+ runId: props.runId,
3370
+ pipelineId: props.pipeline.id,
3371
+ output: jsonOutput
3372
+ });
3373
+ }).catch(async (error) => {
3374
+ const log = await appendPipelineLog(
3375
+ props.store,
3376
+ pipelineRunFailedLog(props.pipeline.id, props.runId, error, props.startedAt)
3377
+ );
3378
+ if (log !== void 0) {
3379
+ events.push({ type: "pipeline_log", log });
3380
+ }
3381
+ events.push({ type: "error", error: serializeError2(error) });
3382
+ }).finally(() => events.close());
3383
+ try {
3384
+ while (true) {
3385
+ const next = await events.next();
3386
+ if (next.done === true) {
3387
+ break;
3388
+ }
3389
+ yield next.value;
3390
+ }
3391
+ } finally {
3392
+ await run;
3393
+ }
3394
+ }
3395
+ async function parsePipelineRunRequest(c) {
3396
+ let body;
3397
+ try {
3398
+ body = await c.req.json();
3399
+ } catch {
3400
+ return { error: errorResponse(c, 400, "bad_request", "Request body must be JSON") };
3401
+ }
3402
+ if (!isObject(body)) {
3403
+ return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
3404
+ }
3405
+ if (!("input" in body) || !isJsonValue2(body.input)) {
3406
+ return { error: errorResponse(c, 400, "bad_request", "input must be JSON-compatible") };
3407
+ }
3408
+ const request = {
3409
+ input: body.input
3410
+ };
3411
+ if ("stream" in body) {
3412
+ if (typeof body.stream !== "boolean") {
3413
+ return { error: errorResponse(c, 400, "bad_request", "stream must be a boolean") };
3414
+ }
3415
+ request.stream = body.stream;
3416
+ }
3417
+ if ("metadata" in body) {
3418
+ if (!isJsonObject(body.metadata)) {
3419
+ return { error: errorResponse(c, 400, "bad_request", "metadata must be an object") };
3420
+ }
3421
+ request.metadata = body.metadata;
3422
+ }
3423
+ return request;
3424
+ }
3425
+ function parsePipelineLogLimit(value) {
3426
+ if (value === void 0 || value.trim().length === 0) {
3427
+ return 200;
3428
+ }
3429
+ const limit = Number(value);
3430
+ if (!Number.isInteger(limit) || limit <= 0) {
3431
+ return void 0;
3432
+ }
3433
+ return Math.min(limit, 1e3);
3434
+ }
3435
+ function parsePipelineLogAfter(value) {
3436
+ if (value === void 0 || value.trim().length === 0) {
3437
+ return void 0;
3438
+ }
3439
+ const after = Number(value);
3440
+ if (!Number.isInteger(after) || after < 0) {
3441
+ return false;
3442
+ }
3443
+ return after;
3444
+ }
3445
+ function isJsonValue2(value) {
3446
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
3447
+ return Number.isFinite(value) || typeof value !== "number";
3448
+ }
3449
+ if (Array.isArray(value)) {
3450
+ return value.every(isJsonValue2);
3451
+ }
3452
+ if (isObject(value)) {
3453
+ return Object.values(value).every((item) => item === void 0 || isJsonValue2(item));
3454
+ }
3455
+ return false;
3456
+ }
3457
+ function toJsonValue3(value) {
3458
+ if (isJsonValue2(value)) {
3459
+ return value;
3460
+ }
3461
+ if (value === void 0) {
3462
+ return null;
3463
+ }
3464
+ try {
3465
+ const parsed = JSON.parse(JSON.stringify(value));
3466
+ return isJsonValue2(parsed) ? parsed : String(value);
3467
+ } catch {
3468
+ return String(value);
3469
+ }
3470
+ }
3471
+
3472
+ // src/runtime/questions.ts
3473
+ import { createHook as createHook2, parseToolArgs as parseToolArgs2 } from "@anvia/core";
3474
+ function registerQuestionRoutes(app, questions) {
3475
+ app.get("/questions", (c) => {
3476
+ const status = parseQuestionStatus(c.req.query("status"));
3477
+ if (status === false) {
3478
+ return errorResponse(c, 400, "bad_request", "status must be pending or resolved");
3479
+ }
3480
+ const options = {};
3481
+ const runId = optionalQueryString(c.req.query("runId"));
3482
+ const agentId = optionalQueryString(c.req.query("agentId"));
3483
+ const sessionId = optionalQueryString(c.req.query("sessionId"));
3484
+ if (status !== void 0) {
3485
+ options.status = status;
3486
+ }
3487
+ if (runId !== void 0) {
3488
+ options.runId = runId;
3489
+ }
3490
+ if (agentId !== void 0) {
3491
+ options.agentId = agentId;
3492
+ }
3493
+ if (sessionId !== void 0) {
3494
+ options.sessionId = sessionId;
3495
+ }
3496
+ return c.json({ questions: questions.list(options) });
3497
+ });
3498
+ app.post("/questions/:questionId/answer", async (c) => {
3499
+ const body = await parseQuestionAnswerRequest(c);
3500
+ if ("error" in body) {
3501
+ return body.error;
3502
+ }
3503
+ const result = questions.answer(c.req.param("questionId"), body.answers);
3504
+ if (result === "missing") {
3505
+ return errorResponse(c, 404, "not_found", "Question not found");
3506
+ }
3507
+ if (result === "resolved") {
3508
+ return errorResponse(c, 409, "conflict", "Question is already answered");
3509
+ }
3510
+ return c.json(result);
3511
+ });
3512
+ }
3513
+ function parseQuestionStatus(value) {
3514
+ const status = optionalQueryString(value);
3515
+ if (status === void 0) {
3516
+ return void 0;
3517
+ }
3518
+ return status === "pending" || status === "resolved" ? status : false;
3519
+ }
3520
+ async function parseQuestionAnswerRequest(c) {
3521
+ let body;
3522
+ try {
3523
+ body = await c.req.json();
3524
+ } catch {
3525
+ return { error: errorResponse(c, 400, "bad_request", "Request body must be JSON") };
3526
+ }
3527
+ if (!isObject(body)) {
3528
+ return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
3529
+ }
3530
+ if (!Array.isArray(body.answers)) {
3531
+ return { error: errorResponse(c, 400, "bad_request", "answers must be an array") };
3532
+ }
3533
+ const answers = [];
3534
+ for (const answer of body.answers) {
3535
+ if (!isObject(answer)) {
3536
+ return { error: errorResponse(c, 400, "bad_request", "answers must contain objects") };
3537
+ }
3538
+ if (typeof answer.questionId !== "string" || answer.questionId.trim().length === 0) {
3539
+ return { error: errorResponse(c, 400, "bad_request", "questionId must be a string") };
3540
+ }
3541
+ if (typeof answer.answer !== "string" || answer.answer.trim().length === 0) {
3542
+ return { error: errorResponse(c, 400, "bad_request", "answer must be a string") };
3543
+ }
3544
+ if ("choice" in answer && typeof answer.choice !== "string") {
3545
+ return { error: errorResponse(c, 400, "bad_request", "choice must be a string") };
3546
+ }
3547
+ if ("custom" in answer && typeof answer.custom !== "boolean") {
3548
+ return { error: errorResponse(c, 400, "bad_request", "custom must be a boolean") };
3549
+ }
3550
+ answers.push({
3551
+ questionId: answer.questionId.trim(),
3552
+ answer: answer.answer.trim(),
3553
+ ...typeof answer.choice === "string" ? { choice: answer.choice } : {},
3554
+ ...typeof answer.custom === "boolean" ? { custom: answer.custom } : {}
3555
+ });
3556
+ }
3557
+ return { answers };
3558
+ }
3559
+ function createQuestionRuntime() {
3560
+ const questions = /* @__PURE__ */ new Map();
3561
+ return {
3562
+ questions,
3563
+ createHook(context) {
3564
+ return createHook2({
3565
+ async onToolCall({ toolName, toolCallId, internalCallId, args, tool: control }) {
3566
+ if (toolName !== "ask_question") {
3567
+ return control.run();
3568
+ }
3569
+ const prompts = normalizeQuestionPrompts(parseToolArgs2(args));
3570
+ if ("error" in prompts) {
3571
+ return control.skip(prompts.error);
3572
+ }
3573
+ const answers = await requestQuestion(questions, context, {
3574
+ toolName,
3575
+ ...toolCallId === void 0 ? {} : { toolCallId },
3576
+ internalCallId,
3577
+ args,
3578
+ questions: prompts.questions
3579
+ });
3580
+ return control.skip(JSON.stringify({ answers }));
3581
+ }
3582
+ });
3583
+ },
3584
+ list(options) {
3585
+ return [...questions.values()].filter((question) => {
3586
+ if (options.status === "pending" && question.status !== "pending") {
3587
+ return false;
3588
+ }
3589
+ if (options.status === "resolved" && question.status === "pending") {
3590
+ return false;
3591
+ }
3592
+ if (options.runId !== void 0 && question.runId !== options.runId) {
3593
+ return false;
3594
+ }
3595
+ if (options.agentId !== void 0 && question.agentId !== options.agentId) {
3596
+ return false;
3597
+ }
3598
+ if (options.sessionId !== void 0 && question.sessionId !== options.sessionId) {
3599
+ return false;
3600
+ }
3601
+ return true;
3602
+ }).map(publicQuestion);
3603
+ },
3604
+ answer(id, answers) {
3605
+ const question = questions.get(id);
3606
+ if (question === void 0) {
3607
+ return "missing";
3608
+ }
3609
+ if (!isPendingQuestion(question)) {
3610
+ return "resolved";
3611
+ }
3612
+ const resolved = resolveQuestion(question, answers);
3613
+ questions.set(id, resolved);
3614
+ question.emit?.({ type: "tool_question_result", question: resolved });
3615
+ question.resolve(answers);
3616
+ return publicQuestion(resolved);
3617
+ }
3618
+ };
3619
+ }
3620
+ async function requestQuestion(questions, context, request) {
3621
+ const id = globalThis.crypto.randomUUID();
3622
+ const question = {
3623
+ id,
3624
+ runId: context.runId,
3625
+ agentId: context.agentId,
3626
+ ...context.sessionId === void 0 ? {} : { sessionId: context.sessionId },
3627
+ toolName: request.toolName,
3628
+ ...request.toolCallId === void 0 ? {} : { callId: request.toolCallId },
3629
+ internalCallId: request.internalCallId,
3630
+ args: request.args,
3631
+ questions: request.questions,
3632
+ status: "pending",
3633
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
3634
+ ...context.emit === void 0 ? {} : { emit: context.emit },
3635
+ resolve: () => {
3636
+ }
3637
+ };
3638
+ const answer = new Promise((resolve2) => {
3639
+ question.resolve = (answers) => {
3640
+ resolve2(answers);
3641
+ };
3642
+ });
3643
+ questions.set(id, question);
3644
+ context.emit?.({ type: "tool_question_request", question: publicQuestion(question) });
3645
+ return answer;
3646
+ }
3647
+ function normalizeQuestionPrompts(args) {
3648
+ if (!isObject(args)) {
3649
+ return { error: "ask_question requires a JSON object with questions." };
3650
+ }
3651
+ const rawQuestions = Array.isArray(args.questions) ? args.questions : [args];
3652
+ if (rawQuestions.length === 0) {
3653
+ return { error: "ask_question requires at least one question." };
3654
+ }
3655
+ const questions = [];
3656
+ for (const [index, question] of rawQuestions.entries()) {
3657
+ const normalized = normalizeQuestionPrompt(question, index);
3658
+ if (normalized === void 0) {
3659
+ return {
3660
+ error: "ask_question requires every question to include text and at least one choice."
3661
+ };
3662
+ }
3663
+ questions.push(normalized);
3664
+ }
3665
+ return { questions };
3666
+ }
3667
+ function normalizeQuestionPrompt(value, index) {
3668
+ if (!isObject(value) || typeof value.question !== "string" || value.question.trim().length === 0) {
3669
+ return void 0;
3670
+ }
3671
+ const choices = Array.isArray(value.choices) ? value.choices.map(normalizeQuestionChoice).filter((choice) => choice !== void 0) : [];
3672
+ if (choices.length === 0) {
3673
+ return void 0;
3674
+ }
3675
+ return {
3676
+ id: typeof value.id === "string" && value.id.trim().length > 0 ? value.id.trim() : `question_${index + 1}`,
3677
+ question: value.question.trim(),
3678
+ choices
3679
+ };
3680
+ }
3681
+ function normalizeQuestionChoice(value) {
3682
+ if (typeof value === "string" && value.trim().length > 0) {
3683
+ return { label: value.trim(), value: value.trim() };
3684
+ }
3685
+ if (!isObject(value) || typeof value.label !== "string" || value.label.trim().length === 0) {
3686
+ return void 0;
3687
+ }
3688
+ return {
3689
+ label: value.label.trim(),
3690
+ value: typeof value.value === "string" && value.value.trim().length > 0 ? value.value.trim() : value.label.trim()
3691
+ };
3692
+ }
3693
+ function isPendingQuestion(question) {
3694
+ return question !== void 0 && question.status === "pending" && "resolve" in question;
3695
+ }
3696
+ function resolveQuestion(question, answers) {
3697
+ return publicQuestion({
3698
+ ...question,
3699
+ status: "answered",
3700
+ answeredAt: (/* @__PURE__ */ new Date()).toISOString(),
3701
+ answers
3702
+ });
3703
+ }
3704
+ function publicQuestion(question) {
3705
+ const { emit, resolve: resolve2, ...rest } = question;
3706
+ void emit;
3707
+ void resolve2;
3708
+ return rest;
3709
+ }
3710
+
3711
+ // src/runtime/session-logs.ts
3712
+ async function appendSessionLog(store, input) {
3713
+ return store?.appendSessionLog?.(input);
3714
+ }
3715
+ async function* streamSessionRunLogs(props) {
3716
+ yield* emitLog(props.store, runStartedLog(props.session, props.runId));
3717
+ yield* emitLog(props.store, memoryLoadedLog(props.session, props.runId));
3718
+ try {
3719
+ for await (const event of props.stream) {
3720
+ for (const input of logsFromStreamEvent({
3721
+ event,
3722
+ runId: props.runId,
3723
+ sessionId: props.session.id,
3724
+ startedAt: props.startedAt
3725
+ })) {
3726
+ yield* emitLog(props.store, input);
3727
+ }
3728
+ yield event;
3729
+ }
3730
+ } catch (error) {
3731
+ yield* emitLog(
3732
+ props.store,
3733
+ runFailedLog(props.session.id, props.runId, error, props.startedAt)
3734
+ );
3735
+ throw error;
3736
+ }
3737
+ }
3738
+ function sessionCreatedLog(session) {
3739
+ return {
3740
+ sessionId: session.id,
3741
+ level: "info",
3742
+ category: "session",
3743
+ event: "session.created",
3744
+ message: "Session created",
3745
+ metadata: cleanMetadata2({
3746
+ agentId: session.agentId,
3747
+ hasTitle: session.title !== void 0,
3748
+ titleLength: session.title?.length ?? 0
3749
+ })
3750
+ };
3751
+ }
3752
+ function runReceivedLog(props) {
3753
+ return {
3754
+ sessionId: props.sessionId,
3755
+ runId: props.runId,
3756
+ level: "info",
3757
+ category: "api",
3758
+ event: "run.received",
3759
+ message: "Run request received",
3760
+ metadata: cleanMetadata2({
3761
+ agentId: props.agentId,
3762
+ stream: props.stream,
3763
+ message: messageSummary(props.message),
3764
+ maxTurns: props.maxTurns,
3765
+ toolConcurrency: props.toolConcurrency,
3766
+ hasTrace: props.hasTrace,
3767
+ metadataKeys: Object.keys(props.metadata ?? {})
3768
+ })
3769
+ };
3770
+ }
3771
+ function runStartedLog(session, runId) {
3772
+ return {
3773
+ sessionId: session.id,
3774
+ runId,
3775
+ level: "info",
3776
+ category: "run",
3777
+ event: "run.started",
3778
+ message: "Run started",
3779
+ metadata: cleanMetadata2({
3780
+ agentId: session.agentId,
3781
+ existingMessageCount: session.messageCount
3782
+ })
3783
+ };
3784
+ }
3785
+ function memoryLoadedLog(session, runId) {
3786
+ return {
3787
+ sessionId: session.id,
3788
+ runId,
3789
+ level: "debug",
3790
+ category: "memory",
3791
+ event: "memory.loaded",
3792
+ message: "Session memory loaded",
3793
+ metadata: cleanMetadata2({
3794
+ messageCount: session.messageCount,
3795
+ transcriptEntries: session.transcript.length
3796
+ })
3797
+ };
3798
+ }
3799
+ function runCompletedLog(props) {
3800
+ return {
3801
+ sessionId: props.sessionId,
3802
+ runId: props.runId,
3803
+ level: "info",
3804
+ category: "run",
3805
+ event: "run.completed",
3806
+ message: "Run completed",
3807
+ metadata: cleanMetadata2({
3808
+ durationMs: props.durationMs,
3809
+ usage: usageSummary(props.usage),
3810
+ outputBytes: byteLength2(props.output),
3811
+ messageCount: props.messageCount
3812
+ })
3813
+ };
3814
+ }
3815
+ function memorySavedLog(props) {
3816
+ return {
3817
+ sessionId: props.sessionId,
3818
+ runId: props.runId,
3819
+ level: "debug",
3820
+ category: "memory",
3821
+ event: "memory.saved",
3822
+ message: "Session memory saved",
3823
+ metadata: cleanMetadata2({
3824
+ messageCount: props.messageCount
3825
+ })
3826
+ };
3827
+ }
3828
+ function runFailedLog(sessionId, runId, error, startedAt) {
3829
+ return {
3830
+ sessionId,
3831
+ runId,
3832
+ level: "error",
3833
+ category: "run",
3834
+ event: "run.failed",
3835
+ message: "Run failed",
3836
+ metadata: cleanMetadata2({
3837
+ durationMs: Date.now() - startedAt,
3838
+ error: serializeError2(error)
3839
+ })
3840
+ };
3841
+ }
3842
+ function logsFromStreamEvent(props) {
3843
+ const { event, sessionId, runId } = props;
3844
+ if (event.type === "turn_start") {
3845
+ return [
3846
+ {
3847
+ sessionId,
3848
+ runId,
3849
+ level: "debug",
3850
+ category: "prompt",
3851
+ event: "prompt.prepared",
3852
+ message: `Turn ${event.turn} prompt prepared`,
3853
+ metadata: cleanMetadata2({
3854
+ turn: event.turn,
3855
+ prompt: messageSummary(event.prompt),
3856
+ historyCount: event.history.length
3857
+ })
3858
+ }
3859
+ ];
3860
+ }
3861
+ if (event.type === "tool_call") {
3862
+ return [
3863
+ {
3864
+ sessionId,
3865
+ runId,
3866
+ level: "info",
3867
+ category: "tool",
3868
+ event: "tool.called",
3869
+ message: `Tool ${event.toolCall.function.name} called`,
3870
+ metadata: cleanMetadata2({
3871
+ turn: event.turn,
3872
+ toolName: event.toolCall.function.name,
3873
+ callId: event.toolCall.callId ?? event.toolCall.id,
3874
+ argumentBytes: byteLength2(formatUnknown2(event.toolCall.function.arguments))
3875
+ })
3876
+ }
3877
+ ];
3878
+ }
3879
+ if (event.type === "tool_result") {
3880
+ return [
3881
+ {
3882
+ sessionId,
3883
+ runId,
3884
+ level: "info",
3885
+ category: "tool",
3886
+ event: "tool.completed",
3887
+ message: `Tool ${event.toolName} completed`,
3888
+ metadata: cleanMetadata2({
3889
+ turn: event.turn,
3890
+ toolName: event.toolName,
3891
+ callId: event.toolCallId,
3892
+ internalCallId: event.internalCallId,
3893
+ argumentBytes: byteLength2(event.args),
3894
+ resultBytes: byteLength2(event.result)
3895
+ })
3896
+ }
3897
+ ];
3898
+ }
3899
+ if (event.type === "turn_end") {
3900
+ return [
3901
+ {
3902
+ sessionId,
3903
+ runId,
3904
+ level: "debug",
3905
+ category: "model",
3906
+ event: "model.turn.completed",
3907
+ message: `Model turn ${event.turn} completed`,
3908
+ metadata: cleanMetadata2({
3909
+ turn: event.turn,
3910
+ contentCount: event.response.choice.length,
3911
+ usage: usageSummary(event.response.usage)
3912
+ })
3913
+ }
3914
+ ];
3915
+ }
3916
+ if (event.type === "final") {
3917
+ return [
3918
+ runCompletedLog({
3919
+ sessionId,
3920
+ runId,
3921
+ durationMs: Date.now() - props.startedAt,
3922
+ usage: event.usage,
3923
+ output: event.output,
3924
+ messageCount: event.messages.length
3925
+ }),
3926
+ memorySavedLog({ sessionId, runId, messageCount: event.messages.length })
3927
+ ];
3928
+ }
3929
+ if (event.type === "error") {
3930
+ return [runFailedLog(sessionId, runId, event.error, props.startedAt)];
3931
+ }
3932
+ if (event.type === "tool_approval_request") {
3933
+ return [
3934
+ {
3935
+ sessionId,
3936
+ runId,
3937
+ level: "info",
3938
+ category: "approval",
3939
+ event: "approval.requested",
3940
+ message: `Approval requested for ${event.approval.toolName}`,
3941
+ metadata: cleanMetadata2({
3942
+ approvalId: event.approval.id,
3943
+ toolName: event.approval.toolName,
3944
+ callId: event.approval.callId,
3945
+ status: event.approval.status,
3946
+ hasReason: event.approval.reason !== void 0,
3947
+ argumentBytes: byteLength2(event.approval.args)
3948
+ })
3949
+ }
3950
+ ];
3951
+ }
3952
+ if (event.type === "tool_approval_result") {
3953
+ return [
3954
+ {
3955
+ sessionId,
3956
+ runId,
3957
+ level: event.approval.status === "approved" ? "info" : "warn",
3958
+ category: "approval",
3959
+ event: "approval.resolved",
3960
+ message: `Approval ${event.approval.status} for ${event.approval.toolName}`,
3961
+ metadata: cleanMetadata2({
3962
+ approvalId: event.approval.id,
3963
+ toolName: event.approval.toolName,
3964
+ callId: event.approval.callId,
3965
+ status: event.approval.status,
3966
+ hasReason: event.approval.reason !== void 0
3967
+ })
3968
+ }
3969
+ ];
3970
+ }
3971
+ if (event.type === "tool_question_request") {
3972
+ return [
3973
+ {
3974
+ sessionId,
3975
+ runId,
3976
+ level: "info",
3977
+ category: "question",
3978
+ event: "question.requested",
3979
+ message: `Question requested by ${event.question.toolName}`,
3980
+ metadata: cleanMetadata2({
3981
+ questionId: event.question.id,
3982
+ toolName: event.question.toolName,
3983
+ callId: event.question.callId,
3984
+ status: event.question.status,
3985
+ questionCount: event.question.questions.length,
3986
+ argumentBytes: byteLength2(event.question.args)
3987
+ })
3988
+ }
3989
+ ];
3990
+ }
3991
+ if (event.type === "tool_question_result") {
3992
+ return [
3993
+ {
3994
+ sessionId,
3995
+ runId,
3996
+ level: "info",
3997
+ category: "question",
3998
+ event: "question.answered",
3999
+ message: `Question answered for ${event.question.toolName}`,
4000
+ metadata: cleanMetadata2({
4001
+ questionId: event.question.id,
4002
+ toolName: event.question.toolName,
4003
+ callId: event.question.callId,
4004
+ status: event.question.status,
4005
+ answerCount: event.question.answers?.length ?? 0
4006
+ })
4007
+ }
4008
+ ];
4009
+ }
4010
+ if (event.type === "agent_tool_event") {
4011
+ return childAgentLog(event, sessionId, runId);
4012
+ }
4013
+ return [];
4014
+ }
4015
+ async function* emitLog(store, input) {
4016
+ const log = await appendSessionLog(store, input);
4017
+ if (log !== void 0) {
4018
+ yield { type: "session_log", log };
4019
+ }
4020
+ }
4021
+ function childAgentLog(event, sessionId, runId) {
4022
+ const child = event.event;
4023
+ if (child.type === "tool_call") {
4024
+ return [
4025
+ {
4026
+ sessionId,
4027
+ runId,
4028
+ level: "debug",
4029
+ category: "tool",
4030
+ event: "child_tool.called",
4031
+ message: `Child agent ${event.agentName ?? event.agentId} called ${child.toolCall.function.name}`,
4032
+ metadata: cleanMetadata2({
4033
+ parentToolName: event.toolName,
4034
+ agentId: event.agentId,
4035
+ hasAgentName: event.agentName !== void 0,
4036
+ turn: event.turn,
4037
+ childTurn: child.turn,
4038
+ toolName: child.toolCall.function.name,
4039
+ callId: child.toolCall.callId ?? child.toolCall.id,
4040
+ argumentBytes: byteLength2(formatUnknown2(child.toolCall.function.arguments))
4041
+ })
4042
+ }
4043
+ ];
4044
+ }
4045
+ if (child.type === "tool_result") {
4046
+ return [
4047
+ {
4048
+ sessionId,
4049
+ runId,
4050
+ level: "debug",
4051
+ category: "tool",
4052
+ event: "child_tool.completed",
4053
+ message: `Child agent ${event.agentName ?? event.agentId} completed ${child.toolName}`,
4054
+ metadata: cleanMetadata2({
4055
+ parentToolName: event.toolName,
4056
+ agentId: event.agentId,
4057
+ hasAgentName: event.agentName !== void 0,
4058
+ turn: event.turn,
4059
+ childTurn: child.turn,
4060
+ toolName: child.toolName,
4061
+ callId: child.toolCallId,
4062
+ resultBytes: byteLength2(child.result)
4063
+ })
4064
+ }
4065
+ ];
4066
+ }
4067
+ if (child.type === "turn_start") {
4068
+ return [
4069
+ {
4070
+ sessionId,
4071
+ runId,
4072
+ level: "debug",
4073
+ category: "run",
4074
+ event: "child_agent.turn_started",
4075
+ message: `Child agent ${event.agentName ?? event.agentId} turn ${child.turn} started`,
4076
+ metadata: cleanMetadata2({
4077
+ parentToolName: event.toolName,
4078
+ agentId: event.agentId,
4079
+ hasAgentName: event.agentName !== void 0,
4080
+ childTurn: child.turn,
4081
+ historyCount: child.history.length
4082
+ })
4083
+ }
4084
+ ];
4085
+ }
4086
+ if (child.type === "final") {
4087
+ return [
4088
+ {
4089
+ sessionId,
4090
+ runId,
4091
+ level: "debug",
4092
+ category: "run",
4093
+ event: "child_agent.completed",
4094
+ message: `Child agent ${event.agentName ?? event.agentId} completed`,
4095
+ metadata: cleanMetadata2({
4096
+ parentToolName: event.toolName,
4097
+ agentId: event.agentId,
4098
+ hasAgentName: event.agentName !== void 0,
4099
+ usage: usageSummary(child.usage),
4100
+ outputBytes: byteLength2(child.output),
4101
+ messageCount: child.messages.length
4102
+ })
4103
+ }
4104
+ ];
4105
+ }
4106
+ if (child.type === "error") {
4107
+ return [
4108
+ {
4109
+ sessionId,
4110
+ runId,
4111
+ level: "error",
4112
+ category: "run",
4113
+ event: "child_agent.failed",
4114
+ message: `Child agent ${event.agentName ?? event.agentId} failed`,
4115
+ metadata: cleanMetadata2({
4116
+ parentToolName: event.toolName,
4117
+ agentId: event.agentId,
4118
+ hasAgentName: event.agentName !== void 0,
4119
+ error: serializeError2(child.error)
4120
+ })
4121
+ }
4122
+ ];
4123
+ }
4124
+ return [];
4125
+ }
4126
+ function messageSummary(message) {
4127
+ if (typeof message === "string") {
4128
+ return {
4129
+ role: "user",
4130
+ contentKind: "text",
4131
+ byteLength: byteLength2(message)
4132
+ };
4133
+ }
4134
+ return {
4135
+ role: message.role,
4136
+ contentKind: Array.isArray(message.content) ? "parts" : "text",
4137
+ partCount: Array.isArray(message.content) ? message.content.length : 1,
4138
+ byteLength: byteLength2(formatUnknown2(message.content))
4139
+ };
4140
+ }
4141
+ function usageSummary(value) {
4142
+ if (value === void 0 || value === null || typeof value !== "object") {
4143
+ return void 0;
4144
+ }
4145
+ const record = value;
4146
+ return cleanMetadata2({
4147
+ inputTokens: numericValue(record.inputTokens),
4148
+ outputTokens: numericValue(record.outputTokens),
4149
+ totalTokens: numericValue(record.totalTokens),
4150
+ cachedInputTokens: numericValue(record.cachedInputTokens),
4151
+ cacheCreationInputTokens: numericValue(record.cacheCreationInputTokens)
4152
+ });
4153
+ }
4154
+ function cleanMetadata2(value) {
4155
+ const cleaned = {};
4156
+ for (const [key, item] of Object.entries(value)) {
4157
+ if (item === void 0) {
4158
+ continue;
4159
+ }
4160
+ const jsonValue = cleanJsonValue(item);
4161
+ if (jsonValue !== void 0) {
4162
+ cleaned[key] = jsonValue;
4163
+ }
4164
+ }
4165
+ return cleaned;
4166
+ }
4167
+ function cleanJsonValue(value) {
4168
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
4169
+ return value;
4170
+ }
4171
+ if (Array.isArray(value)) {
4172
+ return value.map((item) => cleanJsonValue(item)).filter((item) => item !== void 0);
4173
+ }
4174
+ if (typeof value === "object" && value !== null) {
4175
+ return cleanMetadata2(value);
4176
+ }
4177
+ return void 0;
4178
+ }
4179
+ function numericValue(value) {
4180
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
4181
+ }
4182
+ function byteLength2(value) {
4183
+ return value === void 0 ? 0 : new TextEncoder().encode(value).byteLength;
4184
+ }
4185
+ function formatUnknown2(value) {
4186
+ if (typeof value === "string") {
4187
+ return value;
4188
+ }
4189
+ try {
4190
+ return JSON.stringify(value);
4191
+ } catch {
4192
+ return String(value);
4193
+ }
4194
+ }
4195
+
4196
+ // src/runtime/sessions.ts
4197
+ function registerSessionRoutes(app, props) {
4198
+ app.get("/sessions", async (c) => {
4199
+ const agentId = optionalQueryString(c.req.query("agentId"));
4200
+ if (agentId !== void 0 && !props.agentMap.has(agentId)) {
4201
+ return errorResponse(c, 404, "not_found", "Agent not found");
4202
+ }
4203
+ const limit = parseLimit(c.req.query("limit"));
4204
+ if (limit === void 0) {
4205
+ return errorResponse(c, 400, "bad_request", "limit must be a positive integer");
4206
+ }
4207
+ const sessions = await props.sessionStore.listSessions({
4208
+ ...agentId === void 0 ? {} : { agentId },
4209
+ limit
4210
+ });
4211
+ return c.json({ sessions });
4212
+ });
4213
+ app.post("/sessions", async (c) => {
4214
+ const body = await parseCreateSessionRequest(c);
4215
+ if ("error" in body) {
4216
+ return body.error;
4217
+ }
4218
+ if (!props.agentMap.has(body.agentId)) {
4219
+ return errorResponse(c, 404, "not_found", "Agent not found");
4220
+ }
4221
+ const session = await props.sessionStore.createSession({
4222
+ id: globalThis.crypto.randomUUID(),
4223
+ agentId: body.agentId,
4224
+ ...body.title === void 0 ? {} : { title: body.title },
4225
+ ...body.metadata === void 0 ? {} : { metadata: body.metadata }
4226
+ });
4227
+ await appendSessionLog(props.sessionStore, sessionCreatedLog(session));
4228
+ return c.json(session, 201);
4229
+ });
4230
+ app.get("/sessions/:sessionId", async (c) => {
4231
+ const session = await props.sessionStore.getSession(c.req.param("sessionId"));
4232
+ if (session === void 0) {
4233
+ return errorResponse(c, 404, "not_found", "Session not found");
4234
+ }
4235
+ return c.json(session);
4236
+ });
4237
+ app.get("/sessions/:sessionId/logs", async (c) => {
4238
+ const sessionId = c.req.param("sessionId");
4239
+ const session = await props.sessionStore.getSession(sessionId);
4240
+ if (session === void 0) {
4241
+ return errorResponse(c, 404, "not_found", "Session not found");
4242
+ }
4243
+ if (props.sessionStore.listSessionLogs === void 0) {
4244
+ return errorResponse(
4245
+ c,
4246
+ 501,
4247
+ "unsupported_capability",
4248
+ 'Capability "sessions.logs" is not implemented by this runner',
4249
+ { capability: "sessions", operation: "logs" }
4250
+ );
4251
+ }
4252
+ const limit = parseSessionLogLimit(c.req.query("limit"));
4253
+ if (limit === void 0) {
4254
+ return errorResponse(c, 400, "bad_request", "limit must be a positive integer");
4255
+ }
4256
+ const after = parseSessionLogAfter(c.req.query("after"));
4257
+ if (after === false) {
4258
+ return errorResponse(c, 400, "bad_request", "after must be a non-negative integer");
4259
+ }
4260
+ const logs = await props.sessionStore.listSessionLogs({
4261
+ sessionId,
4262
+ limit,
4263
+ ...after === void 0 ? {} : { after }
4264
+ });
4265
+ const last = logs.at(-1);
4266
+ return c.json({
4267
+ logs,
4268
+ ...logs.length === limit && last !== void 0 ? { nextCursor: last.sequence } : {}
4269
+ });
4270
+ });
4271
+ app.delete("/sessions/:sessionId", async (c) => {
4272
+ if (props.sessionStore.deleteSession === void 0) {
4273
+ return errorResponse(
4274
+ c,
4275
+ 501,
4276
+ "unsupported_capability",
4277
+ 'Capability "sessions.delete" is not implemented by this runner',
4278
+ { capability: "sessions", operation: "delete" }
4279
+ );
4280
+ }
4281
+ const deleted = await props.sessionStore.deleteSession(c.req.param("sessionId"));
4282
+ if (!deleted) {
4283
+ return errorResponse(c, 404, "not_found", "Session not found");
4284
+ }
4285
+ return c.body(null, 204);
4286
+ });
4287
+ app.get("/sessions/:sessionId/traces", async (c) => {
4288
+ if (props.traceStore === void 0) {
4289
+ return unsupportedCapability(c, "traces");
4290
+ }
4291
+ const sessionId = c.req.param("sessionId");
4292
+ const session = await props.sessionStore.getSession(sessionId);
4293
+ if (session === void 0) {
2785
4294
  return errorResponse(c, 404, "not_found", "Session not found");
2786
4295
  }
2787
4296
  const limit = parseLimit(c.req.query("limit"));
@@ -2792,6 +4301,26 @@ function registerSessionRoutes(app, props) {
2792
4301
  return c.json({ traces });
2793
4302
  });
2794
4303
  }
4304
+ function parseSessionLogLimit(value) {
4305
+ if (value === void 0 || value.trim().length === 0) {
4306
+ return 200;
4307
+ }
4308
+ const limit = Number(value);
4309
+ if (!Number.isInteger(limit) || limit <= 0) {
4310
+ return void 0;
4311
+ }
4312
+ return Math.min(limit, 1e3);
4313
+ }
4314
+ function parseSessionLogAfter(value) {
4315
+ if (value === void 0 || value.trim().length === 0) {
4316
+ return void 0;
4317
+ }
4318
+ const after = Number(value);
4319
+ if (!Number.isInteger(after) || after < 0) {
4320
+ return false;
4321
+ }
4322
+ return after;
4323
+ }
2795
4324
  async function parseCreateSessionRequest(c) {
2796
4325
  let body;
2797
4326
  try {
@@ -2826,6 +4355,47 @@ async function parseCreateSessionRequest(c) {
2826
4355
  return request;
2827
4356
  }
2828
4357
 
4358
+ // src/runtime/tools.ts
4359
+ function registerToolRoutes(app, props) {
4360
+ app.get("/agents/:agentId/tools", async (c) => {
4361
+ const agentId = c.req.param("agentId");
4362
+ const agent = props.agentMap.get(agentId);
4363
+ if (agent === void 0) {
4364
+ return errorResponse(c, 404, "not_found", "Agent not found");
4365
+ }
4366
+ return c.json({
4367
+ agentId,
4368
+ tools: await agentToolMetadata(agent)
4369
+ });
4370
+ });
4371
+ }
4372
+ async function agentToolMetadata(agent) {
4373
+ const seen = /* @__PURE__ */ new Set();
4374
+ const metadata = [];
4375
+ for (const { tool, source } of agentToolItems(agent)) {
4376
+ const key = `${source}:${tool.name}`;
4377
+ if (seen.has(key)) {
4378
+ continue;
4379
+ }
4380
+ seen.add(key);
4381
+ const definition = await tool.definition("");
4382
+ metadata.push({
4383
+ agentId: agent.id,
4384
+ name: definition.name,
4385
+ description: definition.description,
4386
+ parameters: definition.parameters,
4387
+ source,
4388
+ approval: approvalMetadata(tool)
4389
+ });
4390
+ }
4391
+ return metadata.sort((left, right) => {
4392
+ if (left.source !== right.source) {
4393
+ return left.source === "static" ? -1 : 1;
4394
+ }
4395
+ return left.name.localeCompare(right.name);
4396
+ });
4397
+ }
4398
+
2829
4399
  // src/runtime/trace-routes.ts
2830
4400
  function registerTraceRoutes(app, traceStore) {
2831
4401
  app.get("/traces", async (c) => {
@@ -2871,8 +4441,8 @@ var Studio = class {
2871
4441
  studio;
2872
4442
  server;
2873
4443
  sigintHandler;
2874
- constructor(agents = [], options = {}) {
2875
- this.options = studioOptionsFromAgents(agents, options);
4444
+ constructor(targets = [], options = {}) {
4445
+ this.options = studioOptionsFromTargets(targets, options);
2876
4446
  this.studio = createStudioApp(this.options);
2877
4447
  }
2878
4448
  get app() {
@@ -2925,9 +4495,14 @@ var Studio = class {
2925
4495
  this.studio.close();
2926
4496
  }
2927
4497
  };
2928
- function studioOptionsFromAgents(agents, options) {
4498
+ function studioOptionsFromTargets(targets, options) {
4499
+ const agents = targets.filter((target) => target instanceof Agent);
4500
+ const pipelines = targets.filter(
4501
+ (target) => target instanceof Pipeline
4502
+ );
2929
4503
  return {
2930
- agents: inferStudioAgents(agents, options.quickPrompts ?? {})
4504
+ agents: inferStudioAgents(agents, options.quickPrompts ?? {}),
4505
+ pipelines: inferStudioPipelines(pipelines)
2931
4506
  };
2932
4507
  }
2933
4508
  function inferStudioAgents(agents, quickPrompts) {
@@ -2942,6 +4517,19 @@ function inferStudioAgents(agents, quickPrompts) {
2942
4517
  };
2943
4518
  });
2944
4519
  }
4520
+ function inferStudioPipelines(pipelines) {
4521
+ const ids = /* @__PURE__ */ new Set();
4522
+ return pipelines.map((pipeline) => {
4523
+ const id = uniqueAgentId(pipeline.id || "pipeline", ids);
4524
+ return {
4525
+ id,
4526
+ pipeline,
4527
+ ...pipeline.name === void 0 ? {} : { name: pipeline.name },
4528
+ ...pipeline.description === void 0 ? {} : { description: pipeline.description },
4529
+ ...pipeline.metadata === void 0 ? {} : { metadata: pipeline.metadata }
4530
+ };
4531
+ });
4532
+ }
2945
4533
  function uniqueAgentId(baseId, ids) {
2946
4534
  let id = baseId;
2947
4535
  let suffix = 2;
@@ -2959,6 +4547,7 @@ function agentMetadata(agent) {
2959
4547
  dynamicContextCount: agent.dynamicContexts.length,
2960
4548
  dynamicToolCount: agent.dynamicTools.length,
2961
4549
  hasOutputSchema: agent.outputSchema !== void 0,
4550
+ hasHook: agent.hook !== void 0,
2962
4551
  observerCount: agent.observers.length,
2963
4552
  approvalToolCount: agent.toolSet.values().filter((tool) => tool.approval !== void 0).length
2964
4553
  };
@@ -2966,7 +4555,9 @@ function agentMetadata(agent) {
2966
4555
  function createStudioApp(options) {
2967
4556
  const stores = resolveStores(options);
2968
4557
  const agents = normalizeAgents(options.agents).map((agent) => withStudioSessionMemory(agent, stores.sessions)).map((agent) => withStudioTraceObserver(agent, stores.traces));
4558
+ const pipelines = normalizePipelines(options.pipelines);
2969
4559
  const agentMap = new Map(agents.map((agent) => [agent.id, agent]));
4560
+ const pipelineMap = new Map(pipelines.map((pipeline) => [pipeline.id, pipeline]));
2970
4561
  const approvalRuntime = createApprovalRuntime();
2971
4562
  const questionRuntime = createQuestionRuntime();
2972
4563
  const app = new HonoApp();
@@ -2988,7 +4579,7 @@ function createStudioApp(options) {
2988
4579
  }
2989
4580
  })
2990
4581
  );
2991
- app.get("/config", (c) => c.json(buildConfig(options, agents, stores)));
4582
+ app.get("/config", (c) => c.json(buildConfig(options, agents, pipelines, stores)));
2992
4583
  app.get("/agents", (c) => c.json({ agents: agents.map(agentConfig) }));
2993
4584
  app.get("/agents/:agentId", (c) => {
2994
4585
  const agent = agentMap.get(c.req.param("agentId"));
@@ -2997,12 +4588,19 @@ function createStudioApp(options) {
2997
4588
  }
2998
4589
  return c.json(agentConfig(agent));
2999
4590
  });
4591
+ registerMcpRoutes(app, { agentMap });
4592
+ registerToolRoutes(app, { agentMap });
3000
4593
  registerApprovalRoutes(app, approvalRuntime);
3001
4594
  registerQuestionRoutes(app, questionRuntime);
3002
4595
  registerKnowledgeRoutes(app, {
3003
4596
  agents,
3004
4597
  ...stores.traces === void 0 ? {} : { traceStore: stores.traces }
3005
4598
  });
4599
+ registerPipelineRoutes(app, {
4600
+ pipelines,
4601
+ pipelineMap,
4602
+ ...stores.pipelineLogs === void 0 ? {} : { store: stores.pipelineLogs }
4603
+ });
3006
4604
  app.post("/agents/:agentId/runs", async (c) => {
3007
4605
  const agentId = c.req.param("agentId");
3008
4606
  const agent = agentMap.get(agentId);
@@ -3024,6 +4622,23 @@ function createStudioApp(options) {
3024
4622
  return errorResponse(c, 400, "bad_request", "Session belongs to another agent");
3025
4623
  }
3026
4624
  const runId = globalThis.crypto.randomUUID();
4625
+ const runStartedAt = Date.now();
4626
+ if (session !== void 0) {
4627
+ await appendSessionLog(
4628
+ stores.sessions,
4629
+ runReceivedLog({
4630
+ sessionId: session.id,
4631
+ runId,
4632
+ agentId,
4633
+ message: body.message,
4634
+ stream: body.stream === true,
4635
+ ...body.maxTurns === void 0 ? {} : { maxTurns: body.maxTurns },
4636
+ ...body.toolConcurrency === void 0 ? {} : { toolConcurrency: body.toolConcurrency },
4637
+ hasTrace: body.trace !== void 0,
4638
+ ...body.metadata === void 0 ? {} : { metadata: body.metadata }
4639
+ })
4640
+ );
4641
+ }
3027
4642
  const memoryMetadata = {
3028
4643
  agentId,
3029
4644
  ...body.metadata ?? {},
@@ -3070,7 +4685,13 @@ function createStudioApp(options) {
3070
4685
  }
3071
4686
  const runStream = mergeRunAndApprovalEvents(request.stream(), runtimeEvents);
3072
4687
  const stream = session === void 0 || stores.sessions === void 0 ? runStream : persistStreamingSessionTranscript({
3073
- stream: runStream,
4688
+ stream: streamSessionRunLogs({
4689
+ stream: runStream,
4690
+ store: stores.sessions,
4691
+ session,
4692
+ runId,
4693
+ startedAt: runStartedAt
4694
+ }),
3074
4695
  store: stores.sessions,
3075
4696
  session,
3076
4697
  message: body.message,
@@ -3079,6 +4700,10 @@ function createStudioApp(options) {
3079
4700
  return streamAgentRunEvents(c, stream);
3080
4701
  }
3081
4702
  try {
4703
+ if (session !== void 0) {
4704
+ await appendSessionLog(stores.sessions, runStartedLog(session, runId));
4705
+ await appendSessionLog(stores.sessions, memoryLoadedLog(session, runId));
4706
+ }
3082
4707
  const effectiveHook = composeHooks(
3083
4708
  composeHooks(
3084
4709
  agent.agent.hook,
@@ -3109,6 +4734,25 @@ function createStudioApp(options) {
3109
4734
  transcript: transcriptFromMessages(response.messages),
3110
4735
  status: "success"
3111
4736
  });
4737
+ await appendSessionLog(
4738
+ stores.sessions,
4739
+ runCompletedLog({
4740
+ sessionId: session.id,
4741
+ runId,
4742
+ durationMs: Date.now() - runStartedAt,
4743
+ usage: response.usage,
4744
+ output: response.output,
4745
+ messageCount: response.messages.length
4746
+ })
4747
+ );
4748
+ await appendSessionLog(
4749
+ stores.sessions,
4750
+ memorySavedLog({
4751
+ sessionId: session.id,
4752
+ runId,
4753
+ messageCount: response.messages.length
4754
+ })
4755
+ );
3112
4756
  }
3113
4757
  return c.json(response);
3114
4758
  } catch (error) {
@@ -3125,6 +4769,10 @@ function createStudioApp(options) {
3125
4769
  status: "error",
3126
4770
  error: serializeError2(error)
3127
4771
  });
4772
+ await appendSessionLog(
4773
+ stores.sessions,
4774
+ runFailedLog(session.id, runId, error, runStartedAt)
4775
+ );
3128
4776
  }
3129
4777
  return errorResponse(c, 500, "internal_error", "Agent run failed", serializeError2(error));
3130
4778
  }
@@ -3149,7 +4797,7 @@ function createStudioApp(options) {
3149
4797
  return app.fetch(request);
3150
4798
  },
3151
4799
  config() {
3152
- return buildConfig(options, agents, stores);
4800
+ return buildConfig(options, agents, pipelines, stores);
3153
4801
  },
3154
4802
  close() {
3155
4803
  },
@@ -3237,6 +4885,9 @@ function composeHooks(first, second) {
3237
4885
  if (firstAction?.type === "skip" || firstAction?.type === "terminate") {
3238
4886
  return firstAction;
3239
4887
  }
4888
+ if (firstAction?.type === "approval_request") {
4889
+ return await approvalRequestHandler(second)?.(args, firstAction) ?? firstAction;
4890
+ }
3240
4891
  const secondAction = await second.onToolCall?.(args);
3241
4892
  return secondAction ?? firstAction ?? void 0;
3242
4893
  },
@@ -3246,6 +4897,10 @@ function composeHooks(first, second) {
3246
4897
  }
3247
4898
  });
3248
4899
  }
4900
+ function approvalRequestHandler(hook) {
4901
+ const candidate = hook;
4902
+ return typeof candidate.handleApprovalRequest === "function" ? candidate.handleApprovalRequest : void 0;
4903
+ }
3249
4904
  export {
3250
4905
  Studio,
3251
4906
  StudioTraceObserver,