@anvia/studio 0.1.3 → 0.2.1

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,255 @@ 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
+ }
1059
+ savePipelineRun(input) {
1060
+ const db = this.database();
1061
+ db.prepare(
1062
+ `INSERT INTO runner_pipeline_runs (
1063
+ run_id,
1064
+ pipeline_id,
1065
+ status,
1066
+ input_json,
1067
+ output_json,
1068
+ error_json,
1069
+ metadata_json,
1070
+ started_at,
1071
+ ended_at,
1072
+ duration_ms
1073
+ ) VALUES (
1074
+ $runId,
1075
+ $pipelineId,
1076
+ $status,
1077
+ $input,
1078
+ $output,
1079
+ $error,
1080
+ $metadata,
1081
+ $startedAt,
1082
+ $endedAt,
1083
+ $durationMs
1084
+ )
1085
+ ON CONFLICT(run_id) DO UPDATE SET
1086
+ pipeline_id = excluded.pipeline_id,
1087
+ status = excluded.status,
1088
+ input_json = excluded.input_json,
1089
+ output_json = excluded.output_json,
1090
+ error_json = excluded.error_json,
1091
+ metadata_json = excluded.metadata_json,
1092
+ started_at = excluded.started_at,
1093
+ ended_at = excluded.ended_at,
1094
+ duration_ms = excluded.duration_ms`
1095
+ ).run({
1096
+ $runId: input.runId,
1097
+ $pipelineId: input.pipelineId,
1098
+ $status: input.status,
1099
+ $input: JSON.stringify(input.input),
1100
+ $output: input.output === void 0 ? null : JSON.stringify(input.output),
1101
+ $error: input.error === void 0 ? null : JSON.stringify(input.error),
1102
+ $metadata: input.metadata === void 0 ? null : JSON.stringify(input.metadata),
1103
+ $startedAt: input.startedAt,
1104
+ $endedAt: input.endedAt ?? null,
1105
+ $durationMs: input.durationMs ?? null
1106
+ });
1107
+ return {
1108
+ runId: input.runId,
1109
+ pipelineId: input.pipelineId,
1110
+ status: input.status,
1111
+ input: input.input,
1112
+ ...input.output === void 0 ? {} : { output: input.output },
1113
+ ...input.error === void 0 ? {} : { error: input.error },
1114
+ ...input.metadata === void 0 ? {} : { metadata: input.metadata },
1115
+ startedAt: input.startedAt,
1116
+ ...input.endedAt === void 0 ? {} : { endedAt: input.endedAt },
1117
+ ...input.durationMs === void 0 ? {} : { durationMs: input.durationMs }
1118
+ };
1119
+ }
1120
+ listPipelineRuns(options) {
1121
+ const db = this.database();
1122
+ const rows = db.prepare(
1123
+ `SELECT run_id, pipeline_id, status, input_json, output_json, error_json,
1124
+ metadata_json, started_at, ended_at, duration_ms
1125
+ FROM runner_pipeline_runs
1126
+ WHERE pipeline_id = $pipelineId
1127
+ ORDER BY started_at DESC
1128
+ LIMIT $limit`
1129
+ ).all({
1130
+ $pipelineId: options.pipelineId,
1131
+ $limit: options.limit
1132
+ });
1133
+ return rows.map(toPipelineRun);
1134
+ }
888
1135
  deleteSession(id) {
889
1136
  const db = this.database();
890
1137
  try {
891
1138
  db.exec("BEGIN IMMEDIATE");
892
1139
  db.prepare("DELETE FROM runner_traces WHERE session_id = $id").run({ $id: id });
893
1140
  db.prepare("DELETE FROM runner_session_runs WHERE session_id = $id").run({ $id: id });
1141
+ db.prepare("DELETE FROM runner_session_logs WHERE session_id = $id").run({ $id: id });
894
1142
  const result = db.prepare("DELETE FROM runner_sessions WHERE id = $id").run({ $id: id });
895
1143
  db.exec("COMMIT");
896
1144
  return Number(result.changes) > 0;
@@ -1039,18 +1287,41 @@ var SqliteSessionStore = class {
1039
1287
  db.exec(`
1040
1288
  PRAGMA journal_mode = WAL;
1041
1289
  PRAGMA foreign_keys = ON;
1290
+ `);
1291
+ guardAgainstLegacySessionSchema(db);
1292
+ db.exec(`
1042
1293
  CREATE TABLE IF NOT EXISTS runner_sessions (
1043
1294
  id TEXT PRIMARY KEY,
1044
1295
  agent_id TEXT NOT NULL,
1045
1296
  title TEXT,
1046
1297
  metadata_json TEXT,
1047
- messages_json TEXT NOT NULL,
1048
- transcript_json TEXT NOT NULL,
1049
1298
  created_at TEXT NOT NULL,
1050
1299
  updated_at TEXT NOT NULL
1051
1300
  ) STRICT;
1052
1301
  CREATE INDEX IF NOT EXISTS runner_sessions_agent_updated_idx
1053
1302
  ON runner_sessions(agent_id, updated_at DESC);
1303
+ CREATE TABLE IF NOT EXISTS runner_session_messages (
1304
+ session_id TEXT NOT NULL,
1305
+ message_index INTEGER NOT NULL,
1306
+ role TEXT NOT NULL,
1307
+ message_id TEXT,
1308
+ created_at TEXT NOT NULL,
1309
+ PRIMARY KEY(session_id, message_index),
1310
+ FOREIGN KEY(session_id) REFERENCES runner_sessions(id) ON DELETE CASCADE
1311
+ ) STRICT;
1312
+ CREATE INDEX IF NOT EXISTS runner_session_messages_session_idx
1313
+ ON runner_session_messages(session_id, message_index ASC);
1314
+ CREATE TABLE IF NOT EXISTS runner_session_message_parts (
1315
+ session_id TEXT NOT NULL,
1316
+ message_index INTEGER NOT NULL,
1317
+ part_index INTEGER NOT NULL,
1318
+ type TEXT NOT NULL,
1319
+ part_json TEXT NOT NULL,
1320
+ PRIMARY KEY(session_id, message_index, part_index),
1321
+ FOREIGN KEY(session_id, message_index)
1322
+ REFERENCES runner_session_messages(session_id, message_index)
1323
+ ON DELETE CASCADE
1324
+ ) STRICT;
1054
1325
  CREATE TABLE IF NOT EXISTS runner_session_runs (
1055
1326
  run_id TEXT PRIMARY KEY,
1056
1327
  session_id TEXT NOT NULL,
@@ -1064,6 +1335,51 @@ var SqliteSessionStore = class {
1064
1335
  ) STRICT;
1065
1336
  CREATE INDEX IF NOT EXISTS runner_session_runs_session_created_idx
1066
1337
  ON runner_session_runs(session_id, created_at ASC);
1338
+ CREATE TABLE IF NOT EXISTS runner_session_logs (
1339
+ id TEXT PRIMARY KEY,
1340
+ session_id TEXT NOT NULL,
1341
+ run_id TEXT,
1342
+ sequence INTEGER NOT NULL,
1343
+ timestamp TEXT NOT NULL,
1344
+ level TEXT NOT NULL,
1345
+ category TEXT NOT NULL,
1346
+ event TEXT NOT NULL,
1347
+ message TEXT NOT NULL,
1348
+ metadata_json TEXT,
1349
+ UNIQUE(session_id, sequence),
1350
+ FOREIGN KEY(session_id) REFERENCES runner_sessions(id) ON DELETE CASCADE
1351
+ ) STRICT;
1352
+ CREATE INDEX IF NOT EXISTS runner_session_logs_session_sequence_idx
1353
+ ON runner_session_logs(session_id, sequence ASC);
1354
+ CREATE TABLE IF NOT EXISTS runner_pipeline_logs (
1355
+ id TEXT PRIMARY KEY,
1356
+ pipeline_id TEXT NOT NULL,
1357
+ run_id TEXT,
1358
+ sequence INTEGER NOT NULL,
1359
+ timestamp TEXT NOT NULL,
1360
+ level TEXT NOT NULL,
1361
+ category TEXT NOT NULL,
1362
+ event TEXT NOT NULL,
1363
+ message TEXT NOT NULL,
1364
+ metadata_json TEXT,
1365
+ UNIQUE(pipeline_id, sequence)
1366
+ ) STRICT;
1367
+ CREATE INDEX IF NOT EXISTS runner_pipeline_logs_pipeline_sequence_idx
1368
+ ON runner_pipeline_logs(pipeline_id, sequence ASC);
1369
+ CREATE TABLE IF NOT EXISTS runner_pipeline_runs (
1370
+ run_id TEXT PRIMARY KEY,
1371
+ pipeline_id TEXT NOT NULL,
1372
+ status TEXT NOT NULL,
1373
+ input_json TEXT NOT NULL,
1374
+ output_json TEXT,
1375
+ error_json TEXT,
1376
+ metadata_json TEXT,
1377
+ started_at TEXT NOT NULL,
1378
+ ended_at TEXT,
1379
+ duration_ms INTEGER
1380
+ ) STRICT;
1381
+ CREATE INDEX IF NOT EXISTS runner_pipeline_runs_pipeline_started_idx
1382
+ ON runner_pipeline_runs(pipeline_id, started_at DESC);
1067
1383
  CREATE TABLE IF NOT EXISTS runner_traces (
1068
1384
  id TEXT PRIMARY KEY,
1069
1385
  session_id TEXT NOT NULL,
@@ -1088,7 +1404,7 @@ var SqliteSessionStore = class {
1088
1404
  }
1089
1405
  getSessionRow(id) {
1090
1406
  return this.database().prepare(
1091
- `SELECT id, agent_id, title, metadata_json, messages_json, transcript_json, created_at, updated_at
1407
+ `SELECT id, agent_id, title, metadata_json, created_at, updated_at
1092
1408
  FROM runner_sessions
1093
1409
  WHERE id = $id`
1094
1410
  ).get({ $id: id });
@@ -1108,21 +1424,110 @@ var SqliteSessionStore = class {
1108
1424
  ORDER BY created_at ASC`
1109
1425
  ).all({ $sessionId: sessionId });
1110
1426
  }
1427
+ nextSessionLogSequence(sessionId) {
1428
+ const row = this.database().prepare(
1429
+ `SELECT COALESCE(MAX(sequence) + 1, 0) AS next_sequence
1430
+ FROM runner_session_logs
1431
+ WHERE session_id = $sessionId`
1432
+ ).get({ $sessionId: sessionId });
1433
+ return row.next_sequence;
1434
+ }
1435
+ nextPipelineLogSequence(pipelineId) {
1436
+ const row = this.database().prepare(
1437
+ `SELECT COALESCE(MAX(sequence) + 1, 0) AS next_sequence
1438
+ FROM runner_pipeline_logs
1439
+ WHERE pipeline_id = $pipelineId`
1440
+ ).get({ $pipelineId: pipelineId });
1441
+ return row.next_sequence;
1442
+ }
1443
+ listSessionMessages(sessionId) {
1444
+ const db = this.database();
1445
+ const messageRows = db.prepare(
1446
+ `SELECT session_id, message_index, role, message_id, created_at
1447
+ FROM runner_session_messages
1448
+ WHERE session_id = $sessionId
1449
+ ORDER BY message_index ASC`
1450
+ ).all({ $sessionId: sessionId });
1451
+ if (messageRows.length === 0) {
1452
+ return [];
1453
+ }
1454
+ const partRows = db.prepare(
1455
+ `SELECT session_id, message_index, part_index, type, part_json
1456
+ FROM runner_session_message_parts
1457
+ WHERE session_id = $sessionId
1458
+ ORDER BY message_index ASC, part_index ASC`
1459
+ ).all({ $sessionId: sessionId });
1460
+ const partsByMessage = /* @__PURE__ */ new Map();
1461
+ for (const partRow of partRows) {
1462
+ const parts = partsByMessage.get(partRow.message_index) ?? [];
1463
+ parts.push(partRow);
1464
+ partsByMessage.set(partRow.message_index, parts);
1465
+ }
1466
+ return messageRows.map(
1467
+ (row) => messageFromRows(row, partsByMessage.get(row.message_index) ?? [])
1468
+ );
1469
+ }
1470
+ nextMessageIndex(sessionId) {
1471
+ const row = this.database().prepare(
1472
+ `SELECT COALESCE(MAX(message_index) + 1, 0) AS next_index
1473
+ FROM runner_session_messages
1474
+ WHERE session_id = $sessionId`
1475
+ ).get({ $sessionId: sessionId });
1476
+ return row.next_index;
1477
+ }
1478
+ insertMessages(sessionId, messages, startIndex, createdAt) {
1479
+ const db = this.database();
1480
+ const insertMessage = db.prepare(
1481
+ `INSERT INTO runner_session_messages (
1482
+ session_id,
1483
+ message_index,
1484
+ role,
1485
+ message_id,
1486
+ created_at
1487
+ ) VALUES ($sessionId, $messageIndex, $role, $messageId, $createdAt)`
1488
+ );
1489
+ const insertPart = db.prepare(
1490
+ `INSERT INTO runner_session_message_parts (
1491
+ session_id,
1492
+ message_index,
1493
+ part_index,
1494
+ type,
1495
+ part_json
1496
+ ) VALUES ($sessionId, $messageIndex, $partIndex, $type, $partJson)`
1497
+ );
1498
+ messages.forEach((message, messageOffset) => {
1499
+ const messageIndex = startIndex + messageOffset;
1500
+ insertMessage.run({
1501
+ $sessionId: sessionId,
1502
+ $messageIndex: messageIndex,
1503
+ $role: message.role,
1504
+ $messageId: message.role === "assistant" ? message.id ?? null : null,
1505
+ $createdAt: createdAt
1506
+ });
1507
+ messageParts(message).forEach((part, partIndex) => {
1508
+ insertPart.run({
1509
+ $sessionId: sessionId,
1510
+ $messageIndex: messageIndex,
1511
+ $partIndex: partIndex,
1512
+ $type: part.type,
1513
+ $partJson: JSON.stringify(part.value)
1514
+ });
1515
+ });
1516
+ });
1517
+ }
1111
1518
  };
1112
- function toSession(row, runRows = []) {
1113
- const summary = toSessionSummary(row);
1114
- const legacyTranscript = parseJsonArray(row.transcript_json);
1519
+ function toSession(row, messages, runRows = []) {
1520
+ const summary = toSessionSummary({ ...row, message_count: messages.length });
1115
1521
  const runTranscript = runRows.flatMap(
1116
1522
  (runRow) => parseJsonArray(runRow.transcript_json)
1117
1523
  );
1118
1524
  return {
1119
1525
  ...summary,
1120
- messages: parseJsonArray(row.messages_json),
1121
- transcript: renumberTranscript([...legacyTranscript, ...runTranscript])
1526
+ messages,
1527
+ transcript: renumberTranscript(runTranscript)
1122
1528
  };
1123
1529
  }
1124
1530
  function toSessionSummary(row) {
1125
- const messages = parseJsonArray(row.messages_json);
1126
1531
  const metadata = parseJsonValue(row.metadata_json);
1127
1532
  return {
1128
1533
  id: row.id,
@@ -1130,10 +1535,107 @@ function toSessionSummary(row) {
1130
1535
  ...row.title === null ? {} : { title: row.title },
1131
1536
  createdAt: row.created_at,
1132
1537
  updatedAt: row.updated_at,
1133
- messageCount: messages.length,
1538
+ messageCount: row.message_count,
1539
+ ...metadata === void 0 ? {} : { metadata }
1540
+ };
1541
+ }
1542
+ function toSessionLog(row) {
1543
+ const metadata = parseJsonValue(row.metadata_json);
1544
+ return {
1545
+ id: row.id,
1546
+ sessionId: row.session_id,
1547
+ ...row.run_id === null ? {} : { runId: row.run_id },
1548
+ sequence: row.sequence,
1549
+ timestamp: row.timestamp,
1550
+ level: row.level,
1551
+ category: row.category,
1552
+ event: row.event,
1553
+ message: row.message,
1554
+ ...metadata === void 0 ? {} : { metadata }
1555
+ };
1556
+ }
1557
+ function toPipelineLog(row) {
1558
+ const metadata = parseJsonValue(row.metadata_json);
1559
+ return {
1560
+ id: row.id,
1561
+ pipelineId: row.pipeline_id,
1562
+ ...row.run_id === null ? {} : { runId: row.run_id },
1563
+ sequence: row.sequence,
1564
+ timestamp: row.timestamp,
1565
+ level: row.level,
1566
+ category: row.category,
1567
+ event: row.event,
1568
+ message: row.message,
1134
1569
  ...metadata === void 0 ? {} : { metadata }
1135
1570
  };
1136
1571
  }
1572
+ function toPipelineRun(row) {
1573
+ const output = parseJsonValue(row.output_json);
1574
+ const error = parseJsonValue(row.error_json);
1575
+ const metadata = parseJsonValue(row.metadata_json);
1576
+ return {
1577
+ runId: row.run_id,
1578
+ pipelineId: row.pipeline_id,
1579
+ status: row.status,
1580
+ input: JSON.parse(row.input_json),
1581
+ ...output === void 0 ? {} : { output },
1582
+ ...error === void 0 ? {} : { error },
1583
+ ...metadata === void 0 ? {} : { metadata },
1584
+ startedAt: row.started_at,
1585
+ ...row.ended_at === null ? {} : { endedAt: row.ended_at },
1586
+ ...row.duration_ms === null ? {} : { durationMs: row.duration_ms }
1587
+ };
1588
+ }
1589
+ function messageParts(message) {
1590
+ if (message.role === "system") {
1591
+ return [{ type: "text", value: { type: "text", text: message.content } }];
1592
+ }
1593
+ return message.content.map((content) => ({
1594
+ type: content.type,
1595
+ value: content
1596
+ }));
1597
+ }
1598
+ function messageFromRows(row, partRows) {
1599
+ const parts = partRows.map((partRow) => JSON.parse(partRow.part_json));
1600
+ if (row.role === "system") {
1601
+ return { role: "system", content: systemContentFromParts(parts) };
1602
+ }
1603
+ if (row.role === "user") {
1604
+ return {
1605
+ role: "user",
1606
+ content: parts
1607
+ };
1608
+ }
1609
+ if (row.role === "assistant") {
1610
+ return {
1611
+ role: "assistant",
1612
+ ...row.message_id === null ? {} : { id: row.message_id },
1613
+ content: parts
1614
+ };
1615
+ }
1616
+ if (row.role === "tool") {
1617
+ return {
1618
+ role: "tool",
1619
+ content: parts
1620
+ };
1621
+ }
1622
+ throw new Error(`Unsupported stored message role: ${row.role}`);
1623
+ }
1624
+ function systemContentFromParts(parts) {
1625
+ const first = parts[0];
1626
+ if (typeof first === "object" && first !== null && "type" in first && first.type === "text" && "text" in first && typeof first.text === "string") {
1627
+ return first.text;
1628
+ }
1629
+ return "";
1630
+ }
1631
+ function guardAgainstLegacySessionSchema(db) {
1632
+ const columns = db.prepare("PRAGMA table_info('runner_sessions')").all();
1633
+ if (columns.some((column) => column.name === "messages_json")) {
1634
+ throw new Error(
1635
+ "Existing Studio SQLite DB uses the legacy messages_json schema. Delete or recreate the Studio SQLite DB to use normalized session messages."
1636
+ );
1637
+ }
1638
+ }
1137
1639
  function toTrace(row) {
1138
1640
  const trace = parseJsonValue(row.trace_json);
1139
1641
  const input = parseJsonValue(row.input_json);
@@ -1268,15 +1770,58 @@ function formatJson(value) {
1268
1770
  }
1269
1771
  }
1270
1772
 
1773
+ // src/runtime/tool-metadata.ts
1774
+ import { ToolSet } from "@anvia/core";
1775
+ var MCP_TOOL_METADATA_KEY = /* @__PURE__ */ Symbol.for("anvia.mcp.tool.metadata");
1776
+ function agentToolItems(agent) {
1777
+ return [
1778
+ ...agent.agent.toolSet.values().map((tool) => ({ tool, source: "static" })),
1779
+ ...agent.agent.dynamicTools.flatMap((registration) => {
1780
+ const maybeToolSet = registration.index.toolSet;
1781
+ if (!(maybeToolSet instanceof ToolSet)) {
1782
+ return [];
1783
+ }
1784
+ return maybeToolSet.values().map((tool) => ({ tool, source: "dynamic" }));
1785
+ })
1786
+ ];
1787
+ }
1788
+ function approvalMetadata(tool) {
1789
+ const approval = tool.approval;
1790
+ if (approval === void 0 || typeof approval !== "object" || approval === null) {
1791
+ return { required: false };
1792
+ }
1793
+ const policy = approval;
1794
+ return {
1795
+ required: true,
1796
+ ...typeof policy.reason === "string" ? { reason: policy.reason } : {},
1797
+ ...typeof policy.rejectMessage === "string" ? { rejectMessage: policy.rejectMessage } : {}
1798
+ };
1799
+ }
1800
+ function mcpServerName(tool) {
1801
+ const metadata = tool[MCP_TOOL_METADATA_KEY];
1802
+ if (typeof metadata !== "object" || metadata === null) {
1803
+ return void 0;
1804
+ }
1805
+ const serverName = metadata.serverName;
1806
+ return typeof serverName === "string" && serverName.length > 0 ? serverName : void 0;
1807
+ }
1808
+ function agentHasMcpTools(agent) {
1809
+ return agentToolItems(agent).some(({ tool }) => mcpServerName(tool) !== void 0);
1810
+ }
1811
+
1271
1812
  // src/runtime/shared.ts
1272
1813
  function resolveStores(options) {
1273
1814
  const defaultPath = process.env.ANVIA_STUDIO_DB ?? process.env.AION_STUDIO_DB ?? join(process.cwd(), ".anvia-studio", `${safeFileName(runnerId(options))}.sqlite`);
1274
1815
  const defaultStore = createSqliteSessionStore({ path: defaultPath });
1275
1816
  const sessions = resolveSessionStore(options, defaultStore);
1276
1817
  const traces = resolveTraceStore(options, sessions, defaultStore);
1818
+ const pipelineLogs = resolvePipelineLogStore(options, sessions, defaultStore);
1819
+ const pipelineRuns = resolvePipelineRunStore(options, sessions, pipelineLogs, defaultStore);
1277
1820
  return {
1278
1821
  ...sessions === void 0 ? {} : { sessions },
1279
- ...traces === void 0 ? {} : { traces }
1822
+ ...traces === void 0 ? {} : { traces },
1823
+ ...pipelineLogs === void 0 ? {} : { pipelineLogs },
1824
+ ...pipelineRuns === void 0 ? {} : { pipelineRuns }
1280
1825
  };
1281
1826
  }
1282
1827
  function resolveSessionStore(options, defaultStore) {
@@ -1300,10 +1845,45 @@ function resolveTraceStore(options, sessionStore, defaultStore) {
1300
1845
  }
1301
1846
  return defaultStore;
1302
1847
  }
1848
+ function resolvePipelineLogStore(options, sessionStore, defaultStore) {
1849
+ if (options.stores?.pipelineLogs === false) {
1850
+ return void 0;
1851
+ }
1852
+ if (options.stores?.pipelineLogs !== void 0) {
1853
+ return options.stores.pipelineLogs;
1854
+ }
1855
+ if (sessionStore !== void 0 && isPipelineLogStore(sessionStore)) {
1856
+ return sessionStore;
1857
+ }
1858
+ return defaultStore;
1859
+ }
1860
+ function resolvePipelineRunStore(options, sessionStore, pipelineLogStore, defaultStore) {
1861
+ if (options.stores?.pipelineRuns === false) {
1862
+ return void 0;
1863
+ }
1864
+ if (options.stores?.pipelineRuns !== void 0) {
1865
+ return options.stores.pipelineRuns;
1866
+ }
1867
+ if (sessionStore !== void 0 && isPipelineRunStore(sessionStore)) {
1868
+ return sessionStore;
1869
+ }
1870
+ if (pipelineLogStore !== void 0 && isPipelineRunStore(pipelineLogStore)) {
1871
+ return pipelineLogStore;
1872
+ }
1873
+ return defaultStore;
1874
+ }
1303
1875
  function isTraceStore(store) {
1304
1876
  const candidate = store;
1305
1877
  return typeof candidate.listSessionTraces === "function" && typeof candidate.getTrace === "function" && typeof candidate.saveTrace === "function";
1306
1878
  }
1879
+ function isPipelineLogStore(store) {
1880
+ const candidate = store;
1881
+ return typeof candidate.appendPipelineLog === "function" && typeof candidate.listPipelineLogs === "function";
1882
+ }
1883
+ function isPipelineRunStore(store) {
1884
+ const candidate = store;
1885
+ return typeof candidate.savePipelineRun === "function" && typeof candidate.listPipelineRuns === "function";
1886
+ }
1307
1887
  function unsupportedCapabilities(stores) {
1308
1888
  return [
1309
1889
  ...stores.sessions === void 0 ? ["sessions"] : [],
@@ -1324,17 +1904,32 @@ function normalizeAgents(agents) {
1324
1904
  return { ...agent, id };
1325
1905
  });
1326
1906
  }
1327
- function buildConfig(options, agents, stores) {
1907
+ function normalizePipelines(pipelines) {
1908
+ const ids = /* @__PURE__ */ new Set();
1909
+ return pipelines.map((pipeline) => {
1910
+ const id = pipeline.id.trim();
1911
+ if (id.length === 0) {
1912
+ throw new Error("Studio pipeline id cannot be empty");
1913
+ }
1914
+ if (ids.has(id)) {
1915
+ throw new Error(`Duplicate Studio pipeline id: ${id}`);
1916
+ }
1917
+ ids.add(id);
1918
+ return { ...pipeline, id };
1919
+ });
1920
+ }
1921
+ function buildConfig(options, agents, pipelines, stores) {
1328
1922
  return {
1329
1923
  id: runnerId(options),
1330
1924
  ...options.name === void 0 ? {} : { name: options.name },
1331
1925
  ...options.description === void 0 ? {} : { description: options.description },
1332
1926
  ...options.version === void 0 ? {} : { version: options.version },
1333
1927
  agents: agents.map(agentConfig),
1928
+ pipelines: pipelines.map(pipelineConfig),
1334
1929
  chat: {
1335
1930
  quickPrompts: Object.fromEntries(agents.map((agent) => [agent.id, agent.quickPrompts ?? []]))
1336
1931
  },
1337
- capabilities: capabilityConfig(options, agents, stores),
1932
+ capabilities: capabilityConfig(options, agents, pipelines, stores),
1338
1933
  unsupportedCapabilities: unsupportedCapabilities(stores)
1339
1934
  };
1340
1935
  }
@@ -1352,7 +1947,22 @@ function agentConfig(agent) {
1352
1947
  ...agent.metadata === void 0 ? {} : { metadata: agent.metadata }
1353
1948
  };
1354
1949
  }
1355
- function capabilityConfig(_options, agents, stores) {
1950
+ function pipelineConfig(pipeline) {
1951
+ const graph = pipeline.pipeline.graph();
1952
+ const stageNodes = graph.nodes.filter((node) => node.kind !== "input" && node.kind !== "output");
1953
+ return {
1954
+ id: pipeline.id,
1955
+ ...pipeline.name === void 0 ? {} : { name: pipeline.name },
1956
+ ...pipeline.description === void 0 ? {} : { description: pipeline.description },
1957
+ ...pipeline.metadata === void 0 ? {} : { metadata: pipeline.metadata },
1958
+ stageCount: stageNodes.length,
1959
+ edgeCount: graph.edges.length,
1960
+ hasParallelStages: graph.nodes.some((node) => node.kind === "parallel"),
1961
+ agentCount: graph.nodes.filter((node) => node.kind === "agent").length,
1962
+ extractorCount: graph.nodes.filter((node) => node.kind === "extractor").length
1963
+ };
1964
+ }
1965
+ function capabilityConfig(_options, agents, pipelines, stores) {
1356
1966
  const capabilities = {
1357
1967
  agents: { enabled: true }
1358
1968
  };
@@ -1362,10 +1972,23 @@ function capabilityConfig(_options, agents, stores) {
1362
1972
  if (stores.traces !== void 0) {
1363
1973
  capabilities.traces = { enabled: true };
1364
1974
  }
1975
+ if (pipelines.length > 0) {
1976
+ capabilities.pipelines = { enabled: true };
1977
+ }
1978
+ if (agents.some(
1979
+ (agent) => agent.agent.toolSet.values().length > 0 || agent.agent.dynamicTools.length > 0
1980
+ )) {
1981
+ capabilities.tools = { enabled: true };
1982
+ }
1983
+ if (agents.some(agentHasMcpTools)) {
1984
+ capabilities.mcps = { enabled: true };
1985
+ }
1365
1986
  if (agents.some((agent) => agent.agent.observers.length > 0)) {
1366
1987
  capabilities.observability = { enabled: true };
1367
1988
  }
1368
- if (agents.some((agent) => agent.agent.toolSet.values().some((tool) => tool.approval))) {
1989
+ if (agents.some(
1990
+ (agent) => agent.agent.hook !== void 0 || agent.agent.toolSet.values().some((tool) => tool.approval)
1991
+ )) {
1369
1992
  capabilities.approvals = { enabled: true };
1370
1993
  }
1371
1994
  if (agents.some(
@@ -1560,45 +2183,62 @@ function createApprovalRuntime() {
1560
2183
  return {
1561
2184
  approvals,
1562
2185
  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 }
2186
+ const handleApprovalRequest = async ({ toolName, toolCallId, internalCallId, args, tool: control }, request) => {
2187
+ const decision = await requestApproval(approvals, context, {
2188
+ toolName,
2189
+ ...toolCallId === void 0 ? {} : { toolCallId },
2190
+ internalCallId,
2191
+ args,
2192
+ ...request.reason === void 0 ? {} : { reason: request.reason },
2193
+ ...request.rejectMessage === void 0 ? {} : { rejectMessage: request.rejectMessage }
2194
+ });
2195
+ return decision.approved ? control.run() : control.skip(decision.reason ?? request.rejectMessage ?? "Rejected in Anvia Studio.");
2196
+ };
2197
+ return {
2198
+ ...createHook({
2199
+ async onToolCall({ toolName, toolCallId, internalCallId, args, tool: control }) {
2200
+ const registeredTool = context.getTool(toolName);
2201
+ if (registeredTool?.approval === void 0) {
2202
+ return control.run();
1583
2203
  }
1584
- };
1585
- const required = await approval.when(approvalContext);
1586
- if (!required) {
1587
- return control.run();
2204
+ const approval = registeredTool.approval;
2205
+ const rawParsedArgs = parseToolArgs(args);
2206
+ const parsedArgs = registeredTool.parseApprovalArgs?.(rawParsedArgs) ?? rawParsedArgs;
2207
+ const approvalContext = {
2208
+ toolName,
2209
+ args: parsedArgs,
2210
+ rawArgs: args,
2211
+ ...toolCallId === void 0 ? {} : { toolCallId },
2212
+ internalCallId,
2213
+ run: {
2214
+ agentId: context.agentId,
2215
+ runId: context.runId,
2216
+ ...context.sessionId === void 0 ? {} : { sessionId: context.sessionId },
2217
+ ...context.metadata === void 0 ? {} : { metadata: context.metadata }
2218
+ }
2219
+ };
2220
+ const required = await approval.when(approvalContext);
2221
+ if (!required) {
2222
+ return control.run();
2223
+ }
2224
+ const reason = await resolveApprovalText(approval.reason, approvalContext);
2225
+ const rejectMessage = await resolveApprovalText(
2226
+ approval.rejectMessage,
2227
+ approvalContext
2228
+ );
2229
+ const decision = await requestApproval(approvals, context, {
2230
+ toolName,
2231
+ ...toolCallId === void 0 ? {} : { toolCallId },
2232
+ internalCallId,
2233
+ args,
2234
+ ...reason === void 0 ? {} : { reason },
2235
+ ...rejectMessage === void 0 ? {} : { rejectMessage }
2236
+ });
2237
+ return decision.approved ? control.run() : control.skip(decision.reason ?? rejectMessage ?? "Rejected in Anvia Studio.");
1588
2238
  }
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
- });
2239
+ }),
2240
+ handleApprovalRequest
2241
+ };
1602
2242
  },
1603
2243
  list(options) {
1604
2244
  return [...approvals.values()].filter((approval) => {
@@ -1894,243 +2534,207 @@ function isRecord2(value) {
1894
2534
  return typeof value === "object" && value !== null && !Array.isArray(value);
1895
2535
  }
1896
2536
 
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;
1920
- }
1921
- return c.json({ questions: questions.list(options) });
1922
- });
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");
1931
- }
1932
- if (result === "resolved") {
1933
- return errorResponse(c, 409, "conflict", "Question is already answered");
2537
+ // src/runtime/mcps.ts
2538
+ function registerMcpRoutes(app, props) {
2539
+ app.get("/agents/:agentId/mcps", async (c) => {
2540
+ const agentId = c.req.param("agentId");
2541
+ const agent = props.agentMap.get(agentId);
2542
+ if (agent === void 0) {
2543
+ return errorResponse(c, 404, "not_found", "Agent not found");
1934
2544
  }
1935
- return c.json(result);
2545
+ return c.json({
2546
+ agentId,
2547
+ servers: await agentMcpMetadata(agent)
2548
+ });
1936
2549
  });
1937
2550
  }
1938
- function parseQuestionStatus(value) {
1939
- const status = optionalQueryString(value);
1940
- if (status === void 0) {
1941
- return void 0;
1942
- }
1943
- return status === "pending" || status === "resolved" ? status : false;
1944
- }
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") };
2551
+ async function agentMcpMetadata(agent) {
2552
+ const servers = /* @__PURE__ */ new Map();
2553
+ const seen = /* @__PURE__ */ new Set();
2554
+ for (const { tool, source } of agentToolItems(agent)) {
2555
+ const serverName = mcpServerName(tool);
2556
+ if (serverName === void 0) {
2557
+ continue;
1971
2558
  }
1972
- if ("custom" in answer && typeof answer.custom !== "boolean") {
1973
- return { error: errorResponse(c, 400, "bad_request", "custom must be a boolean") };
2559
+ const definition = await tool.definition("");
2560
+ const key = `${serverName}:${source}:${definition.name}`;
2561
+ if (seen.has(key)) {
2562
+ continue;
1974
2563
  }
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 } : {}
2564
+ seen.add(key);
2565
+ const tools = servers.get(serverName) ?? [];
2566
+ tools.push({
2567
+ name: definition.name,
2568
+ description: definition.description,
2569
+ parameters: definition.parameters,
2570
+ source
1980
2571
  });
2572
+ servers.set(serverName, tools);
1981
2573
  }
1982
- return { answers };
2574
+ return [...servers.entries()].map(([name, tools]) => {
2575
+ const sortedTools = tools.sort((left, right) => {
2576
+ if (left.source !== right.source) {
2577
+ return left.source === "static" ? -1 : 1;
2578
+ }
2579
+ return left.name.localeCompare(right.name);
2580
+ });
2581
+ return {
2582
+ agentId: agent.id,
2583
+ name,
2584
+ toolCount: sortedTools.length,
2585
+ tools: sortedTools
2586
+ };
2587
+ }).sort((left, right) => left.name.localeCompare(right.name));
1983
2588
  }
1984
- function createQuestionRuntime() {
1985
- const questions = /* @__PURE__ */ new Map();
2589
+
2590
+ // src/runtime/pipelines.ts
2591
+ import { stream as streamResponse2 } from "hono/streaming";
2592
+
2593
+ // src/runtime/pipeline-logs.ts
2594
+ async function appendPipelineLog(store, input) {
2595
+ return store?.appendPipelineLog(input);
2596
+ }
2597
+ async function* emitPipelineLog(store, input) {
2598
+ const log = await appendPipelineLog(store, input);
2599
+ if (log !== void 0) {
2600
+ yield { type: "pipeline_log", log };
2601
+ }
2602
+ }
2603
+ function pipelineRunReceivedLog(props) {
1986
2604
  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
- }
2605
+ pipelineId: props.pipeline.id,
2606
+ runId: props.runId,
2607
+ level: "info",
2608
+ category: "api",
2609
+ event: "pipeline.run_received",
2610
+ message: "Pipeline run request received",
2611
+ metadata: cleanMetadata({
2612
+ stream: props.stream,
2613
+ inputBytes: byteLength(formatUnknown(props.input)),
2614
+ metadataKeys: Object.keys(props.metadata ?? {})
2615
+ })
2043
2616
  };
2044
2617
  }
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
- }
2618
+ function pipelineRunStartedLog(pipeline, runId) {
2619
+ const graph = pipeline.pipeline.graph();
2620
+ return {
2621
+ pipelineId: pipeline.id,
2622
+ runId,
2623
+ level: "info",
2624
+ category: "run",
2625
+ event: "pipeline.run_started",
2626
+ message: "Pipeline run started",
2627
+ metadata: cleanMetadata({
2628
+ stageCount: graph.nodes.filter((node) => node.kind !== "input" && node.kind !== "output").length,
2629
+ edgeCount: graph.edges.length
2630
+ })
2062
2631
  };
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
2632
  }
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 };
2633
+ function pipelineRunCompletedLog(props) {
2634
+ return {
2635
+ pipelineId: props.pipelineId,
2636
+ runId: props.runId,
2637
+ level: "info",
2638
+ category: "run",
2639
+ event: "pipeline.run_completed",
2640
+ message: "Pipeline run completed",
2641
+ metadata: cleanMetadata({
2642
+ durationMs: props.durationMs,
2643
+ outputBytes: byteLength(formatUnknown(props.output))
2644
+ })
2645
+ };
2091
2646
  }
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
- }
2647
+ function pipelineRunFailedLog(pipelineId, runId, error, startedAt) {
2100
2648
  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
2649
+ pipelineId,
2650
+ runId,
2651
+ level: "error",
2652
+ category: "run",
2653
+ event: "pipeline.run_failed",
2654
+ message: "Pipeline run failed",
2655
+ metadata: cleanMetadata({
2656
+ durationMs: Date.now() - startedAt,
2657
+ error: serializeError2(error)
2658
+ })
2104
2659
  };
2105
2660
  }
2106
- function normalizeQuestionChoice(value) {
2107
- if (typeof value === "string" && value.trim().length > 0) {
2108
- return { label: value.trim(), value: value.trim() };
2661
+ function pipelineStageLog(pipelineId, runId, event) {
2662
+ const category = stageCategory(event.node);
2663
+ if (event.type === "stage_started") {
2664
+ return {
2665
+ pipelineId,
2666
+ runId,
2667
+ level: "debug",
2668
+ category,
2669
+ event: `${event.node.kind}.started`,
2670
+ message: `${event.node.label} started`,
2671
+ metadata: nodeMetadata(event.node)
2672
+ };
2109
2673
  }
2110
- if (!isObject(value) || typeof value.label !== "string" || value.label.trim().length === 0) {
2111
- return void 0;
2674
+ if (event.type === "stage_completed") {
2675
+ return {
2676
+ pipelineId,
2677
+ runId,
2678
+ level: "debug",
2679
+ category,
2680
+ event: `${event.node.kind}.completed`,
2681
+ message: `${event.node.label} completed`,
2682
+ metadata: cleanMetadata({
2683
+ ...nodeMetadata(event.node),
2684
+ durationMs: event.durationMs
2685
+ })
2686
+ };
2112
2687
  }
2113
2688
  return {
2114
- label: value.label.trim(),
2115
- value: typeof value.value === "string" && value.value.trim().length > 0 ? value.value.trim() : value.label.trim()
2689
+ pipelineId,
2690
+ runId,
2691
+ level: "error",
2692
+ category,
2693
+ event: `${event.node.kind}.failed`,
2694
+ message: `${event.node.label} failed`,
2695
+ metadata: cleanMetadata({
2696
+ ...nodeMetadata(event.node),
2697
+ durationMs: event.durationMs,
2698
+ error: serializeError2(event.error)
2699
+ })
2116
2700
  };
2117
2701
  }
2118
- function isPendingQuestion(question) {
2119
- return question !== void 0 && question.status === "pending" && "resolve" in question;
2702
+ function stageCategory(node) {
2703
+ if (node.kind === "parallel" || node.kind === "branch") {
2704
+ return "parallel";
2705
+ }
2706
+ if (node.kind === "agent") {
2707
+ return "agent";
2708
+ }
2709
+ if (node.kind === "extractor") {
2710
+ return "extractor";
2711
+ }
2712
+ return "stage";
2120
2713
  }
2121
- function resolveQuestion(question, answers) {
2122
- return publicQuestion({
2123
- ...question,
2124
- status: "answered",
2125
- answeredAt: (/* @__PURE__ */ new Date()).toISOString(),
2126
- answers
2714
+ function nodeMetadata(node) {
2715
+ return cleanMetadata({
2716
+ nodeId: node.id,
2717
+ kind: node.kind,
2718
+ label: node.label,
2719
+ agentId: node.agentId,
2720
+ pipelineId: node.pipelineId,
2721
+ branchKey: node.branchKey
2127
2722
  });
2128
2723
  }
2129
- function publicQuestion(question) {
2130
- const { emit, resolve: resolve2, ...rest } = question;
2131
- void emit;
2132
- void resolve2;
2133
- return rest;
2724
+ function cleanMetadata(value) {
2725
+ return Object.fromEntries(
2726
+ Object.entries(value).filter(([, item]) => item !== void 0)
2727
+ );
2728
+ }
2729
+ function byteLength(value) {
2730
+ return value === void 0 ? void 0 : new TextEncoder().encode(value).length;
2731
+ }
2732
+ function formatUnknown(value) {
2733
+ try {
2734
+ return JSON.stringify(value);
2735
+ } catch {
2736
+ return void 0;
2737
+ }
2134
2738
  }
2135
2739
 
2136
2740
  // src/runtime/runs.ts
@@ -2581,142 +3185,1224 @@ function assignTranscriptTraceId(transcript, traceId) {
2581
3185
  return;
2582
3186
  }
2583
3187
  }
2584
- }
2585
- function appendTranscriptReasoningText(transcript, delta, reasoningId) {
2586
- const last = transcript.at(-1);
2587
- if (last?.kind === "reasoning" && (last.reasoningId ?? "") === (reasoningId ?? "")) {
2588
- last.text = `${last.text}${delta}`;
2589
- return;
3188
+ }
3189
+ function appendTranscriptReasoningText(transcript, delta, reasoningId) {
3190
+ const last = transcript.at(-1);
3191
+ if (last?.kind === "reasoning" && (last.reasoningId ?? "") === (reasoningId ?? "")) {
3192
+ last.text = `${last.text}${delta}`;
3193
+ return;
3194
+ }
3195
+ transcript.push({
3196
+ entryId: transcript.length,
3197
+ kind: "reasoning",
3198
+ ...reasoningId === void 0 ? {} : { reasoningId },
3199
+ text: delta
3200
+ });
3201
+ }
3202
+ function findTranscriptToolEntry(transcript, toolName, callId) {
3203
+ for (let index = transcript.length - 1; index >= 0; index -= 1) {
3204
+ const entry = transcript[index];
3205
+ if (entry?.kind !== "tool" || entry.toolName !== toolName || entry.result !== void 0) {
3206
+ continue;
3207
+ }
3208
+ if (callId === void 0 || entry.callId === callId) {
3209
+ return entry;
3210
+ }
3211
+ }
3212
+ return void 0;
3213
+ }
3214
+ function titleFromMessage(message) {
3215
+ const text = extractMessageText(message).replace(/\s+/g, " ").trim();
3216
+ if (text.length === 0) {
3217
+ return void 0;
3218
+ }
3219
+ return text.length > 72 ? `${text.slice(0, 69)}...` : text;
3220
+ }
3221
+ function optionalTitle(message) {
3222
+ const title = titleFromMessage(message);
3223
+ return title === void 0 ? {} : { title };
3224
+ }
3225
+ function extractMessageText(message) {
3226
+ if (typeof message === "string") {
3227
+ return message;
3228
+ }
3229
+ if (message.role === "system") {
3230
+ return message.content;
3231
+ }
3232
+ return message.content.flatMap((item) => {
3233
+ if (item.type === "text" || item.type === "reasoning") {
3234
+ return [item.text];
3235
+ }
3236
+ if (item.type === "tool_call") {
3237
+ return [`${item.function.name}(${formatJson2(item.function.arguments)})`];
3238
+ }
3239
+ if (item.type === "tool_result") {
3240
+ return item.content.map((result) => "text" in result ? result.text : "[image]");
3241
+ }
3242
+ return [];
3243
+ }).join("\n");
3244
+ }
3245
+ function formatJson2(value) {
3246
+ try {
3247
+ return JSON.stringify(value, null, 2);
3248
+ } catch {
3249
+ return String(value);
3250
+ }
3251
+ }
3252
+ async function parseRunRequest(c) {
3253
+ let body;
3254
+ try {
3255
+ body = await c.req.json();
3256
+ } catch {
3257
+ return { error: errorResponse(c, 400, "bad_request", "Request body must be JSON") };
3258
+ }
3259
+ if (!isObject(body)) {
3260
+ return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
3261
+ }
3262
+ if (!("message" in body) || !isMessageInput(body.message)) {
3263
+ return {
3264
+ error: errorResponse(c, 400, "bad_request", "Request body requires a string or Message")
3265
+ };
3266
+ }
3267
+ const request = {
3268
+ message: typeof body.message === "string" ? body.message : body.message
3269
+ };
3270
+ if ("history" in body) {
3271
+ if (!Array.isArray(body.history) || !body.history.every(isMessage)) {
3272
+ return { error: errorResponse(c, 400, "bad_request", "history must be a Message array") };
3273
+ }
3274
+ request.history = body.history;
3275
+ }
3276
+ if ("sessionId" in body) {
3277
+ if (typeof body.sessionId !== "string" || body.sessionId.trim().length === 0) {
3278
+ return { error: errorResponse(c, 400, "bad_request", "sessionId must be a string") };
3279
+ }
3280
+ if (request.history !== void 0) {
3281
+ return {
3282
+ error: errorResponse(c, 400, "bad_request", "sessionId cannot be combined with history")
3283
+ };
3284
+ }
3285
+ request.sessionId = body.sessionId;
3286
+ }
3287
+ if ("stream" in body) {
3288
+ if (typeof body.stream !== "boolean") {
3289
+ return { error: errorResponse(c, 400, "bad_request", "stream must be a boolean") };
3290
+ }
3291
+ request.stream = body.stream;
3292
+ }
3293
+ if ("maxTurns" in body) {
3294
+ if (!isNonNegativeInteger(body.maxTurns)) {
3295
+ return {
3296
+ error: errorResponse(c, 400, "bad_request", "maxTurns must be a non-negative integer")
3297
+ };
3298
+ }
3299
+ request.maxTurns = body.maxTurns;
3300
+ }
3301
+ if ("toolConcurrency" in body) {
3302
+ if (!isPositiveInteger(body.toolConcurrency)) {
3303
+ return {
3304
+ error: errorResponse(c, 400, "bad_request", "toolConcurrency must be a positive integer")
3305
+ };
3306
+ }
3307
+ request.toolConcurrency = body.toolConcurrency;
3308
+ }
3309
+ if ("metadata" in body) {
3310
+ if (!isJsonObject(body.metadata)) {
3311
+ return { error: errorResponse(c, 400, "bad_request", "metadata must be an object") };
3312
+ }
3313
+ request.metadata = body.metadata;
3314
+ }
3315
+ if ("trace" in body) {
3316
+ if (!isAgentTraceOptions(body.trace)) {
3317
+ return {
3318
+ error: errorResponse(c, 400, "bad_request", "trace must be an AgentTraceOptions object")
3319
+ };
3320
+ }
3321
+ request.trace = body.trace;
3322
+ }
3323
+ return request;
3324
+ }
3325
+
3326
+ // src/runtime/pipelines.ts
3327
+ function registerPipelineRoutes(app, props) {
3328
+ app.get(
3329
+ "/pipelines",
3330
+ (c) => c.json({
3331
+ pipelines: props.pipelines.map(pipelineConfig)
3332
+ })
3333
+ );
3334
+ app.get("/pipelines/:pipelineId", (c) => {
3335
+ const pipeline = props.pipelineMap.get(c.req.param("pipelineId"));
3336
+ if (pipeline === void 0) {
3337
+ return errorResponse(c, 404, "not_found", "Pipeline not found");
3338
+ }
3339
+ return c.json(pipelineDetail(pipeline));
3340
+ });
3341
+ app.get("/pipelines/:pipelineId/logs", async (c) => {
3342
+ const pipelineId = c.req.param("pipelineId");
3343
+ if (!props.pipelineMap.has(pipelineId)) {
3344
+ return errorResponse(c, 404, "not_found", "Pipeline not found");
3345
+ }
3346
+ if (props.logStore === void 0) {
3347
+ return errorResponse(
3348
+ c,
3349
+ 501,
3350
+ "unsupported_capability",
3351
+ 'Capability "pipelines.logs" is not implemented by this runner',
3352
+ { capability: "pipelines", operation: "logs" }
3353
+ );
3354
+ }
3355
+ const limit = parsePipelineLogLimit(c.req.query("limit"));
3356
+ if (limit === void 0) {
3357
+ return errorResponse(c, 400, "bad_request", "limit must be a positive integer");
3358
+ }
3359
+ const after = parsePipelineLogAfter(c.req.query("after"));
3360
+ if (after === false) {
3361
+ return errorResponse(c, 400, "bad_request", "after must be a non-negative integer");
3362
+ }
3363
+ const logs = await props.logStore.listPipelineLogs({
3364
+ pipelineId,
3365
+ limit,
3366
+ ...after === void 0 ? {} : { after }
3367
+ });
3368
+ const last = logs.at(-1);
3369
+ return c.json({
3370
+ logs,
3371
+ ...logs.length === limit && last !== void 0 ? { nextCursor: last.sequence } : {}
3372
+ });
3373
+ });
3374
+ app.get("/pipelines/:pipelineId/runs", async (c) => {
3375
+ const pipelineId = c.req.param("pipelineId");
3376
+ if (!props.pipelineMap.has(pipelineId)) {
3377
+ return errorResponse(c, 404, "not_found", "Pipeline not found");
3378
+ }
3379
+ if (props.runStore === void 0) {
3380
+ return errorResponse(
3381
+ c,
3382
+ 501,
3383
+ "unsupported_capability",
3384
+ 'Capability "pipelines.runs" is not implemented by this runner',
3385
+ { capability: "pipelines", operation: "runs" }
3386
+ );
3387
+ }
3388
+ const limit = parsePipelineLogLimit(c.req.query("limit"));
3389
+ if (limit === void 0) {
3390
+ return errorResponse(c, 400, "bad_request", "limit must be a positive integer");
3391
+ }
3392
+ const runs = await props.runStore.listPipelineRuns({ pipelineId, limit });
3393
+ return c.json({ runs });
3394
+ });
3395
+ app.post("/pipelines/:pipelineId/runs", async (c) => {
3396
+ const pipeline = props.pipelineMap.get(c.req.param("pipelineId"));
3397
+ if (pipeline === void 0) {
3398
+ return errorResponse(c, 404, "not_found", "Pipeline not found");
3399
+ }
3400
+ const body = await parsePipelineRunRequest(c);
3401
+ if ("error" in body) {
3402
+ return body.error;
3403
+ }
3404
+ const runId = globalThis.crypto.randomUUID();
3405
+ const startedAt = Date.now();
3406
+ const startedAtIso = new Date(startedAt).toISOString();
3407
+ await appendPipelineLog(
3408
+ props.logStore,
3409
+ pipelineRunReceivedLog({
3410
+ pipeline,
3411
+ runId,
3412
+ stream: body.stream === true,
3413
+ input: body.input,
3414
+ ...body.metadata === void 0 ? {} : { metadata: body.metadata }
3415
+ })
3416
+ );
3417
+ await savePipelineRun(props.runStore, {
3418
+ runId,
3419
+ pipelineId: pipeline.id,
3420
+ status: "running",
3421
+ input: body.input,
3422
+ ...body.metadata === void 0 ? {} : { metadata: body.metadata },
3423
+ startedAt: startedAtIso
3424
+ });
3425
+ if (body.stream === true) {
3426
+ return streamPipelineRun(c, {
3427
+ pipeline,
3428
+ runId,
3429
+ input: body.input,
3430
+ startedAt,
3431
+ startedAtIso,
3432
+ ...body.metadata === void 0 ? {} : { metadata: body.metadata },
3433
+ ...props.logStore === void 0 ? {} : { logStore: props.logStore },
3434
+ ...props.runStore === void 0 ? {} : { runStore: props.runStore }
3435
+ });
3436
+ }
3437
+ try {
3438
+ await appendPipelineLog(props.logStore, pipelineRunStartedLog(pipeline, runId));
3439
+ const output = await pipeline.pipeline.run(body.input, {
3440
+ observer: {
3441
+ async onEvent(event) {
3442
+ await appendPipelineLog(props.logStore, pipelineStageLog(pipeline.id, runId, event));
3443
+ }
3444
+ }
3445
+ });
3446
+ const jsonOutput = toJsonValue3(output);
3447
+ const endedAt = Date.now();
3448
+ await savePipelineRun(props.runStore, {
3449
+ runId,
3450
+ pipelineId: pipeline.id,
3451
+ status: "success",
3452
+ input: body.input,
3453
+ output: jsonOutput,
3454
+ ...body.metadata === void 0 ? {} : { metadata: body.metadata },
3455
+ startedAt: startedAtIso,
3456
+ endedAt: new Date(endedAt).toISOString(),
3457
+ durationMs: endedAt - startedAt
3458
+ });
3459
+ await appendPipelineLog(
3460
+ props.logStore,
3461
+ pipelineRunCompletedLog({
3462
+ pipelineId: pipeline.id,
3463
+ runId,
3464
+ durationMs: endedAt - startedAt,
3465
+ output: jsonOutput
3466
+ })
3467
+ );
3468
+ const response = {
3469
+ runId,
3470
+ pipelineId: pipeline.id,
3471
+ output: jsonOutput
3472
+ };
3473
+ return c.json(response);
3474
+ } catch (error) {
3475
+ const endedAt = Date.now();
3476
+ await savePipelineRun(props.runStore, {
3477
+ runId,
3478
+ pipelineId: pipeline.id,
3479
+ status: "error",
3480
+ input: body.input,
3481
+ error: serializeError2(error),
3482
+ ...body.metadata === void 0 ? {} : { metadata: body.metadata },
3483
+ startedAt: startedAtIso,
3484
+ endedAt: new Date(endedAt).toISOString(),
3485
+ durationMs: endedAt - startedAt
3486
+ });
3487
+ await appendPipelineLog(
3488
+ props.logStore,
3489
+ pipelineRunFailedLog(pipeline.id, runId, error, startedAt)
3490
+ );
3491
+ return errorResponse(c, 500, "internal_error", "Pipeline run failed", serializeError2(error));
3492
+ }
3493
+ });
3494
+ }
3495
+ function pipelineDetail(pipeline) {
3496
+ const graph = pipeline.pipeline.graph();
3497
+ graph.id = pipeline.id;
3498
+ return {
3499
+ ...pipelineConfig(pipeline),
3500
+ graph
3501
+ };
3502
+ }
3503
+ function streamPipelineRun(c, props) {
3504
+ c.header("content-type", "application/x-ndjson; charset=utf-8");
3505
+ c.header("cache-control", "no-cache, no-transform");
3506
+ c.header("connection", "keep-alive");
3507
+ c.header("transfer-encoding", "chunked");
3508
+ c.header("x-accel-buffering", "no");
3509
+ return streamResponse2(
3510
+ c,
3511
+ async (stream) => {
3512
+ for await (const event of pipelineRunEvents(props)) {
3513
+ await stream.write(`${JSON.stringify(event)}
3514
+ `);
3515
+ }
3516
+ },
3517
+ async (error, stream) => {
3518
+ await stream.write(`${JSON.stringify({ type: "error", error: serializeError2(error) })}
3519
+ `);
3520
+ }
3521
+ );
3522
+ }
3523
+ async function* pipelineRunEvents(props) {
3524
+ yield* emitPipelineLog(props.logStore, pipelineRunStartedLog(props.pipeline, props.runId));
3525
+ const events = new AsyncEventQueue();
3526
+ const run = props.pipeline.pipeline.run(props.input, {
3527
+ observer: {
3528
+ async onEvent(event) {
3529
+ const log = await appendPipelineLog(
3530
+ props.logStore,
3531
+ pipelineStageLog(props.pipeline.id, props.runId, event)
3532
+ );
3533
+ if (log !== void 0) {
3534
+ events.push({ type: "pipeline_log", log });
3535
+ }
3536
+ }
3537
+ }
3538
+ }).then(async (output) => {
3539
+ const jsonOutput = toJsonValue3(output);
3540
+ const endedAt = Date.now();
3541
+ await savePipelineRun(props.runStore, {
3542
+ runId: props.runId,
3543
+ pipelineId: props.pipeline.id,
3544
+ status: "success",
3545
+ input: props.input,
3546
+ output: jsonOutput,
3547
+ ...props.metadata === void 0 ? {} : { metadata: props.metadata },
3548
+ startedAt: props.startedAtIso,
3549
+ endedAt: new Date(endedAt).toISOString(),
3550
+ durationMs: endedAt - props.startedAt
3551
+ });
3552
+ const log = await appendPipelineLog(
3553
+ props.logStore,
3554
+ pipelineRunCompletedLog({
3555
+ pipelineId: props.pipeline.id,
3556
+ runId: props.runId,
3557
+ durationMs: endedAt - props.startedAt,
3558
+ output: jsonOutput
3559
+ })
3560
+ );
3561
+ if (log !== void 0) {
3562
+ events.push({ type: "pipeline_log", log });
3563
+ }
3564
+ events.push({
3565
+ type: "pipeline_final",
3566
+ runId: props.runId,
3567
+ pipelineId: props.pipeline.id,
3568
+ output: jsonOutput
3569
+ });
3570
+ }).catch(async (error) => {
3571
+ const endedAt = Date.now();
3572
+ await savePipelineRun(props.runStore, {
3573
+ runId: props.runId,
3574
+ pipelineId: props.pipeline.id,
3575
+ status: "error",
3576
+ input: props.input,
3577
+ error: serializeError2(error),
3578
+ ...props.metadata === void 0 ? {} : { metadata: props.metadata },
3579
+ startedAt: props.startedAtIso,
3580
+ endedAt: new Date(endedAt).toISOString(),
3581
+ durationMs: endedAt - props.startedAt
3582
+ });
3583
+ const log = await appendPipelineLog(
3584
+ props.logStore,
3585
+ pipelineRunFailedLog(props.pipeline.id, props.runId, error, props.startedAt)
3586
+ );
3587
+ if (log !== void 0) {
3588
+ events.push({ type: "pipeline_log", log });
3589
+ }
3590
+ events.push({ type: "error", error: serializeError2(error) });
3591
+ }).finally(() => events.close());
3592
+ try {
3593
+ while (true) {
3594
+ const next = await events.next();
3595
+ if (next.done === true) {
3596
+ break;
3597
+ }
3598
+ yield next.value;
3599
+ }
3600
+ } finally {
3601
+ await run;
3602
+ }
3603
+ }
3604
+ async function savePipelineRun(store, input) {
3605
+ return store?.savePipelineRun(input);
3606
+ }
3607
+ async function parsePipelineRunRequest(c) {
3608
+ let body;
3609
+ try {
3610
+ body = await c.req.json();
3611
+ } catch {
3612
+ return { error: errorResponse(c, 400, "bad_request", "Request body must be JSON") };
3613
+ }
3614
+ if (!isObject(body)) {
3615
+ return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
3616
+ }
3617
+ if (!("input" in body) || !isJsonValue2(body.input)) {
3618
+ return { error: errorResponse(c, 400, "bad_request", "input must be JSON-compatible") };
3619
+ }
3620
+ const request = {
3621
+ input: body.input
3622
+ };
3623
+ if ("stream" in body) {
3624
+ if (typeof body.stream !== "boolean") {
3625
+ return { error: errorResponse(c, 400, "bad_request", "stream must be a boolean") };
3626
+ }
3627
+ request.stream = body.stream;
3628
+ }
3629
+ if ("metadata" in body) {
3630
+ if (!isJsonObject(body.metadata)) {
3631
+ return { error: errorResponse(c, 400, "bad_request", "metadata must be an object") };
3632
+ }
3633
+ request.metadata = body.metadata;
3634
+ }
3635
+ return request;
3636
+ }
3637
+ function parsePipelineLogLimit(value) {
3638
+ if (value === void 0 || value.trim().length === 0) {
3639
+ return 200;
3640
+ }
3641
+ const limit = Number(value);
3642
+ if (!Number.isInteger(limit) || limit <= 0) {
3643
+ return void 0;
3644
+ }
3645
+ return Math.min(limit, 1e3);
3646
+ }
3647
+ function parsePipelineLogAfter(value) {
3648
+ if (value === void 0 || value.trim().length === 0) {
3649
+ return void 0;
3650
+ }
3651
+ const after = Number(value);
3652
+ if (!Number.isInteger(after) || after < 0) {
3653
+ return false;
3654
+ }
3655
+ return after;
3656
+ }
3657
+ function isJsonValue2(value) {
3658
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
3659
+ return Number.isFinite(value) || typeof value !== "number";
3660
+ }
3661
+ if (Array.isArray(value)) {
3662
+ return value.every(isJsonValue2);
3663
+ }
3664
+ if (isObject(value)) {
3665
+ return Object.values(value).every((item) => item === void 0 || isJsonValue2(item));
3666
+ }
3667
+ return false;
3668
+ }
3669
+ function toJsonValue3(value) {
3670
+ if (isJsonValue2(value)) {
3671
+ return value;
3672
+ }
3673
+ if (value === void 0) {
3674
+ return null;
3675
+ }
3676
+ try {
3677
+ const parsed = JSON.parse(JSON.stringify(value));
3678
+ return isJsonValue2(parsed) ? parsed : String(value);
3679
+ } catch {
3680
+ return String(value);
3681
+ }
3682
+ }
3683
+
3684
+ // src/runtime/questions.ts
3685
+ import { createHook as createHook2, parseToolArgs as parseToolArgs2 } from "@anvia/core";
3686
+ function registerQuestionRoutes(app, questions) {
3687
+ app.get("/questions", (c) => {
3688
+ const status = parseQuestionStatus(c.req.query("status"));
3689
+ if (status === false) {
3690
+ return errorResponse(c, 400, "bad_request", "status must be pending or resolved");
3691
+ }
3692
+ const options = {};
3693
+ const runId = optionalQueryString(c.req.query("runId"));
3694
+ const agentId = optionalQueryString(c.req.query("agentId"));
3695
+ const sessionId = optionalQueryString(c.req.query("sessionId"));
3696
+ if (status !== void 0) {
3697
+ options.status = status;
3698
+ }
3699
+ if (runId !== void 0) {
3700
+ options.runId = runId;
3701
+ }
3702
+ if (agentId !== void 0) {
3703
+ options.agentId = agentId;
3704
+ }
3705
+ if (sessionId !== void 0) {
3706
+ options.sessionId = sessionId;
3707
+ }
3708
+ return c.json({ questions: questions.list(options) });
3709
+ });
3710
+ app.post("/questions/:questionId/answer", async (c) => {
3711
+ const body = await parseQuestionAnswerRequest(c);
3712
+ if ("error" in body) {
3713
+ return body.error;
3714
+ }
3715
+ const result = questions.answer(c.req.param("questionId"), body.answers);
3716
+ if (result === "missing") {
3717
+ return errorResponse(c, 404, "not_found", "Question not found");
3718
+ }
3719
+ if (result === "resolved") {
3720
+ return errorResponse(c, 409, "conflict", "Question is already answered");
3721
+ }
3722
+ return c.json(result);
3723
+ });
3724
+ }
3725
+ function parseQuestionStatus(value) {
3726
+ const status = optionalQueryString(value);
3727
+ if (status === void 0) {
3728
+ return void 0;
3729
+ }
3730
+ return status === "pending" || status === "resolved" ? status : false;
3731
+ }
3732
+ async function parseQuestionAnswerRequest(c) {
3733
+ let body;
3734
+ try {
3735
+ body = await c.req.json();
3736
+ } catch {
3737
+ return { error: errorResponse(c, 400, "bad_request", "Request body must be JSON") };
3738
+ }
3739
+ if (!isObject(body)) {
3740
+ return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
3741
+ }
3742
+ if (!Array.isArray(body.answers)) {
3743
+ return { error: errorResponse(c, 400, "bad_request", "answers must be an array") };
3744
+ }
3745
+ const answers = [];
3746
+ for (const answer of body.answers) {
3747
+ if (!isObject(answer)) {
3748
+ return { error: errorResponse(c, 400, "bad_request", "answers must contain objects") };
3749
+ }
3750
+ if (typeof answer.questionId !== "string" || answer.questionId.trim().length === 0) {
3751
+ return { error: errorResponse(c, 400, "bad_request", "questionId must be a string") };
3752
+ }
3753
+ if (typeof answer.answer !== "string" || answer.answer.trim().length === 0) {
3754
+ return { error: errorResponse(c, 400, "bad_request", "answer must be a string") };
3755
+ }
3756
+ if ("choice" in answer && typeof answer.choice !== "string") {
3757
+ return { error: errorResponse(c, 400, "bad_request", "choice must be a string") };
3758
+ }
3759
+ if ("custom" in answer && typeof answer.custom !== "boolean") {
3760
+ return { error: errorResponse(c, 400, "bad_request", "custom must be a boolean") };
3761
+ }
3762
+ answers.push({
3763
+ questionId: answer.questionId.trim(),
3764
+ answer: answer.answer.trim(),
3765
+ ...typeof answer.choice === "string" ? { choice: answer.choice } : {},
3766
+ ...typeof answer.custom === "boolean" ? { custom: answer.custom } : {}
3767
+ });
3768
+ }
3769
+ return { answers };
3770
+ }
3771
+ function createQuestionRuntime() {
3772
+ const questions = /* @__PURE__ */ new Map();
3773
+ return {
3774
+ questions,
3775
+ createHook(context) {
3776
+ return createHook2({
3777
+ async onToolCall({ toolName, toolCallId, internalCallId, args, tool: control }) {
3778
+ if (toolName !== "ask_question") {
3779
+ return control.run();
3780
+ }
3781
+ const prompts = normalizeQuestionPrompts(parseToolArgs2(args));
3782
+ if ("error" in prompts) {
3783
+ return control.skip(prompts.error);
3784
+ }
3785
+ const answers = await requestQuestion(questions, context, {
3786
+ toolName,
3787
+ ...toolCallId === void 0 ? {} : { toolCallId },
3788
+ internalCallId,
3789
+ args,
3790
+ questions: prompts.questions
3791
+ });
3792
+ return control.skip(JSON.stringify({ answers }));
3793
+ }
3794
+ });
3795
+ },
3796
+ list(options) {
3797
+ return [...questions.values()].filter((question) => {
3798
+ if (options.status === "pending" && question.status !== "pending") {
3799
+ return false;
3800
+ }
3801
+ if (options.status === "resolved" && question.status === "pending") {
3802
+ return false;
3803
+ }
3804
+ if (options.runId !== void 0 && question.runId !== options.runId) {
3805
+ return false;
3806
+ }
3807
+ if (options.agentId !== void 0 && question.agentId !== options.agentId) {
3808
+ return false;
3809
+ }
3810
+ if (options.sessionId !== void 0 && question.sessionId !== options.sessionId) {
3811
+ return false;
3812
+ }
3813
+ return true;
3814
+ }).map(publicQuestion);
3815
+ },
3816
+ answer(id, answers) {
3817
+ const question = questions.get(id);
3818
+ if (question === void 0) {
3819
+ return "missing";
3820
+ }
3821
+ if (!isPendingQuestion(question)) {
3822
+ return "resolved";
3823
+ }
3824
+ const resolved = resolveQuestion(question, answers);
3825
+ questions.set(id, resolved);
3826
+ question.emit?.({ type: "tool_question_result", question: resolved });
3827
+ question.resolve(answers);
3828
+ return publicQuestion(resolved);
3829
+ }
3830
+ };
3831
+ }
3832
+ async function requestQuestion(questions, context, request) {
3833
+ const id = globalThis.crypto.randomUUID();
3834
+ const question = {
3835
+ id,
3836
+ runId: context.runId,
3837
+ agentId: context.agentId,
3838
+ ...context.sessionId === void 0 ? {} : { sessionId: context.sessionId },
3839
+ toolName: request.toolName,
3840
+ ...request.toolCallId === void 0 ? {} : { callId: request.toolCallId },
3841
+ internalCallId: request.internalCallId,
3842
+ args: request.args,
3843
+ questions: request.questions,
3844
+ status: "pending",
3845
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
3846
+ ...context.emit === void 0 ? {} : { emit: context.emit },
3847
+ resolve: () => {
3848
+ }
3849
+ };
3850
+ const answer = new Promise((resolve2) => {
3851
+ question.resolve = (answers) => {
3852
+ resolve2(answers);
3853
+ };
3854
+ });
3855
+ questions.set(id, question);
3856
+ context.emit?.({ type: "tool_question_request", question: publicQuestion(question) });
3857
+ return answer;
3858
+ }
3859
+ function normalizeQuestionPrompts(args) {
3860
+ if (!isObject(args)) {
3861
+ return { error: "ask_question requires a JSON object with questions." };
3862
+ }
3863
+ const rawQuestions = Array.isArray(args.questions) ? args.questions : [args];
3864
+ if (rawQuestions.length === 0) {
3865
+ return { error: "ask_question requires at least one question." };
3866
+ }
3867
+ const questions = [];
3868
+ for (const [index, question] of rawQuestions.entries()) {
3869
+ const normalized = normalizeQuestionPrompt(question, index);
3870
+ if (normalized === void 0) {
3871
+ return {
3872
+ error: "ask_question requires every question to include text and at least one choice."
3873
+ };
3874
+ }
3875
+ questions.push(normalized);
3876
+ }
3877
+ return { questions };
3878
+ }
3879
+ function normalizeQuestionPrompt(value, index) {
3880
+ if (!isObject(value) || typeof value.question !== "string" || value.question.trim().length === 0) {
3881
+ return void 0;
3882
+ }
3883
+ const choices = Array.isArray(value.choices) ? value.choices.map(normalizeQuestionChoice).filter((choice) => choice !== void 0) : [];
3884
+ if (choices.length === 0) {
3885
+ return void 0;
3886
+ }
3887
+ return {
3888
+ id: typeof value.id === "string" && value.id.trim().length > 0 ? value.id.trim() : `question_${index + 1}`,
3889
+ question: value.question.trim(),
3890
+ choices
3891
+ };
3892
+ }
3893
+ function normalizeQuestionChoice(value) {
3894
+ if (typeof value === "string" && value.trim().length > 0) {
3895
+ return { label: value.trim(), value: value.trim() };
3896
+ }
3897
+ if (!isObject(value) || typeof value.label !== "string" || value.label.trim().length === 0) {
3898
+ return void 0;
3899
+ }
3900
+ return {
3901
+ label: value.label.trim(),
3902
+ value: typeof value.value === "string" && value.value.trim().length > 0 ? value.value.trim() : value.label.trim()
3903
+ };
3904
+ }
3905
+ function isPendingQuestion(question) {
3906
+ return question !== void 0 && question.status === "pending" && "resolve" in question;
3907
+ }
3908
+ function resolveQuestion(question, answers) {
3909
+ return publicQuestion({
3910
+ ...question,
3911
+ status: "answered",
3912
+ answeredAt: (/* @__PURE__ */ new Date()).toISOString(),
3913
+ answers
3914
+ });
3915
+ }
3916
+ function publicQuestion(question) {
3917
+ const { emit, resolve: resolve2, ...rest } = question;
3918
+ void emit;
3919
+ void resolve2;
3920
+ return rest;
3921
+ }
3922
+
3923
+ // src/runtime/session-logs.ts
3924
+ async function appendSessionLog(store, input) {
3925
+ return store?.appendSessionLog?.(input);
3926
+ }
3927
+ async function* streamSessionRunLogs(props) {
3928
+ yield* emitLog(props.store, runStartedLog(props.session, props.runId));
3929
+ yield* emitLog(props.store, memoryLoadedLog(props.session, props.runId));
3930
+ try {
3931
+ for await (const event of props.stream) {
3932
+ for (const input of logsFromStreamEvent({
3933
+ event,
3934
+ runId: props.runId,
3935
+ sessionId: props.session.id,
3936
+ startedAt: props.startedAt
3937
+ })) {
3938
+ yield* emitLog(props.store, input);
3939
+ }
3940
+ yield event;
3941
+ }
3942
+ } catch (error) {
3943
+ yield* emitLog(
3944
+ props.store,
3945
+ runFailedLog(props.session.id, props.runId, error, props.startedAt)
3946
+ );
3947
+ throw error;
3948
+ }
3949
+ }
3950
+ function sessionCreatedLog(session) {
3951
+ return {
3952
+ sessionId: session.id,
3953
+ level: "info",
3954
+ category: "session",
3955
+ event: "session.created",
3956
+ message: "Session created",
3957
+ metadata: cleanMetadata2({
3958
+ agentId: session.agentId,
3959
+ hasTitle: session.title !== void 0,
3960
+ titleLength: session.title?.length ?? 0
3961
+ })
3962
+ };
3963
+ }
3964
+ function runReceivedLog(props) {
3965
+ return {
3966
+ sessionId: props.sessionId,
3967
+ runId: props.runId,
3968
+ level: "info",
3969
+ category: "api",
3970
+ event: "run.received",
3971
+ message: "Run request received",
3972
+ metadata: cleanMetadata2({
3973
+ agentId: props.agentId,
3974
+ stream: props.stream,
3975
+ message: messageSummary(props.message),
3976
+ maxTurns: props.maxTurns,
3977
+ toolConcurrency: props.toolConcurrency,
3978
+ hasTrace: props.hasTrace,
3979
+ metadataKeys: Object.keys(props.metadata ?? {})
3980
+ })
3981
+ };
3982
+ }
3983
+ function runStartedLog(session, runId) {
3984
+ return {
3985
+ sessionId: session.id,
3986
+ runId,
3987
+ level: "info",
3988
+ category: "run",
3989
+ event: "run.started",
3990
+ message: "Run started",
3991
+ metadata: cleanMetadata2({
3992
+ agentId: session.agentId,
3993
+ existingMessageCount: session.messageCount
3994
+ })
3995
+ };
3996
+ }
3997
+ function memoryLoadedLog(session, runId) {
3998
+ return {
3999
+ sessionId: session.id,
4000
+ runId,
4001
+ level: "debug",
4002
+ category: "memory",
4003
+ event: "memory.loaded",
4004
+ message: "Session memory loaded",
4005
+ metadata: cleanMetadata2({
4006
+ messageCount: session.messageCount,
4007
+ transcriptEntries: session.transcript.length
4008
+ })
4009
+ };
4010
+ }
4011
+ function runCompletedLog(props) {
4012
+ return {
4013
+ sessionId: props.sessionId,
4014
+ runId: props.runId,
4015
+ level: "info",
4016
+ category: "run",
4017
+ event: "run.completed",
4018
+ message: "Run completed",
4019
+ metadata: cleanMetadata2({
4020
+ durationMs: props.durationMs,
4021
+ usage: usageSummary(props.usage),
4022
+ outputBytes: byteLength2(props.output),
4023
+ messageCount: props.messageCount
4024
+ })
4025
+ };
4026
+ }
4027
+ function memorySavedLog(props) {
4028
+ return {
4029
+ sessionId: props.sessionId,
4030
+ runId: props.runId,
4031
+ level: "debug",
4032
+ category: "memory",
4033
+ event: "memory.saved",
4034
+ message: "Session memory saved",
4035
+ metadata: cleanMetadata2({
4036
+ messageCount: props.messageCount
4037
+ })
4038
+ };
4039
+ }
4040
+ function runFailedLog(sessionId, runId, error, startedAt) {
4041
+ return {
4042
+ sessionId,
4043
+ runId,
4044
+ level: "error",
4045
+ category: "run",
4046
+ event: "run.failed",
4047
+ message: "Run failed",
4048
+ metadata: cleanMetadata2({
4049
+ durationMs: Date.now() - startedAt,
4050
+ error: serializeError2(error)
4051
+ })
4052
+ };
4053
+ }
4054
+ function logsFromStreamEvent(props) {
4055
+ const { event, sessionId, runId } = props;
4056
+ if (event.type === "turn_start") {
4057
+ return [
4058
+ {
4059
+ sessionId,
4060
+ runId,
4061
+ level: "debug",
4062
+ category: "prompt",
4063
+ event: "prompt.prepared",
4064
+ message: `Turn ${event.turn} prompt prepared`,
4065
+ metadata: cleanMetadata2({
4066
+ turn: event.turn,
4067
+ prompt: messageSummary(event.prompt),
4068
+ historyCount: event.history.length
4069
+ })
4070
+ }
4071
+ ];
4072
+ }
4073
+ if (event.type === "tool_call") {
4074
+ return [
4075
+ {
4076
+ sessionId,
4077
+ runId,
4078
+ level: "info",
4079
+ category: "tool",
4080
+ event: "tool.called",
4081
+ message: `Tool ${event.toolCall.function.name} called`,
4082
+ metadata: cleanMetadata2({
4083
+ turn: event.turn,
4084
+ toolName: event.toolCall.function.name,
4085
+ callId: event.toolCall.callId ?? event.toolCall.id,
4086
+ argumentBytes: byteLength2(formatUnknown2(event.toolCall.function.arguments))
4087
+ })
4088
+ }
4089
+ ];
4090
+ }
4091
+ if (event.type === "tool_result") {
4092
+ return [
4093
+ {
4094
+ sessionId,
4095
+ runId,
4096
+ level: "info",
4097
+ category: "tool",
4098
+ event: "tool.completed",
4099
+ message: `Tool ${event.toolName} completed`,
4100
+ metadata: cleanMetadata2({
4101
+ turn: event.turn,
4102
+ toolName: event.toolName,
4103
+ callId: event.toolCallId,
4104
+ internalCallId: event.internalCallId,
4105
+ argumentBytes: byteLength2(event.args),
4106
+ resultBytes: byteLength2(event.result)
4107
+ })
4108
+ }
4109
+ ];
4110
+ }
4111
+ if (event.type === "turn_end") {
4112
+ return [
4113
+ {
4114
+ sessionId,
4115
+ runId,
4116
+ level: "debug",
4117
+ category: "model",
4118
+ event: "model.turn.completed",
4119
+ message: `Model turn ${event.turn} completed`,
4120
+ metadata: cleanMetadata2({
4121
+ turn: event.turn,
4122
+ contentCount: event.response.choice.length,
4123
+ usage: usageSummary(event.response.usage)
4124
+ })
4125
+ }
4126
+ ];
4127
+ }
4128
+ if (event.type === "final") {
4129
+ return [
4130
+ runCompletedLog({
4131
+ sessionId,
4132
+ runId,
4133
+ durationMs: Date.now() - props.startedAt,
4134
+ usage: event.usage,
4135
+ output: event.output,
4136
+ messageCount: event.messages.length
4137
+ }),
4138
+ memorySavedLog({ sessionId, runId, messageCount: event.messages.length })
4139
+ ];
4140
+ }
4141
+ if (event.type === "error") {
4142
+ return [runFailedLog(sessionId, runId, event.error, props.startedAt)];
4143
+ }
4144
+ if (event.type === "tool_approval_request") {
4145
+ return [
4146
+ {
4147
+ sessionId,
4148
+ runId,
4149
+ level: "info",
4150
+ category: "approval",
4151
+ event: "approval.requested",
4152
+ message: `Approval requested for ${event.approval.toolName}`,
4153
+ metadata: cleanMetadata2({
4154
+ approvalId: event.approval.id,
4155
+ toolName: event.approval.toolName,
4156
+ callId: event.approval.callId,
4157
+ status: event.approval.status,
4158
+ hasReason: event.approval.reason !== void 0,
4159
+ argumentBytes: byteLength2(event.approval.args)
4160
+ })
4161
+ }
4162
+ ];
4163
+ }
4164
+ if (event.type === "tool_approval_result") {
4165
+ return [
4166
+ {
4167
+ sessionId,
4168
+ runId,
4169
+ level: event.approval.status === "approved" ? "info" : "warn",
4170
+ category: "approval",
4171
+ event: "approval.resolved",
4172
+ message: `Approval ${event.approval.status} for ${event.approval.toolName}`,
4173
+ metadata: cleanMetadata2({
4174
+ approvalId: event.approval.id,
4175
+ toolName: event.approval.toolName,
4176
+ callId: event.approval.callId,
4177
+ status: event.approval.status,
4178
+ hasReason: event.approval.reason !== void 0
4179
+ })
4180
+ }
4181
+ ];
4182
+ }
4183
+ if (event.type === "tool_question_request") {
4184
+ return [
4185
+ {
4186
+ sessionId,
4187
+ runId,
4188
+ level: "info",
4189
+ category: "question",
4190
+ event: "question.requested",
4191
+ message: `Question requested by ${event.question.toolName}`,
4192
+ metadata: cleanMetadata2({
4193
+ questionId: event.question.id,
4194
+ toolName: event.question.toolName,
4195
+ callId: event.question.callId,
4196
+ status: event.question.status,
4197
+ questionCount: event.question.questions.length,
4198
+ argumentBytes: byteLength2(event.question.args)
4199
+ })
4200
+ }
4201
+ ];
2590
4202
  }
2591
- transcript.push({
2592
- entryId: transcript.length,
2593
- kind: "reasoning",
2594
- ...reasoningId === void 0 ? {} : { reasoningId },
2595
- text: delta
2596
- });
2597
- }
2598
- function findTranscriptToolEntry(transcript, toolName, callId) {
2599
- for (let index = transcript.length - 1; index >= 0; index -= 1) {
2600
- const entry = transcript[index];
2601
- if (entry?.kind !== "tool" || entry.toolName !== toolName || entry.result !== void 0) {
2602
- continue;
2603
- }
2604
- if (callId === void 0 || entry.callId === callId) {
2605
- return entry;
2606
- }
4203
+ if (event.type === "tool_question_result") {
4204
+ return [
4205
+ {
4206
+ sessionId,
4207
+ runId,
4208
+ level: "info",
4209
+ category: "question",
4210
+ event: "question.answered",
4211
+ message: `Question answered for ${event.question.toolName}`,
4212
+ metadata: cleanMetadata2({
4213
+ questionId: event.question.id,
4214
+ toolName: event.question.toolName,
4215
+ callId: event.question.callId,
4216
+ status: event.question.status,
4217
+ answerCount: event.question.answers?.length ?? 0
4218
+ })
4219
+ }
4220
+ ];
2607
4221
  }
2608
- return void 0;
2609
- }
2610
- function titleFromMessage(message) {
2611
- const text = extractMessageText(message).replace(/\s+/g, " ").trim();
2612
- if (text.length === 0) {
2613
- return void 0;
4222
+ if (event.type === "agent_tool_event") {
4223
+ return childAgentLog(event, sessionId, runId);
2614
4224
  }
2615
- return text.length > 72 ? `${text.slice(0, 69)}...` : text;
4225
+ return [];
2616
4226
  }
2617
- function optionalTitle(message) {
2618
- const title = titleFromMessage(message);
2619
- return title === void 0 ? {} : { title };
4227
+ async function* emitLog(store, input) {
4228
+ const log = await appendSessionLog(store, input);
4229
+ if (log !== void 0) {
4230
+ yield { type: "session_log", log };
4231
+ }
2620
4232
  }
2621
- function extractMessageText(message) {
2622
- if (typeof message === "string") {
2623
- return message;
4233
+ function childAgentLog(event, sessionId, runId) {
4234
+ const child = event.event;
4235
+ if (child.type === "tool_call") {
4236
+ return [
4237
+ {
4238
+ sessionId,
4239
+ runId,
4240
+ level: "debug",
4241
+ category: "tool",
4242
+ event: "child_tool.called",
4243
+ message: `Child agent ${event.agentName ?? event.agentId} called ${child.toolCall.function.name}`,
4244
+ metadata: cleanMetadata2({
4245
+ parentToolName: event.toolName,
4246
+ agentId: event.agentId,
4247
+ hasAgentName: event.agentName !== void 0,
4248
+ turn: event.turn,
4249
+ childTurn: child.turn,
4250
+ toolName: child.toolCall.function.name,
4251
+ callId: child.toolCall.callId ?? child.toolCall.id,
4252
+ argumentBytes: byteLength2(formatUnknown2(child.toolCall.function.arguments))
4253
+ })
4254
+ }
4255
+ ];
2624
4256
  }
2625
- if (message.role === "system") {
2626
- return message.content;
4257
+ if (child.type === "tool_result") {
4258
+ return [
4259
+ {
4260
+ sessionId,
4261
+ runId,
4262
+ level: "debug",
4263
+ category: "tool",
4264
+ event: "child_tool.completed",
4265
+ message: `Child agent ${event.agentName ?? event.agentId} completed ${child.toolName}`,
4266
+ metadata: cleanMetadata2({
4267
+ parentToolName: event.toolName,
4268
+ agentId: event.agentId,
4269
+ hasAgentName: event.agentName !== void 0,
4270
+ turn: event.turn,
4271
+ childTurn: child.turn,
4272
+ toolName: child.toolName,
4273
+ callId: child.toolCallId,
4274
+ resultBytes: byteLength2(child.result)
4275
+ })
4276
+ }
4277
+ ];
2627
4278
  }
2628
- return message.content.flatMap((item) => {
2629
- if (item.type === "text" || item.type === "reasoning") {
2630
- return [item.text];
2631
- }
2632
- if (item.type === "tool_call") {
2633
- return [`${item.function.name}(${formatJson2(item.function.arguments)})`];
2634
- }
2635
- if (item.type === "tool_result") {
2636
- return item.content.map((result) => "text" in result ? result.text : "[image]");
2637
- }
2638
- return [];
2639
- }).join("\n");
2640
- }
2641
- function formatJson2(value) {
2642
- try {
2643
- return JSON.stringify(value, null, 2);
2644
- } catch {
2645
- return String(value);
4279
+ if (child.type === "turn_start") {
4280
+ return [
4281
+ {
4282
+ sessionId,
4283
+ runId,
4284
+ level: "debug",
4285
+ category: "run",
4286
+ event: "child_agent.turn_started",
4287
+ message: `Child agent ${event.agentName ?? event.agentId} turn ${child.turn} started`,
4288
+ metadata: cleanMetadata2({
4289
+ parentToolName: event.toolName,
4290
+ agentId: event.agentId,
4291
+ hasAgentName: event.agentName !== void 0,
4292
+ childTurn: child.turn,
4293
+ historyCount: child.history.length
4294
+ })
4295
+ }
4296
+ ];
2646
4297
  }
2647
- }
2648
- async function parseRunRequest(c) {
2649
- let body;
2650
- try {
2651
- body = await c.req.json();
2652
- } catch {
2653
- return { error: errorResponse(c, 400, "bad_request", "Request body must be JSON") };
4298
+ if (child.type === "final") {
4299
+ return [
4300
+ {
4301
+ sessionId,
4302
+ runId,
4303
+ level: "debug",
4304
+ category: "run",
4305
+ event: "child_agent.completed",
4306
+ message: `Child agent ${event.agentName ?? event.agentId} completed`,
4307
+ metadata: cleanMetadata2({
4308
+ parentToolName: event.toolName,
4309
+ agentId: event.agentId,
4310
+ hasAgentName: event.agentName !== void 0,
4311
+ usage: usageSummary(child.usage),
4312
+ outputBytes: byteLength2(child.output),
4313
+ messageCount: child.messages.length
4314
+ })
4315
+ }
4316
+ ];
2654
4317
  }
2655
- if (!isObject(body)) {
2656
- return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
4318
+ if (child.type === "error") {
4319
+ return [
4320
+ {
4321
+ sessionId,
4322
+ runId,
4323
+ level: "error",
4324
+ category: "run",
4325
+ event: "child_agent.failed",
4326
+ message: `Child agent ${event.agentName ?? event.agentId} failed`,
4327
+ metadata: cleanMetadata2({
4328
+ parentToolName: event.toolName,
4329
+ agentId: event.agentId,
4330
+ hasAgentName: event.agentName !== void 0,
4331
+ error: serializeError2(child.error)
4332
+ })
4333
+ }
4334
+ ];
2657
4335
  }
2658
- if (!("message" in body) || !isMessageInput(body.message)) {
4336
+ return [];
4337
+ }
4338
+ function messageSummary(message) {
4339
+ if (typeof message === "string") {
2659
4340
  return {
2660
- error: errorResponse(c, 400, "bad_request", "Request body requires a string or Message")
4341
+ role: "user",
4342
+ contentKind: "text",
4343
+ byteLength: byteLength2(message)
2661
4344
  };
2662
4345
  }
2663
- const request = {
2664
- message: typeof body.message === "string" ? body.message : body.message
4346
+ return {
4347
+ role: message.role,
4348
+ contentKind: Array.isArray(message.content) ? "parts" : "text",
4349
+ partCount: Array.isArray(message.content) ? message.content.length : 1,
4350
+ byteLength: byteLength2(formatUnknown2(message.content))
2665
4351
  };
2666
- if ("history" in body) {
2667
- if (!Array.isArray(body.history) || !body.history.every(isMessage)) {
2668
- return { error: errorResponse(c, 400, "bad_request", "history must be a Message array") };
2669
- }
2670
- request.history = body.history;
4352
+ }
4353
+ function usageSummary(value) {
4354
+ if (value === void 0 || value === null || typeof value !== "object") {
4355
+ return void 0;
2671
4356
  }
2672
- if ("sessionId" in body) {
2673
- if (typeof body.sessionId !== "string" || body.sessionId.trim().length === 0) {
2674
- return { error: errorResponse(c, 400, "bad_request", "sessionId must be a string") };
4357
+ const record = value;
4358
+ return cleanMetadata2({
4359
+ inputTokens: numericValue(record.inputTokens),
4360
+ outputTokens: numericValue(record.outputTokens),
4361
+ totalTokens: numericValue(record.totalTokens),
4362
+ cachedInputTokens: numericValue(record.cachedInputTokens),
4363
+ cacheCreationInputTokens: numericValue(record.cacheCreationInputTokens)
4364
+ });
4365
+ }
4366
+ function cleanMetadata2(value) {
4367
+ const cleaned = {};
4368
+ for (const [key, item] of Object.entries(value)) {
4369
+ if (item === void 0) {
4370
+ continue;
2675
4371
  }
2676
- if (request.history !== void 0) {
2677
- return {
2678
- error: errorResponse(c, 400, "bad_request", "sessionId cannot be combined with history")
2679
- };
4372
+ const jsonValue = cleanJsonValue(item);
4373
+ if (jsonValue !== void 0) {
4374
+ cleaned[key] = jsonValue;
2680
4375
  }
2681
- request.sessionId = body.sessionId;
2682
4376
  }
2683
- if ("stream" in body) {
2684
- if (typeof body.stream !== "boolean") {
2685
- return { error: errorResponse(c, 400, "bad_request", "stream must be a boolean") };
2686
- }
2687
- request.stream = body.stream;
4377
+ return cleaned;
4378
+ }
4379
+ function cleanJsonValue(value) {
4380
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
4381
+ return value;
2688
4382
  }
2689
- if ("maxTurns" in body) {
2690
- if (!isNonNegativeInteger(body.maxTurns)) {
2691
- return {
2692
- error: errorResponse(c, 400, "bad_request", "maxTurns must be a non-negative integer")
2693
- };
2694
- }
2695
- request.maxTurns = body.maxTurns;
4383
+ if (Array.isArray(value)) {
4384
+ return value.map((item) => cleanJsonValue(item)).filter((item) => item !== void 0);
2696
4385
  }
2697
- if ("toolConcurrency" in body) {
2698
- if (!isPositiveInteger(body.toolConcurrency)) {
2699
- return {
2700
- error: errorResponse(c, 400, "bad_request", "toolConcurrency must be a positive integer")
2701
- };
2702
- }
2703
- request.toolConcurrency = body.toolConcurrency;
4386
+ if (typeof value === "object" && value !== null) {
4387
+ return cleanMetadata2(value);
2704
4388
  }
2705
- if ("metadata" in body) {
2706
- if (!isJsonObject(body.metadata)) {
2707
- return { error: errorResponse(c, 400, "bad_request", "metadata must be an object") };
2708
- }
2709
- request.metadata = body.metadata;
4389
+ return void 0;
4390
+ }
4391
+ function numericValue(value) {
4392
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
4393
+ }
4394
+ function byteLength2(value) {
4395
+ return value === void 0 ? 0 : new TextEncoder().encode(value).byteLength;
4396
+ }
4397
+ function formatUnknown2(value) {
4398
+ if (typeof value === "string") {
4399
+ return value;
2710
4400
  }
2711
- if ("trace" in body) {
2712
- if (!isAgentTraceOptions(body.trace)) {
2713
- return {
2714
- error: errorResponse(c, 400, "bad_request", "trace must be an AgentTraceOptions object")
2715
- };
2716
- }
2717
- request.trace = body.trace;
4401
+ try {
4402
+ return JSON.stringify(value);
4403
+ } catch {
4404
+ return String(value);
2718
4405
  }
2719
- return request;
2720
4406
  }
2721
4407
 
2722
4408
  // src/runtime/sessions.ts
@@ -2750,6 +4436,7 @@ function registerSessionRoutes(app, props) {
2750
4436
  ...body.title === void 0 ? {} : { title: body.title },
2751
4437
  ...body.metadata === void 0 ? {} : { metadata: body.metadata }
2752
4438
  });
4439
+ await appendSessionLog(props.sessionStore, sessionCreatedLog(session));
2753
4440
  return c.json(session, 201);
2754
4441
  });
2755
4442
  app.get("/sessions/:sessionId", async (c) => {
@@ -2759,6 +4446,40 @@ function registerSessionRoutes(app, props) {
2759
4446
  }
2760
4447
  return c.json(session);
2761
4448
  });
4449
+ app.get("/sessions/:sessionId/logs", async (c) => {
4450
+ const sessionId = c.req.param("sessionId");
4451
+ const session = await props.sessionStore.getSession(sessionId);
4452
+ if (session === void 0) {
4453
+ return errorResponse(c, 404, "not_found", "Session not found");
4454
+ }
4455
+ if (props.sessionStore.listSessionLogs === void 0) {
4456
+ return errorResponse(
4457
+ c,
4458
+ 501,
4459
+ "unsupported_capability",
4460
+ 'Capability "sessions.logs" is not implemented by this runner',
4461
+ { capability: "sessions", operation: "logs" }
4462
+ );
4463
+ }
4464
+ const limit = parseSessionLogLimit(c.req.query("limit"));
4465
+ if (limit === void 0) {
4466
+ return errorResponse(c, 400, "bad_request", "limit must be a positive integer");
4467
+ }
4468
+ const after = parseSessionLogAfter(c.req.query("after"));
4469
+ if (after === false) {
4470
+ return errorResponse(c, 400, "bad_request", "after must be a non-negative integer");
4471
+ }
4472
+ const logs = await props.sessionStore.listSessionLogs({
4473
+ sessionId,
4474
+ limit,
4475
+ ...after === void 0 ? {} : { after }
4476
+ });
4477
+ const last = logs.at(-1);
4478
+ return c.json({
4479
+ logs,
4480
+ ...logs.length === limit && last !== void 0 ? { nextCursor: last.sequence } : {}
4481
+ });
4482
+ });
2762
4483
  app.delete("/sessions/:sessionId", async (c) => {
2763
4484
  if (props.sessionStore.deleteSession === void 0) {
2764
4485
  return errorResponse(
@@ -2792,6 +4513,26 @@ function registerSessionRoutes(app, props) {
2792
4513
  return c.json({ traces });
2793
4514
  });
2794
4515
  }
4516
+ function parseSessionLogLimit(value) {
4517
+ if (value === void 0 || value.trim().length === 0) {
4518
+ return 200;
4519
+ }
4520
+ const limit = Number(value);
4521
+ if (!Number.isInteger(limit) || limit <= 0) {
4522
+ return void 0;
4523
+ }
4524
+ return Math.min(limit, 1e3);
4525
+ }
4526
+ function parseSessionLogAfter(value) {
4527
+ if (value === void 0 || value.trim().length === 0) {
4528
+ return void 0;
4529
+ }
4530
+ const after = Number(value);
4531
+ if (!Number.isInteger(after) || after < 0) {
4532
+ return false;
4533
+ }
4534
+ return after;
4535
+ }
2795
4536
  async function parseCreateSessionRequest(c) {
2796
4537
  let body;
2797
4538
  try {
@@ -2826,6 +4567,47 @@ async function parseCreateSessionRequest(c) {
2826
4567
  return request;
2827
4568
  }
2828
4569
 
4570
+ // src/runtime/tools.ts
4571
+ function registerToolRoutes(app, props) {
4572
+ app.get("/agents/:agentId/tools", async (c) => {
4573
+ const agentId = c.req.param("agentId");
4574
+ const agent = props.agentMap.get(agentId);
4575
+ if (agent === void 0) {
4576
+ return errorResponse(c, 404, "not_found", "Agent not found");
4577
+ }
4578
+ return c.json({
4579
+ agentId,
4580
+ tools: await agentToolMetadata(agent)
4581
+ });
4582
+ });
4583
+ }
4584
+ async function agentToolMetadata(agent) {
4585
+ const seen = /* @__PURE__ */ new Set();
4586
+ const metadata = [];
4587
+ for (const { tool, source } of agentToolItems(agent)) {
4588
+ const key = `${source}:${tool.name}`;
4589
+ if (seen.has(key)) {
4590
+ continue;
4591
+ }
4592
+ seen.add(key);
4593
+ const definition = await tool.definition("");
4594
+ metadata.push({
4595
+ agentId: agent.id,
4596
+ name: definition.name,
4597
+ description: definition.description,
4598
+ parameters: definition.parameters,
4599
+ source,
4600
+ approval: approvalMetadata(tool)
4601
+ });
4602
+ }
4603
+ return metadata.sort((left, right) => {
4604
+ if (left.source !== right.source) {
4605
+ return left.source === "static" ? -1 : 1;
4606
+ }
4607
+ return left.name.localeCompare(right.name);
4608
+ });
4609
+ }
4610
+
2829
4611
  // src/runtime/trace-routes.ts
2830
4612
  function registerTraceRoutes(app, traceStore) {
2831
4613
  app.get("/traces", async (c) => {
@@ -2871,8 +4653,8 @@ var Studio = class {
2871
4653
  studio;
2872
4654
  server;
2873
4655
  sigintHandler;
2874
- constructor(agents = [], options = {}) {
2875
- this.options = studioOptionsFromAgents(agents, options);
4656
+ constructor(targets = [], options = {}) {
4657
+ this.options = studioOptionsFromTargets(targets, options);
2876
4658
  this.studio = createStudioApp(this.options);
2877
4659
  }
2878
4660
  get app() {
@@ -2925,9 +4707,14 @@ var Studio = class {
2925
4707
  this.studio.close();
2926
4708
  }
2927
4709
  };
2928
- function studioOptionsFromAgents(agents, options) {
4710
+ function studioOptionsFromTargets(targets, options) {
4711
+ const agents = targets.filter((target) => target instanceof Agent);
4712
+ const pipelines = targets.filter(
4713
+ (target) => target instanceof Pipeline
4714
+ );
2929
4715
  return {
2930
- agents: inferStudioAgents(agents, options.quickPrompts ?? {})
4716
+ agents: inferStudioAgents(agents, options.quickPrompts ?? {}),
4717
+ pipelines: inferStudioPipelines(pipelines)
2931
4718
  };
2932
4719
  }
2933
4720
  function inferStudioAgents(agents, quickPrompts) {
@@ -2942,6 +4729,19 @@ function inferStudioAgents(agents, quickPrompts) {
2942
4729
  };
2943
4730
  });
2944
4731
  }
4732
+ function inferStudioPipelines(pipelines) {
4733
+ const ids = /* @__PURE__ */ new Set();
4734
+ return pipelines.map((pipeline) => {
4735
+ const id = uniqueAgentId(pipeline.id || "pipeline", ids);
4736
+ return {
4737
+ id,
4738
+ pipeline,
4739
+ ...pipeline.name === void 0 ? {} : { name: pipeline.name },
4740
+ ...pipeline.description === void 0 ? {} : { description: pipeline.description },
4741
+ ...pipeline.metadata === void 0 ? {} : { metadata: pipeline.metadata }
4742
+ };
4743
+ });
4744
+ }
2945
4745
  function uniqueAgentId(baseId, ids) {
2946
4746
  let id = baseId;
2947
4747
  let suffix = 2;
@@ -2959,6 +4759,7 @@ function agentMetadata(agent) {
2959
4759
  dynamicContextCount: agent.dynamicContexts.length,
2960
4760
  dynamicToolCount: agent.dynamicTools.length,
2961
4761
  hasOutputSchema: agent.outputSchema !== void 0,
4762
+ hasHook: agent.hook !== void 0,
2962
4763
  observerCount: agent.observers.length,
2963
4764
  approvalToolCount: agent.toolSet.values().filter((tool) => tool.approval !== void 0).length
2964
4765
  };
@@ -2966,7 +4767,9 @@ function agentMetadata(agent) {
2966
4767
  function createStudioApp(options) {
2967
4768
  const stores = resolveStores(options);
2968
4769
  const agents = normalizeAgents(options.agents).map((agent) => withStudioSessionMemory(agent, stores.sessions)).map((agent) => withStudioTraceObserver(agent, stores.traces));
4770
+ const pipelines = normalizePipelines(options.pipelines);
2969
4771
  const agentMap = new Map(agents.map((agent) => [agent.id, agent]));
4772
+ const pipelineMap = new Map(pipelines.map((pipeline) => [pipeline.id, pipeline]));
2970
4773
  const approvalRuntime = createApprovalRuntime();
2971
4774
  const questionRuntime = createQuestionRuntime();
2972
4775
  const app = new HonoApp();
@@ -2988,7 +4791,7 @@ function createStudioApp(options) {
2988
4791
  }
2989
4792
  })
2990
4793
  );
2991
- app.get("/config", (c) => c.json(buildConfig(options, agents, stores)));
4794
+ app.get("/config", (c) => c.json(buildConfig(options, agents, pipelines, stores)));
2992
4795
  app.get("/agents", (c) => c.json({ agents: agents.map(agentConfig) }));
2993
4796
  app.get("/agents/:agentId", (c) => {
2994
4797
  const agent = agentMap.get(c.req.param("agentId"));
@@ -2997,12 +4800,20 @@ function createStudioApp(options) {
2997
4800
  }
2998
4801
  return c.json(agentConfig(agent));
2999
4802
  });
4803
+ registerMcpRoutes(app, { agentMap });
4804
+ registerToolRoutes(app, { agentMap });
3000
4805
  registerApprovalRoutes(app, approvalRuntime);
3001
4806
  registerQuestionRoutes(app, questionRuntime);
3002
4807
  registerKnowledgeRoutes(app, {
3003
4808
  agents,
3004
4809
  ...stores.traces === void 0 ? {} : { traceStore: stores.traces }
3005
4810
  });
4811
+ registerPipelineRoutes(app, {
4812
+ pipelines,
4813
+ pipelineMap,
4814
+ ...stores.pipelineLogs === void 0 ? {} : { logStore: stores.pipelineLogs },
4815
+ ...stores.pipelineRuns === void 0 ? {} : { runStore: stores.pipelineRuns }
4816
+ });
3006
4817
  app.post("/agents/:agentId/runs", async (c) => {
3007
4818
  const agentId = c.req.param("agentId");
3008
4819
  const agent = agentMap.get(agentId);
@@ -3024,6 +4835,23 @@ function createStudioApp(options) {
3024
4835
  return errorResponse(c, 400, "bad_request", "Session belongs to another agent");
3025
4836
  }
3026
4837
  const runId = globalThis.crypto.randomUUID();
4838
+ const runStartedAt = Date.now();
4839
+ if (session !== void 0) {
4840
+ await appendSessionLog(
4841
+ stores.sessions,
4842
+ runReceivedLog({
4843
+ sessionId: session.id,
4844
+ runId,
4845
+ agentId,
4846
+ message: body.message,
4847
+ stream: body.stream === true,
4848
+ ...body.maxTurns === void 0 ? {} : { maxTurns: body.maxTurns },
4849
+ ...body.toolConcurrency === void 0 ? {} : { toolConcurrency: body.toolConcurrency },
4850
+ hasTrace: body.trace !== void 0,
4851
+ ...body.metadata === void 0 ? {} : { metadata: body.metadata }
4852
+ })
4853
+ );
4854
+ }
3027
4855
  const memoryMetadata = {
3028
4856
  agentId,
3029
4857
  ...body.metadata ?? {},
@@ -3070,7 +4898,13 @@ function createStudioApp(options) {
3070
4898
  }
3071
4899
  const runStream = mergeRunAndApprovalEvents(request.stream(), runtimeEvents);
3072
4900
  const stream = session === void 0 || stores.sessions === void 0 ? runStream : persistStreamingSessionTranscript({
3073
- stream: runStream,
4901
+ stream: streamSessionRunLogs({
4902
+ stream: runStream,
4903
+ store: stores.sessions,
4904
+ session,
4905
+ runId,
4906
+ startedAt: runStartedAt
4907
+ }),
3074
4908
  store: stores.sessions,
3075
4909
  session,
3076
4910
  message: body.message,
@@ -3079,6 +4913,10 @@ function createStudioApp(options) {
3079
4913
  return streamAgentRunEvents(c, stream);
3080
4914
  }
3081
4915
  try {
4916
+ if (session !== void 0) {
4917
+ await appendSessionLog(stores.sessions, runStartedLog(session, runId));
4918
+ await appendSessionLog(stores.sessions, memoryLoadedLog(session, runId));
4919
+ }
3082
4920
  const effectiveHook = composeHooks(
3083
4921
  composeHooks(
3084
4922
  agent.agent.hook,
@@ -3109,6 +4947,25 @@ function createStudioApp(options) {
3109
4947
  transcript: transcriptFromMessages(response.messages),
3110
4948
  status: "success"
3111
4949
  });
4950
+ await appendSessionLog(
4951
+ stores.sessions,
4952
+ runCompletedLog({
4953
+ sessionId: session.id,
4954
+ runId,
4955
+ durationMs: Date.now() - runStartedAt,
4956
+ usage: response.usage,
4957
+ output: response.output,
4958
+ messageCount: response.messages.length
4959
+ })
4960
+ );
4961
+ await appendSessionLog(
4962
+ stores.sessions,
4963
+ memorySavedLog({
4964
+ sessionId: session.id,
4965
+ runId,
4966
+ messageCount: response.messages.length
4967
+ })
4968
+ );
3112
4969
  }
3113
4970
  return c.json(response);
3114
4971
  } catch (error) {
@@ -3125,6 +4982,10 @@ function createStudioApp(options) {
3125
4982
  status: "error",
3126
4983
  error: serializeError2(error)
3127
4984
  });
4985
+ await appendSessionLog(
4986
+ stores.sessions,
4987
+ runFailedLog(session.id, runId, error, runStartedAt)
4988
+ );
3128
4989
  }
3129
4990
  return errorResponse(c, 500, "internal_error", "Agent run failed", serializeError2(error));
3130
4991
  }
@@ -3149,7 +5010,7 @@ function createStudioApp(options) {
3149
5010
  return app.fetch(request);
3150
5011
  },
3151
5012
  config() {
3152
- return buildConfig(options, agents, stores);
5013
+ return buildConfig(options, agents, pipelines, stores);
3153
5014
  },
3154
5015
  close() {
3155
5016
  },
@@ -3237,6 +5098,9 @@ function composeHooks(first, second) {
3237
5098
  if (firstAction?.type === "skip" || firstAction?.type === "terminate") {
3238
5099
  return firstAction;
3239
5100
  }
5101
+ if (firstAction?.type === "approval_request") {
5102
+ return await approvalRequestHandler(second)?.(args, firstAction) ?? firstAction;
5103
+ }
3240
5104
  const secondAction = await second.onToolCall?.(args);
3241
5105
  return secondAction ?? firstAction ?? void 0;
3242
5106
  },
@@ -3246,6 +5110,10 @@ function composeHooks(first, second) {
3246
5110
  }
3247
5111
  });
3248
5112
  }
5113
+ function approvalRequestHandler(hook) {
5114
+ const candidate = hook;
5115
+ return typeof candidate.handleApprovalRequest === "function" ? candidate.handleApprovalRequest : void 0;
5116
+ }
3249
5117
  export {
3250
5118
  Studio,
3251
5119
  StudioTraceObserver,