@anvia/studio 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  // src/runtime/studio.ts
2
2
  import {
3
3
  Agent,
4
- createHook as createHook3
4
+ createHook as createHook3,
5
+ Message,
6
+ resolveMemoryOptions
5
7
  } from "@anvia/core";
6
8
  import { serve } from "@hono/node-server";
7
9
  import { Hono as HonoApp } from "hono";
@@ -77,34 +79,38 @@ var StudioRunTraceObserver = class {
77
79
  }
78
80
  startTool(args) {
79
81
  const startedAt = /* @__PURE__ */ new Date();
82
+ const childTrace = new ChildAgentToolTraceAccumulator(args);
80
83
  return {
84
+ streamEvent: (streamArgs) => {
85
+ childTrace.accept(streamArgs);
86
+ },
81
87
  end: (endArgs) => {
82
- this.observations.push(
83
- traceObservation({
84
- kind: "tool",
85
- name: args.toolName,
86
- status: "success",
87
- turn: args.turn,
88
- startedAt,
89
- input: parseOrString(args.args),
90
- output: parseOrString(endArgs.result),
91
- metadata: toolMetadata(args, endArgs.skipped)
92
- })
93
- );
88
+ const parentObservation = traceObservation({
89
+ kind: "tool",
90
+ name: args.toolName,
91
+ status: "success",
92
+ turn: args.turn,
93
+ startedAt,
94
+ input: parseOrString(args.args),
95
+ output: parseOrString(endArgs.result),
96
+ metadata: toolMetadata(args, endArgs.skipped)
97
+ });
98
+ this.observations.push(parentObservation);
99
+ this.observations.push(...childTrace.observations(parentObservation.id));
94
100
  },
95
101
  error: (errorArgs) => {
96
- this.observations.push(
97
- traceObservation({
98
- kind: "tool",
99
- name: args.toolName,
100
- status: "error",
101
- turn: args.turn,
102
- startedAt,
103
- input: parseOrString(args.args),
104
- error: serializeError(errorArgs.error),
105
- metadata: toolMetadata(args, false)
106
- })
107
- );
102
+ const parentObservation = traceObservation({
103
+ kind: "tool",
104
+ name: args.toolName,
105
+ status: "error",
106
+ turn: args.turn,
107
+ startedAt,
108
+ input: parseOrString(args.args),
109
+ error: serializeError(errorArgs.error),
110
+ metadata: toolMetadata(args, false)
111
+ });
112
+ this.observations.push(parentObservation);
113
+ this.observations.push(...childTrace.observations(parentObservation.id));
108
114
  }
109
115
  };
110
116
  }
@@ -155,10 +161,191 @@ var StudioRunTraceObserver = class {
155
161
  await store.saveTrace(trace);
156
162
  }
157
163
  };
