@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/LICENSE +21 -0
- package/dist/index.d.ts +184 -6
- package/dist/index.js +2270 -402
- package/dist/index.js.map +1 -1
- package/dist/ui/assets/index-BYYOd6bv.css +1 -0
- package/dist/ui/assets/index-D7QvySKU.js +91 -0
- package/dist/ui/assets/index-D7QvySKU.js.map +1 -0
- package/dist/ui/index.html +6 -2
- package/package.json +10 -9
- package/dist/ui/assets/index-C0oFCJYF.js +0 -85
- package/dist/ui/assets/index-C0oFCJYF.js.map +0 -1
- package/dist/ui/assets/index-VMCHaVgC.css +0 -1
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,
|
|
698
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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(
|
|
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,
|
|
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
|
|
1121
|
-
transcript: renumberTranscript(
|
|
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:
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
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
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
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
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
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/
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
const
|
|
1902
|
-
if (
|
|
1903
|
-
return errorResponse(c,
|
|
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(
|
|
2545
|
+
return c.json({
|
|
2546
|
+
agentId,
|
|
2547
|
+
servers: await agentMcpMetadata(agent)
|
|
2548
|
+
});
|
|
1936
2549
|
});
|
|
1937
2550
|
}
|
|
1938
|
-
function
|
|
1939
|
-
const
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
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
|
-
|
|
1973
|
-
|
|
2559
|
+
const definition = await tool.definition("");
|
|
2560
|
+
const key = `${serverName}:${source}:${definition.name}`;
|
|
2561
|
+
if (seen.has(key)) {
|
|
2562
|
+
continue;
|
|
1974
2563
|
}
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
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
|
|
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
|
-
|
|
1985
|
-
|
|
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
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
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
|
-
|
|
2046
|
-
const
|
|
2047
|
-
|
|
2048
|
-
id,
|
|
2049
|
-
runId
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
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
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
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
|
|
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
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
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
|
|
2107
|
-
|
|
2108
|
-
|
|
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 (
|
|
2111
|
-
return
|
|
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
|
-
|
|
2115
|
-
|
|
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
|
|
2119
|
-
|
|
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
|
|
2122
|
-
return
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
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
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
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
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
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
|
-
|
|
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
|
|
4225
|
+
return [];
|
|
2616
4226
|
}
|
|
2617
|
-
function
|
|
2618
|
-
const
|
|
2619
|
-
|
|
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
|
|
2622
|
-
|
|
2623
|
-
|
|
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 (
|
|
2626
|
-
return
|
|
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
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
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
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
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 (
|
|
2656
|
-
return
|
|
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
|
-
|
|
4336
|
+
return [];
|
|
4337
|
+
}
|
|
4338
|
+
function messageSummary(message) {
|
|
4339
|
+
if (typeof message === "string") {
|
|
2659
4340
|
return {
|
|
2660
|
-
|
|
4341
|
+
role: "user",
|
|
4342
|
+
contentKind: "text",
|
|
4343
|
+
byteLength: byteLength2(message)
|
|
2661
4344
|
};
|
|
2662
4345
|
}
|
|
2663
|
-
|
|
2664
|
-
|
|
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
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
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
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
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
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
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
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
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 (
|
|
2690
|
-
|
|
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 ("
|
|
2698
|
-
|
|
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
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
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
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
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(
|
|
2875
|
-
this.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
|
|
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:
|
|
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,
|