@anvia/studio 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2657 @@
1
+ // src/runtime/studio.ts
2
+ import {
3
+ Agent,
4
+ createHook as createHook3
5
+ } from "@anvia/core";
6
+ import { serve } from "@hono/node-server";
7
+ import { Hono as HonoApp } from "hono";
8
+
9
+ // src/traces/trace-observer.ts
10
+ var StudioTraceObserver = class {
11
+ constructor(options) {
12
+ this.options = options;
13
+ }
14
+ options;
15
+ startRun(args) {
16
+ const traceId = args.trace?.traceId ?? globalThis.crypto.randomUUID().replaceAll("-", "");
17
+ const observationId = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, 16);
18
+ return new StudioRunTraceObserver({
19
+ id: traceId,
20
+ observationId,
21
+ args,
22
+ store: this.store()
23
+ });
24
+ }
25
+ store() {
26
+ return typeof this.options.store === "function" ? this.options.store() : this.options.store;
27
+ }
28
+ };
29
+ var StudioRunTraceObserver = class {
30
+ constructor(props) {
31
+ this.props = props;
32
+ this.trace = { traceId: props.id, observationId: props.observationId };
33
+ }
34
+ props;
35
+ trace;
36
+ startedAt = /* @__PURE__ */ new Date();
37
+ observations = [];
38
+ startGeneration(args) {
39
+ const startedAt = /* @__PURE__ */ new Date();
40
+ return {
41
+ end: (endArgs) => {
42
+ this.observations.push(
43
+ traceObservation({
44
+ kind: "generation",
45
+ name: `model.turn.${args.turn}`,
46
+ status: "success",
47
+ turn: args.turn,
48
+ startedAt,
49
+ input: toJsonValue(args.request),
50
+ output: toJsonValue(endArgs.response),
51
+ metadata: {
52
+ model: args.request.model ?? "default",
53
+ toolCount: args.request.tools.length,
54
+ ...endArgs.firstDeltaMs === void 0 ? {} : { firstDeltaMs: endArgs.firstDeltaMs }
55
+ }
56
+ })
57
+ );
58
+ },
59
+ error: (errorArgs) => {
60
+ this.observations.push(
61
+ traceObservation({
62
+ kind: "generation",
63
+ name: `model.turn.${args.turn}`,
64
+ status: "error",
65
+ turn: args.turn,
66
+ startedAt,
67
+ input: toJsonValue(args.request),
68
+ error: serializeError(errorArgs.error),
69
+ metadata: {
70
+ model: args.request.model ?? "default",
71
+ toolCount: args.request.tools.length
72
+ }
73
+ })
74
+ );
75
+ }
76
+ };
77
+ }
78
+ startTool(args) {
79
+ const startedAt = /* @__PURE__ */ new Date();
80
+ return {
81
+ 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
+ );
94
+ },
95
+ 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
+ );
108
+ }
109
+ };
110
+ }
111
+ async end(args) {
112
+ await this.save("success", {
113
+ endedAt: /* @__PURE__ */ new Date(),
114
+ output: args.output,
115
+ usage: args.usage,
116
+ messages: toJsonValue(args.messages)
117
+ });
118
+ }
119
+ async error(args) {
120
+ await this.save("error", {
121
+ endedAt: /* @__PURE__ */ new Date(),
122
+ error: serializeError(args.error),
123
+ usage: args.usage,
124
+ messages: toJsonValue(args.messages)
125
+ });
126
+ }
127
+ async save(status, result) {
128
+ const sessionId = this.props.args.trace?.sessionId;
129
+ const store = this.props.store;
130
+ if (sessionId === void 0 || store === void 0) {
131
+ return;
132
+ }
133
+ const metadata = traceMetadata(this.props.args, result.messages);
134
+ const trace = {
135
+ id: this.props.id,
136
+ sessionId,
137
+ ...this.props.args.trace?.name === void 0 ? {} : { name: this.props.args.trace.name },
138
+ status,
139
+ trace: this.trace,
140
+ startedAt: this.startedAt.toISOString(),
141
+ endedAt: result.endedAt.toISOString(),
142
+ durationMs: durationMs(this.startedAt, result.endedAt),
143
+ input: toJsonValue({
144
+ instructions: this.props.args.instructions,
145
+ prompt: this.props.args.prompt,
146
+ history: this.props.args.history
147
+ }),
148
+ ...result.output === void 0 ? {} : { output: result.output },
149
+ ...result.error === void 0 ? {} : { error: result.error },
150
+ ...result.usage === void 0 ? {} : { usage: result.usage },
151
+ metadata,
152
+ observations: this.observations,
153
+ observationCount: this.observations.length
154
+ };
155
+ await store.saveTrace(trace);
156
+ }
157
+ };
158
+ function traceObservation(props) {
159
+ const endedAt = /* @__PURE__ */ new Date();
160
+ return {
161
+ id: globalThis.crypto.randomUUID(),
162
+ kind: props.kind,
163
+ name: props.name,
164
+ status: props.status,
165
+ turn: props.turn,
166
+ startedAt: props.startedAt.toISOString(),
167
+ endedAt: endedAt.toISOString(),
168
+ durationMs: durationMs(props.startedAt, endedAt),
169
+ ...props.input === void 0 ? {} : { input: props.input },
170
+ ...props.output === void 0 ? {} : { output: props.output },
171
+ ...props.error === void 0 ? {} : { error: props.error },
172
+ ...props.metadata === void 0 ? {} : { metadata: props.metadata }
173
+ };
174
+ }
175
+ function traceMetadata(args, messages) {
176
+ return compactJsonObject({
177
+ agentName: args.agentName,
178
+ agentDescription: args.agentDescription,
179
+ maxTurns: args.maxTurns,
180
+ userId: args.trace?.userId,
181
+ tags: args.trace?.tags,
182
+ version: args.trace?.version,
183
+ metadata: toJsonValue(args.trace?.metadata ?? {}),
184
+ messages
185
+ });
186
+ }
187
+ function toolMetadata(args, skipped) {
188
+ return compactJsonObject({
189
+ internalCallId: args.internalCallId,
190
+ toolCallId: args.toolCallId,
191
+ skipped
192
+ });
193
+ }
194
+ function compactJsonObject(values) {
195
+ const entries = Object.entries(values).flatMap(
196
+ ([key, value]) => value === void 0 ? [] : [[key, toJsonValue(value)]]
197
+ );
198
+ return Object.fromEntries(entries);
199
+ }
200
+ function durationMs(startedAt, endedAt) {
201
+ return Math.max(0, endedAt.getTime() - startedAt.getTime());
202
+ }
203
+ function parseOrString(value) {
204
+ try {
205
+ return toJsonValue(JSON.parse(value));
206
+ } catch {
207
+ return value;
208
+ }
209
+ }
210
+ function serializeError(error) {
211
+ if (error instanceof Error) {
212
+ return compactJsonObject({
213
+ name: error.name,
214
+ message: error.message,
215
+ stack: error.stack
216
+ });
217
+ }
218
+ return toJsonValue(error);
219
+ }
220
+ function toJsonValue(value) {
221
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
222
+ return value;
223
+ }
224
+ if (value === void 0) {
225
+ return null;
226
+ }
227
+ if (Array.isArray(value)) {
228
+ return value.map((item) => toJsonValue(item));
229
+ }
230
+ if (typeof value === "object") {
231
+ return compactJsonObject(value);
232
+ }
233
+ return String(value);
234
+ }
235
+
236
+ // src/ui/routes.tsx
237
+ import { readFile } from "fs/promises";
238
+ import { fileURLToPath } from "url";
239
+ function resolveStudioUiOptions(ui) {
240
+ const options = typeof ui === "object" ? ui : {};
241
+ return {
242
+ path: normalizeUiPath(options.path ?? "/ui"),
243
+ title: options.title ?? "Anvia Studio",
244
+ rootRoutes: options.rootRoutes ?? true,
245
+ redirectRoot: options.redirectRoot ?? true,
246
+ ...options.clientScript === void 0 ? {} : { clientScript: options.clientScript },
247
+ protectShell: options.protectShell ?? false
248
+ };
249
+ }
250
+ function isStudioUiEnabled(ui) {
251
+ return ui !== false;
252
+ }
253
+ function registerStudioUi(app, options) {
254
+ const scriptPath = `${options.path}/assets/client.js`;
255
+ const stylePath = `${options.path}/assets/styles.css`;
256
+ const renderShell = () => renderStudioUi({
257
+ options,
258
+ routePath: options.path,
259
+ clientScript: scriptPath,
260
+ stylesheet: stylePath
261
+ });
262
+ const renderRootShell = () => renderStudioUi({
263
+ options,
264
+ routePath: "",
265
+ clientScript: scriptPath,
266
+ stylesheet: stylePath
267
+ });
268
+ if (options.redirectRoot) {
269
+ app.get("/", (c) => c.redirect(studioUiEntryPath(options)));
270
+ }
271
+ app.get(options.path, async (c) => c.html(await renderShell()));
272
+ app.get(`${options.path}/:sessionId`, async (c) => c.html(await renderShell()));
273
+ app.get(`${options.path}/playground`, async (c) => c.html(await renderShell()));
274
+ app.get(`${options.path}/playground/:sessionId`, async (c) => c.html(await renderShell()));
275
+ app.get(`${options.path}/tracing`, async (c) => c.html(await renderShell()));
276
+ app.get(`${options.path}/tracing/:traceId`, async (c) => c.html(await renderShell()));
277
+ app.get(`${options.path}/tracing/sessions/:sessionId`, async (c) => c.html(await renderShell()));
278
+ app.get(`${options.path}/tracing/*`, async (c) => c.html(await renderShell()));
279
+ app.get(`${options.path}/sessions`, async (c) => c.html(await renderShell()));
280
+ app.get(`${options.path}/agents`, async (c) => c.html(await renderShell()));
281
+ app.get(`${options.path}/knowledge`, async (c) => c.html(await renderShell()));
282
+ if (options.rootRoutes) {
283
+ app.get("/playground", async (c) => c.html(await renderRootShell()));
284
+ app.get("/playground/:sessionId", async (c) => c.html(await renderRootShell()));
285
+ app.get("/tracing", async (c) => c.html(await renderRootShell()));
286
+ app.get("/tracing/:traceId", async (c) => c.html(await renderRootShell()));
287
+ app.get("/tracing/sessions/:sessionId", async (c) => c.html(await renderRootShell()));
288
+ app.get("/tracing/*", async (c) => c.html(await renderRootShell()));
289
+ }
290
+ app.get(scriptPath, async () => {
291
+ if (options.clientScript === void 0) {
292
+ return new Response(null, { status: 404 });
293
+ }
294
+ return new Response(options.clientScript, {
295
+ headers: {
296
+ "content-type": "text/javascript; charset=utf-8",
297
+ "cache-control": "no-cache"
298
+ }
299
+ });
300
+ });
301
+ app.get(stylePath, async () => {
302
+ const source = await readBundledLegacyStylesheet();
303
+ return new Response(source, {
304
+ headers: {
305
+ "content-type": "text/css; charset=utf-8",
306
+ "cache-control": "no-cache"
307
+ }
308
+ });
309
+ });
310
+ app.get(`${options.path}/assets/:asset`, async (c) => {
311
+ const asset = c.req.param("asset");
312
+ if (asset.includes("/") || asset.includes("..")) {
313
+ return new Response(null, { status: 404 });
314
+ }
315
+ const source = await readBundledUiAsset(asset);
316
+ if (source === void 0) {
317
+ return new Response(null, { status: 404 });
318
+ }
319
+ const body = new ArrayBuffer(source.byteLength);
320
+ new Uint8Array(body).set(source);
321
+ return new Response(body, {
322
+ headers: {
323
+ "content-type": contentTypeForAsset(asset),
324
+ "cache-control": "no-cache"
325
+ }
326
+ });
327
+ });
328
+ }
329
+ async function renderStudioUi(props) {
330
+ const { options } = props;
331
+ if (options.clientScript !== void 0) {
332
+ return renderLegacyStudioUiShell({
333
+ title: options.title,
334
+ uiPath: props.routePath,
335
+ compatUiPath: options.path,
336
+ clientScript: props.clientScript,
337
+ stylesheet: props.stylesheet
338
+ });
339
+ }
340
+ const index = await readBundledUiIndex();
341
+ return index.replace(/<title>.*?<\/title>/, `<title>${escapeHtml(options.title)}</title>`).replace(
342
+ 'data-ui-path="/ui"',
343
+ `data-ui-path="${escapeHtml(props.routePath)}" data-ui-compat-path="${escapeHtml(options.path)}"`
344
+ ).replaceAll('"/ui/', `"${escapeHtml(options.path)}/`);
345
+ }
346
+ function renderLegacyStudioUiShell(props) {
347
+ const title = escapeHtml(props.title);
348
+ return [
349
+ "<!doctype html>",
350
+ '<html lang="en" class="dark">',
351
+ "<head>",
352
+ '<meta charset="utf-8">',
353
+ '<meta name="viewport" content="width=device-width, initial-scale=1">',
354
+ `<title>${title}</title>`,
355
+ '<script>(()=>{try{if((localStorage.getItem("anvia-studio-theme")??localStorage.getItem("aion-studio-theme"))==="light"){document.documentElement.classList.remove("dark")}}catch{}})();</script>',
356
+ `<link rel="icon" type="image/png" href="${escapeHtml(props.compatUiPath)}/assets/logo.png">`,
357
+ `<link rel="stylesheet" href="${escapeHtml(props.stylesheet)}">`,
358
+ "</head>",
359
+ "<body>",
360
+ `<div id="anvia-ui" data-ui-path="${escapeHtml(props.uiPath)}" data-ui-compat-path="${escapeHtml(props.compatUiPath)}">`,
361
+ '<main class="shell-loading">',
362
+ "<div>",
363
+ `<img src="${escapeHtml(props.compatUiPath)}/assets/logo.png" alt="" width="32" height="32">`,
364
+ '<p class="eyebrow">Anvia</p>',
365
+ `<h1>${title}</h1>`,
366
+ "</div>",
367
+ "</main>",
368
+ "</div>",
369
+ `<script type="module" src="${escapeHtml(props.clientScript)}"></script>`,
370
+ "</body>",
371
+ "</html>"
372
+ ].join("");
373
+ }
374
+ function normalizeUiPath(path) {
375
+ const trimmed = path.trim();
376
+ const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
377
+ const withoutTrailingSlash = withSlash.length > 1 ? withSlash.replace(/\/+$/, "") : withSlash;
378
+ if (withoutTrailingSlash.length === 0 || withoutTrailingSlash === "/") {
379
+ return "/ui";
380
+ }
381
+ return withoutTrailingSlash;
382
+ }
383
+ function studioUiEntryPath(options) {
384
+ return options.rootRoutes ? "/playground" : options.path;
385
+ }
386
+ async function readBundledUiIndex() {
387
+ try {
388
+ return await readFile(fileURLToPath(new URL("./ui/index.html", import.meta.url)), "utf8");
389
+ } catch (error) {
390
+ if (!isNotFoundError(error)) {
391
+ throw error;
392
+ }
393
+ }
394
+ try {
395
+ return await readFile(
396
+ fileURLToPath(new URL("../../dist/ui/index.html", import.meta.url)),
397
+ "utf8"
398
+ );
399
+ } catch (error) {
400
+ if (!isNotFoundError(error)) {
401
+ throw error;
402
+ }
403
+ return readFile(fileURLToPath(new URL("./app/index.html", import.meta.url)), "utf8");
404
+ }
405
+ }
406
+ async function readBundledUiAsset(asset) {
407
+ try {
408
+ return await readFile(fileURLToPath(new URL(`./assets/${asset}`, import.meta.url)));
409
+ } catch (sourceError) {
410
+ if (!isNotFoundError(sourceError)) {
411
+ throw sourceError;
412
+ }
413
+ }
414
+ try {
415
+ return await readFile(fileURLToPath(new URL(`./ui/assets/${asset}`, import.meta.url)));
416
+ } catch (error) {
417
+ if (!isNotFoundError(error)) {
418
+ throw error;
419
+ }
420
+ try {
421
+ return await readFile(
422
+ fileURLToPath(new URL(`../../dist/ui/assets/${asset}`, import.meta.url))
423
+ );
424
+ } catch (fallbackError) {
425
+ if (!isNotFoundError(fallbackError)) {
426
+ throw fallbackError;
427
+ }
428
+ return void 0;
429
+ }
430
+ }
431
+ }
432
+ async function readBundledLegacyStylesheet() {
433
+ try {
434
+ return await readFile(fileURLToPath(new URL("./ui/styles.css", import.meta.url)), "utf8");
435
+ } catch (error) {
436
+ if (!isNotFoundError(error)) {
437
+ throw error;
438
+ }
439
+ try {
440
+ return await readFile(
441
+ fileURLToPath(new URL("../../dist/ui/styles.css", import.meta.url)),
442
+ "utf8"
443
+ );
444
+ } catch (fallbackError) {
445
+ if (!isNotFoundError(fallbackError)) {
446
+ throw fallbackError;
447
+ }
448
+ return "";
449
+ }
450
+ }
451
+ }
452
+ function isNotFoundError(error) {
453
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
454
+ }
455
+ function contentTypeForAsset(asset) {
456
+ if (asset.endsWith(".js")) {
457
+ return "text/javascript; charset=utf-8";
458
+ }
459
+ if (asset.endsWith(".css")) {
460
+ return "text/css; charset=utf-8";
461
+ }
462
+ if (asset.endsWith(".svg")) {
463
+ return "image/svg+xml";
464
+ }
465
+ if (asset.endsWith(".png")) {
466
+ return "image/png";
467
+ }
468
+ if (asset.endsWith(".woff2")) {
469
+ return "font/woff2";
470
+ }
471
+ return "application/octet-stream";
472
+ }
473
+ function escapeHtml(value) {
474
+ return value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
475
+ }
476
+
477
+ // src/runtime/approvals.ts
478
+ import { createHook, parseToolArgs } from "@anvia/core";
479
+
480
+ // src/runtime/shared.ts
481
+ import { join } from "path";
482
+
483
+ // src/storage/sqlite-store.ts
484
+ import { mkdirSync } from "fs";
485
+ import { createRequire } from "module";
486
+ import { dirname, resolve } from "path";
487
+ var { DatabaseSync } = createRequire(import.meta.url)(
488
+ "node:sqlite"
489
+ );
490
+ function createSqliteSessionStore(options = {}) {
491
+ return new SqliteSessionStore(options.path ?? ":memory:");
492
+ }
493
+ var SqliteSessionStore = class {
494
+ constructor(path) {
495
+ this.path = path;
496
+ }
497
+ path;
498
+ kind = "sqlite";
499
+ db;
500
+ listSessions(options) {
501
+ const db = this.database();
502
+ const agentClause = options.agentId === void 0 ? "" : "WHERE agent_id = $agentId";
503
+ const rows = db.prepare(
504
+ `SELECT id, agent_id, title, metadata_json, messages_json, transcript_json, created_at, updated_at
505
+ FROM runner_sessions
506
+ ${agentClause}
507
+ ORDER BY updated_at DESC
508
+ LIMIT $limit`
509
+ ).all({
510
+ $agentId: options.agentId ?? null,
511
+ $limit: options.limit
512
+ });
513
+ return rows.map(toSessionSummary);
514
+ }
515
+ createSession(input) {
516
+ const db = this.database();
517
+ const now = (/* @__PURE__ */ new Date()).toISOString();
518
+ db.prepare(
519
+ `INSERT INTO runner_sessions (
520
+ id,
521
+ agent_id,
522
+ title,
523
+ metadata_json,
524
+ messages_json,
525
+ transcript_json,
526
+ created_at,
527
+ updated_at
528
+ ) VALUES ($id, $agentId, $title, $metadata, '[]', '[]', $now, $now)`
529
+ ).run({
530
+ $id: input.id,
531
+ $agentId: input.agentId,
532
+ $title: input.title ?? null,
533
+ $metadata: input.metadata === void 0 ? null : JSON.stringify(input.metadata),
534
+ $now: now
535
+ });
536
+ return {
537
+ id: input.id,
538
+ agentId: input.agentId,
539
+ ...input.title === void 0 ? {} : { title: input.title },
540
+ createdAt: now,
541
+ updatedAt: now,
542
+ messageCount: 0,
543
+ ...input.metadata === void 0 ? {} : { metadata: input.metadata }
544
+ };
545
+ }
546
+ 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);
554
+ }
555
+ appendSessionRun(input) {
556
+ const db = this.database();
557
+ try {
558
+ db.exec("BEGIN IMMEDIATE");
559
+ const row = db.prepare(
560
+ `SELECT id, agent_id, title, metadata_json, messages_json, transcript_json, created_at, updated_at
561
+ FROM runner_sessions
562
+ WHERE id = $id`
563
+ ).get({ $id: input.id });
564
+ if (row === void 0) {
565
+ db.exec("ROLLBACK");
566
+ return void 0;
567
+ }
568
+ const current = toSession(row);
569
+ const messages = [...current.messages, ...input.messages];
570
+ const transcript = renumberTranscript([...current.transcript, ...input.transcript]);
571
+ const title = current.title ?? input.title;
572
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
573
+ db.prepare(
574
+ `UPDATE runner_sessions
575
+ SET title = $title,
576
+ messages_json = $messages,
577
+ transcript_json = $transcript,
578
+ updated_at = $updatedAt
579
+ WHERE id = $id`
580
+ ).run({
581
+ $id: input.id,
582
+ $title: title ?? null,
583
+ $messages: JSON.stringify(messages),
584
+ $transcript: JSON.stringify(transcript),
585
+ $updatedAt: updatedAt
586
+ });
587
+ db.exec("COMMIT");
588
+ return {
589
+ ...current,
590
+ ...title === void 0 ? {} : { title },
591
+ updatedAt,
592
+ messageCount: messages.length,
593
+ messages,
594
+ transcript
595
+ };
596
+ } catch (error) {
597
+ if (db.isTransaction) {
598
+ db.exec("ROLLBACK");
599
+ }
600
+ throw error;
601
+ }
602
+ }
603
+ deleteSession(id) {
604
+ const db = this.database();
605
+ try {
606
+ db.exec("BEGIN IMMEDIATE");
607
+ db.prepare("DELETE FROM runner_traces WHERE session_id = $id").run({ $id: id });
608
+ const result = db.prepare("DELETE FROM runner_sessions WHERE id = $id").run({ $id: id });
609
+ db.exec("COMMIT");
610
+ return Number(result.changes) > 0;
611
+ } catch (error) {
612
+ if (db.isTransaction) {
613
+ db.exec("ROLLBACK");
614
+ }
615
+ throw error;
616
+ }
617
+ }
618
+ listTraces(options) {
619
+ const db = this.database();
620
+ const filters = [];
621
+ const values = {
622
+ $limit: options.limit
623
+ };
624
+ if (options.agentId !== void 0) {
625
+ filters.push(
626
+ "(s.agent_id = $agentId OR json_extract(t.metadata_json, '$.metadata.agentId') = $agentId)"
627
+ );
628
+ values.$agentId = options.agentId;
629
+ }
630
+ if (options.sessionId !== void 0) {
631
+ filters.push("t.session_id = $sessionId");
632
+ values.$sessionId = options.sessionId;
633
+ }
634
+ if (options.status !== void 0) {
635
+ filters.push("t.status = $status");
636
+ values.$status = options.status;
637
+ }
638
+ const whereClause = filters.length === 0 ? "" : `WHERE ${filters.join(" AND ")}`;
639
+ const rows = db.prepare(
640
+ `SELECT t.id, t.session_id, t.name, t.status, t.trace_json, t.input_json, t.output,
641
+ t.error_json, t.usage_json, t.metadata_json, t.observations_json,
642
+ t.started_at, t.ended_at, t.duration_ms
643
+ FROM runner_traces t
644
+ LEFT JOIN runner_sessions s ON s.id = t.session_id
645
+ ${whereClause}
646
+ ORDER BY t.started_at DESC
647
+ LIMIT $limit`
648
+ ).all(values);
649
+ return rows.map(toTraceSummary);
650
+ }
651
+ listSessionTraces(options) {
652
+ const db = this.database();
653
+ const rows = db.prepare(
654
+ `SELECT id, session_id, name, status, trace_json, input_json, output, error_json,
655
+ usage_json, metadata_json, observations_json, started_at, ended_at, duration_ms
656
+ FROM runner_traces
657
+ WHERE session_id = $sessionId
658
+ ORDER BY started_at DESC
659
+ LIMIT $limit`
660
+ ).all({
661
+ $sessionId: options.sessionId,
662
+ $limit: options.limit
663
+ });
664
+ return rows.map(toTraceSummary);
665
+ }
666
+ getTrace(id) {
667
+ const db = this.database();
668
+ const row = db.prepare(
669
+ `SELECT id, session_id, name, status, trace_json, input_json, output, error_json,
670
+ usage_json, metadata_json, observations_json, started_at, ended_at, duration_ms
671
+ FROM runner_traces
672
+ WHERE id = $id`
673
+ ).get({ $id: id });
674
+ return row === void 0 ? void 0 : toTrace(row);
675
+ }
676
+ saveTrace(trace) {
677
+ const db = this.database();
678
+ db.prepare(
679
+ `INSERT INTO runner_traces (
680
+ id,
681
+ session_id,
682
+ name,
683
+ status,
684
+ trace_json,
685
+ input_json,
686
+ output,
687
+ error_json,
688
+ usage_json,
689
+ metadata_json,
690
+ observations_json,
691
+ started_at,
692
+ ended_at,
693
+ duration_ms
694
+ ) VALUES (
695
+ $id,
696
+ $sessionId,
697
+ $name,
698
+ $status,
699
+ $trace,
700
+ $input,
701
+ $output,
702
+ $error,
703
+ $usage,
704
+ $metadata,
705
+ $observations,
706
+ $startedAt,
707
+ $endedAt,
708
+ $durationMs
709
+ )
710
+ ON CONFLICT(id) DO UPDATE SET
711
+ session_id = excluded.session_id,
712
+ name = excluded.name,
713
+ status = excluded.status,
714
+ trace_json = excluded.trace_json,
715
+ input_json = excluded.input_json,
716
+ output = excluded.output,
717
+ error_json = excluded.error_json,
718
+ usage_json = excluded.usage_json,
719
+ metadata_json = excluded.metadata_json,
720
+ observations_json = excluded.observations_json,
721
+ started_at = excluded.started_at,
722
+ ended_at = excluded.ended_at,
723
+ duration_ms = excluded.duration_ms`
724
+ ).run({
725
+ $id: trace.id,
726
+ $sessionId: trace.sessionId,
727
+ $name: trace.name ?? null,
728
+ $status: trace.status,
729
+ $trace: trace.trace === void 0 ? null : JSON.stringify(trace.trace),
730
+ $input: trace.input === void 0 ? null : JSON.stringify(trace.input),
731
+ $output: trace.output ?? null,
732
+ $error: trace.error === void 0 ? null : JSON.stringify(trace.error),
733
+ $usage: trace.usage === void 0 ? null : JSON.stringify(trace.usage),
734
+ $metadata: trace.metadata === void 0 ? null : JSON.stringify(trace.metadata),
735
+ $observations: JSON.stringify(trace.observations),
736
+ $startedAt: trace.startedAt,
737
+ $endedAt: trace.endedAt ?? null,
738
+ $durationMs: trace.durationMs ?? null
739
+ });
740
+ return trace;
741
+ }
742
+ database() {
743
+ if (this.db !== void 0) {
744
+ return this.db;
745
+ }
746
+ if (this.path !== ":memory:") {
747
+ mkdirSync(dirname(resolve(this.path)), { recursive: true });
748
+ }
749
+ const db = new DatabaseSync(this.path, {
750
+ allowUnknownNamedParameters: true,
751
+ timeout: 5e3
752
+ });
753
+ db.exec(`
754
+ PRAGMA journal_mode = WAL;
755
+ PRAGMA foreign_keys = ON;
756
+ CREATE TABLE IF NOT EXISTS runner_sessions (
757
+ id TEXT PRIMARY KEY,
758
+ agent_id TEXT NOT NULL,
759
+ title TEXT,
760
+ metadata_json TEXT,
761
+ messages_json TEXT NOT NULL,
762
+ transcript_json TEXT NOT NULL,
763
+ created_at TEXT NOT NULL,
764
+ updated_at TEXT NOT NULL
765
+ ) STRICT;
766
+ CREATE INDEX IF NOT EXISTS runner_sessions_agent_updated_idx
767
+ ON runner_sessions(agent_id, updated_at DESC);
768
+ CREATE TABLE IF NOT EXISTS runner_traces (
769
+ id TEXT PRIMARY KEY,
770
+ session_id TEXT NOT NULL,
771
+ name TEXT,
772
+ status TEXT NOT NULL,
773
+ trace_json TEXT,
774
+ input_json TEXT,
775
+ output TEXT,
776
+ error_json TEXT,
777
+ usage_json TEXT,
778
+ metadata_json TEXT,
779
+ observations_json TEXT NOT NULL,
780
+ started_at TEXT NOT NULL,
781
+ ended_at TEXT,
782
+ duration_ms INTEGER
783
+ ) STRICT;
784
+ CREATE INDEX IF NOT EXISTS runner_traces_session_started_idx
785
+ ON runner_traces(session_id, started_at DESC);
786
+ `);
787
+ this.db = db;
788
+ return db;
789
+ }
790
+ };
791
+ function toSession(row) {
792
+ const summary = toSessionSummary(row);
793
+ return {
794
+ ...summary,
795
+ messages: parseJsonArray(row.messages_json),
796
+ transcript: renumberTranscript(parseJsonArray(row.transcript_json))
797
+ };
798
+ }
799
+ function toSessionSummary(row) {
800
+ const messages = parseJsonArray(row.messages_json);
801
+ const metadata = parseJsonValue(row.metadata_json);
802
+ return {
803
+ id: row.id,
804
+ agentId: row.agent_id,
805
+ ...row.title === null ? {} : { title: row.title },
806
+ createdAt: row.created_at,
807
+ updatedAt: row.updated_at,
808
+ messageCount: messages.length,
809
+ ...metadata === void 0 ? {} : { metadata }
810
+ };
811
+ }
812
+ function toTrace(row) {
813
+ const trace = parseJsonValue(row.trace_json);
814
+ const input = parseJsonValue(row.input_json);
815
+ return {
816
+ ...toTraceSummary(row),
817
+ ...trace === void 0 ? {} : { trace },
818
+ ...input === void 0 ? {} : { input },
819
+ observations: parseJsonArray(row.observations_json)
820
+ };
821
+ }
822
+ function toTraceSummary(row) {
823
+ const observations = parseJsonArray(row.observations_json);
824
+ const error = parseJsonValue(row.error_json);
825
+ const usage = parseJsonValue(row.usage_json);
826
+ const metadata = parseJsonValue(row.metadata_json);
827
+ return {
828
+ id: row.id,
829
+ sessionId: row.session_id,
830
+ ...row.name === null ? {} : { name: row.name },
831
+ status: row.status,
832
+ startedAt: row.started_at,
833
+ ...row.ended_at === null ? {} : { endedAt: row.ended_at },
834
+ ...row.duration_ms === null ? {} : { durationMs: row.duration_ms },
835
+ ...row.output === null ? {} : { output: row.output },
836
+ ...error === void 0 ? {} : { error },
837
+ ...usage === void 0 ? {} : { usage },
838
+ ...metadata === void 0 ? {} : { metadata },
839
+ observationCount: observations.length
840
+ };
841
+ }
842
+ function parseJsonArray(value) {
843
+ const parsed = JSON.parse(value);
844
+ return Array.isArray(parsed) ? parsed : [];
845
+ }
846
+ function parseJsonValue(value) {
847
+ if (value === null) {
848
+ return void 0;
849
+ }
850
+ return JSON.parse(value);
851
+ }
852
+ function renumberTranscript(entries) {
853
+ return entries.map((entry, entryId) => ({ ...entry, entryId }));
854
+ }
855
+
856
+ // src/runtime/shared.ts
857
+ function resolveStores(options) {
858
+ const defaultPath = process.env.ANVIA_STUDIO_DB ?? process.env.AION_STUDIO_DB ?? join(process.cwd(), ".anvia-studio", `${safeFileName(runnerId(options))}.sqlite`);
859
+ const defaultStore = createSqliteSessionStore({ path: defaultPath });
860
+ const sessions = resolveSessionStore(options, defaultStore);
861
+ const traces = resolveTraceStore(options, sessions, defaultStore);
862
+ return {
863
+ ...sessions === void 0 ? {} : { sessions },
864
+ ...traces === void 0 ? {} : { traces }
865
+ };
866
+ }
867
+ function resolveSessionStore(options, defaultStore) {
868
+ if (options.stores?.sessions === false) {
869
+ return void 0;
870
+ }
871
+ if (options.stores?.sessions !== void 0) {
872
+ return options.stores.sessions;
873
+ }
874
+ return defaultStore;
875
+ }
876
+ function resolveTraceStore(options, sessionStore, defaultStore) {
877
+ if (options.stores?.traces !== void 0) {
878
+ return options.stores.traces;
879
+ }
880
+ if (sessionStore === void 0) {
881
+ return void 0;
882
+ }
883
+ if (isTraceStore(sessionStore)) {
884
+ return sessionStore;
885
+ }
886
+ return defaultStore;
887
+ }
888
+ function isTraceStore(store) {
889
+ const candidate = store;
890
+ return typeof candidate.listSessionTraces === "function" && typeof candidate.getTrace === "function" && typeof candidate.saveTrace === "function";
891
+ }
892
+ function unsupportedCapabilities(stores) {
893
+ return [
894
+ ...stores.sessions === void 0 ? ["sessions"] : [],
895
+ ...stores.traces === void 0 ? ["traces"] : []
896
+ ];
897
+ }
898
+ function normalizeAgents(agents) {
899
+ const ids = /* @__PURE__ */ new Set();
900
+ return agents.map((agent) => {
901
+ const id = agent.id.trim();
902
+ if (id.length === 0) {
903
+ throw new Error("Studio agent id cannot be empty");
904
+ }
905
+ if (ids.has(id)) {
906
+ throw new Error(`Duplicate runner agent id: ${id}`);
907
+ }
908
+ ids.add(id);
909
+ return { ...agent, id };
910
+ });
911
+ }
912
+ function buildConfig(options, agents, stores) {
913
+ return {
914
+ id: runnerId(options),
915
+ ...options.name === void 0 ? {} : { name: options.name },
916
+ ...options.description === void 0 ? {} : { description: options.description },
917
+ ...options.version === void 0 ? {} : { version: options.version },
918
+ agents: agents.map(agentConfig),
919
+ chat: {
920
+ quickPrompts: Object.fromEntries(agents.map((agent) => [agent.id, agent.quickPrompts ?? []]))
921
+ },
922
+ capabilities: capabilityConfig(options, agents, stores),
923
+ unsupportedCapabilities: unsupportedCapabilities(stores)
924
+ };
925
+ }
926
+ function runnerId(options) {
927
+ return options.id ?? "anvia-studio";
928
+ }
929
+ function agentConfig(agent) {
930
+ const name = agent.name ?? agent.agent.name;
931
+ const description = agent.description ?? agent.agent.description;
932
+ return {
933
+ id: agent.id,
934
+ ...name === void 0 ? {} : { name },
935
+ ...description === void 0 ? {} : { description },
936
+ quickPrompts: agent.quickPrompts ?? [],
937
+ ...agent.metadata === void 0 ? {} : { metadata: agent.metadata }
938
+ };
939
+ }
940
+ function capabilityConfig(_options, agents, stores) {
941
+ const capabilities = {
942
+ agents: { enabled: true }
943
+ };
944
+ if (stores.sessions !== void 0) {
945
+ capabilities.sessions = { enabled: true };
946
+ }
947
+ if (stores.traces !== void 0) {
948
+ capabilities.traces = { enabled: true };
949
+ }
950
+ if (agents.some((agent) => agent.agent.observers.length > 0)) {
951
+ capabilities.observability = { enabled: true };
952
+ }
953
+ if (agents.some((agent) => agent.agent.toolSet.values().some((tool) => tool.approval))) {
954
+ capabilities.approvals = { enabled: true };
955
+ }
956
+ if (agents.some(
957
+ (agent) => agent.agent.staticContext.length > 0 || agent.agent.dynamicContexts.length > 0 || agent.agent.dynamicTools.length > 0
958
+ )) {
959
+ capabilities.knowledge = { enabled: true };
960
+ }
961
+ return capabilities;
962
+ }
963
+ function optionalQueryString(value) {
964
+ const trimmed = value?.trim();
965
+ return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
966
+ }
967
+ function parseLimit(value) {
968
+ if (value === void 0 || value.trim().length === 0) {
969
+ return 50;
970
+ }
971
+ const limit = Number(value);
972
+ if (!Number.isInteger(limit) || limit <= 0) {
973
+ return void 0;
974
+ }
975
+ return Math.min(limit, 100);
976
+ }
977
+ function parseTraceStatus(value) {
978
+ const status = optionalQueryString(value);
979
+ if (status === void 0) {
980
+ return void 0;
981
+ }
982
+ return status === "running" || status === "success" || status === "error" ? status : false;
983
+ }
984
+ function safeFileName(value) {
985
+ return value.replace(/[^a-zA-Z0-9._-]+/g, "_") || "anvia-studio";
986
+ }
987
+ function isMessageInput(value) {
988
+ return typeof value === "string" || isMessage(value);
989
+ }
990
+ function isMessage(value) {
991
+ if (!isObject(value) || typeof value.role !== "string") {
992
+ return false;
993
+ }
994
+ if (value.role === "system") {
995
+ return typeof value.content === "string";
996
+ }
997
+ if (value.role === "user" || value.role === "assistant" || value.role === "tool") {
998
+ return Array.isArray(value.content);
999
+ }
1000
+ return false;
1001
+ }
1002
+ function isObject(value) {
1003
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1004
+ }
1005
+ function isJsonObject(value) {
1006
+ return isObject(value) && Object.values(value).every(isJsonValue);
1007
+ }
1008
+ function isJsonValue(value) {
1009
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1010
+ return true;
1011
+ }
1012
+ if (Array.isArray(value)) {
1013
+ return value.every(isJsonValue);
1014
+ }
1015
+ return isJsonObject(value);
1016
+ }
1017
+ function isAgentTraceOptions(value) {
1018
+ if (!isObject(value)) {
1019
+ return false;
1020
+ }
1021
+ return optionalString(value.name) && optionalString(value.userId) && optionalString(value.sessionId) && optionalString(value.version) && optionalString(value.traceId) && optionalBoolean(value.failOnObserverError) && optionalStringArray(value.tags) && optionalObject(value.metadata);
1022
+ }
1023
+ function optionalString(value) {
1024
+ return value === void 0 || typeof value === "string";
1025
+ }
1026
+ function optionalBoolean(value) {
1027
+ return value === void 0 || typeof value === "boolean";
1028
+ }
1029
+ function optionalStringArray(value) {
1030
+ return value === void 0 || Array.isArray(value) && value.every((item) => typeof item === "string");
1031
+ }
1032
+ function optionalObject(value) {
1033
+ return value === void 0 || isObject(value);
1034
+ }
1035
+ function isNonNegativeInteger(value) {
1036
+ return Number.isInteger(value) && typeof value === "number" && value >= 0;
1037
+ }
1038
+ function isPositiveInteger(value) {
1039
+ return Number.isInteger(value) && typeof value === "number" && value > 0;
1040
+ }
1041
+ function unsupportedCapability(c, capability) {
1042
+ return errorResponse(
1043
+ c,
1044
+ 501,
1045
+ "unsupported_capability",
1046
+ `Capability "${capability}" is not implemented by this runner`,
1047
+ { capability }
1048
+ );
1049
+ }
1050
+ function errorResponse(c, status, code, message, details) {
1051
+ const body = {
1052
+ error: {
1053
+ code,
1054
+ message
1055
+ }
1056
+ };
1057
+ if (details !== void 0) {
1058
+ body.error.details = details;
1059
+ }
1060
+ return c.json(body, status);
1061
+ }
1062
+ function serializeError2(error) {
1063
+ if (error instanceof Error) {
1064
+ return {
1065
+ name: error.name,
1066
+ message: error.message,
1067
+ ...error.stack === void 0 ? {} : { stack: error.stack }
1068
+ };
1069
+ }
1070
+ return isJsonValue(error) ? error : String(error);
1071
+ }
1072
+
1073
+ // src/runtime/approvals.ts
1074
+ function registerApprovalRoutes(app, approvals) {
1075
+ app.get("/approvals", (c) => {
1076
+ const status = parseApprovalStatus(c.req.query("status"));
1077
+ if (status === false) {
1078
+ return errorResponse(c, 400, "bad_request", "status must be pending or resolved");
1079
+ }
1080
+ const options = {};
1081
+ const runId = optionalQueryString(c.req.query("runId"));
1082
+ const agentId = optionalQueryString(c.req.query("agentId"));
1083
+ const sessionId = optionalQueryString(c.req.query("sessionId"));
1084
+ if (status !== void 0) {
1085
+ options.status = status;
1086
+ }
1087
+ if (runId !== void 0) {
1088
+ options.runId = runId;
1089
+ }
1090
+ if (agentId !== void 0) {
1091
+ options.agentId = agentId;
1092
+ }
1093
+ if (sessionId !== void 0) {
1094
+ options.sessionId = sessionId;
1095
+ }
1096
+ return c.json({
1097
+ approvals: approvals.list(options)
1098
+ });
1099
+ });
1100
+ app.post("/approvals/:approvalId/decision", async (c) => {
1101
+ const body = await parseApprovalDecisionRequest(c);
1102
+ if ("error" in body) {
1103
+ return body.error;
1104
+ }
1105
+ const result = approvals.decide(c.req.param("approvalId"), body);
1106
+ if (result === "missing") {
1107
+ return errorResponse(c, 404, "not_found", "Approval not found");
1108
+ }
1109
+ if (result === "resolved") {
1110
+ return errorResponse(c, 409, "conflict", "Approval is already resolved");
1111
+ }
1112
+ return c.json(result);
1113
+ });
1114
+ }
1115
+ function parseApprovalStatus(value) {
1116
+ const status = optionalQueryString(value);
1117
+ if (status === void 0) {
1118
+ return void 0;
1119
+ }
1120
+ return status === "pending" || status === "resolved" ? status : false;
1121
+ }
1122
+ async function parseApprovalDecisionRequest(c) {
1123
+ let body;
1124
+ try {
1125
+ body = await c.req.json();
1126
+ } catch {
1127
+ return { error: errorResponse(c, 400, "bad_request", "Request body must be JSON") };
1128
+ }
1129
+ if (!isObject(body)) {
1130
+ return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
1131
+ }
1132
+ if (typeof body.approved !== "boolean") {
1133
+ return { error: errorResponse(c, 400, "bad_request", "approved must be a boolean") };
1134
+ }
1135
+ if ("reason" in body && typeof body.reason !== "string") {
1136
+ return { error: errorResponse(c, 400, "bad_request", "reason must be a string") };
1137
+ }
1138
+ return {
1139
+ approved: body.approved,
1140
+ ...typeof body.reason === "string" && body.reason.trim().length > 0 ? { reason: body.reason.trim() } : {}
1141
+ };
1142
+ }
1143
+ function createApprovalRuntime() {
1144
+ const approvals = /* @__PURE__ */ new Map();
1145
+ return {
1146
+ approvals,
1147
+ createHook(context) {
1148
+ return createHook({
1149
+ async onToolCall({ toolName, toolCallId, internalCallId, args, tool: control }) {
1150
+ const registeredTool = context.getTool(toolName);
1151
+ if (registeredTool?.approval === void 0) {
1152
+ return control.run();
1153
+ }
1154
+ const approval = registeredTool.approval;
1155
+ const rawParsedArgs = parseToolArgs(args);
1156
+ const parsedArgs = registeredTool.parseApprovalArgs?.(rawParsedArgs) ?? rawParsedArgs;
1157
+ const approvalContext = {
1158
+ toolName,
1159
+ args: parsedArgs,
1160
+ rawArgs: args,
1161
+ ...toolCallId === void 0 ? {} : { toolCallId },
1162
+ internalCallId,
1163
+ run: {
1164
+ agentId: context.agentId,
1165
+ runId: context.runId,
1166
+ ...context.sessionId === void 0 ? {} : { sessionId: context.sessionId },
1167
+ ...context.metadata === void 0 ? {} : { metadata: context.metadata }
1168
+ }
1169
+ };
1170
+ const required = await approval.when(approvalContext);
1171
+ if (!required) {
1172
+ return control.run();
1173
+ }
1174
+ const reason = await resolveApprovalText(approval.reason, approvalContext);
1175
+ const rejectMessage = await resolveApprovalText(approval.rejectMessage, approvalContext);
1176
+ const decision = await requestApproval(approvals, context, {
1177
+ toolName,
1178
+ ...toolCallId === void 0 ? {} : { toolCallId },
1179
+ internalCallId,
1180
+ args,
1181
+ ...reason === void 0 ? {} : { reason },
1182
+ ...rejectMessage === void 0 ? {} : { rejectMessage }
1183
+ });
1184
+ return decision.approved ? control.run() : control.skip(decision.reason ?? rejectMessage ?? "Rejected in Anvia Studio.");
1185
+ }
1186
+ });
1187
+ },
1188
+ list(options) {
1189
+ return [...approvals.values()].filter((approval) => {
1190
+ if (options.status === "pending" && approval.status !== "pending") {
1191
+ return false;
1192
+ }
1193
+ if (options.status === "resolved" && approval.status === "pending") {
1194
+ return false;
1195
+ }
1196
+ if (options.runId !== void 0 && approval.runId !== options.runId) {
1197
+ return false;
1198
+ }
1199
+ if (options.agentId !== void 0 && approval.agentId !== options.agentId) {
1200
+ return false;
1201
+ }
1202
+ if (options.sessionId !== void 0 && approval.sessionId !== options.sessionId) {
1203
+ return false;
1204
+ }
1205
+ return true;
1206
+ }).map(publicApproval);
1207
+ },
1208
+ decide(id, decision) {
1209
+ const approval = approvals.get(id);
1210
+ if (approval === void 0) {
1211
+ return "missing";
1212
+ }
1213
+ if (!isPendingApproval(approval)) {
1214
+ return "resolved";
1215
+ }
1216
+ const reason = decision.approved ? decision.reason : decision.reason ?? approval.rejectMessage ?? "Rejected in Anvia Studio.";
1217
+ const resolved = resolveApproval(approval, decision.approved ? "approved" : "rejected", {
1218
+ ...reason === void 0 ? {} : { reason }
1219
+ });
1220
+ approvals.set(id, resolved);
1221
+ approval.emit?.({ type: "tool_approval_result", approval: resolved });
1222
+ approval.resolve({
1223
+ approved: decision.approved,
1224
+ ...reason === void 0 ? {} : { reason }
1225
+ });
1226
+ return publicApproval(resolved);
1227
+ }
1228
+ };
1229
+ }
1230
+ async function requestApproval(approvals, context, request) {
1231
+ const id = globalThis.crypto.randomUUID();
1232
+ const approval = {
1233
+ id,
1234
+ runId: context.runId,
1235
+ agentId: context.agentId,
1236
+ ...context.sessionId === void 0 ? {} : { sessionId: context.sessionId },
1237
+ toolName: request.toolName,
1238
+ ...request.toolCallId === void 0 ? {} : { callId: request.toolCallId },
1239
+ internalCallId: request.internalCallId,
1240
+ args: request.args,
1241
+ status: "pending",
1242
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
1243
+ ...request.reason === void 0 ? {} : { reason: request.reason },
1244
+ ...request.rejectMessage === void 0 ? {} : { rejectMessage: request.rejectMessage },
1245
+ ...context.emit === void 0 ? {} : { emit: context.emit },
1246
+ resolve: () => {
1247
+ }
1248
+ };
1249
+ const decision = new Promise((resolve2) => {
1250
+ approval.resolve = (decision2) => {
1251
+ const current = approvals.get(id);
1252
+ if (!isPendingApproval(current)) {
1253
+ if (current !== void 0) {
1254
+ resolve2({
1255
+ approved: current.status === "approved",
1256
+ ...current.reason === void 0 ? {} : { reason: current.reason }
1257
+ });
1258
+ }
1259
+ return;
1260
+ }
1261
+ const reason = decision2.approved ? decision2.reason : decision2.reason ?? request.rejectMessage ?? "Rejected in Anvia Studio.";
1262
+ const resolved = resolveApproval(current, decision2.approved ? "approved" : "rejected", {
1263
+ ...reason === void 0 ? {} : { reason }
1264
+ });
1265
+ approvals.set(id, resolved);
1266
+ context.emit?.({ type: "tool_approval_result", approval: resolved });
1267
+ resolve2({
1268
+ approved: decision2.approved,
1269
+ ...reason === void 0 ? {} : { reason }
1270
+ });
1271
+ };
1272
+ });
1273
+ approvals.set(id, approval);
1274
+ context.emit?.({ type: "tool_approval_request", approval: publicApproval(approval) });
1275
+ return decision;
1276
+ }
1277
+ async function resolveApprovalText(value, context) {
1278
+ return typeof value === "function" ? value(context) : value;
1279
+ }
1280
+ function isPendingApproval(approval) {
1281
+ return approval !== void 0 && approval.status === "pending" && "resolve" in approval;
1282
+ }
1283
+ function resolveApproval(approval, status, options = {}) {
1284
+ return publicApproval({
1285
+ ...approval,
1286
+ status,
1287
+ resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
1288
+ ...options.reason === void 0 ? {} : { reason: options.reason }
1289
+ });
1290
+ }
1291
+ function publicApproval(approval) {
1292
+ const { emit, rejectMessage, resolve: resolve2, ...rest } = approval;
1293
+ void emit;
1294
+ void rejectMessage;
1295
+ void resolve2;
1296
+ return rest;
1297
+ }
1298
+
1299
+ // src/runtime/json.ts
1300
+ function toJsonValue2(value) {
1301
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1302
+ return value;
1303
+ }
1304
+ if (value === void 0) {
1305
+ return null;
1306
+ }
1307
+ if (Array.isArray(value)) {
1308
+ return value.map((item) => toJsonValue2(item));
1309
+ }
1310
+ if (typeof value === "object") {
1311
+ return compactJsonObject2(value);
1312
+ }
1313
+ return String(value);
1314
+ }
1315
+ function compactJsonObject2(values) {
1316
+ const entries = Object.entries(values).flatMap(
1317
+ ([key, value]) => value === void 0 ? [] : [[key, toJsonValue2(value)]]
1318
+ );
1319
+ return Object.fromEntries(entries);
1320
+ }
1321
+
1322
+ // src/runtime/knowledge.ts
1323
+ function registerKnowledgeRoutes(app, props) {
1324
+ app.get("/knowledge", async (c) => {
1325
+ const limit = parseLimit(c.req.query("limit"));
1326
+ if (limit === void 0) {
1327
+ return errorResponse(c, 400, "bad_request", "Invalid limit");
1328
+ }
1329
+ const summary = {
1330
+ agents: props.agents.map(agentKnowledgeConfig),
1331
+ evidence: await recentKnowledgeEvidence(props.traceStore, limit)
1332
+ };
1333
+ return c.json(summary);
1334
+ });
1335
+ }
1336
+ function agentKnowledgeConfig(agent) {
1337
+ const agentName = agent.name ?? agent.agent.name;
1338
+ return {
1339
+ agentId: agent.id,
1340
+ ...agentName === void 0 ? {} : { agentName },
1341
+ sources: [
1342
+ { kind: "static_context", count: agent.agent.staticContext.length },
1343
+ { kind: "dynamic_context", count: agent.agent.dynamicContexts.length },
1344
+ { kind: "dynamic_tools", count: agent.agent.dynamicTools.length }
1345
+ ],
1346
+ staticContext: agent.agent.staticContext.map((document) => ({
1347
+ id: document.id,
1348
+ text: document.text,
1349
+ ...document.additionalProps === void 0 ? {} : { additionalProps: jsonObjectFromRecord(document.additionalProps) }
1350
+ }))
1351
+ };
1352
+ }
1353
+ async function recentKnowledgeEvidence(traceStore, limit) {
1354
+ if (traceStore?.listTraces === void 0) {
1355
+ return [];
1356
+ }
1357
+ const store = traceStore;
1358
+ const listTraces = store.listTraces;
1359
+ if (listTraces === void 0) {
1360
+ return [];
1361
+ }
1362
+ const summaries = await listTraces.call(store, { limit });
1363
+ const traces = await Promise.all(
1364
+ summaries.map((summary) => Promise.resolve(store.getTrace(summary.id)).catch(() => void 0))
1365
+ );
1366
+ return traces.flatMap(
1367
+ (trace) => trace === void 0 ? [] : evidenceFromTrace(trace)
1368
+ );
1369
+ }
1370
+ function evidenceFromTrace(trace) {
1371
+ return trace.observations.flatMap((observation) => {
1372
+ if (observation.kind !== "generation" || !isRecord(observation.input)) {
1373
+ return [];
1374
+ }
1375
+ const documents = Array.isArray(observation.input.documents) ? observation.input.documents.flatMap((document) => evidenceDocument(document)) : [];
1376
+ const tools = Array.isArray(observation.input.tools) ? observation.input.tools.flatMap((tool) => evidenceToolName(tool)) : [];
1377
+ if (documents.length === 0 && tools.length === 0) {
1378
+ return [];
1379
+ }
1380
+ const query = queryFromGenerationInput(observation.input);
1381
+ return [
1382
+ {
1383
+ traceId: trace.id,
1384
+ sessionId: trace.sessionId,
1385
+ observationId: observation.id,
1386
+ observationName: observation.name,
1387
+ turn: observation.turn,
1388
+ startedAt: observation.startedAt,
1389
+ ...query === void 0 ? {} : { query },
1390
+ documentCount: documents.length,
1391
+ toolCount: tools.length,
1392
+ documents,
1393
+ tools
1394
+ }
1395
+ ];
1396
+ });
1397
+ }
1398
+ function queryFromGenerationInput(value) {
1399
+ const promptText = messageText(value.prompt);
1400
+ if (promptText.length > 0) {
1401
+ return promptText;
1402
+ }
1403
+ if (Array.isArray(value.chatHistory)) {
1404
+ for (let index = value.chatHistory.length - 1; index >= 0; index -= 1) {
1405
+ const text = messageText(value.chatHistory[index]);
1406
+ if (text.length > 0) {
1407
+ return text;
1408
+ }
1409
+ }
1410
+ }
1411
+ if (Array.isArray(value.history)) {
1412
+ for (let index = value.history.length - 1; index >= 0; index -= 1) {
1413
+ const text = messageText(value.history[index]);
1414
+ if (text.length > 0) {
1415
+ return text;
1416
+ }
1417
+ }
1418
+ }
1419
+ return void 0;
1420
+ }
1421
+ function messageText(value) {
1422
+ if (typeof value === "string") {
1423
+ return value.trim();
1424
+ }
1425
+ if (!isRecord(value)) {
1426
+ return "";
1427
+ }
1428
+ if (typeof value.text === "string") {
1429
+ return value.text.trim();
1430
+ }
1431
+ if (typeof value.content === "string") {
1432
+ return value.content.trim();
1433
+ }
1434
+ if (Array.isArray(value.content)) {
1435
+ return value.content.map(contentText).filter(Boolean).join("\n").trim();
1436
+ }
1437
+ return "";
1438
+ }
1439
+ function contentText(value) {
1440
+ if (typeof value === "string") {
1441
+ return value.trim();
1442
+ }
1443
+ if (!isRecord(value)) {
1444
+ return "";
1445
+ }
1446
+ if (typeof value.text === "string") {
1447
+ return value.text.trim();
1448
+ }
1449
+ return "";
1450
+ }
1451
+ function evidenceDocument(value) {
1452
+ if (!isRecord(value)) {
1453
+ return [];
1454
+ }
1455
+ const id = typeof value.id === "string" ? value.id : void 0;
1456
+ const text = typeof value.text === "string" ? value.text : void 0;
1457
+ const additionalProps = isRecord(value.additionalProps) ? jsonObjectFromRecord(value.additionalProps) : void 0;
1458
+ if (id === void 0 && text === void 0 && additionalProps === void 0) {
1459
+ return [];
1460
+ }
1461
+ return [
1462
+ {
1463
+ ...id === void 0 ? {} : { id },
1464
+ ...text === void 0 ? {} : { text },
1465
+ ...additionalProps === void 0 ? {} : { additionalProps }
1466
+ }
1467
+ ];
1468
+ }
1469
+ function evidenceToolName(value) {
1470
+ if (!isRecord(value) || typeof value.name !== "string") {
1471
+ return [];
1472
+ }
1473
+ return [value.name];
1474
+ }
1475
+ function jsonObjectFromRecord(value) {
1476
+ return compactJsonObject2(value);
1477
+ }
1478
+ function isRecord(value) {
1479
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1480
+ }
1481
+
1482
+ // src/runtime/questions.ts
1483
+ import { createHook as createHook2, parseToolArgs as parseToolArgs2 } from "@anvia/core";
1484
+ function registerQuestionRoutes(app, questions) {
1485
+ app.get("/questions", (c) => {
1486
+ const status = parseQuestionStatus(c.req.query("status"));
1487
+ if (status === false) {
1488
+ return errorResponse(c, 400, "bad_request", "status must be pending or resolved");
1489
+ }
1490
+ const options = {};
1491
+ const runId = optionalQueryString(c.req.query("runId"));
1492
+ const agentId = optionalQueryString(c.req.query("agentId"));
1493
+ const sessionId = optionalQueryString(c.req.query("sessionId"));
1494
+ if (status !== void 0) {
1495
+ options.status = status;
1496
+ }
1497
+ if (runId !== void 0) {
1498
+ options.runId = runId;
1499
+ }
1500
+ if (agentId !== void 0) {
1501
+ options.agentId = agentId;
1502
+ }
1503
+ if (sessionId !== void 0) {
1504
+ options.sessionId = sessionId;
1505
+ }
1506
+ return c.json({ questions: questions.list(options) });
1507
+ });
1508
+ app.post("/questions/:questionId/answer", async (c) => {
1509
+ const body = await parseQuestionAnswerRequest(c);
1510
+ if ("error" in body) {
1511
+ return body.error;
1512
+ }
1513
+ const result = questions.answer(c.req.param("questionId"), body.answers);
1514
+ if (result === "missing") {
1515
+ return errorResponse(c, 404, "not_found", "Question not found");
1516
+ }
1517
+ if (result === "resolved") {
1518
+ return errorResponse(c, 409, "conflict", "Question is already answered");
1519
+ }
1520
+ return c.json(result);
1521
+ });
1522
+ }
1523
+ function parseQuestionStatus(value) {
1524
+ const status = optionalQueryString(value);
1525
+ if (status === void 0) {
1526
+ return void 0;
1527
+ }
1528
+ return status === "pending" || status === "resolved" ? status : false;
1529
+ }
1530
+ async function parseQuestionAnswerRequest(c) {
1531
+ let body;
1532
+ try {
1533
+ body = await c.req.json();
1534
+ } catch {
1535
+ return { error: errorResponse(c, 400, "bad_request", "Request body must be JSON") };
1536
+ }
1537
+ if (!isObject(body)) {
1538
+ return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
1539
+ }
1540
+ if (!Array.isArray(body.answers)) {
1541
+ return { error: errorResponse(c, 400, "bad_request", "answers must be an array") };
1542
+ }
1543
+ const answers = [];
1544
+ for (const answer of body.answers) {
1545
+ if (!isObject(answer)) {
1546
+ return { error: errorResponse(c, 400, "bad_request", "answers must contain objects") };
1547
+ }
1548
+ if (typeof answer.questionId !== "string" || answer.questionId.trim().length === 0) {
1549
+ return { error: errorResponse(c, 400, "bad_request", "questionId must be a string") };
1550
+ }
1551
+ if (typeof answer.answer !== "string" || answer.answer.trim().length === 0) {
1552
+ return { error: errorResponse(c, 400, "bad_request", "answer must be a string") };
1553
+ }
1554
+ if ("choice" in answer && typeof answer.choice !== "string") {
1555
+ return { error: errorResponse(c, 400, "bad_request", "choice must be a string") };
1556
+ }
1557
+ if ("custom" in answer && typeof answer.custom !== "boolean") {
1558
+ return { error: errorResponse(c, 400, "bad_request", "custom must be a boolean") };
1559
+ }
1560
+ answers.push({
1561
+ questionId: answer.questionId.trim(),
1562
+ answer: answer.answer.trim(),
1563
+ ...typeof answer.choice === "string" ? { choice: answer.choice } : {},
1564
+ ...typeof answer.custom === "boolean" ? { custom: answer.custom } : {}
1565
+ });
1566
+ }
1567
+ return { answers };
1568
+ }
1569
+ function createQuestionRuntime() {
1570
+ const questions = /* @__PURE__ */ new Map();
1571
+ return {
1572
+ questions,
1573
+ createHook(context) {
1574
+ return createHook2({
1575
+ async onToolCall({ toolName, toolCallId, internalCallId, args, tool: control }) {
1576
+ if (toolName !== "ask_question") {
1577
+ return control.run();
1578
+ }
1579
+ const prompts = normalizeQuestionPrompts(parseToolArgs2(args));
1580
+ if ("error" in prompts) {
1581
+ return control.skip(prompts.error);
1582
+ }
1583
+ const answers = await requestQuestion(questions, context, {
1584
+ toolName,
1585
+ ...toolCallId === void 0 ? {} : { toolCallId },
1586
+ internalCallId,
1587
+ args,
1588
+ questions: prompts.questions
1589
+ });
1590
+ return control.skip(JSON.stringify({ answers }));
1591
+ }
1592
+ });
1593
+ },
1594
+ list(options) {
1595
+ return [...questions.values()].filter((question) => {
1596
+ if (options.status === "pending" && question.status !== "pending") {
1597
+ return false;
1598
+ }
1599
+ if (options.status === "resolved" && question.status === "pending") {
1600
+ return false;
1601
+ }
1602
+ if (options.runId !== void 0 && question.runId !== options.runId) {
1603
+ return false;
1604
+ }
1605
+ if (options.agentId !== void 0 && question.agentId !== options.agentId) {
1606
+ return false;
1607
+ }
1608
+ if (options.sessionId !== void 0 && question.sessionId !== options.sessionId) {
1609
+ return false;
1610
+ }
1611
+ return true;
1612
+ }).map(publicQuestion);
1613
+ },
1614
+ answer(id, answers) {
1615
+ const question = questions.get(id);
1616
+ if (question === void 0) {
1617
+ return "missing";
1618
+ }
1619
+ if (!isPendingQuestion(question)) {
1620
+ return "resolved";
1621
+ }
1622
+ const resolved = resolveQuestion(question, answers);
1623
+ questions.set(id, resolved);
1624
+ question.emit?.({ type: "tool_question_result", question: resolved });
1625
+ question.resolve(answers);
1626
+ return publicQuestion(resolved);
1627
+ }
1628
+ };
1629
+ }
1630
+ async function requestQuestion(questions, context, request) {
1631
+ const id = globalThis.crypto.randomUUID();
1632
+ const question = {
1633
+ id,
1634
+ runId: context.runId,
1635
+ agentId: context.agentId,
1636
+ ...context.sessionId === void 0 ? {} : { sessionId: context.sessionId },
1637
+ toolName: request.toolName,
1638
+ ...request.toolCallId === void 0 ? {} : { callId: request.toolCallId },
1639
+ internalCallId: request.internalCallId,
1640
+ args: request.args,
1641
+ questions: request.questions,
1642
+ status: "pending",
1643
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
1644
+ ...context.emit === void 0 ? {} : { emit: context.emit },
1645
+ resolve: () => {
1646
+ }
1647
+ };
1648
+ const answer = new Promise((resolve2) => {
1649
+ question.resolve = (answers) => {
1650
+ resolve2(answers);
1651
+ };
1652
+ });
1653
+ questions.set(id, question);
1654
+ context.emit?.({ type: "tool_question_request", question: publicQuestion(question) });
1655
+ return answer;
1656
+ }
1657
+ function normalizeQuestionPrompts(args) {
1658
+ if (!isObject(args)) {
1659
+ return { error: "ask_question requires a JSON object with questions." };
1660
+ }
1661
+ const rawQuestions = Array.isArray(args.questions) ? args.questions : [args];
1662
+ if (rawQuestions.length === 0) {
1663
+ return { error: "ask_question requires at least one question." };
1664
+ }
1665
+ const questions = [];
1666
+ for (const [index, question] of rawQuestions.entries()) {
1667
+ const normalized = normalizeQuestionPrompt(question, index);
1668
+ if (normalized === void 0) {
1669
+ return {
1670
+ error: "ask_question requires every question to include text and at least one choice."
1671
+ };
1672
+ }
1673
+ questions.push(normalized);
1674
+ }
1675
+ return { questions };
1676
+ }
1677
+ function normalizeQuestionPrompt(value, index) {
1678
+ if (!isObject(value) || typeof value.question !== "string" || value.question.trim().length === 0) {
1679
+ return void 0;
1680
+ }
1681
+ const choices = Array.isArray(value.choices) ? value.choices.map(normalizeQuestionChoice).filter((choice) => choice !== void 0) : [];
1682
+ if (choices.length === 0) {
1683
+ return void 0;
1684
+ }
1685
+ return {
1686
+ id: typeof value.id === "string" && value.id.trim().length > 0 ? value.id.trim() : `question_${index + 1}`,
1687
+ question: value.question.trim(),
1688
+ choices
1689
+ };
1690
+ }
1691
+ function normalizeQuestionChoice(value) {
1692
+ if (typeof value === "string" && value.trim().length > 0) {
1693
+ return { label: value.trim(), value: value.trim() };
1694
+ }
1695
+ if (!isObject(value) || typeof value.label !== "string" || value.label.trim().length === 0) {
1696
+ return void 0;
1697
+ }
1698
+ return {
1699
+ label: value.label.trim(),
1700
+ value: typeof value.value === "string" && value.value.trim().length > 0 ? value.value.trim() : value.label.trim()
1701
+ };
1702
+ }
1703
+ function isPendingQuestion(question) {
1704
+ return question !== void 0 && question.status === "pending" && "resolve" in question;
1705
+ }
1706
+ function resolveQuestion(question, answers) {
1707
+ return publicQuestion({
1708
+ ...question,
1709
+ status: "answered",
1710
+ answeredAt: (/* @__PURE__ */ new Date()).toISOString(),
1711
+ answers
1712
+ });
1713
+ }
1714
+ function publicQuestion(question) {
1715
+ const { emit, resolve: resolve2, ...rest } = question;
1716
+ void emit;
1717
+ void resolve2;
1718
+ return rest;
1719
+ }
1720
+
1721
+ // src/runtime/runs.ts
1722
+ import { stream as streamResponse } from "hono/streaming";
1723
+ var AsyncEventQueue = class {
1724
+ values = [];
1725
+ resolvers = [];
1726
+ closed = false;
1727
+ push(value) {
1728
+ if (this.closed) {
1729
+ return;
1730
+ }
1731
+ const resolver = this.resolvers.shift();
1732
+ if (resolver !== void 0) {
1733
+ resolver({ done: false, value });
1734
+ return;
1735
+ }
1736
+ this.values.push(value);
1737
+ }
1738
+ close() {
1739
+ this.closed = true;
1740
+ for (const resolver of this.resolvers.splice(0)) {
1741
+ resolver({ done: true, value: void 0 });
1742
+ }
1743
+ }
1744
+ next() {
1745
+ const value = this.values.shift();
1746
+ if (value !== void 0) {
1747
+ return Promise.resolve({ done: false, value });
1748
+ }
1749
+ if (this.closed) {
1750
+ return Promise.resolve({ done: true, value: void 0 });
1751
+ }
1752
+ return new Promise((resolve2) => this.resolvers.push(resolve2));
1753
+ }
1754
+ };
1755
+ async function* mergeRunAndApprovalEvents(runEvents, approvalEvents) {
1756
+ const runIterator = runEvents[Symbol.asyncIterator]();
1757
+ let runDone = false;
1758
+ let runNext = runIterator.next();
1759
+ let approvalNext = approvalEvents.next();
1760
+ try {
1761
+ while (runNext !== void 0 || approvalNext !== void 0) {
1762
+ const pending = [];
1763
+ if (runNext !== void 0) {
1764
+ pending.push(runNext.then((value) => ({ source: "run", value })));
1765
+ }
1766
+ if (approvalNext !== void 0) {
1767
+ pending.push(approvalNext.then((value) => ({ source: "approval", value })));
1768
+ }
1769
+ const result = await Promise.race(pending);
1770
+ if (result.source === "run") {
1771
+ if (result.value.done === true) {
1772
+ runDone = true;
1773
+ runNext = void 0;
1774
+ approvalEvents.close();
1775
+ } else {
1776
+ runNext = runIterator.next();
1777
+ yield result.value.value;
1778
+ }
1779
+ continue;
1780
+ }
1781
+ if (result.value.done === true) {
1782
+ approvalNext = void 0;
1783
+ } else {
1784
+ approvalNext = approvalEvents.next();
1785
+ yield result.value.value;
1786
+ }
1787
+ }
1788
+ } finally {
1789
+ if (!runDone && runIterator.return !== void 0) {
1790
+ await runIterator.return();
1791
+ }
1792
+ approvalEvents.close();
1793
+ }
1794
+ }
1795
+ function streamAgentRunEvents(c, events) {
1796
+ c.header("content-type", "application/x-ndjson; charset=utf-8");
1797
+ c.header("cache-control", "no-cache, no-transform");
1798
+ c.header("connection", "keep-alive");
1799
+ c.header("transfer-encoding", "chunked");
1800
+ c.header("x-accel-buffering", "no");
1801
+ return streamResponse(
1802
+ c,
1803
+ async (stream) => {
1804
+ for await (const event of events) {
1805
+ await stream.write(`${JSON.stringify(event)}
1806
+ `);
1807
+ }
1808
+ },
1809
+ async (error, stream) => {
1810
+ await stream.write(`${JSON.stringify({ type: "error", error: serializeError2(error) })}
1811
+ `);
1812
+ }
1813
+ );
1814
+ }
1815
+ function traceForRun(trace, agentId, session) {
1816
+ const metadata = {
1817
+ ...trace?.metadata ?? {},
1818
+ agentId
1819
+ };
1820
+ return {
1821
+ ...trace ?? {},
1822
+ metadata,
1823
+ ...trace?.sessionId !== void 0 ? { sessionId: trace.sessionId } : session === void 0 ? {} : { sessionId: session.id }
1824
+ };
1825
+ }
1826
+ async function* persistStreamingSessionRun(props) {
1827
+ 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({
1832
+ id: props.session.id,
1833
+ ...optionalTitle(props.message),
1834
+ messages: event.messages,
1835
+ transcript
1836
+ });
1837
+ if (nextSession === void 0) {
1838
+ throw new Error("Session not found");
1839
+ }
1840
+ }
1841
+ yield event;
1842
+ }
1843
+ }
1844
+ function acceptTranscriptStreamEvent(transcript, event) {
1845
+ if (event.type === "text_delta") {
1846
+ appendTranscriptAssistantText(transcript, event.delta);
1847
+ }
1848
+ if (event.type === "reasoning_delta") {
1849
+ appendTranscriptReasoningText(transcript, event.delta, event.id);
1850
+ }
1851
+ if (event.type === "tool_call") {
1852
+ transcript.push({
1853
+ entryId: transcript.length,
1854
+ kind: "tool",
1855
+ toolName: event.toolCall.function.name,
1856
+ callId: event.toolCall.callId ?? event.toolCall.id,
1857
+ args: formatJson(event.toolCall.function.arguments)
1858
+ });
1859
+ }
1860
+ if (event.type === "tool_result") {
1861
+ const matched = findTranscriptToolEntry(transcript, event.toolName, event.toolCallId);
1862
+ if (matched === void 0) {
1863
+ transcript.push({
1864
+ entryId: transcript.length,
1865
+ kind: "tool",
1866
+ toolName: event.toolName,
1867
+ ...event.toolCallId === void 0 ? {} : { callId: event.toolCallId },
1868
+ args: event.args,
1869
+ result: event.result
1870
+ });
1871
+ return;
1872
+ }
1873
+ matched.args = matched.args ?? event.args;
1874
+ matched.result = event.result;
1875
+ }
1876
+ if (event.type === "tool_approval_request") {
1877
+ const matched = findTranscriptToolEntry(
1878
+ transcript,
1879
+ event.approval.toolName,
1880
+ approvalCallId(event.approval)
1881
+ );
1882
+ if (matched !== void 0) {
1883
+ matched.approval = {
1884
+ id: event.approval.id,
1885
+ status: event.approval.status,
1886
+ requestedAt: event.approval.requestedAt
1887
+ };
1888
+ }
1889
+ }
1890
+ if (event.type === "tool_approval_result") {
1891
+ const matched = findTranscriptToolEntry(
1892
+ transcript,
1893
+ event.approval.toolName,
1894
+ approvalCallId(event.approval)
1895
+ );
1896
+ if (matched !== void 0) {
1897
+ matched.approval = {
1898
+ id: event.approval.id,
1899
+ status: event.approval.status,
1900
+ requestedAt: event.approval.requestedAt,
1901
+ ...event.approval.resolvedAt === void 0 ? {} : { resolvedAt: event.approval.resolvedAt },
1902
+ ...event.approval.reason === void 0 ? {} : { reason: event.approval.reason }
1903
+ };
1904
+ }
1905
+ }
1906
+ if (event.type === "tool_question_request") {
1907
+ const matched = findTranscriptToolEntry(
1908
+ transcript,
1909
+ event.question.toolName,
1910
+ questionCallId(event.question)
1911
+ );
1912
+ if (matched !== void 0) {
1913
+ matched.question = {
1914
+ id: event.question.id,
1915
+ status: event.question.status,
1916
+ requestedAt: event.question.requestedAt,
1917
+ questions: event.question.questions
1918
+ };
1919
+ }
1920
+ }
1921
+ if (event.type === "tool_question_result") {
1922
+ const matched = findTranscriptToolEntry(
1923
+ transcript,
1924
+ event.question.toolName,
1925
+ questionCallId(event.question)
1926
+ );
1927
+ if (matched !== void 0) {
1928
+ matched.question = {
1929
+ id: event.question.id,
1930
+ status: event.question.status,
1931
+ requestedAt: event.question.requestedAt,
1932
+ ...event.question.answeredAt === void 0 ? {} : { answeredAt: event.question.answeredAt },
1933
+ questions: event.question.questions,
1934
+ ...event.question.answers === void 0 ? {} : { answers: event.question.answers }
1935
+ };
1936
+ }
1937
+ }
1938
+ if (event.type === "final" && event.trace?.traceId !== void 0) {
1939
+ assignTranscriptTraceId(transcript, event.trace.traceId);
1940
+ }
1941
+ }
1942
+ function approvalCallId(approval) {
1943
+ return approval.callId ?? approval.toolCallId;
1944
+ }
1945
+ function questionCallId(question) {
1946
+ return question.callId ?? question.toolCallId;
1947
+ }
1948
+ function transcriptFromMessages(messages) {
1949
+ const transcript = [];
1950
+ for (const message of messages) {
1951
+ if (message.role === "system") {
1952
+ continue;
1953
+ }
1954
+ if (message.role === "user") {
1955
+ for (const content of message.content) {
1956
+ if (content.type === "text") {
1957
+ transcript.push({
1958
+ entryId: transcript.length,
1959
+ kind: "message",
1960
+ role: "user",
1961
+ text: content.text
1962
+ });
1963
+ }
1964
+ }
1965
+ continue;
1966
+ }
1967
+ if (message.role === "tool") {
1968
+ for (const content of message.content) {
1969
+ transcript.push({
1970
+ entryId: transcript.length,
1971
+ kind: "tool",
1972
+ toolName: "tool_result",
1973
+ callId: content.callId ?? content.id,
1974
+ result: content.content.map((item) => "text" in item ? item.text : "[image]").join("\n")
1975
+ });
1976
+ }
1977
+ continue;
1978
+ }
1979
+ for (const content of message.content) {
1980
+ if (content.type === "text") {
1981
+ appendTranscriptAssistantText(transcript, content.text);
1982
+ } else if (content.type === "reasoning") {
1983
+ appendTranscriptReasoningText(transcript, content.text, content.id);
1984
+ } else if (content.type === "tool_call") {
1985
+ transcript.push({
1986
+ entryId: transcript.length,
1987
+ kind: "tool",
1988
+ toolName: content.function.name,
1989
+ callId: content.callId ?? content.id,
1990
+ args: formatJson(content.function.arguments)
1991
+ });
1992
+ }
1993
+ }
1994
+ }
1995
+ return transcript;
1996
+ }
1997
+ function messageToTranscriptEntry(message, entryId) {
1998
+ const role = typeof message === "string" || message.role !== "assistant" ? "user" : "assistant";
1999
+ return {
2000
+ entryId,
2001
+ kind: "message",
2002
+ role,
2003
+ text: extractMessageText(message)
2004
+ };
2005
+ }
2006
+ function appendTranscriptAssistantText(transcript, delta) {
2007
+ const last = transcript.at(-1);
2008
+ if (last?.kind === "message" && last.role === "assistant") {
2009
+ last.text = `${last.text}${delta}`;
2010
+ return;
2011
+ }
2012
+ transcript.push({
2013
+ entryId: transcript.length,
2014
+ kind: "message",
2015
+ role: "assistant",
2016
+ text: delta
2017
+ });
2018
+ }
2019
+ function assignTranscriptTraceId(transcript, traceId) {
2020
+ for (let index = transcript.length - 1; index >= 0; index -= 1) {
2021
+ const entry = transcript[index];
2022
+ if (entry?.kind === "message" && entry.role === "assistant") {
2023
+ transcript[index] = { ...entry, traceId };
2024
+ return;
2025
+ }
2026
+ }
2027
+ }
2028
+ function appendTranscriptReasoningText(transcript, delta, reasoningId) {
2029
+ const last = transcript.at(-1);
2030
+ if (last?.kind === "reasoning" && (last.reasoningId ?? "") === (reasoningId ?? "")) {
2031
+ last.text = `${last.text}${delta}`;
2032
+ return;
2033
+ }
2034
+ transcript.push({
2035
+ entryId: transcript.length,
2036
+ kind: "reasoning",
2037
+ ...reasoningId === void 0 ? {} : { reasoningId },
2038
+ text: delta
2039
+ });
2040
+ }
2041
+ function findTranscriptToolEntry(transcript, toolName, callId) {
2042
+ for (let index = transcript.length - 1; index >= 0; index -= 1) {
2043
+ const entry = transcript[index];
2044
+ if (entry?.kind !== "tool" || entry.toolName !== toolName || entry.result !== void 0) {
2045
+ continue;
2046
+ }
2047
+ if (callId === void 0 || entry.callId === callId) {
2048
+ return entry;
2049
+ }
2050
+ }
2051
+ return void 0;
2052
+ }
2053
+ function titleFromMessage(message) {
2054
+ const text = extractMessageText(message).replace(/\s+/g, " ").trim();
2055
+ if (text.length === 0) {
2056
+ return void 0;
2057
+ }
2058
+ return text.length > 72 ? `${text.slice(0, 69)}...` : text;
2059
+ }
2060
+ function optionalTitle(message) {
2061
+ const title = titleFromMessage(message);
2062
+ return title === void 0 ? {} : { title };
2063
+ }
2064
+ function extractMessageText(message) {
2065
+ if (typeof message === "string") {
2066
+ return message;
2067
+ }
2068
+ if (message.role === "system") {
2069
+ return message.content;
2070
+ }
2071
+ return message.content.flatMap((item) => {
2072
+ if (item.type === "text" || item.type === "reasoning") {
2073
+ return [item.text];
2074
+ }
2075
+ if (item.type === "tool_call") {
2076
+ return [`${item.function.name}(${formatJson(item.function.arguments)})`];
2077
+ }
2078
+ if (item.type === "tool_result") {
2079
+ return item.content.map((result) => "text" in result ? result.text : "[image]");
2080
+ }
2081
+ return [];
2082
+ }).join("\n");
2083
+ }
2084
+ function formatJson(value) {
2085
+ try {
2086
+ return JSON.stringify(value, null, 2);
2087
+ } catch {
2088
+ return String(value);
2089
+ }
2090
+ }
2091
+ async function parseRunRequest(c) {
2092
+ let body;
2093
+ try {
2094
+ body = await c.req.json();
2095
+ } catch {
2096
+ return { error: errorResponse(c, 400, "bad_request", "Request body must be JSON") };
2097
+ }
2098
+ if (!isObject(body)) {
2099
+ return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
2100
+ }
2101
+ if (!("message" in body) || !isMessageInput(body.message)) {
2102
+ return {
2103
+ error: errorResponse(c, 400, "bad_request", "Request body requires a string or Message")
2104
+ };
2105
+ }
2106
+ const request = {
2107
+ message: typeof body.message === "string" ? body.message : body.message
2108
+ };
2109
+ if ("history" in body) {
2110
+ if (!Array.isArray(body.history) || !body.history.every(isMessage)) {
2111
+ return { error: errorResponse(c, 400, "bad_request", "history must be a Message array") };
2112
+ }
2113
+ request.history = body.history;
2114
+ }
2115
+ if ("sessionId" in body) {
2116
+ if (typeof body.sessionId !== "string" || body.sessionId.trim().length === 0) {
2117
+ return { error: errorResponse(c, 400, "bad_request", "sessionId must be a string") };
2118
+ }
2119
+ if (request.history !== void 0) {
2120
+ return {
2121
+ error: errorResponse(c, 400, "bad_request", "sessionId cannot be combined with history")
2122
+ };
2123
+ }
2124
+ request.sessionId = body.sessionId;
2125
+ }
2126
+ if ("stream" in body) {
2127
+ if (typeof body.stream !== "boolean") {
2128
+ return { error: errorResponse(c, 400, "bad_request", "stream must be a boolean") };
2129
+ }
2130
+ request.stream = body.stream;
2131
+ }
2132
+ if ("maxTurns" in body) {
2133
+ if (!isNonNegativeInteger(body.maxTurns)) {
2134
+ return {
2135
+ error: errorResponse(c, 400, "bad_request", "maxTurns must be a non-negative integer")
2136
+ };
2137
+ }
2138
+ request.maxTurns = body.maxTurns;
2139
+ }
2140
+ if ("toolConcurrency" in body) {
2141
+ if (!isPositiveInteger(body.toolConcurrency)) {
2142
+ return {
2143
+ error: errorResponse(c, 400, "bad_request", "toolConcurrency must be a positive integer")
2144
+ };
2145
+ }
2146
+ request.toolConcurrency = body.toolConcurrency;
2147
+ }
2148
+ if ("metadata" in body) {
2149
+ if (!isJsonObject(body.metadata)) {
2150
+ return { error: errorResponse(c, 400, "bad_request", "metadata must be an object") };
2151
+ }
2152
+ request.metadata = body.metadata;
2153
+ }
2154
+ if ("trace" in body) {
2155
+ if (!isAgentTraceOptions(body.trace)) {
2156
+ return {
2157
+ error: errorResponse(c, 400, "bad_request", "trace must be an AgentTraceOptions object")
2158
+ };
2159
+ }
2160
+ request.trace = body.trace;
2161
+ }
2162
+ return request;
2163
+ }
2164
+
2165
+ // src/runtime/sessions.ts
2166
+ function registerSessionRoutes(app, props) {
2167
+ app.get("/sessions", async (c) => {
2168
+ const agentId = optionalQueryString(c.req.query("agentId"));
2169
+ if (agentId !== void 0 && !props.agentMap.has(agentId)) {
2170
+ return errorResponse(c, 404, "not_found", "Agent not found");
2171
+ }
2172
+ const limit = parseLimit(c.req.query("limit"));
2173
+ if (limit === void 0) {
2174
+ return errorResponse(c, 400, "bad_request", "limit must be a positive integer");
2175
+ }
2176
+ const sessions = await props.sessionStore.listSessions({
2177
+ ...agentId === void 0 ? {} : { agentId },
2178
+ limit
2179
+ });
2180
+ return c.json({ sessions });
2181
+ });
2182
+ app.post("/sessions", async (c) => {
2183
+ const body = await parseCreateSessionRequest(c);
2184
+ if ("error" in body) {
2185
+ return body.error;
2186
+ }
2187
+ if (!props.agentMap.has(body.agentId)) {
2188
+ return errorResponse(c, 404, "not_found", "Agent not found");
2189
+ }
2190
+ const session = await props.sessionStore.createSession({
2191
+ id: globalThis.crypto.randomUUID(),
2192
+ agentId: body.agentId,
2193
+ ...body.title === void 0 ? {} : { title: body.title },
2194
+ ...body.metadata === void 0 ? {} : { metadata: body.metadata }
2195
+ });
2196
+ return c.json(session, 201);
2197
+ });
2198
+ app.get("/sessions/:sessionId", async (c) => {
2199
+ const session = await props.sessionStore.getSession(c.req.param("sessionId"));
2200
+ if (session === void 0) {
2201
+ return errorResponse(c, 404, "not_found", "Session not found");
2202
+ }
2203
+ return c.json(session);
2204
+ });
2205
+ app.delete("/sessions/:sessionId", async (c) => {
2206
+ if (props.sessionStore.deleteSession === void 0) {
2207
+ return errorResponse(
2208
+ c,
2209
+ 501,
2210
+ "unsupported_capability",
2211
+ 'Capability "sessions.delete" is not implemented by this runner',
2212
+ { capability: "sessions", operation: "delete" }
2213
+ );
2214
+ }
2215
+ const deleted = await props.sessionStore.deleteSession(c.req.param("sessionId"));
2216
+ if (!deleted) {
2217
+ return errorResponse(c, 404, "not_found", "Session not found");
2218
+ }
2219
+ return c.body(null, 204);
2220
+ });
2221
+ app.get("/sessions/:sessionId/traces", async (c) => {
2222
+ if (props.traceStore === void 0) {
2223
+ return unsupportedCapability(c, "traces");
2224
+ }
2225
+ const sessionId = c.req.param("sessionId");
2226
+ const session = await props.sessionStore.getSession(sessionId);
2227
+ if (session === void 0) {
2228
+ return errorResponse(c, 404, "not_found", "Session not found");
2229
+ }
2230
+ const limit = parseLimit(c.req.query("limit"));
2231
+ if (limit === void 0) {
2232
+ return errorResponse(c, 400, "bad_request", "limit must be a positive integer");
2233
+ }
2234
+ const traces = await props.traceStore.listSessionTraces({ sessionId, limit });
2235
+ return c.json({ traces });
2236
+ });
2237
+ }
2238
+ async function parseCreateSessionRequest(c) {
2239
+ let body;
2240
+ try {
2241
+ body = await c.req.json();
2242
+ } catch {
2243
+ return { error: errorResponse(c, 400, "bad_request", "Request body must be JSON") };
2244
+ }
2245
+ if (!isObject(body)) {
2246
+ return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
2247
+ }
2248
+ if (typeof body.agentId !== "string" || body.agentId.trim().length === 0) {
2249
+ return { error: errorResponse(c, 400, "bad_request", "agentId must be a string") };
2250
+ }
2251
+ const request = {
2252
+ agentId: body.agentId.trim()
2253
+ };
2254
+ if ("title" in body) {
2255
+ if (typeof body.title !== "string") {
2256
+ return { error: errorResponse(c, 400, "bad_request", "title must be a string") };
2257
+ }
2258
+ const title = body.title.trim();
2259
+ if (title.length > 0) {
2260
+ request.title = title;
2261
+ }
2262
+ }
2263
+ if ("metadata" in body) {
2264
+ if (!isJsonObject(body.metadata)) {
2265
+ return { error: errorResponse(c, 400, "bad_request", "metadata must be an object") };
2266
+ }
2267
+ request.metadata = body.metadata;
2268
+ }
2269
+ return request;
2270
+ }
2271
+
2272
+ // src/runtime/trace-routes.ts
2273
+ function registerTraceRoutes(app, traceStore) {
2274
+ app.get("/traces", async (c) => {
2275
+ if (traceStore.listTraces === void 0) {
2276
+ return errorResponse(
2277
+ c,
2278
+ 501,
2279
+ "unsupported_capability",
2280
+ 'Capability "traces.list" is not implemented by this runner',
2281
+ { capability: "traces", operation: "list" }
2282
+ );
2283
+ }
2284
+ const limit = parseLimit(c.req.query("limit"));
2285
+ if (limit === void 0) {
2286
+ return errorResponse(c, 400, "bad_request", "limit must be a positive integer");
2287
+ }
2288
+ const status = parseTraceStatus(c.req.query("status"));
2289
+ if (status === false) {
2290
+ return errorResponse(c, 400, "bad_request", "status must be running, success, or error");
2291
+ }
2292
+ const agentId = optionalQueryString(c.req.query("agentId"));
2293
+ const sessionId = optionalQueryString(c.req.query("sessionId"));
2294
+ const traces = await traceStore.listTraces({
2295
+ limit,
2296
+ ...agentId === void 0 ? {} : { agentId },
2297
+ ...sessionId === void 0 ? {} : { sessionId },
2298
+ ...status === void 0 ? {} : { status }
2299
+ });
2300
+ return c.json({ traces });
2301
+ });
2302
+ app.get("/traces/:traceId", async (c) => {
2303
+ const trace = await traceStore.getTrace(c.req.param("traceId"));
2304
+ if (trace === void 0) {
2305
+ return errorResponse(c, 404, "not_found", "Trace not found");
2306
+ }
2307
+ return c.json(trace);
2308
+ });
2309
+ }
2310
+
2311
+ // src/runtime/studio.ts
2312
+ var Studio = class {
2313
+ options;
2314
+ studio;
2315
+ server;
2316
+ sigintHandler;
2317
+ constructor(agents = [], options = {}) {
2318
+ this.options = studioOptionsFromAgents(agents, options);
2319
+ this.studio = createStudioApp(this.options);
2320
+ }
2321
+ get app() {
2322
+ return this.studio.app;
2323
+ }
2324
+ fetch(request) {
2325
+ return this.studio.fetch(request);
2326
+ }
2327
+ config() {
2328
+ return this.studio.config();
2329
+ }
2330
+ traceObserver() {
2331
+ return new StudioTraceObserver({
2332
+ store: () => this.studio.traceStore
2333
+ });
2334
+ }
2335
+ start(serveOptions = {}) {
2336
+ this.close();
2337
+ this.studio = createStudioApp(this.options);
2338
+ const port = serveOptions.port ?? Number(process.env.RUNNER_PORT ?? 4021);
2339
+ this.server = serve({
2340
+ fetch: (request) => this.fetch(request),
2341
+ ...serveOptions.hostname === void 0 ? {} : { hostname: serveOptions.hostname },
2342
+ port
2343
+ });
2344
+ const log = serveOptions.log ?? true;
2345
+ if (log) {
2346
+ const host = serveOptions.hostname ?? "localhost";
2347
+ if (isStudioUiEnabled(this.options.ui)) {
2348
+ const uiPath = studioUiEntryPath(resolveStudioUiOptions(this.options.ui));
2349
+ console.log(`Studio UI: http://${host}:${port}${uiPath}`);
2350
+ } else {
2351
+ console.log(`Studio API: http://${host}:${port}`);
2352
+ }
2353
+ }
2354
+ this.sigintHandler = () => {
2355
+ this.close();
2356
+ process.exit(0);
2357
+ };
2358
+ process.once("SIGINT", this.sigintHandler);
2359
+ return this;
2360
+ }
2361
+ close() {
2362
+ if (this.sigintHandler !== void 0) {
2363
+ process.off("SIGINT", this.sigintHandler);
2364
+ this.sigintHandler = void 0;
2365
+ }
2366
+ this.server?.close();
2367
+ this.server = void 0;
2368
+ this.studio.close();
2369
+ }
2370
+ };
2371
+ function studioOptionsFromAgents(agents, options) {
2372
+ return {
2373
+ agents: inferStudioAgents(agents, options.quickPrompts ?? {})
2374
+ };
2375
+ }
2376
+ function inferStudioAgents(agents, quickPrompts) {
2377
+ const ids = /* @__PURE__ */ new Set();
2378
+ return agents.map((agent) => {
2379
+ const id = uniqueAgentId(agent.id, ids);
2380
+ return {
2381
+ id,
2382
+ agent,
2383
+ quickPrompts: quickPrompts[id] ?? [],
2384
+ metadata: agentMetadata(agent)
2385
+ };
2386
+ });
2387
+ }
2388
+ function uniqueAgentId(baseId, ids) {
2389
+ let id = baseId;
2390
+ let suffix = 2;
2391
+ while (ids.has(id)) {
2392
+ id = `${baseId}-${suffix}`;
2393
+ suffix += 1;
2394
+ }
2395
+ ids.add(id);
2396
+ return id;
2397
+ }
2398
+ function agentMetadata(agent) {
2399
+ return {
2400
+ ...agent.defaultMaxTurns === void 0 ? {} : { defaultMaxTurns: agent.defaultMaxTurns },
2401
+ staticContextCount: agent.staticContext.length,
2402
+ dynamicContextCount: agent.dynamicContexts.length,
2403
+ dynamicToolCount: agent.dynamicTools.length,
2404
+ hasOutputSchema: agent.outputSchema !== void 0,
2405
+ observerCount: agent.observers.length,
2406
+ approvalToolCount: agent.toolSet.values().filter((tool) => tool.approval !== void 0).length
2407
+ };
2408
+ }
2409
+ function createStudioApp(options) {
2410
+ const stores = resolveStores(options);
2411
+ const agents = normalizeAgents(options.agents).map(
2412
+ (agent) => withStudioTraceObserver(agent, stores.traces)
2413
+ );
2414
+ const agentMap = new Map(agents.map((agent) => [agent.id, agent]));
2415
+ const approvalRuntime = createApprovalRuntime();
2416
+ const questionRuntime = createQuestionRuntime();
2417
+ const app = new HonoApp();
2418
+ const uiOptions = isStudioUiEnabled(options.ui) ? resolveStudioUiOptions(options.ui) : void 0;
2419
+ if (uiOptions !== void 0 && !uiOptions.protectShell) {
2420
+ registerStudioUi(app, uiOptions);
2421
+ }
2422
+ if (uiOptions?.protectShell) {
2423
+ registerStudioUi(app, uiOptions);
2424
+ }
2425
+ app.get(
2426
+ "/health",
2427
+ (c) => c.json({
2428
+ status: "ok",
2429
+ runner: {
2430
+ id: runnerId(options),
2431
+ name: options.name,
2432
+ version: options.version
2433
+ }
2434
+ })
2435
+ );
2436
+ app.get("/config", (c) => c.json(buildConfig(options, agents, stores)));
2437
+ app.get("/agents", (c) => c.json({ agents: agents.map(agentConfig) }));
2438
+ app.get("/agents/:agentId", (c) => {
2439
+ const agent = agentMap.get(c.req.param("agentId"));
2440
+ if (agent === void 0) {
2441
+ return errorResponse(c, 404, "not_found", "Agent not found");
2442
+ }
2443
+ return c.json(agentConfig(agent));
2444
+ });
2445
+ registerApprovalRoutes(app, approvalRuntime);
2446
+ registerQuestionRoutes(app, questionRuntime);
2447
+ registerKnowledgeRoutes(app, {
2448
+ agents,
2449
+ ...stores.traces === void 0 ? {} : { traceStore: stores.traces }
2450
+ });
2451
+ app.post("/agents/:agentId/runs", async (c) => {
2452
+ const agentId = c.req.param("agentId");
2453
+ const agent = agentMap.get(agentId);
2454
+ if (agent === void 0) {
2455
+ return errorResponse(c, 404, "not_found", "Agent not found");
2456
+ }
2457
+ const body = await parseRunRequest(c);
2458
+ if ("error" in body) {
2459
+ return body.error;
2460
+ }
2461
+ if (body.sessionId !== void 0 && stores.sessions === void 0) {
2462
+ return unsupportedCapability(c, "sessions");
2463
+ }
2464
+ const session = body.sessionId === void 0 ? void 0 : await stores.sessions?.getSession(body.sessionId);
2465
+ if (body.sessionId !== void 0 && session === void 0) {
2466
+ return errorResponse(c, 404, "not_found", "Session not found");
2467
+ }
2468
+ if (session !== void 0 && session.agentId !== agentId) {
2469
+ return errorResponse(c, 400, "bad_request", "Session belongs to another agent");
2470
+ }
2471
+ 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
+ }
2478
+ if (body.maxTurns !== void 0) {
2479
+ request.maxTurns(body.maxTurns);
2480
+ }
2481
+ if (body.toolConcurrency !== void 0) {
2482
+ request.withToolConcurrency(body.toolConcurrency);
2483
+ }
2484
+ if (body.trace !== void 0) {
2485
+ request.withTrace(traceForRun(body.trace, agentId, session));
2486
+ } else if (session !== void 0) {
2487
+ request.withTrace(traceForRun(void 0, agentId, session));
2488
+ }
2489
+ if (body.stream === true) {
2490
+ const runtimeEvents = new AsyncEventQueue();
2491
+ const effectiveHook = composeHooks(
2492
+ composeHooks(
2493
+ agent.agent.hook,
2494
+ approvalRuntime.createHook({
2495
+ runId,
2496
+ agentId,
2497
+ ...session?.id === void 0 ? {} : { sessionId: session.id },
2498
+ ...body.metadata === void 0 ? {} : { metadata: body.metadata },
2499
+ getTool: (toolName) => agent.agent.getTool(toolName),
2500
+ emit: (event) => runtimeEvents.push(event)
2501
+ })
2502
+ ),
2503
+ questionRuntime.createHook({
2504
+ runId,
2505
+ agentId,
2506
+ ...session?.id === void 0 ? {} : { sessionId: session.id },
2507
+ ...body.metadata === void 0 ? {} : { metadata: body.metadata },
2508
+ emit: (event) => runtimeEvents.push(event)
2509
+ })
2510
+ );
2511
+ if (effectiveHook !== void 0) {
2512
+ request.requestHook(effectiveHook);
2513
+ }
2514
+ const runStream = mergeRunAndApprovalEvents(request.stream(), runtimeEvents);
2515
+ const stream = session === void 0 || stores.sessions === void 0 ? runStream : persistStreamingSessionRun({
2516
+ stream: runStream,
2517
+ store: stores.sessions,
2518
+ session,
2519
+ message: body.message
2520
+ });
2521
+ return streamAgentRunEvents(c, stream);
2522
+ }
2523
+ try {
2524
+ const effectiveHook = composeHooks(
2525
+ composeHooks(
2526
+ agent.agent.hook,
2527
+ approvalRuntime.createHook({
2528
+ runId,
2529
+ agentId,
2530
+ ...session?.id === void 0 ? {} : { sessionId: session.id },
2531
+ ...body.metadata === void 0 ? {} : { metadata: body.metadata },
2532
+ getTool: (toolName) => agent.agent.getTool(toolName)
2533
+ })
2534
+ ),
2535
+ questionRuntime.createHook({
2536
+ runId,
2537
+ agentId,
2538
+ ...session?.id === void 0 ? {} : { sessionId: session.id },
2539
+ ...body.metadata === void 0 ? {} : { metadata: body.metadata }
2540
+ })
2541
+ );
2542
+ if (effectiveHook !== void 0) {
2543
+ request.requestHook(effectiveHook);
2544
+ }
2545
+ const response = await request.send();
2546
+ if (session !== void 0 && stores.sessions !== void 0) {
2547
+ await stores.sessions.appendSessionRun({
2548
+ id: session.id,
2549
+ ...optionalTitle(body.message),
2550
+ messages: response.messages,
2551
+ transcript: transcriptFromMessages(response.messages)
2552
+ });
2553
+ }
2554
+ return c.json(response);
2555
+ } catch (error) {
2556
+ return errorResponse(c, 500, "internal_error", "Agent run failed", serializeError2(error));
2557
+ }
2558
+ });
2559
+ if (stores.sessions !== void 0) {
2560
+ registerSessionRoutes(app, {
2561
+ agentMap,
2562
+ sessionStore: stores.sessions,
2563
+ ...stores.traces === void 0 ? {} : { traceStore: stores.traces }
2564
+ });
2565
+ }
2566
+ if (stores.traces !== void 0) {
2567
+ registerTraceRoutes(app, stores.traces);
2568
+ }
2569
+ for (const capability of unsupportedCapabilities(stores)) {
2570
+ app.all(`/${capability}`, (c) => unsupportedCapability(c, capability));
2571
+ app.all(`/${capability}/*`, (c) => unsupportedCapability(c, capability));
2572
+ }
2573
+ return {
2574
+ app,
2575
+ fetch(request) {
2576
+ return app.fetch(request);
2577
+ },
2578
+ config() {
2579
+ return buildConfig(options, agents, stores);
2580
+ },
2581
+ close() {
2582
+ },
2583
+ ...stores.sessions === void 0 ? {} : { sessionStore: stores.sessions },
2584
+ ...stores.traces === void 0 ? {} : { traceStore: stores.traces }
2585
+ };
2586
+ }
2587
+ function withStudioTraceObserver(studioAgent, traceStore) {
2588
+ if (traceStore === void 0 || hasStudioTraceObserver(studioAgent.agent)) {
2589
+ return studioAgent;
2590
+ }
2591
+ return {
2592
+ ...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,
2608
+ observers: [
2609
+ ...studioAgent.agent.observers,
2610
+ { observer: new StudioTraceObserver({ store: traceStore }) }
2611
+ ],
2612
+ dynamicContexts: studioAgent.agent.dynamicContexts,
2613
+ dynamicTools: studioAgent.agent.dynamicTools
2614
+ })
2615
+ };
2616
+ }
2617
+ function hasStudioTraceObserver(agent) {
2618
+ return agent.observers.some(
2619
+ (registration) => registration.observer instanceof StudioTraceObserver
2620
+ );
2621
+ }
2622
+ function composeHooks(first, second) {
2623
+ if (first === void 0) {
2624
+ return second;
2625
+ }
2626
+ if (second === void 0) {
2627
+ return first;
2628
+ }
2629
+ return createHook3({
2630
+ async onCompletionCall(args) {
2631
+ const firstAction = await first.onCompletionCall?.(args);
2632
+ return firstAction?.type === "terminate" ? firstAction : await second.onCompletionCall?.(args) ?? void 0;
2633
+ },
2634
+ async onCompletionResponse(args) {
2635
+ const firstAction = await first.onCompletionResponse?.(args);
2636
+ return firstAction?.type === "terminate" ? firstAction : await second.onCompletionResponse?.(args) ?? void 0;
2637
+ },
2638
+ async onToolCall(args) {
2639
+ const firstAction = await first.onToolCall?.(args);
2640
+ if (firstAction?.type === "skip" || firstAction?.type === "terminate") {
2641
+ return firstAction;
2642
+ }
2643
+ const secondAction = await second.onToolCall?.(args);
2644
+ return secondAction ?? firstAction ?? void 0;
2645
+ },
2646
+ async onToolResult(args) {
2647
+ const firstAction = await first.onToolResult?.(args);
2648
+ return firstAction?.type === "terminate" ? firstAction : await second.onToolResult?.(args) ?? void 0;
2649
+ }
2650
+ });
2651
+ }
2652
+ export {
2653
+ Studio,
2654
+ StudioTraceObserver,
2655
+ createSqliteSessionStore
2656
+ };
2657
+ //# sourceMappingURL=index.js.map