164
+ var ChildAgentToolTraceAccumulator = class {
165
+ constructor(parent) {
166
+ this.parent = parent;
167
+ }
168
+ parent;
169
+ agentStarts = /* @__PURE__ */ new Map();
170
+ generationStarts = /* @__PURE__ */ new Map();
171
+ toolStarts = [];
172
+ completedObservations = [];
173
+ accept(args) {
174
+ const wrapper = args.event;
175
+ const child = isRecord(wrapper.event) ? wrapper.event : void 0;
176
+ if (child === void 0) {
177
+ return;
178
+ }
179
+ const agentId = wrapper.agentId;
180
+ const agentName = wrapper.agentName;
181
+ const childTurn = typeof child.turn === "number" ? child.turn : this.parent.turn;
182
+ if (!this.agentStarts.has(agentId)) {
183
+ this.agentStarts.set(agentId, {
184
+ startedAt: /* @__PURE__ */ new Date(),
185
+ agentId,
186
+ ...agentName === void 0 ? {} : { agentName }
187
+ });
188
+ }
189
+ if (child.type === "turn_start") {
190
+ this.generationStarts.set(generationKey(agentId, childTurn), {
191
+ startedAt: /* @__PURE__ */ new Date(),
192
+ input: toJsonValue({
193
+ prompt: child.prompt,
194
+ history: child.history
195
+ }),
196
+ agentId,
197
+ ...agentName === void 0 ? {} : { agentName },
198
+ childTurn
199
+ });
200
+ return;
201
+ }
202
+ if (child.type === "turn_end") {
203
+ const key = generationKey(agentId, childTurn);
204
+ const start = this.generationStarts.get(key);
205
+ this.generationStarts.delete(key);
206
+ this.completedObservations.push(
207
+ traceObservation({
208
+ kind: "generation",
209
+ name: `${agentLabel(agentId, agentName)}.model.turn.${childTurn}`,
210
+ status: "success",
211
+ turn: this.parent.turn,
212
+ startedAt: start?.startedAt ?? /* @__PURE__ */ new Date(),
213
+ ...start?.input === void 0 ? {} : { input: start.input },
214
+ output: toJsonValue(child.response),
215
+ metadata: this.childMetadata(agentId, agentName, childTurn)
216
+ })
217
+ );
218
+ return;
219
+ }
220
+ if (child.type === "tool_call" && isRecord(child.toolCall)) {
221
+ const toolCall = child.toolCall;
222
+ const toolCallFunction = isRecord(toolCall.function) ? toolCall.function : void 0;
223
+ const toolName = typeof toolCallFunction?.name === "string" ? toolCallFunction.name : "tool";
224
+ const callId = typeof toolCall.callId === "string" ? toolCall.callId : typeof toolCall.id === "string" ? toolCall.id : void 0;
225
+ this.toolStarts.push({
226
+ startedAt: /* @__PURE__ */ new Date(),
227
+ agentId,
228
+ ...agentName === void 0 ? {} : { agentName },
229
+ childTurn,
230
+ toolName,
231
+ ...callId === void 0 ? {} : { toolCallId: callId },
232
+ input: toJsonValue(toolCallFunction?.arguments ?? {}),
233
+ completed: false
234
+ });
235
+ return;
236
+ }
237
+ if (child.type === "tool_result") {
238
+ const toolName = typeof child.toolName === "string" ? child.toolName : "tool";
239
+ const toolCallId = typeof child.toolCallId === "string" ? child.toolCallId : void 0;
240
+ const internalCallId = typeof child.internalCallId === "string" ? child.internalCallId : void 0;
241
+ const start = this.findToolStart(agentId, toolName, toolCallId);
242
+ const input = start?.input ?? (typeof child.args === "string" ? parseOrString(child.args) : void 0);
243
+ if (start !== void 0) {
244
+ start.completed = true;
245
+ }
246
+ this.completedObservations.push(
247
+ traceObservation({
248
+ kind: "tool",
249
+ name: `${agentLabel(agentId, agentName)}.${toolName}`,
250
+ status: "success",
251
+ turn: this.parent.turn,
252
+ startedAt: start?.startedAt ?? /* @__PURE__ */ new Date(),
253
+ ...input === void 0 ? {} : { input },
254
+ ...typeof child.result === "string" ? { output: parseOrString(child.result) } : {},
255
+ metadata: {
256
+ ...this.childMetadata(agentId, agentName, childTurn),
257
+ ...toolCallId === void 0 ? {} : { toolCallId },
258
+ ...internalCallId === void 0 ? {} : { internalCallId }
259
+ }
260
+ })
261
+ );
262
+ return;
263
+ }
264
+ if (child.type === "error") {
265
+ this.completedObservations.push(
266
+ traceObservation({
267
+ kind: "tool",
268
+ name: `${agentLabel(agentId, agentName)}.error`,
269
+ status: "error",
270
+ turn: this.parent.turn,
271
+ startedAt: /* @__PURE__ */ new Date(),
272
+ error: serializeError(child.error),
273
+ metadata: this.childMetadata(agentId, agentName, childTurn)
274
+ })
275
+ );
276
+ }
277
+ }
278
+ observations(parentObservationId) {
279
+ const observations = [];
280
+ const agentObservationIds = /* @__PURE__ */ new Map();
281
+ for (const agentStart of this.agentStarts.values()) {
282
+ const agentChildren = this.completedObservations.filter(
283
+ (observation) => isRecord(observation.metadata) && observation.metadata.childAgentId === agentStart.agentId
284
+ );
285
+ const childStartTimes = agentChildren.map((observation) => Date.parse(observation.startedAt));
286
+ const childEndTimes = agentChildren.map(
287
+ (observation) => Date.parse(observation.endedAt ?? observation.startedAt)
288
+ );
289
+ const startedAt = childStartTimes.length === 0 ? agentStart.startedAt : new Date(Math.min(agentStart.startedAt.getTime(), ...childStartTimes));
290
+ const endedAt = childEndTimes.length === 0 ? /* @__PURE__ */ new Date() : new Date(Math.max(...childEndTimes));
291
+ const agentObservation = traceObservation({
292
+ parentObservationId,
293
+ kind: "agent",
294
+ name: `${agentLabel(agentStart.agentId, agentStart.agentName)}.run`,
295
+ status: agentChildren.some((observation) => observation.status === "error") ? "error" : "success",
296
+ turn: this.parent.turn,
297
+ startedAt,
298
+ endedAt,
299
+ metadata: this.childMetadata(agentStart.agentId, agentStart.agentName, this.parent.turn)
300
+ });
301
+ observations.push(agentObservation);
302
+ agentObservationIds.set(agentStart.agentId, agentObservation.id);
303
+ }
304
+ for (const observation of this.completedObservations) {
305
+ const childAgentId = isRecord(observation.metadata) ? stringValue(observation.metadata.childAgentId) : void 0;
306
+ const childAgentObservationId = childAgentId === void 0 ? void 0 : agentObservationIds.get(childAgentId);
307
+ observations.push({
308
+ ...observation,
309
+ parentObservationId: childAgentObservationId ?? parentObservationId
310
+ });
311
+ }
312
+ return observations;
313
+ }
314
+ findToolStart(agentId, toolName, toolCallId) {
315
+ for (let index = this.toolStarts.length - 1; index >= 0; index -= 1) {
316
+ const start = this.toolStarts[index];
317
+ if (start === void 0 || start.completed || start.agentId !== agentId || start.toolName !== toolName) {
318
+ continue;
319
+ }
320
+ if (toolCallId === void 0 || start.toolCallId === toolCallId) {
321
+ return start;
322
+ }
323
+ }
324
+ return void 0;
325
+ }
326
+ childMetadata(agentId, agentName, childTurn) {
327
+ return compactJsonObject({
328
+ source: "agent_tool_event",
329
+ childAgentId: agentId,
330
+ childAgentName: agentName,
331
+ childTurn,
332
+ parentToolName: this.parent.toolName,
333
+ parentInternalCallId: this.parent.internalCallId,
334
+ parentToolCallId: this.parent.toolCallId
335
+ });
336
+ }
337
+ };
338
+ function generationKey(agentId, turn) {
339
+ return `${agentId}:${turn}`;
340
+ }
341
+ function agentLabel(agentId, agentName) {
342
+ return (agentName ?? agentId).replaceAll(/\s+/g, "_");
343
+ }
158
344
  function traceObservation(props) {
159
- const endedAt = /* @__PURE__ */ new Date();
345
+ const endedAt = props.endedAt ?? /* @__PURE__ */ new Date();
160
346
  return {
161
347
  id: globalThis.crypto.randomUUID(),
348
+ ...props.parentObservationId === void 0 ? {} : { parentObservationId: props.parentObservationId },
162
349
  kind: props.kind,
163
350
  name: props.name,
164
351
  status: props.status,
@@ -207,6 +394,12 @@ function parseOrString(value) {
207
394
  return value;
208
395
  }
209
396
  }
397
+ function isRecord(value) {
398
+ return typeof value === "object" && value !== null && !Array.isArray(value);
399
+ }
400
+ function stringValue(value) {
401
+ return typeof value === "string" ? value : void 0;
402
+ }
210
403
  function serializeError(error) {
211
404
  if (error instanceof Error) {
212
405
  return compactJsonObject({
@@ -544,15 +737,14 @@ var SqliteSessionStore = class {
544
737
  };
545
738
  }
546
739
  getSession(id) {
547
- const db = this.database();
548
- const row = db.prepare(
549
- `SELECT id, agent_id, title, metadata_json, messages_json, transcript_json, created_at, updated_at
550
- FROM runner_sessions
551
- WHERE id = $id`
552
- ).get({ $id: id });
553
- return row === void 0 ? void 0 : toSession(row);
740
+ const row = this.getSessionRow(id);
741
+ return row === void 0 ? void 0 : toSession(row, this.listSessionRunRows(id));
742
+ }
743
+ load(context) {
744
+ const session = this.getSession(context.sessionId);
745
+ return Promise.resolve(session?.messages ?? []);
554
746
  }
555
- appendSessionRun(input) {
747
+ append(input) {
556
748
  const db = this.database();
557
749
  try {
558
750
  db.exec("BEGIN IMMEDIATE");
@@ -560,39 +752,132 @@ var SqliteSessionStore = class {
560
752
  `SELECT id, agent_id, title, metadata_json, messages_json, transcript_json, created_at, updated_at
561
753
  FROM runner_sessions
562
754
  WHERE id = $id`
563
- ).get({ $id: input.id });
755
+ ).get({ $id: input.context.sessionId });
564
756
  if (row === void 0) {
565
757
  db.exec("ROLLBACK");
566
- return void 0;
758
+ return Promise.resolve();
567
759
  }
568
760
  const current = toSession(row);
569
761
  const messages = [...current.messages, ...input.messages];
570
- const transcript = renumberTranscript([...current.transcript, ...input.transcript]);
571
- const title = current.title ?? input.title;
572
762
  const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
763
+ db.prepare(
764
+ `UPDATE runner_sessions
765
+ SET messages_json = $messages,
766
+ updated_at = $updatedAt
767
+ WHERE id = $id`
768
+ ).run({
769
+ $id: input.context.sessionId,
770
+ $messages: JSON.stringify(messages),
771
+ $updatedAt: updatedAt
772
+ });
773
+ db.exec("COMMIT");
774
+ return Promise.resolve();
775
+ } catch (error) {
776
+ if (db.isTransaction) {
777
+ db.exec("ROLLBACK");
778
+ }
779
+ throw error;
780
+ }
781
+ }
782
+ clear(context) {
783
+ const db = this.database();
784
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
785
+ try {
786
+ db.exec("BEGIN IMMEDIATE");
787
+ db.prepare(
788
+ `UPDATE runner_sessions
789
+ SET messages_json = '[]',
790
+ transcript_json = '[]',
791
+ updated_at = $updatedAt
792
+ WHERE id = $id`
793
+ ).run({
794
+ $id: context.sessionId,
795
+ $updatedAt: updatedAt
796
+ });
797
+ db.prepare("DELETE FROM runner_session_runs WHERE session_id = $id").run({
798
+ $id: context.sessionId
799
+ });
800
+ db.exec("COMMIT");
801
+ return Promise.resolve();
802
+ } catch (error) {
803
+ if (db.isTransaction) {
804
+ db.exec("ROLLBACK");
805
+ }
806
+ throw error;
807
+ }
808
+ }
809
+ async recordError(input) {
810
+ const runId = studioRunId(input.context) ?? input.runId;
811
+ const existing = this.getSessionRun(input.context.sessionId, runId);
812
+ const transcript = existing === void 0 || parseJsonArray(existing.transcript_json).length === 0 ? transcriptFromMessagesFallback(input.messages) : parseJsonArray(existing.transcript_json);
813
+ await this.saveSessionRunTranscript({
814
+ id: input.context.sessionId,
815
+ runId,
816
+ transcript,
817
+ status: "error",
818
+ error: serializeJsonError(input.error)
819
+ });
820
+ }
821
+ saveSessionRunTranscript(input) {
822
+ const db = this.database();
823
+ const now = (/* @__PURE__ */ new Date()).toISOString();
824
+ try {
825
+ db.exec("BEGIN IMMEDIATE");
826
+ const row = this.getSessionRow(input.id);
827
+ if (row === void 0) {
828
+ db.exec("ROLLBACK");
829
+ return void 0;
830
+ }
831
+ const current = toSession(row, this.listSessionRunRows(input.id));
832
+ const title = current.title ?? input.title;
833
+ db.prepare(
834
+ `INSERT INTO runner_session_runs (
835
+ run_id,
836
+ session_id,
837
+ status,
838
+ title,
839
+ transcript_json,
840
+ error_json,
841
+ created_at,
842
+ updated_at
843
+ ) VALUES (
844
+ $runId,
845
+ $sessionId,
846
+ $status,
847
+ $title,
848
+ $transcript,
849
+ $error,
850
+ $now,
851
+ $now
852
+ )
853
+ ON CONFLICT(run_id) DO UPDATE SET
854
+ status = excluded.status,
855
+ title = COALESCE(runner_session_runs.title, excluded.title),
856
+ transcript_json = excluded.transcript_json,
857
+ error_json = excluded.error_json,
858
+ updated_at = excluded.updated_at`
859
+ ).run({
860
+ $runId: input.runId,
861
+ $sessionId: input.id,
862
+ $status: input.status,
863
+ $title: input.title ?? null,
864
+ $transcript: JSON.stringify(renumberTranscript(input.transcript)),
865
+ $error: input.error === void 0 ? null : JSON.stringify(input.error),
866
+ $now: now
867
+ });
573
868
  db.prepare(
574
869
  `UPDATE runner_sessions
575
870
  SET title = $title,
576
- messages_json = $messages,
577
- transcript_json = $transcript,
578
871
  updated_at = $updatedAt
579
872
  WHERE id = $id`
580
873
  ).run({
581
874
  $id: input.id,
582
875
  $title: title ?? null,
583
- $messages: JSON.stringify(messages),
584
- $transcript: JSON.stringify(transcript),
585
- $updatedAt: updatedAt
876
+ $updatedAt: now
586
877
  });
587
878
  db.exec("COMMIT");
588
- return {
589
- ...current,
590
- ...title === void 0 ? {} : { title },
591
- updatedAt,
592
- messageCount: messages.length,
593
- messages,
594
- transcript
595
- };
879
+ const updated = this.getSession(input.id);
880
+ return updated;
596
881
  } catch (error) {
597
882
  if (db.isTransaction) {
598
883
  db.exec("ROLLBACK");
@@ -605,6 +890,7 @@ var SqliteSessionStore = class {
605
890
  try {
606
891
  db.exec("BEGIN IMMEDIATE");
607
892
  db.prepare("DELETE FROM runner_traces WHERE session_id = $id").run({ $id: id });
893
+ db.prepare("DELETE FROM runner_session_runs WHERE session_id = $id").run({ $id: id });
608
894
  const result = db.prepare("DELETE FROM runner_sessions WHERE id = $id").run({ $id: id });
609
895
  db.exec("COMMIT");
610
896
  return Number(result.changes) > 0;
@@ -765,6 +1051,19 @@ var SqliteSessionStore = class {
765
1051
  ) STRICT;
766
1052
  CREATE INDEX IF NOT EXISTS runner_sessions_agent_updated_idx
767
1053
  ON runner_sessions(agent_id, updated_at DESC);
1054
+ CREATE TABLE IF NOT EXISTS runner_session_runs (
1055
+ run_id TEXT PRIMARY KEY,
1056
+ session_id TEXT NOT NULL,
1057
+ status TEXT NOT NULL,
1058
+ title TEXT,
1059
+ transcript_json TEXT NOT NULL,
1060
+ error_json TEXT,
1061
+ created_at TEXT NOT NULL,
1062
+ updated_at TEXT NOT NULL,
1063
+ FOREIGN KEY(session_id) REFERENCES runner_sessions(id) ON DELETE CASCADE
1064
+ ) STRICT;
1065
+ CREATE INDEX IF NOT EXISTS runner_session_runs_session_created_idx
1066
+ ON runner_session_runs(session_id, created_at ASC);
768
1067
  CREATE TABLE IF NOT EXISTS runner_traces (
769
1068
  id TEXT PRIMARY KEY,
770
1069
  session_id TEXT NOT NULL,
@@ -787,13 +1086,39 @@ var SqliteSessionStore = class {
787
1086
  this.db = db;
788
1087
  return db;
789
1088
  }
1089
+ getSessionRow(id) {
1090
+ return this.database().prepare(
1091
+ `SELECT id, agent_id, title, metadata_json, messages_json, transcript_json, created_at, updated_at
1092
+ FROM runner_sessions
1093
+ WHERE id = $id`
1094
+ ).get({ $id: id });
1095
+ }
1096
+ getSessionRun(sessionId, runId) {
1097
+ return this.database().prepare(
1098
+ `SELECT run_id, session_id, status, title, transcript_json, error_json, created_at, updated_at
1099
+ FROM runner_session_runs
1100
+ WHERE session_id = $sessionId AND run_id = $runId`
1101
+ ).get({ $sessionId: sessionId, $runId: runId });
1102
+ }
1103
+ listSessionRunRows(sessionId) {
1104
+ return this.database().prepare(
1105
+ `SELECT run_id, session_id, status, title, transcript_json, error_json, created_at, updated_at
1106
+ FROM runner_session_runs
1107
+ WHERE session_id = $sessionId
1108
+ ORDER BY created_at ASC`
1109
+ ).all({ $sessionId: sessionId });
1110
+ }
790
1111
  };
791
- function toSession(row) {
1112
+ function toSession(row, runRows = []) {
792
1113
  const summary = toSessionSummary(row);
1114
+ const legacyTranscript = parseJsonArray(row.transcript_json);
1115
+ const runTranscript = runRows.flatMap(
1116
+ (runRow) => parseJsonArray(runRow.transcript_json)
1117
+ );
793
1118
  return {
794
1119
  ...summary,
795
1120
  messages: parseJsonArray(row.messages_json),
796
- transcript: renumberTranscript(parseJsonArray(row.transcript_json))
1121
+ transcript: renumberTranscript([...legacyTranscript, ...runTranscript])
797
1122
  };
798
1123
  }
799
1124
  function toSessionSummary(row) {
@@ -852,6 +1177,96 @@ function parseJsonValue(value) {
852
1177
  function renumberTranscript(entries) {
853
1178
  return entries.map((entry, entryId) => ({ ...entry, entryId }));
854
1179
  }
1180
+ function studioRunId(context) {
1181
+ const value = context.metadata?.studioRunId;
1182
+ return typeof value === "string" && value.length > 0 ? value : void 0;
1183
+ }
1184
+ function serializeJsonError(error) {
1185
+ if (error instanceof Error) {
1186
+ return {
1187
+ name: error.name,
1188
+ message: error.message
1189
+ };
1190
+ }
1191
+ if (error === null || typeof error === "string" || typeof error === "number" || typeof error === "boolean") {
1192
+ return error;
1193
+ }
1194
+ return String(error);
1195
+ }
1196
+ function transcriptFromMessagesFallback(messages) {
1197
+ const transcript = [];
1198
+ for (const message of messages) {
1199
+ if (message.role === "system") {
1200
+ continue;
1201
+ }
1202
+ if (message.role === "user") {
1203
+ for (const content of message.content) {
1204
+ if (content.type === "text") {
1205
+ transcript.push({
1206
+ entryId: transcript.length,
1207
+ kind: "message",
1208
+ role: "user",
1209
+ text: content.text
1210
+ });
1211
+ }
1212
+ }
1213
+ continue;
1214
+ }
1215
+ if (message.role === "tool") {
1216
+ for (const content of message.content) {
1217
+ transcript.push({
1218
+ entryId: transcript.length,
1219
+ kind: "tool",
1220
+ toolName: "tool_result",
1221
+ callId: content.callId ?? content.id,
1222
+ result: content.content.map((item) => "text" in item ? item.text : "[image]").join("\n")
1223
+ });
1224
+ }
1225
+ continue;
1226
+ }
1227
+ for (const content of message.content) {
1228
+ if (content.type === "text") {
1229
+ appendAssistantTranscriptText(transcript, content.text);
1230
+ } else if (content.type === "reasoning") {
1231
+ transcript.push({
1232
+ entryId: transcript.length,
1233
+ kind: "reasoning",
1234
+ ...content.id === void 0 ? {} : { reasoningId: content.id },
1235
+ text: content.text
1236
+ });
1237
+ } else if (content.type === "tool_call") {
1238
+ transcript.push({
1239
+ entryId: transcript.length,
1240
+ kind: "tool",
1241
+ toolName: content.function.name,
1242
+ callId: content.callId ?? content.id,
1243
+ args: formatJson(content.function.arguments)
1244
+ });
1245
+ }
1246
+ }
1247
+ }
1248
+ return transcript;
1249
+ }
1250
+ function appendAssistantTranscriptText(transcript, text) {
1251
+ const last = transcript.at(-1);
1252
+ if (last?.kind === "message" && last.role === "assistant") {
1253
+ last.text = `${last.text}${text}`;
1254
+ return;
1255
+ }
1256
+ transcript.push({
1257
+ entryId: transcript.length,
1258
+ kind: "message",
1259
+ role: "assistant",
1260
+ text
1261
+ });
1262
+ }
1263
+ function formatJson(value) {
1264
+ try {
1265
+ return JSON.stringify(value, null, 2);
1266
+ } catch {
1267
+ return String(value);
1268
+ }
1269
+ }
855
1270
 
856
1271
  // src/runtime/shared.ts
857
1272
  function resolveStores(options) {
@@ -1369,7 +1784,7 @@ async function recentKnowledgeEvidence(traceStore, limit) {
1369
1784
  }
1370
1785
  function evidenceFromTrace(trace) {
1371
1786
  return trace.observations.flatMap((observation) => {
1372
- if (observation.kind !== "generation" || !isRecord(observation.input)) {
1787
+ if (observation.kind !== "generation" || !isRecord2(observation.input)) {
1373
1788
  return [];
1374
1789
  }
1375
1790
  const documents = Array.isArray(observation.input.documents) ? observation.input.documents.flatMap((document) => evidenceDocument(document)) : [];
@@ -1422,7 +1837,7 @@ function messageText(value) {
1422
1837
  if (typeof value === "string") {
1423
1838
  return value.trim();
1424
1839
  }
1425
- if (!isRecord(value)) {
1840
+ if (!isRecord2(value)) {
1426
1841
  return "";
1427
1842
  }
1428
1843
  if (typeof value.text === "string") {
@@ -1440,7 +1855,7 @@ function contentText(value) {
1440
1855
  if (typeof value === "string") {
1441
1856
  return value.trim();
1442
1857
  }
1443
- if (!isRecord(value)) {
1858
+ if (!isRecord2(value)) {
1444
1859
  return "";
1445
1860
  }
1446
1861
  if (typeof value.text === "string") {
@@ -1449,12 +1864,12 @@ function contentText(value) {
1449
1864
  return "";
1450
1865
  }
1451
1866
  function evidenceDocument(value) {
1452
- if (!isRecord(value)) {
1867
+ if (!isRecord2(value)) {
1453
1868
  return [];
1454
1869
  }
1455
1870
  const id = typeof value.id === "string" ? value.id : void 0;
1456
1871
  const text = typeof value.text === "string" ? value.text : void 0;
1457
- const additionalProps = isRecord(value.additionalProps) ? jsonObjectFromRecord(value.additionalProps) : void 0;
1872
+ const additionalProps = isRecord2(value.additionalProps) ? jsonObjectFromRecord(value.additionalProps) : void 0;
1458
1873
  if (id === void 0 && text === void 0 && additionalProps === void 0) {
1459
1874
  return [];
1460
1875
  }
@@ -1467,7 +1882,7 @@ function evidenceDocument(value) {
1467
1882
  ];
1468
1883
  }
1469
1884
  function evidenceToolName(value) {
1470
- if (!isRecord(value) || typeof value.name !== "string") {
1885
+ if (!isRecord2(value) || typeof value.name !== "string") {
1471
1886
  return [];
1472
1887
  }
1473
1888
  return [value.name];
@@ -1475,7 +1890,7 @@ function evidenceToolName(value) {
1475
1890
  function jsonObjectFromRecord(value) {
1476
1891
  return compactJsonObject2(value);
1477
1892
  }
1478
- function isRecord(value) {
1893
+ function isRecord2(value) {
1479
1894
  return typeof value === "object" && value !== null && !Array.isArray(value);
1480
1895
  }
1481
1896
 
@@ -1823,22 +2238,42 @@ function traceForRun(trace, agentId, session) {
1823
2238
  ...trace?.sessionId !== void 0 ? { sessionId: trace.sessionId } : session === void 0 ? {} : { sessionId: session.id }
1824
2239
  };
1825
2240
  }
1826
- async function* persistStreamingSessionRun(props) {
2241
+ async function* persistStreamingSessionTranscript(props) {
1827
2242
  const transcript = [messageToTranscriptEntry(props.message, 0)];
1828
- for await (const event of props.stream) {
1829
- acceptTranscriptStreamEvent(transcript, event);
1830
- if (event.type === "final") {
1831
- const nextSession = await props.store.appendSessionRun({
2243
+ const title = optionalTitle(props.message);
2244
+ await props.store.saveSessionRunTranscript({
2245
+ id: props.session.id,
2246
+ runId: props.runId,
2247
+ ...title,
2248
+ transcript,
2249
+ status: "running"
2250
+ });
2251
+ try {
2252
+ for await (const event of props.stream) {
2253
+ acceptTranscriptStreamEvent(transcript, event);
2254
+ const nextSession = await props.store.saveSessionRunTranscript({
1832
2255
  id: props.session.id,
1833
- ...optionalTitle(props.message),
1834
- messages: event.messages,
1835
- transcript
2256
+ runId: props.runId,
2257
+ ...title,
2258
+ transcript,
2259
+ status: event.type === "final" ? "success" : event.type === "error" ? "error" : "running",
2260
+ ...event.type === "error" ? { error: serializeError2(event.error) } : {}
1836
2261
  });
1837
2262
  if (nextSession === void 0) {
1838
2263
  throw new Error("Session not found");
1839
2264
  }
2265
+ yield event;
1840
2266
  }
1841
- yield event;
2267
+ } catch (error) {
2268
+ await props.store.saveSessionRunTranscript({
2269
+ id: props.session.id,
2270
+ runId: props.runId,
2271
+ ...title,
2272
+ transcript,
2273
+ status: "error",
2274
+ error: serializeError2(error)
2275
+ });
2276
+ throw error;
1842
2277
  }
1843
2278
  }
1844
2279
  function acceptTranscriptStreamEvent(transcript, event) {
@@ -1854,7 +2289,7 @@ function acceptTranscriptStreamEvent(transcript, event) {
1854
2289
  kind: "tool",
1855
2290
  toolName: event.toolCall.function.name,
1856
2291
  callId: event.toolCall.callId ?? event.toolCall.id,
1857
- args: formatJson(event.toolCall.function.arguments)
2292
+ args: formatJson2(event.toolCall.function.arguments)
1858
2293
  });
1859
2294
  }
1860
2295
  if (event.type === "tool_result") {
@@ -1873,6 +2308,22 @@ function acceptTranscriptStreamEvent(transcript, event) {
1873
2308
  matched.args = matched.args ?? event.args;
1874
2309
  matched.result = event.result;
1875
2310
  }
2311
+ if (event.type === "agent_tool_event") {
2312
+ const matched = findTranscriptToolEntry(transcript, event.toolName, event.toolCallId);
2313
+ if (matched === void 0) {
2314
+ transcript.push({
2315
+ entryId: transcript.length,
2316
+ kind: "tool",
2317
+ toolName: event.toolName,
2318
+ ...event.toolCallId === void 0 ? {} : { callId: event.toolCallId },
2319
+ childEvents: [childAgentTranscriptEvent(event)].filter(
2320
+ (childEvent) => childEvent !== void 0
2321
+ )
2322
+ });
2323
+ return;
2324
+ }
2325
+ appendChildAgentTranscriptEvent(matched, event);
2326
+ }
1876
2327
  if (event.type === "tool_approval_request") {
1877
2328
  const matched = findTranscriptToolEntry(
1878
2329
  transcript,
@@ -1945,6 +2396,112 @@ function approvalCallId(approval) {
1945
2396
  function questionCallId(question) {
1946
2397
  return question.callId ?? question.toolCallId;
1947
2398
  }
2399
+ function appendChildAgentTranscriptEvent(entry, event) {
2400
+ const childEvent = childAgentTranscriptEvent(event);
2401
+ if (childEvent === void 0) {
2402
+ return;
2403
+ }
2404
+ const childEvents = entry.childEvents ?? [];
2405
+ if (childEvent.kind === "message") {
2406
+ const last = childEvents.at(-1);
2407
+ if (last?.kind === "message" && last.agentId === childEvent.agentId) {
2408
+ last.text = `${last.text}${childEvent.text}`;
2409
+ } else {
2410
+ childEvents.push(childEvent);
2411
+ }
2412
+ } else if (childEvent.kind === "reasoning") {
2413
+ const last = childEvents.at(-1);
2414
+ if (last?.kind === "reasoning" && last.agentId === childEvent.agentId && (last.reasoningId ?? "") === (childEvent.reasoningId ?? "")) {
2415
+ last.text = `${last.text}${childEvent.text}`;
2416
+ } else {
2417
+ childEvents.push(childEvent);
2418
+ }
2419
+ } else {
2420
+ const matched = findChildAgentToolEvent(childEvents, childEvent);
2421
+ if (matched === void 0) {
2422
+ childEvents.push(childEvent);
2423
+ } else {
2424
+ if (matched.args === void 0 && childEvent.args !== void 0) {
2425
+ matched.args = childEvent.args;
2426
+ }
2427
+ if (childEvent.result !== void 0) {
2428
+ matched.result = childEvent.result;
2429
+ }
2430
+ }
2431
+ }
2432
+ entry.childEvents = childEvents;
2433
+ }
2434
+ function childAgentTranscriptEvent(event) {
2435
+ const child = event.event;
2436
+ if (child.type === "text_delta") {
2437
+ return {
2438
+ kind: "message",
2439
+ agentId: event.agentId,
2440
+ ...event.agentName === void 0 ? {} : { agentName: event.agentName },
2441
+ text: child.delta
2442
+ };
2443
+ }
2444
+ if (child.type === "reasoning_delta") {
2445
+ return {
2446
+ kind: "reasoning",
2447
+ agentId: event.agentId,
2448
+ ...event.agentName === void 0 ? {} : { agentName: event.agentName },
2449
+ ...child.id === void 0 ? {} : { reasoningId: child.id },
2450
+ text: child.delta
2451
+ };
2452
+ }
2453
+ if (child.type === "tool_call") {
2454
+ return {
2455
+ kind: "tool",
2456
+ agentId: event.agentId,
2457
+ ...event.agentName === void 0 ? {} : { agentName: event.agentName },
2458
+ toolName: child.toolCall.function.name,
2459
+ ...child.toolCall.callId === void 0 && child.toolCall.id === void 0 ? {} : { callId: child.toolCall.callId ?? child.toolCall.id },
2460
+ args: formatJson2(child.toolCall.function.arguments)
2461
+ };
2462
+ }
2463
+ if (child.type === "tool_result") {
2464
+ return {
2465
+ kind: "tool",
2466
+ agentId: event.agentId,
2467
+ ...event.agentName === void 0 ? {} : { agentName: event.agentName },
2468
+ toolName: child.toolName,
2469
+ ...child.toolCallId === void 0 ? {} : { callId: child.toolCallId },
2470
+ args: child.args,
2471
+ result: child.result
2472
+ };
2473
+ }
2474
+ if (child.type === "error") {
2475
+ return {
2476
+ kind: "message",
2477
+ agentId: event.agentId,
2478
+ ...event.agentName === void 0 ? {} : { agentName: event.agentName },
2479
+ text: `Error: ${errorText(child.error)}`
2480
+ };
2481
+ }
2482
+ return void 0;
2483
+ }
2484
+ function errorText(error) {
2485
+ if (error instanceof Error) {
2486
+ return error.message;
2487
+ }
2488
+ if (typeof error === "string") {
2489
+ return error;
2490
+ }
2491
+ return JSON.stringify(serializeError2(error));
2492
+ }
2493
+ function findChildAgentToolEvent(childEvents, event) {
2494
+ for (let index = childEvents.length - 1; index >= 0; index -= 1) {
2495
+ const childEvent = childEvents[index];
2496
+ if (childEvent?.kind !== "tool" || childEvent.agentId !== event.agentId || childEvent.toolName !== event.toolName || childEvent.result !== void 0) {
2497
+ continue;
2498
+ }
2499
+ if (event.callId === void 0 || childEvent.callId === event.callId) {
2500
+ return childEvent;
2501
+ }
2502
+ }
2503
+ return void 0;
2504
+ }
1948
2505
  function transcriptFromMessages(messages) {
1949
2506
  const transcript = [];
1950
2507
  for (const message of messages) {
@@ -1987,7 +2544,7 @@ function transcriptFromMessages(messages) {
1987
2544
  kind: "tool",
1988
2545
  toolName: content.function.name,
1989
2546
  callId: content.callId ?? content.id,
1990
- args: formatJson(content.function.arguments)
2547
+ args: formatJson2(content.function.arguments)
1991
2548
  });
1992
2549
  }
1993
2550
  }
@@ -2073,7 +2630,7 @@ function extractMessageText(message) {
2073
2630
  return [item.text];
2074
2631
  }
2075
2632
  if (item.type === "tool_call") {
2076
- return [`${item.function.name}(${formatJson(item.function.arguments)})`];
2633
+ return [`${item.function.name}(${formatJson2(item.function.arguments)})`];
2077
2634
  }
2078
2635
  if (item.type === "tool_result") {
2079
2636
  return item.content.map((result) => "text" in result ? result.text : "[image]");
@@ -2081,7 +2638,7 @@ function extractMessageText(message) {
2081
2638
  return [];
2082
2639
  }).join("\n");
2083
2640
  }
2084
- function formatJson(value) {
2641
+ function formatJson2(value) {
2085
2642
  try {
2086
2643
  return JSON.stringify(value, null, 2);
2087
2644
  } catch {
@@ -2408,9 +2965,7 @@ function agentMetadata(agent) {
2408
2965
  }
2409
2966
  function createStudioApp(options) {
2410
2967
  const stores = resolveStores(options);
2411
- const agents = normalizeAgents(options.agents).map(
2412
- (agent) => withStudioTraceObserver(agent, stores.traces)
2413
- );
2968
+ const agents = normalizeAgents(options.agents).map((agent) => withStudioSessionMemory(agent, stores.sessions)).map((agent) => withStudioTraceObserver(agent, stores.traces));
2414
2969
  const agentMap = new Map(agents.map((agent) => [agent.id, agent]));
2415
2970
  const approvalRuntime = createApprovalRuntime();
2416
2971
  const questionRuntime = createQuestionRuntime();
@@ -2469,12 +3024,14 @@ function createStudioApp(options) {
2469
3024
  return errorResponse(c, 400, "bad_request", "Session belongs to another agent");
2470
3025
  }
2471
3026
  const runId = globalThis.crypto.randomUUID();
2472
- const request = agent.agent.prompt(body.message);
2473
- if (session !== void 0) {
2474
- request.withHistory(session.messages);
2475
- } else if (body.history !== void 0) {
2476
- request.withHistory(body.history);
2477
- }
3027
+ const memoryMetadata = {
3028
+ agentId,
3029
+ ...body.metadata ?? {},
3030
+ studioRunId: runId
3031
+ };
3032
+ const request = session !== void 0 ? agent.agent.session(session.id, { metadata: memoryMetadata }).prompt(body.message) : agent.agent.prompt(
3033
+ body.history !== void 0 ? [...body.history, normalizePromptMessage(body.message)] : body.message
3034
+ );
2478
3035
  if (body.maxTurns !== void 0) {
2479
3036
  request.maxTurns(body.maxTurns);
2480
3037
  }
@@ -2512,11 +3069,12 @@ function createStudioApp(options) {
2512
3069
  request.requestHook(effectiveHook);
2513
3070
  }
2514
3071
  const runStream = mergeRunAndApprovalEvents(request.stream(), runtimeEvents);
2515
- const stream = session === void 0 || stores.sessions === void 0 ? runStream : persistStreamingSessionRun({
3072
+ const stream = session === void 0 || stores.sessions === void 0 ? runStream : persistStreamingSessionTranscript({
2516
3073
  stream: runStream,
2517
3074
  store: stores.sessions,
2518
3075
  session,
2519
- message: body.message
3076
+ message: body.message,
3077
+ runId
2520
3078
  });
2521
3079
  return streamAgentRunEvents(c, stream);
2522
3080
  }
@@ -2544,15 +3102,30 @@ function createStudioApp(options) {
2544
3102
  }
2545
3103
  const response = await request.send();
2546
3104
  if (session !== void 0 && stores.sessions !== void 0) {
2547
- await stores.sessions.appendSessionRun({
3105
+ await stores.sessions.saveSessionRunTranscript({
2548
3106
  id: session.id,
3107
+ runId,
2549
3108
  ...optionalTitle(body.message),
2550
- messages: response.messages,
2551
- transcript: transcriptFromMessages(response.messages)
3109
+ transcript: transcriptFromMessages(response.messages),
3110
+ status: "success"
2552
3111
  });
2553
3112
  }
2554
3113
  return c.json(response);
2555
3114
  } catch (error) {
3115
+ if (session !== void 0 && stores.sessions !== void 0) {
3116
+ const messages = await stores.sessions.load({
3117
+ sessionId: session.id,
3118
+ metadata: memoryMetadata
3119
+ });
3120
+ await stores.sessions.saveSessionRunTranscript({
3121
+ id: session.id,
3122
+ runId,
3123
+ ...optionalTitle(body.message),
3124
+ transcript: transcriptFromMessages(messages.slice(session.messageCount)),
3125
+ status: "error",
3126
+ error: serializeError2(error)
3127
+ });
3128
+ }
2556
3129
  return errorResponse(c, 500, "internal_error", "Agent run failed", serializeError2(error));
2557
3130
  }
2558
3131
  });
@@ -2584,36 +3157,60 @@ function createStudioApp(options) {
2584
3157
  ...stores.traces === void 0 ? {} : { traceStore: stores.traces }
2585
3158
  };
2586
3159
  }
3160
+ function normalizePromptMessage(message) {
3161
+ return typeof message === "string" ? Message.user(message) : message;
3162
+ }
3163
+ function withStudioSessionMemory(studioAgent, sessionStore) {
3164
+ if (sessionStore === void 0) {
3165
+ return studioAgent;
3166
+ }
3167
+ return {
3168
+ ...studioAgent,
3169
+ agent: cloneAgent(studioAgent.agent, {
3170
+ memory: {
3171
+ store: sessionStore,
3172
+ options: resolveMemoryOptions({ savePolicy: "message" })
3173
+ }
3174
+ })
3175
+ };
3176
+ }
2587
3177
  function withStudioTraceObserver(studioAgent, traceStore) {
2588
3178
  if (traceStore === void 0 || hasStudioTraceObserver(studioAgent.agent)) {
2589
3179
  return studioAgent;
2590
3180
  }
2591
3181
  return {
2592
3182
  ...studioAgent,
2593
- agent: new Agent({
2594
- id: studioAgent.agent.id,
2595
- name: studioAgent.agent.name,
2596
- description: studioAgent.agent.description,
2597
- model: studioAgent.agent.model,
2598
- instructions: studioAgent.agent.instructions,
2599
- staticContext: studioAgent.agent.staticContext,
2600
- temperature: studioAgent.agent.temperature,
2601
- maxTokens: studioAgent.agent.maxTokens,
2602
- additionalParams: studioAgent.agent.additionalParams,
2603
- toolSet: studioAgent.agent.toolSet,
2604
- toolChoice: studioAgent.agent.toolChoice,
2605
- defaultMaxTurns: studioAgent.agent.defaultMaxTurns,
2606
- hook: studioAgent.agent.hook,
2607
- outputSchema: studioAgent.agent.outputSchema,
3183
+ agent: cloneAgent(studioAgent.agent, {
2608
3184
  observers: [
2609
3185
  ...studioAgent.agent.observers,
2610
3186
  { observer: new StudioTraceObserver({ store: traceStore }) }
2611
- ],
2612
- dynamicContexts: studioAgent.agent.dynamicContexts,
2613
- dynamicTools: studioAgent.agent.dynamicTools
3187
+ ]
2614
3188
  })
2615
3189
  };
2616
3190
  }
3191
+ function cloneAgent(agent, overrides = {}) {
3192
+ return new Agent({
3193
+ id: agent.id,
3194
+ name: agent.name,
3195
+ description: agent.description,
3196
+ model: agent.model,
3197
+ instructions: agent.instructions,
3198
+ staticContext: agent.staticContext,
3199
+ temperature: agent.temperature,
3200
+ maxTokens: agent.maxTokens,
3201
+ additionalParams: agent.additionalParams,
3202
+ toolSet: agent.toolSet,
3203
+ toolChoice: agent.toolChoice,
3204
+ defaultMaxTurns: agent.defaultMaxTurns,
3205
+ hook: agent.hook,
3206
+ outputSchema: agent.outputSchema,
3207
+ observers: agent.observers,
3208
+ dynamicContexts: agent.dynamicContexts,
3209
+ dynamicTools: agent.dynamicTools,
3210
+ memory: agent.memory,
3211
+ ...overrides
3212
+ });
3213
+ }
2617
3214
  function hasStudioTraceObserver(agent) {
2618
3215
  return agent.observers.some(
2619
3216
  (registration) => registration.observer instanceof StudioTraceObserver