@axlsdk/studio 0.14.0 → 0.15.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.
@@ -0,0 +1,2142 @@
1
+ // src/server/index.ts
2
+ import { existsSync, readFileSync } from "fs";
3
+ import { resolve } from "path";
4
+ import { Hono as Hono15 } from "hono";
5
+ import { cors } from "hono/cors";
6
+ import { serveStatic } from "@hono/node-server/serve-static";
7
+
8
+ // src/server/redact.ts
9
+ var REDACTED = "[redacted]";
10
+ var SAFE_ERROR_NAMES = /* @__PURE__ */ new Set([
11
+ "QuorumNotMet",
12
+ "NoConsensus",
13
+ "TimeoutError",
14
+ "MaxTurnsError",
15
+ "BudgetExceededError",
16
+ "ToolDenied"
17
+ ]);
18
+ function redactErrorMessage(err, redact) {
19
+ const raw = err instanceof Error ? err.message : String(err);
20
+ if (!redact) return raw;
21
+ const name = err instanceof Error ? err.name : "";
22
+ return SAFE_ERROR_NAMES.has(name) ? raw : REDACTED;
23
+ }
24
+ function redactValue(value, redact) {
25
+ if (!redact) return value;
26
+ return REDACTED;
27
+ }
28
+ function redactExecutionInfo(info, redact) {
29
+ if (!redact) return info;
30
+ return {
31
+ ...info,
32
+ ...info.result !== void 0 ? { result: REDACTED } : {},
33
+ ...info.error !== void 0 ? { error: REDACTED } : {}
34
+ };
35
+ }
36
+ function redactExecutionList(infos, redact) {
37
+ if (!redact) return infos;
38
+ return infos.map((info) => redactExecutionInfo(info, redact));
39
+ }
40
+ function redactMemoryValue(value, redact) {
41
+ if (!redact) return value;
42
+ return REDACTED;
43
+ }
44
+ function redactMemoryList(entries, redact) {
45
+ if (!redact) return entries;
46
+ return entries.map((entry) => ({ key: entry.key, value: REDACTED }));
47
+ }
48
+ function redactChatMessage(msg) {
49
+ const scrubbed = {
50
+ role: msg.role,
51
+ content: REDACTED,
52
+ ...msg.name !== void 0 ? { name: msg.name } : {},
53
+ ...msg.tool_call_id !== void 0 ? { tool_call_id: msg.tool_call_id } : {},
54
+ ...msg.tool_calls !== void 0 ? {
55
+ tool_calls: msg.tool_calls.map((tc) => ({
56
+ id: tc.id,
57
+ type: tc.type,
58
+ function: {
59
+ name: tc.function.name,
60
+ arguments: REDACTED
61
+ }
62
+ }))
63
+ } : {}
64
+ // providerMetadata deliberately omitted — opaque content.
65
+ };
66
+ return scrubbed;
67
+ }
68
+ function redactSessionHistory(history, redact) {
69
+ if (!redact) return history;
70
+ return history.map(redactChatMessage);
71
+ }
72
+ function redactStreamEvent(event, redact) {
73
+ if (!redact) return event;
74
+ switch (event.type) {
75
+ case "token":
76
+ return { type: "token", data: REDACTED };
77
+ case "tool_call":
78
+ return { ...event, args: REDACTED };
79
+ case "tool_result":
80
+ return { ...event, result: REDACTED };
81
+ case "tool_approval":
82
+ return {
83
+ ...event,
84
+ args: REDACTED,
85
+ ...event.reason !== void 0 ? { reason: REDACTED } : {}
86
+ };
87
+ case "done":
88
+ return { type: "done", data: REDACTED };
89
+ case "error":
90
+ return { type: "error", message: REDACTED };
91
+ // Structural events have no user content to scrub.
92
+ case "agent_start":
93
+ case "agent_end":
94
+ case "handoff":
95
+ case "step":
96
+ return event;
97
+ }
98
+ }
99
+ function redactEvalItem(item) {
100
+ const scrubbed = {
101
+ ...item,
102
+ input: REDACTED,
103
+ output: REDACTED,
104
+ ...item.annotations !== void 0 ? { annotations: REDACTED } : {},
105
+ ...item.error !== void 0 ? { error: REDACTED } : {},
106
+ ...item.scorerErrors !== void 0 ? { scorerErrors: item.scorerErrors.map(() => REDACTED) } : {}
107
+ };
108
+ if (item.scoreDetails) {
109
+ const detailsOut = {};
110
+ for (const [name, detail] of Object.entries(item.scoreDetails)) {
111
+ detailsOut[name] = {
112
+ score: detail.score,
113
+ ...detail.duration !== void 0 ? { duration: detail.duration } : {},
114
+ ...detail.cost !== void 0 ? { cost: detail.cost } : {}
115
+ // metadata deliberately omitted — may contain LLM scorer reasoning
116
+ };
117
+ }
118
+ scrubbed.scoreDetails = detailsOut;
119
+ }
120
+ return scrubbed;
121
+ }
122
+ function redactEvalResult(result, redact) {
123
+ if (!redact) return result;
124
+ return {
125
+ ...result,
126
+ items: result.items.map(redactEvalItem)
127
+ };
128
+ }
129
+ function redactEvalHistoryEntry(entry, redact) {
130
+ if (!redact) return entry;
131
+ return {
132
+ ...entry,
133
+ data: redactEvalResult(entry.data, redact)
134
+ };
135
+ }
136
+ function redactEvalHistoryList(entries, redact) {
137
+ if (!redact) return entries;
138
+ return entries.map((e) => redactEvalHistoryEntry(e, redact));
139
+ }
140
+ function redactPendingDecision(decision, redact) {
141
+ if (!redact) return decision;
142
+ return {
143
+ ...decision,
144
+ prompt: REDACTED,
145
+ ...decision.metadata !== void 0 ? { metadata: { redacted: true } } : {}
146
+ };
147
+ }
148
+ function redactPendingDecisionList(decisions, redact) {
149
+ if (!redact) return decisions;
150
+ return decisions.map((d) => redactPendingDecision(d, redact));
151
+ }
152
+
153
+ // src/server/middleware/error-handler.ts
154
+ async function errorHandler(c, next) {
155
+ try {
156
+ await next();
157
+ } catch (err) {
158
+ const rawMessage = err instanceof Error ? err.message : String(err);
159
+ const code = err.code ?? "INTERNAL_ERROR";
160
+ let status = 500;
161
+ if ("status" in err) {
162
+ const errStatus = err.status;
163
+ if (typeof errStatus === "number" && errStatus >= 400 && errStatus < 600) {
164
+ status = errStatus;
165
+ }
166
+ } else if (code === "NOT_FOUND" || rawMessage.includes("not found") || rawMessage.includes("not registered")) {
167
+ status = 404;
168
+ } else if (code === "VALIDATION_ERROR" || rawMessage.includes("Expected") || rawMessage.includes("invalid")) {
169
+ status = 400;
170
+ }
171
+ const runtime = c.get("runtime");
172
+ const redactOn = runtime?.isRedactEnabled?.() ?? false;
173
+ const body = {
174
+ ok: false,
175
+ error: { code, message: redactErrorMessage(err, redactOn) }
176
+ };
177
+ return c.json(body, status);
178
+ }
179
+ }
180
+
181
+ // src/server/ws/connection-manager.ts
182
+ var BUFFER_TTL_MS = 3e4;
183
+ var MAX_BUFFER_EVENTS = 500;
184
+ var MAX_WS_FRAME_BYTES = 65536;
185
+ function isBufferedChannel(channel) {
186
+ return channel.startsWith("execution:") || channel.startsWith("eval:");
187
+ }
188
+ function truncateIfOversized(msg, channel, data) {
189
+ if (msg.length <= MAX_WS_FRAME_BYTES) return msg;
190
+ const event = data ?? {};
191
+ const truncated = {
192
+ type: "event",
193
+ channel,
194
+ data: {
195
+ ...event,
196
+ data: {
197
+ __truncated: true,
198
+ originalBytes: msg.length,
199
+ maxBytes: MAX_WS_FRAME_BYTES,
200
+ hint: "Event exceeded WS frame budget (likely a verbose agent_call with a large messages[] snapshot). Fetch via REST if you need the full payload."
201
+ }
202
+ }
203
+ };
204
+ return JSON.stringify(truncated);
205
+ }
206
+ var ConnectionManager = class {
207
+ /** channel -> set of WS connections */
208
+ channels = /* @__PURE__ */ new Map();
209
+ /** ws -> subscribed channels + optional integrator-supplied metadata */
210
+ connections = /* @__PURE__ */ new Map();
211
+ /** channel -> replay buffer for execution streams */
212
+ buffers = /* @__PURE__ */ new Map();
213
+ maxConnections = 100;
214
+ filter;
215
+ /**
216
+ * Register a broadcast filter. Called once at middleware construction.
217
+ * The filter runs on every outbound event and can drop or deliver based
218
+ * on the destination connection's metadata.
219
+ */
220
+ setFilter(filter) {
221
+ this.filter = filter;
222
+ }
223
+ /** Attach integrator-supplied metadata to an already-added connection. */
224
+ setMetadata(ws, metadata) {
225
+ const entry = this.connections.get(ws);
226
+ if (entry) entry.metadata = metadata;
227
+ }
228
+ /** Register a new WS connection. */
229
+ add(ws) {
230
+ if (this.connections.size >= this.maxConnections) {
231
+ ws.close?.();
232
+ return;
233
+ }
234
+ this.connections.set(ws, { channels: /* @__PURE__ */ new Set() });
235
+ }
236
+ /** Remove a WS connection and all its subscriptions. */
237
+ remove(ws) {
238
+ const entry = this.connections.get(ws);
239
+ if (entry) {
240
+ for (const ch of entry.channels) {
241
+ this.channels.get(ch)?.delete(ws);
242
+ if (this.channels.get(ch)?.size === 0) {
243
+ this.channels.delete(ch);
244
+ }
245
+ }
246
+ }
247
+ this.connections.delete(ws);
248
+ }
249
+ /** Subscribe a connection to a channel. Replays buffered events for execution channels. */
250
+ subscribe(ws, channel) {
251
+ if (!this.connections.has(ws)) return;
252
+ let subs = this.channels.get(channel);
253
+ if (!subs) {
254
+ subs = /* @__PURE__ */ new Set();
255
+ this.channels.set(channel, subs);
256
+ }
257
+ subs.add(ws);
258
+ this.connections.get(ws).channels.add(channel);
259
+ const buffer = this.buffers.get(channel);
260
+ if (buffer) {
261
+ const metadata = this.connections.get(ws)?.metadata;
262
+ for (const event of buffer.events) {
263
+ if (this.filter) {
264
+ try {
265
+ if (!this.filter(event.data, metadata)) continue;
266
+ } catch {
267
+ continue;
268
+ }
269
+ }
270
+ try {
271
+ ws.send(event.msg);
272
+ } catch {
273
+ this.remove(ws);
274
+ return;
275
+ }
276
+ }
277
+ }
278
+ }
279
+ /** Unsubscribe a connection from a channel. */
280
+ unsubscribe(ws, channel) {
281
+ this.channels.get(channel)?.delete(ws);
282
+ if (this.channels.get(channel)?.size === 0) {
283
+ this.channels.delete(channel);
284
+ }
285
+ this.connections.get(ws)?.channels.delete(channel);
286
+ }
287
+ /** Broadcast data to all subscribers of a channel. Buffers events for execution channels. */
288
+ broadcast(channel, data) {
289
+ const msg = truncateIfOversized(
290
+ JSON.stringify({ type: "event", channel, data }),
291
+ channel,
292
+ data
293
+ );
294
+ if (isBufferedChannel(channel)) {
295
+ let buffer = this.buffers.get(channel);
296
+ if (!buffer) {
297
+ buffer = { events: [], complete: false };
298
+ this.buffers.set(channel, buffer);
299
+ }
300
+ const event = data;
301
+ const isTerminal = event.type === "done" || event.type === "error";
302
+ if (buffer.events.length < MAX_BUFFER_EVENTS || isTerminal) {
303
+ buffer.events.push({ msg, data });
304
+ }
305
+ if (isTerminal) {
306
+ buffer.complete = true;
307
+ if (buffer.timer) clearTimeout(buffer.timer);
308
+ buffer.timer = setTimeout(() => {
309
+ this.buffers.delete(channel);
310
+ }, BUFFER_TTL_MS);
311
+ }
312
+ }
313
+ const subs = this.channels.get(channel);
314
+ if (!subs || subs.size === 0) return;
315
+ for (const ws of [...subs]) {
316
+ if (this.filter) {
317
+ const metadata = this.connections.get(ws)?.metadata;
318
+ try {
319
+ if (!this.filter(data, metadata)) continue;
320
+ } catch {
321
+ continue;
322
+ }
323
+ }
324
+ try {
325
+ ws.send(msg);
326
+ } catch {
327
+ this.remove(ws);
328
+ }
329
+ }
330
+ }
331
+ /** Broadcast to channel and all wildcard subscribers (e.g., trace:* matches trace:abc). */
332
+ broadcastWithWildcard(channel, data) {
333
+ this.broadcast(channel, data);
334
+ const colonIdx = channel.indexOf(":");
335
+ if (colonIdx > 0) {
336
+ const wildcardChannel = channel.substring(0, colonIdx) + ":*";
337
+ const subs = this.channels.get(wildcardChannel);
338
+ if (!subs || subs.size === 0) return;
339
+ const msg = truncateIfOversized(
340
+ JSON.stringify({ type: "event", channel, data }),
341
+ channel,
342
+ data
343
+ );
344
+ for (const ws of [...subs]) {
345
+ if (this.filter) {
346
+ const metadata = this.connections.get(ws)?.metadata;
347
+ try {
348
+ if (!this.filter(data, metadata)) continue;
349
+ } catch {
350
+ continue;
351
+ }
352
+ }
353
+ try {
354
+ ws.send(msg);
355
+ } catch {
356
+ this.remove(ws);
357
+ }
358
+ }
359
+ }
360
+ }
361
+ /** Close all connections, clear all state and buffers. Used during shutdown. */
362
+ closeAll() {
363
+ for (const ws of this.connections.keys()) {
364
+ ws.close?.();
365
+ }
366
+ for (const buffer of this.buffers.values()) {
367
+ if (buffer.timer) clearTimeout(buffer.timer);
368
+ }
369
+ this.connections.clear();
370
+ this.channels.clear();
371
+ this.buffers.clear();
372
+ }
373
+ /** Get the number of active connections. */
374
+ get connectionCount() {
375
+ return this.connections.size;
376
+ }
377
+ /** Check if any connections are subscribed to a channel. */
378
+ hasSubscribers(channel) {
379
+ return (this.channels.get(channel)?.size ?? 0) > 0;
380
+ }
381
+ };
382
+
383
+ // src/server/ws/protocol.ts
384
+ var VALID_CHANNEL_PREFIXES = ["execution:", "trace:", "eval:"];
385
+ var VALID_EXACT_CHANNELS = ["costs", "decisions", "eval-trends", "workflow-stats", "trace-stats"];
386
+ var MAX_CHANNEL_LENGTH = 256;
387
+ function handleWsMessage(raw, socket, connMgr) {
388
+ if (raw.length > MAX_WS_FRAME_BYTES) {
389
+ return JSON.stringify({ type: "error", message: "Message too large" });
390
+ }
391
+ let msg;
392
+ try {
393
+ msg = JSON.parse(raw);
394
+ } catch {
395
+ return JSON.stringify({ type: "error", message: "Invalid JSON" });
396
+ }
397
+ switch (msg.type) {
398
+ case "subscribe": {
399
+ const error = validateChannel(msg.channel);
400
+ if (error) return JSON.stringify({ type: "error", message: error });
401
+ connMgr.subscribe(socket, msg.channel);
402
+ return JSON.stringify({ type: "subscribed", channel: msg.channel });
403
+ }
404
+ case "unsubscribe": {
405
+ const error = validateChannel(msg.channel);
406
+ if (error) return JSON.stringify({ type: "error", message: error });
407
+ connMgr.unsubscribe(socket, msg.channel);
408
+ return JSON.stringify({ type: "unsubscribed", channel: msg.channel });
409
+ }
410
+ case "ping":
411
+ return JSON.stringify({ type: "pong" });
412
+ default:
413
+ return JSON.stringify({ type: "error", message: "Unknown message type" });
414
+ }
415
+ }
416
+ function validateChannel(channel) {
417
+ if (typeof channel !== "string" || !channel) {
418
+ return "Missing or invalid channel";
419
+ }
420
+ if (channel.length > MAX_CHANNEL_LENGTH) {
421
+ return `Channel name exceeds ${MAX_CHANNEL_LENGTH} characters`;
422
+ }
423
+ if (!VALID_EXACT_CHANNELS.includes(channel) && !VALID_CHANNEL_PREFIXES.some((p) => channel.startsWith(p))) {
424
+ return `Invalid channel: ${channel}`;
425
+ }
426
+ return null;
427
+ }
428
+
429
+ // src/server/ws/handler.ts
430
+ function createWsHandlers(connMgr) {
431
+ return {
432
+ onOpen(_event, ws) {
433
+ connMgr.add(ws);
434
+ },
435
+ onMessage(event, ws) {
436
+ const reply = handleWsMessage(String(event.data), ws, connMgr);
437
+ if (reply) ws.send(reply);
438
+ },
439
+ onClose(_event, ws) {
440
+ connMgr.remove(ws);
441
+ },
442
+ onError(_event, ws) {
443
+ connMgr.remove(ws);
444
+ }
445
+ };
446
+ }
447
+
448
+ // src/server/aggregates/aggregate-snapshots.ts
449
+ var WINDOW_MS = {
450
+ "24h": 24 * 60 * 60 * 1e3,
451
+ "7d": 7 * 24 * 60 * 60 * 1e3,
452
+ "30d": 30 * 24 * 60 * 60 * 1e3,
453
+ all: Number.POSITIVE_INFINITY
454
+ };
455
+ function withinWindow(ts, window, now) {
456
+ return ts >= now - WINDOW_MS[window];
457
+ }
458
+ var REBUILD_INTERVAL_MS = 5 * 6e4;
459
+ var ALL_WINDOWS = new Set(Object.keys(WINDOW_MS));
460
+ function parseWindowParam(raw, fallback = "7d") {
461
+ return raw && ALL_WINDOWS.has(raw) ? raw : fallback;
462
+ }
463
+ var AggregateSnapshots = class {
464
+ constructor(windows, emptyState, connMgr, channel, broadcastTransform) {
465
+ this.windows = windows;
466
+ this.emptyState = emptyState;
467
+ this.connMgr = connMgr;
468
+ this.channel = channel;
469
+ this.broadcastTransform = broadcastTransform;
470
+ this.snapshots = new Map(windows.map((w) => [w, emptyState()]));
471
+ }
472
+ snapshots;
473
+ /** Replace all snapshots atomically — used after a full rebuild. */
474
+ replace(fresh) {
475
+ this.snapshots = fresh;
476
+ this.broadcast();
477
+ }
478
+ /** Apply a reducer update to every window where `ts` falls inside the window. */
479
+ fold(ts, update) {
480
+ const now = Date.now();
481
+ let changed = false;
482
+ for (const window of this.windows) {
483
+ if (withinWindow(ts, window, now)) {
484
+ const prev = this.snapshots.get(window);
485
+ this.snapshots.set(window, update(prev));
486
+ changed = true;
487
+ }
488
+ }
489
+ if (changed) this.broadcast();
490
+ }
491
+ get(window) {
492
+ return this.snapshots.get(window) ?? this.emptyState();
493
+ }
494
+ getAll() {
495
+ return Object.fromEntries(this.snapshots);
496
+ }
497
+ broadcast() {
498
+ const snapshots = this.broadcastTransform ? Object.fromEntries(
499
+ this.windows.map((w) => [w, this.broadcastTransform(this.snapshots.get(w))])
500
+ ) : this.getAll();
501
+ this.connMgr.broadcast(this.channel, {
502
+ snapshots,
503
+ updatedAt: Date.now()
504
+ });
505
+ }
506
+ };
507
+
508
+ // src/server/aggregates/trace-aggregator.ts
509
+ var TraceAggregator = class {
510
+ snaps;
511
+ interval;
512
+ listener;
513
+ options;
514
+ constructor(options) {
515
+ this.options = options;
516
+ this.snaps = new AggregateSnapshots(
517
+ options.windows,
518
+ options.emptyState,
519
+ options.connMgr,
520
+ options.channel,
521
+ options.broadcastTransform
522
+ );
523
+ }
524
+ async start() {
525
+ await this.rebuild();
526
+ this.listener = (event) => {
527
+ this.snaps.fold(event.timestamp, (prev) => this.options.reducer(prev, event));
528
+ };
529
+ this.options.runtime.on("trace", this.listener);
530
+ this.interval = setInterval(
531
+ () => this.rebuild().catch((err) => console.error("[axl-studio] rebuild failed:", err)),
532
+ REBUILD_INTERVAL_MS
533
+ );
534
+ }
535
+ async rebuild() {
536
+ const executions = await this.options.runtime.getExecutions();
537
+ const cap = this.options.executionCap ?? 2e3;
538
+ const capped = executions.slice(0, cap);
539
+ const now = Date.now();
540
+ const fresh = new Map(
541
+ this.options.windows.map((w) => [w, this.options.emptyState()])
542
+ );
543
+ for (const exec of capped) {
544
+ for (const event of exec.steps) {
545
+ for (const window of this.options.windows) {
546
+ if (withinWindow(event.timestamp, window, now)) {
547
+ fresh.set(window, this.options.reducer(fresh.get(window), event));
548
+ }
549
+ }
550
+ }
551
+ }
552
+ this.snaps.replace(fresh);
553
+ }
554
+ getSnapshot(window) {
555
+ return this.snaps.get(window);
556
+ }
557
+ getAllSnapshots() {
558
+ return this.snaps.getAll();
559
+ }
560
+ close() {
561
+ if (this.listener) this.options.runtime.off("trace", this.listener);
562
+ if (this.interval) clearInterval(this.interval);
563
+ }
564
+ };
565
+
566
+ // src/server/aggregates/reducers.ts
567
+ var finite = (v) => Number.isFinite(v) ? v : 0;
568
+ function isLogEvent(event, eventName) {
569
+ if (event.type === eventName) return true;
570
+ if (event.type === "log" && event.data != null && typeof event.data === "object") {
571
+ return event.data.event === eventName;
572
+ }
573
+ return false;
574
+ }
575
+ function emptyRetry() {
576
+ return {
577
+ primary: 0,
578
+ primaryCalls: 0,
579
+ schema: 0,
580
+ schemaCalls: 0,
581
+ validate: 0,
582
+ validateCalls: 0,
583
+ guardrail: 0,
584
+ guardrailCalls: 0,
585
+ retryCalls: 0
586
+ };
587
+ }
588
+ function emptyCostData() {
589
+ return {
590
+ totalCost: 0,
591
+ totalTokens: { input: 0, output: 0, reasoning: 0 },
592
+ byAgent: {},
593
+ byModel: {},
594
+ byWorkflow: {},
595
+ retry: emptyRetry(),
596
+ byEmbedder: {}
597
+ };
598
+ }
599
+ function reduceCost(acc, event) {
600
+ const isWorkflowStart = isLogEvent(event, "workflow_start");
601
+ if (isWorkflowStart && event.workflow) {
602
+ const byWorkflow2 = { ...acc.byWorkflow };
603
+ const prev = byWorkflow2[event.workflow] ?? { cost: 0, executions: 0 };
604
+ byWorkflow2[event.workflow] = { ...prev, executions: prev.executions + 1 };
605
+ return { ...acc, byWorkflow: byWorkflow2 };
606
+ }
607
+ if (event.cost == null && !event.tokens) return acc;
608
+ const cost = finite(event.cost);
609
+ const tokens = event.tokens ?? {};
610
+ const totalTokens = event.type === "agent_call" ? {
611
+ input: acc.totalTokens.input + finite(tokens.input),
612
+ output: acc.totalTokens.output + finite(tokens.output),
613
+ reasoning: acc.totalTokens.reasoning + finite(tokens.reasoning)
614
+ } : acc.totalTokens;
615
+ const byAgent = { ...acc.byAgent };
616
+ if (event.agent) {
617
+ const prev = byAgent[event.agent] ?? { cost: 0, calls: 0 };
618
+ byAgent[event.agent] = { cost: prev.cost + cost, calls: prev.calls + 1 };
619
+ }
620
+ const byModel = { ...acc.byModel };
621
+ if (event.model) {
622
+ const prev = byModel[event.model] ?? { cost: 0, calls: 0, tokens: { input: 0, output: 0 } };
623
+ byModel[event.model] = {
624
+ cost: prev.cost + cost,
625
+ calls: prev.calls + 1,
626
+ tokens: {
627
+ input: prev.tokens.input + finite(tokens.input),
628
+ output: prev.tokens.output + finite(tokens.output)
629
+ }
630
+ };
631
+ }
632
+ const byWorkflow = { ...acc.byWorkflow };
633
+ if (event.workflow) {
634
+ const prev = byWorkflow[event.workflow] ?? { cost: 0, executions: 0 };
635
+ byWorkflow[event.workflow] = {
636
+ cost: prev.cost + cost,
637
+ executions: prev.executions + (isWorkflowStart ? 1 : 0)
638
+ };
639
+ }
640
+ let retry = acc.retry;
641
+ if (event.type === "agent_call") {
642
+ const d = event.data ?? {};
643
+ const reason = d.retryReason;
644
+ retry = { ...acc.retry };
645
+ if (reason === "schema") {
646
+ retry.schema += cost;
647
+ retry.schemaCalls += 1;
648
+ retry.retryCalls += 1;
649
+ } else if (reason === "validate") {
650
+ retry.validate += cost;
651
+ retry.validateCalls += 1;
652
+ retry.retryCalls += 1;
653
+ } else if (reason === "guardrail") {
654
+ retry.guardrail += cost;
655
+ retry.guardrailCalls += 1;
656
+ retry.retryCalls += 1;
657
+ } else {
658
+ retry.primary += cost;
659
+ retry.primaryCalls += 1;
660
+ }
661
+ }
662
+ let byEmbedder = acc.byEmbedder;
663
+ if (event.type === "log") {
664
+ const d = event.data ?? {};
665
+ if (d.event === "memory_remember" || d.event === "memory_recall") {
666
+ byEmbedder = { ...acc.byEmbedder };
667
+ const modelKey = d.usage?.model ?? "unknown";
668
+ const embedTokens = typeof d.usage?.tokens === "number" ? finite(d.usage.tokens) : 0;
669
+ const prev = byEmbedder[modelKey] ?? { cost: 0, calls: 0, tokens: 0 };
670
+ byEmbedder[modelKey] = {
671
+ cost: prev.cost + cost,
672
+ calls: prev.calls + 1,
673
+ tokens: prev.tokens + embedTokens
674
+ };
675
+ }
676
+ }
677
+ return {
678
+ totalCost: acc.totalCost + cost,
679
+ totalTokens,
680
+ byAgent,
681
+ byModel,
682
+ byWorkflow,
683
+ retry,
684
+ byEmbedder
685
+ };
686
+ }
687
+ function emptyEvalTrendData() {
688
+ return { byEval: {}, totalRuns: 0, totalCost: 0 };
689
+ }
690
+ function extractScores(data) {
691
+ if (!data || typeof data !== "object") return {};
692
+ const result = data;
693
+ const summary = result.summary;
694
+ const scorers = summary?.scorers;
695
+ if (!scorers) return {};
696
+ const out = {};
697
+ for (const [name, entry] of Object.entries(scorers)) {
698
+ if (typeof entry === "number" && Number.isFinite(entry)) {
699
+ out[name] = entry;
700
+ } else if (entry && typeof entry === "object" && Number.isFinite(entry.mean)) {
701
+ out[name] = entry.mean;
702
+ }
703
+ }
704
+ return out;
705
+ }
706
+ function extractCost(data) {
707
+ if (!data || typeof data !== "object") return 0;
708
+ const result = data;
709
+ if (Number.isFinite(result.totalCost)) return result.totalCost;
710
+ const summary = result.summary;
711
+ return Number.isFinite(summary?.totalCost) ? summary.totalCost : 0;
712
+ }
713
+ function extractModel(data) {
714
+ if (!data || typeof data !== "object") return void 0;
715
+ const result = data;
716
+ const metadata = result.metadata;
717
+ const counts = metadata?.modelCounts;
718
+ if (counts && typeof counts === "object" && !Array.isArray(counts)) {
719
+ const entries = Object.entries(counts).filter(
720
+ ([, v]) => typeof v === "number"
721
+ );
722
+ if (entries.length > 0) {
723
+ entries.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
724
+ return entries[0][0];
725
+ }
726
+ }
727
+ const models = metadata?.models;
728
+ if (Array.isArray(models) && typeof models[0] === "string") return models[0];
729
+ return void 0;
730
+ }
731
+ function extractDuration(data) {
732
+ if (!data || typeof data !== "object") return void 0;
733
+ const result = data;
734
+ return Number.isFinite(result.duration) ? result.duration : void 0;
735
+ }
736
+ function computeScoreStats(runs) {
737
+ const scorerNames = /* @__PURE__ */ new Set();
738
+ for (const run of runs) {
739
+ for (const name of Object.keys(run.scores)) scorerNames.add(name);
740
+ }
741
+ const mean = {};
742
+ const std = {};
743
+ for (const name of scorerNames) {
744
+ const values = runs.map((r) => r.scores[name]).filter((v) => v != null);
745
+ if (values.length === 0) continue;
746
+ const m = values.reduce((a, b) => a + b, 0) / values.length;
747
+ mean[name] = m;
748
+ const variance = values.reduce((sum, v) => sum + (v - m) ** 2, 0) / values.length;
749
+ std[name] = Math.sqrt(variance);
750
+ }
751
+ return { mean, std };
752
+ }
753
+ function reduceEvalTrends(acc, entry) {
754
+ const scores = extractScores(entry.data);
755
+ const cost = extractCost(entry.data);
756
+ const model = extractModel(entry.data);
757
+ const duration = extractDuration(entry.data);
758
+ const run = {
759
+ timestamp: entry.timestamp,
760
+ id: entry.id,
761
+ scores,
762
+ cost,
763
+ ...model !== void 0 ? { model } : {},
764
+ ...duration !== void 0 ? { duration } : {}
765
+ };
766
+ const byEval = { ...acc.byEval };
767
+ const prev = byEval[entry.eval];
768
+ const MAX_EVAL_RUNS = 50;
769
+ const allRuns = prev ? [...prev.runs, run] : [run];
770
+ const runs = allRuns.length > MAX_EVAL_RUNS ? allRuns.slice(-MAX_EVAL_RUNS) : allRuns;
771
+ const { mean, std } = computeScoreStats(runs);
772
+ const latestScores = prev && prev.runs.length > 0 && prev.runs[prev.runs.length - 1].timestamp > run.timestamp ? prev.latestScores : scores;
773
+ byEval[entry.eval] = {
774
+ runs,
775
+ latestScores,
776
+ scoreMean: mean,
777
+ scoreStd: std,
778
+ costTotal: (prev?.costTotal ?? 0) + cost,
779
+ runCount: (prev?.runCount ?? 0) + 1
780
+ };
781
+ return {
782
+ byEval,
783
+ totalRuns: acc.totalRuns + 1,
784
+ totalCost: acc.totalCost + cost
785
+ };
786
+ }
787
+ var MAX_DURATIONS = 200;
788
+ function emptyWorkflowStatsData() {
789
+ return { byWorkflow: {}, totalExecutions: 0, failureRate: 0 };
790
+ }
791
+ function percentile(sorted, p) {
792
+ if (sorted.length === 0) return 0;
793
+ const idx = p / 100 * (sorted.length - 1);
794
+ const lower = Math.floor(idx);
795
+ const upper = Math.ceil(idx);
796
+ if (lower === upper) return sorted[lower];
797
+ return sorted[lower] + (sorted[upper] - sorted[lower]) * (idx - lower);
798
+ }
799
+ function reduceWorkflowStats(acc, execution) {
800
+ const byWorkflow = { ...acc.byWorkflow };
801
+ const prev = byWorkflow[execution.workflow] ?? {
802
+ total: 0,
803
+ completed: 0,
804
+ failed: 0,
805
+ durations: [],
806
+ durationSum: 0,
807
+ avgDuration: 0
808
+ };
809
+ const dur = finite(execution.duration);
810
+ const durations = [...prev.durations];
811
+ const insertIdx = durations.findIndex((d) => d > dur);
812
+ if (insertIdx === -1) durations.push(dur);
813
+ else durations.splice(insertIdx, 0, dur);
814
+ if (durations.length > MAX_DURATIONS) durations.shift();
815
+ const total = prev.total + 1;
816
+ const completed = prev.completed + (execution.status === "completed" ? 1 : 0);
817
+ const failed = prev.failed + (execution.status === "failed" ? 1 : 0);
818
+ const durationSum = prev.durationSum + dur;
819
+ const avgDuration = durationSum / total;
820
+ byWorkflow[execution.workflow] = {
821
+ total,
822
+ completed,
823
+ failed,
824
+ durations,
825
+ durationSum,
826
+ avgDuration
827
+ };
828
+ const totalExecutions = acc.totalExecutions + 1;
829
+ const totalFailed = Object.values(byWorkflow).reduce((sum, w) => sum + w.failed, 0);
830
+ const failureRate = totalExecutions > 0 ? totalFailed / totalExecutions : 0;
831
+ return { byWorkflow, totalExecutions, failureRate };
832
+ }
833
+ function getWorkflowPercentiles(entry) {
834
+ return {
835
+ durationP50: percentile(entry.durations, 50),
836
+ durationP95: percentile(entry.durations, 95)
837
+ };
838
+ }
839
+ function enrichWorkflowStats(data) {
840
+ const byWorkflow = {};
841
+ for (const [name, entry] of Object.entries(data.byWorkflow)) {
842
+ const { durationP50, durationP95 } = getWorkflowPercentiles(entry);
843
+ byWorkflow[name] = {
844
+ total: entry.total,
845
+ completed: entry.completed,
846
+ failed: entry.failed,
847
+ durationP50,
848
+ durationP95,
849
+ avgDuration: entry.avgDuration
850
+ };
851
+ }
852
+ return {
853
+ byWorkflow,
854
+ totalExecutions: data.totalExecutions,
855
+ failureRate: data.failureRate
856
+ };
857
+ }
858
+ function emptyTraceStatsData() {
859
+ return {
860
+ eventTypeCounts: {},
861
+ byTool: {},
862
+ retryByAgent: {},
863
+ totalEvents: 0
864
+ };
865
+ }
866
+ function reduceTraceStats(acc, event) {
867
+ const eventTypeCounts = { ...acc.eventTypeCounts };
868
+ eventTypeCounts[event.type] = (eventTypeCounts[event.type] ?? 0) + 1;
869
+ const byTool = { ...acc.byTool };
870
+ if (event.type === "tool_call" || event.type === "tool_denied" || event.type === "tool_approval") {
871
+ const toolName = event.tool;
872
+ const prev = byTool[toolName] ?? { calls: 0, denied: 0, approved: 0 };
873
+ const isDeniedEvent = event.type === "tool_denied";
874
+ const isApprovalEvent = event.type === "tool_approval";
875
+ const eventData = isDeniedEvent || isApprovalEvent ? event.data : void 0;
876
+ const isApproved = isDeniedEvent && eventData?.approved === true || isApprovalEvent && eventData?.approved === true;
877
+ const isDenied = isDeniedEvent && !eventData?.approved || isApprovalEvent && eventData?.approved === false;
878
+ byTool[toolName] = {
879
+ calls: prev.calls + (event.type === "tool_call" ? 1 : 0),
880
+ denied: prev.denied + (isDenied ? 1 : 0),
881
+ approved: prev.approved + (isApproved ? 1 : 0)
882
+ };
883
+ }
884
+ const retryByAgent = { ...acc.retryByAgent };
885
+ if (event.agent && event.type === "agent_call") {
886
+ const data = event.data;
887
+ if (data?.retryReason) {
888
+ const prev = retryByAgent[event.agent] ?? { schema: 0, validate: 0, guardrail: 0 };
889
+ const reason = data.retryReason;
890
+ if (reason in prev) {
891
+ retryByAgent[event.agent] = { ...prev, [reason]: prev[reason] + 1 };
892
+ }
893
+ }
894
+ }
895
+ return {
896
+ eventTypeCounts,
897
+ byTool,
898
+ retryByAgent,
899
+ totalEvents: acc.totalEvents + 1
900
+ };
901
+ }
902
+
903
+ // src/server/aggregates/execution-aggregator.ts
904
+ var ExecutionAggregator = class {
905
+ snaps;
906
+ interval;
907
+ listener;
908
+ options;
909
+ /** Generation counter to prevent stale async fold after rebuild. */
910
+ generation = 0;
911
+ constructor(options) {
912
+ this.options = options;
913
+ this.snaps = new AggregateSnapshots(
914
+ options.windows,
915
+ options.emptyState,
916
+ options.connMgr,
917
+ options.channel,
918
+ options.broadcastTransform
919
+ );
920
+ }
921
+ async start() {
922
+ await this.rebuild();
923
+ this.listener = (event) => {
924
+ if (!isLogEvent(event, "workflow_end")) return;
925
+ const gen = this.generation;
926
+ this.options.runtime.getExecution(event.executionId).then((exec) => {
927
+ if (this.generation !== gen) return;
928
+ if (exec) {
929
+ this.snaps.fold(exec.startedAt, (prev) => this.options.reducer(prev, exec));
930
+ }
931
+ }).catch((err) => console.error("[axl-studio] execution fold failed:", err));
932
+ };
933
+ this.options.runtime.on("trace", this.listener);
934
+ this.interval = setInterval(
935
+ () => this.rebuild().catch((err) => console.error("[axl-studio] rebuild failed:", err)),
936
+ REBUILD_INTERVAL_MS
937
+ );
938
+ }
939
+ async rebuild() {
940
+ this.generation++;
941
+ const executions = await this.options.runtime.getExecutions();
942
+ const cap = this.options.executionCap ?? 2e3;
943
+ const capped = executions.slice(0, cap);
944
+ const now = Date.now();
945
+ const fresh = new Map(
946
+ this.options.windows.map((w) => [w, this.options.emptyState()])
947
+ );
948
+ for (const exec of capped) {
949
+ for (const window of this.options.windows) {
950
+ if (withinWindow(exec.startedAt, window, now)) {
951
+ fresh.set(window, this.options.reducer(fresh.get(window), exec));
952
+ }
953
+ }
954
+ }
955
+ this.snaps.replace(fresh);
956
+ }
957
+ getSnapshot(window) {
958
+ return this.snaps.get(window);
959
+ }
960
+ getAllSnapshots() {
961
+ return this.snaps.getAll();
962
+ }
963
+ close() {
964
+ if (this.listener) this.options.runtime.off("trace", this.listener);
965
+ if (this.interval) clearInterval(this.interval);
966
+ }
967
+ };
968
+
969
+ // src/server/aggregates/eval-aggregator.ts
970
+ var EvalAggregator = class {
971
+ snaps;
972
+ interval;
973
+ listener;
974
+ options;
975
+ constructor(options) {
976
+ this.options = options;
977
+ this.snaps = new AggregateSnapshots(
978
+ options.windows,
979
+ options.emptyState,
980
+ options.connMgr,
981
+ options.channel,
982
+ options.broadcastTransform
983
+ );
984
+ }
985
+ async start() {
986
+ await this.rebuild();
987
+ this.listener = (entry) => {
988
+ this.snaps.fold(entry.timestamp, (prev) => this.options.reducer(prev, entry));
989
+ };
990
+ this.options.runtime.on("eval_result", this.listener);
991
+ this.interval = setInterval(
992
+ () => this.rebuild().catch((err) => console.error("[axl-studio] rebuild failed:", err)),
993
+ REBUILD_INTERVAL_MS
994
+ );
995
+ }
996
+ async rebuild() {
997
+ const history = await this.options.runtime.getEvalHistory();
998
+ const cap = this.options.entryCap ?? 500;
999
+ const capped = history.slice(0, cap);
1000
+ const now = Date.now();
1001
+ const fresh = new Map(
1002
+ this.options.windows.map((w) => [w, this.options.emptyState()])
1003
+ );
1004
+ for (const entry of capped) {
1005
+ for (const window of this.options.windows) {
1006
+ if (withinWindow(entry.timestamp, window, now)) {
1007
+ fresh.set(window, this.options.reducer(fresh.get(window), entry));
1008
+ }
1009
+ }
1010
+ }
1011
+ this.snaps.replace(fresh);
1012
+ }
1013
+ getSnapshot(window) {
1014
+ return this.snaps.get(window);
1015
+ }
1016
+ getAllSnapshots() {
1017
+ return this.snaps.getAll();
1018
+ }
1019
+ close() {
1020
+ if (this.listener) this.options.runtime.off("eval_result", this.listener);
1021
+ if (this.interval) clearInterval(this.interval);
1022
+ }
1023
+ };
1024
+
1025
+ // src/server/routes/health.ts
1026
+ import { Hono } from "hono";
1027
+ function createHealthRoutes(readOnly) {
1028
+ const app6 = new Hono();
1029
+ app6.get("/health", (c) => {
1030
+ const runtime = c.get("runtime");
1031
+ return c.json({
1032
+ ok: true,
1033
+ data: {
1034
+ status: "healthy",
1035
+ readOnly,
1036
+ workflows: runtime.getWorkflowNames().length,
1037
+ agents: runtime.getAgents().length,
1038
+ tools: runtime.getTools().length
1039
+ }
1040
+ });
1041
+ });
1042
+ return app6;
1043
+ }
1044
+
1045
+ // src/server/routes/workflows.ts
1046
+ import { Hono as Hono2 } from "hono";
1047
+ import { zodToJsonSchema } from "@axlsdk/axl";
1048
+ function createWorkflowRoutes(connMgr) {
1049
+ const app6 = new Hono2();
1050
+ app6.get("/workflows", (c) => {
1051
+ const runtime = c.get("runtime");
1052
+ const workflows = runtime.getWorkflows().map((w) => ({
1053
+ name: w.name,
1054
+ hasInputSchema: !!w.inputSchema,
1055
+ hasOutputSchema: !!w.outputSchema
1056
+ }));
1057
+ return c.json({ ok: true, data: workflows });
1058
+ });
1059
+ app6.get("/workflows/:name", (c) => {
1060
+ const runtime = c.get("runtime");
1061
+ const name = c.req.param("name");
1062
+ const workflow = runtime.getWorkflow(name);
1063
+ if (!workflow) {
1064
+ return c.json(
1065
+ { ok: false, error: { code: "NOT_FOUND", message: `Workflow "${name}" not found` } },
1066
+ 404
1067
+ );
1068
+ }
1069
+ return c.json({
1070
+ ok: true,
1071
+ data: {
1072
+ name: workflow.name,
1073
+ inputSchema: workflow.inputSchema ? zodToJsonSchema(workflow.inputSchema) : null,
1074
+ outputSchema: workflow.outputSchema ? zodToJsonSchema(workflow.outputSchema) : null
1075
+ }
1076
+ });
1077
+ });
1078
+ app6.post("/workflows/:name/execute", async (c) => {
1079
+ const runtime = c.get("runtime");
1080
+ const name = c.req.param("name");
1081
+ const workflow = runtime.getWorkflow(name);
1082
+ if (!workflow) {
1083
+ return c.json(
1084
+ { ok: false, error: { code: "NOT_FOUND", message: `Workflow "${name}" not found` } },
1085
+ 404
1086
+ );
1087
+ }
1088
+ const body = await c.req.json();
1089
+ if (body.stream) {
1090
+ const stream = runtime.stream(name, body.input ?? {}, { metadata: body.metadata });
1091
+ const executionId = `stream-${Date.now()}`;
1092
+ const redactOn = runtime.isRedactEnabled();
1093
+ (async () => {
1094
+ for await (const event of stream) {
1095
+ connMgr.broadcastWithWildcard(
1096
+ `execution:${executionId}`,
1097
+ redactStreamEvent(event, redactOn)
1098
+ );
1099
+ }
1100
+ })();
1101
+ return c.json({ ok: true, data: { executionId, streaming: true } });
1102
+ }
1103
+ const result = await runtime.execute(name, body.input ?? {}, { metadata: body.metadata });
1104
+ return c.json({
1105
+ ok: true,
1106
+ data: { result: redactValue(result, runtime.isRedactEnabled()) }
1107
+ });
1108
+ });
1109
+ return app6;
1110
+ }
1111
+
1112
+ // src/server/routes/executions.ts
1113
+ import { Hono as Hono3 } from "hono";
1114
+ var app = new Hono3();
1115
+ app.get("/executions", async (c) => {
1116
+ const runtime = c.get("runtime");
1117
+ const executions = await runtime.getExecutions();
1118
+ return c.json({
1119
+ ok: true,
1120
+ data: redactExecutionList(executions, runtime.isRedactEnabled())
1121
+ });
1122
+ });
1123
+ app.get("/executions/:id", async (c) => {
1124
+ const runtime = c.get("runtime");
1125
+ const id = c.req.param("id");
1126
+ const execution = await runtime.getExecution(id);
1127
+ if (!execution) {
1128
+ return c.json(
1129
+ { ok: false, error: { code: "NOT_FOUND", message: `Execution "${id}" not found` } },
1130
+ 404
1131
+ );
1132
+ }
1133
+ return c.json({
1134
+ ok: true,
1135
+ data: redactExecutionInfo(execution, runtime.isRedactEnabled())
1136
+ });
1137
+ });
1138
+ app.post("/executions/:id/abort", (c) => {
1139
+ const runtime = c.get("runtime");
1140
+ const id = c.req.param("id");
1141
+ runtime.abort(id);
1142
+ return c.json({ ok: true, data: { aborted: true } });
1143
+ });
1144
+ var executions_default = app;
1145
+
1146
+ // src/server/routes/sessions.ts
1147
+ import { Hono as Hono4 } from "hono";
1148
+ function createSessionRoutes(connMgr) {
1149
+ const app6 = new Hono4();
1150
+ app6.get("/sessions", async (c) => {
1151
+ const runtime = c.get("runtime");
1152
+ const store = runtime.getStateStore();
1153
+ if (!store.listSessions) {
1154
+ return c.json({ ok: true, data: [] });
1155
+ }
1156
+ const ids = await store.listSessions();
1157
+ const sessions = [];
1158
+ for (const id of ids) {
1159
+ const history = await store.getSession(id);
1160
+ sessions.push({ id, messageCount: history.length });
1161
+ }
1162
+ return c.json({ ok: true, data: sessions });
1163
+ });
1164
+ app6.get("/sessions/:id", async (c) => {
1165
+ const runtime = c.get("runtime");
1166
+ const store = runtime.getStateStore();
1167
+ const id = c.req.param("id");
1168
+ const history = await store.getSession(id);
1169
+ const handoffHistory = await store.getSessionMeta(id, "handoffHistory");
1170
+ return c.json({
1171
+ ok: true,
1172
+ data: {
1173
+ id,
1174
+ history: redactSessionHistory(history, runtime.isRedactEnabled()),
1175
+ // HandoffRecord has no content fields (source/target/mode/
1176
+ // timestamp/duration) — nothing to scrub.
1177
+ handoffHistory: handoffHistory ?? []
1178
+ }
1179
+ });
1180
+ });
1181
+ app6.post("/sessions/:id/send", async (c) => {
1182
+ const runtime = c.get("runtime");
1183
+ const id = c.req.param("id");
1184
+ const body = await c.req.json();
1185
+ const session = runtime.session(id);
1186
+ const result = await session.send(body.workflow, body.message);
1187
+ return c.json({ ok: true, data: { result } });
1188
+ });
1189
+ app6.post("/sessions/:id/stream", async (c) => {
1190
+ const runtime = c.get("runtime");
1191
+ const id = c.req.param("id");
1192
+ const body = await c.req.json();
1193
+ const session = runtime.session(id);
1194
+ const stream = await session.stream(body.workflow, body.message);
1195
+ const executionId = `session-${id}-${Date.now()}`;
1196
+ (async () => {
1197
+ for await (const event of stream) {
1198
+ connMgr.broadcastWithWildcard(`execution:${executionId}`, event);
1199
+ }
1200
+ })();
1201
+ return c.json({ ok: true, data: { executionId, streaming: true } });
1202
+ });
1203
+ app6.delete("/sessions/:id", async (c) => {
1204
+ const runtime = c.get("runtime");
1205
+ const store = runtime.getStateStore();
1206
+ const id = c.req.param("id");
1207
+ await store.deleteSession(id);
1208
+ return c.json({ ok: true, data: { deleted: true } });
1209
+ });
1210
+ return app6;
1211
+ }
1212
+
1213
+ // src/server/routes/agents.ts
1214
+ import { Hono as Hono5 } from "hono";
1215
+ import { zodToJsonSchema as zodToJsonSchema2 } from "@axlsdk/axl";
1216
+ var app2 = new Hono5();
1217
+ app2.get("/agents", (c) => {
1218
+ const runtime = c.get("runtime");
1219
+ const agents = runtime.getAgents().map((a) => ({
1220
+ name: a._name,
1221
+ model: a.resolveModel(),
1222
+ system: a.resolveSystem(),
1223
+ tools: a._config.tools?.map((t) => t.name) ?? [],
1224
+ handoffs: typeof a._config.handoffs === "function" ? ["(dynamic)"] : a._config.handoffs?.map((h) => h.agent._name) ?? [],
1225
+ maxTurns: a._config.maxTurns,
1226
+ temperature: a._config.temperature,
1227
+ maxTokens: a._config.maxTokens,
1228
+ effort: a._config.effort,
1229
+ thinkingBudget: a._config.thinkingBudget,
1230
+ includeThoughts: a._config.includeThoughts,
1231
+ toolChoice: a._config.toolChoice,
1232
+ stop: a._config.stop
1233
+ }));
1234
+ return c.json({ ok: true, data: agents });
1235
+ });
1236
+ app2.get("/agents/:name", (c) => {
1237
+ const runtime = c.get("runtime");
1238
+ const name = c.req.param("name");
1239
+ const agent = runtime.getAgent(name);
1240
+ if (!agent) {
1241
+ return c.json(
1242
+ { ok: false, error: { code: "NOT_FOUND", message: `Agent "${name}" not found` } },
1243
+ 404
1244
+ );
1245
+ }
1246
+ const cfg = agent._config;
1247
+ return c.json({
1248
+ ok: true,
1249
+ data: {
1250
+ name: agent._name,
1251
+ model: agent.resolveModel(),
1252
+ system: agent.resolveSystem(),
1253
+ tools: cfg.tools?.map((t) => ({
1254
+ name: t.name,
1255
+ description: t.description,
1256
+ inputSchema: zodToJsonSchema2(t.inputSchema)
1257
+ })) ?? [],
1258
+ handoffs: typeof cfg.handoffs === "function" ? [
1259
+ {
1260
+ agent: "(dynamic)",
1261
+ description: "Resolved at runtime from metadata",
1262
+ mode: "oneway"
1263
+ }
1264
+ ] : cfg.handoffs?.map((h) => ({
1265
+ agent: h.agent._name,
1266
+ description: h.description,
1267
+ mode: h.mode ?? "oneway"
1268
+ })) ?? [],
1269
+ maxTurns: cfg.maxTurns,
1270
+ temperature: cfg.temperature,
1271
+ maxTokens: cfg.maxTokens,
1272
+ effort: cfg.effort,
1273
+ thinkingBudget: cfg.thinkingBudget,
1274
+ includeThoughts: cfg.includeThoughts,
1275
+ toolChoice: cfg.toolChoice,
1276
+ stop: cfg.stop,
1277
+ timeout: cfg.timeout,
1278
+ maxContext: cfg.maxContext,
1279
+ version: cfg.version,
1280
+ mcp: cfg.mcp,
1281
+ mcpTools: cfg.mcpTools,
1282
+ hasGuardrails: !!cfg.guardrails,
1283
+ guardrails: cfg.guardrails ? {
1284
+ hasInput: !!cfg.guardrails.input,
1285
+ hasOutput: !!cfg.guardrails.output,
1286
+ onBlock: cfg.guardrails.onBlock ?? "throw",
1287
+ maxRetries: cfg.guardrails.maxRetries
1288
+ } : null
1289
+ }
1290
+ });
1291
+ });
1292
+ var agents_default = app2;
1293
+
1294
+ // src/server/routes/tools.ts
1295
+ import { Hono as Hono6 } from "hono";
1296
+ import { zodToJsonSchema as zodToJsonSchema3 } from "@axlsdk/axl";
1297
+ var app3 = new Hono6();
1298
+ app3.get("/tools", (c) => {
1299
+ const runtime = c.get("runtime");
1300
+ const tools = runtime.getTools().map((t) => ({
1301
+ name: t.name,
1302
+ description: t.description,
1303
+ inputSchema: t.inputSchema ? zodToJsonSchema3(t.inputSchema) : {},
1304
+ sensitive: t.sensitive ?? false,
1305
+ requireApproval: t.requireApproval ?? false
1306
+ }));
1307
+ return c.json({ ok: true, data: tools });
1308
+ });
1309
+ app3.get("/tools/:name", (c) => {
1310
+ const runtime = c.get("runtime");
1311
+ const name = c.req.param("name");
1312
+ const tool = runtime.getTool(name);
1313
+ if (!tool) {
1314
+ return c.json(
1315
+ { ok: false, error: { code: "NOT_FOUND", message: `Tool "${name}" not found` } },
1316
+ 404
1317
+ );
1318
+ }
1319
+ return c.json({
1320
+ ok: true,
1321
+ data: {
1322
+ name: tool.name,
1323
+ description: tool.description,
1324
+ inputSchema: tool.inputSchema ? zodToJsonSchema3(tool.inputSchema) : {},
1325
+ sensitive: tool.sensitive,
1326
+ requireApproval: tool.requireApproval,
1327
+ retry: tool.retry,
1328
+ hasHooks: !!tool.hooks,
1329
+ hooks: tool.hooks ? {
1330
+ hasBefore: !!tool.hooks.before,
1331
+ hasAfter: !!tool.hooks.after
1332
+ } : null
1333
+ }
1334
+ });
1335
+ });
1336
+ app3.post("/tools/:name/test", async (c) => {
1337
+ const runtime = c.get("runtime");
1338
+ const name = c.req.param("name");
1339
+ const tool = runtime.getTool(name);
1340
+ if (!tool) {
1341
+ return c.json(
1342
+ { ok: false, error: { code: "NOT_FOUND", message: `Tool "${name}" not found` } },
1343
+ 404
1344
+ );
1345
+ }
1346
+ const body = await c.req.json();
1347
+ const ctx = runtime.createContext();
1348
+ const result = await tool.run(ctx, body.input);
1349
+ return c.json({
1350
+ ok: true,
1351
+ data: { result: redactValue(result, runtime.isRedactEnabled()) }
1352
+ });
1353
+ });
1354
+ var tools_default = app3;
1355
+
1356
+ // src/server/routes/memory.ts
1357
+ import { Hono as Hono7 } from "hono";
1358
+ var app4 = new Hono7();
1359
+ app4.get("/memory/:scope", async (c) => {
1360
+ const runtime = c.get("runtime");
1361
+ const store = runtime.getStateStore();
1362
+ const scope = c.req.param("scope");
1363
+ if (!store.getAllMemory) {
1364
+ return c.json({ ok: true, data: [] });
1365
+ }
1366
+ const entries = await store.getAllMemory(scope);
1367
+ return c.json({ ok: true, data: redactMemoryList(entries, runtime.isRedactEnabled()) });
1368
+ });
1369
+ app4.get("/memory/:scope/:key", async (c) => {
1370
+ const runtime = c.get("runtime");
1371
+ const store = runtime.getStateStore();
1372
+ const scope = c.req.param("scope");
1373
+ const key = c.req.param("key");
1374
+ if (!store.getMemory) {
1375
+ return c.json(
1376
+ { ok: false, error: { code: "NOT_SUPPORTED", message: "Memory not supported" } },
1377
+ 501
1378
+ );
1379
+ }
1380
+ const value = await store.getMemory(scope, key);
1381
+ if (value === null) {
1382
+ return c.json(
1383
+ { ok: false, error: { code: "NOT_FOUND", message: `Memory "${scope}/${key}" not found` } },
1384
+ 404
1385
+ );
1386
+ }
1387
+ return c.json({
1388
+ ok: true,
1389
+ data: { key, value: redactMemoryValue(value, runtime.isRedactEnabled()) }
1390
+ });
1391
+ });
1392
+ app4.put("/memory/:scope/:key", async (c) => {
1393
+ const runtime = c.get("runtime");
1394
+ const store = runtime.getStateStore();
1395
+ const scope = c.req.param("scope");
1396
+ const key = c.req.param("key");
1397
+ if (!store.saveMemory) {
1398
+ return c.json(
1399
+ { ok: false, error: { code: "NOT_SUPPORTED", message: "Memory not supported" } },
1400
+ 501
1401
+ );
1402
+ }
1403
+ const body = await c.req.json();
1404
+ await store.saveMemory(scope, key, body.value);
1405
+ return c.json({ ok: true, data: { saved: true } });
1406
+ });
1407
+ app4.delete("/memory/:scope/:key", async (c) => {
1408
+ const runtime = c.get("runtime");
1409
+ const store = runtime.getStateStore();
1410
+ const scope = c.req.param("scope");
1411
+ const key = c.req.param("key");
1412
+ if (!store.deleteMemory) {
1413
+ return c.json(
1414
+ { ok: false, error: { code: "NOT_SUPPORTED", message: "Memory not supported" } },
1415
+ 501
1416
+ );
1417
+ }
1418
+ await store.deleteMemory(scope, key);
1419
+ return c.json({ ok: true, data: { deleted: true } });
1420
+ });
1421
+ app4.post("/memory/search", async (c) => {
1422
+ return c.json({
1423
+ ok: true,
1424
+ data: { results: [], message: "Semantic search requires MemoryManager with vector store" }
1425
+ });
1426
+ });
1427
+ var memory_default = app4;
1428
+
1429
+ // src/server/routes/decisions.ts
1430
+ import { Hono as Hono8 } from "hono";
1431
+ var app5 = new Hono8();
1432
+ app5.get("/decisions", async (c) => {
1433
+ const runtime = c.get("runtime");
1434
+ const decisions = await runtime.getPendingDecisions();
1435
+ return c.json({
1436
+ ok: true,
1437
+ data: redactPendingDecisionList(decisions, runtime.isRedactEnabled())
1438
+ });
1439
+ });
1440
+ app5.post("/decisions/:executionId/resolve", async (c) => {
1441
+ const runtime = c.get("runtime");
1442
+ const executionId = c.req.param("executionId");
1443
+ const body = await c.req.json();
1444
+ await runtime.resolveDecision(executionId, body);
1445
+ return c.json({ ok: true, data: { resolved: true } });
1446
+ });
1447
+ var decisions_default = app5;
1448
+
1449
+ // src/server/routes/costs.ts
1450
+ import { Hono as Hono9 } from "hono";
1451
+ function createCostRoutes(costAggregator) {
1452
+ const app6 = new Hono9();
1453
+ app6.get("/costs", (c) => {
1454
+ if (c.req.query("windows") === "all") {
1455
+ return c.json({ ok: true, data: costAggregator.getAllSnapshots() });
1456
+ }
1457
+ const window = parseWindowParam(c.req.query("window"));
1458
+ return c.json({ ok: true, data: costAggregator.getSnapshot(window) });
1459
+ });
1460
+ app6.post("/costs/reset", (c) => {
1461
+ return c.json(
1462
+ {
1463
+ ok: false,
1464
+ error: {
1465
+ code: "GONE",
1466
+ message: "POST /api/costs/reset was removed in @axlsdk/studio 0.15. Cost aggregates are now time-windowed and rebuilt from StateStore history. Use GET /api/costs?window=24h|7d|30d|all to narrow the view instead of resetting."
1467
+ }
1468
+ },
1469
+ 410
1470
+ );
1471
+ });
1472
+ return app6;
1473
+ }
1474
+
1475
+ // src/server/routes/evals.ts
1476
+ import { randomUUID } from "crypto";
1477
+ import { Hono as Hono10 } from "hono";
1478
+ function createEvalRoutes(connMgr, evalLoader) {
1479
+ const app6 = new Hono10();
1480
+ const activeRuns = /* @__PURE__ */ new Map();
1481
+ app6.get("/evals", async (c) => {
1482
+ if (evalLoader) await evalLoader();
1483
+ const runtime = c.get("runtime");
1484
+ const evals = runtime.getRegisteredEvals();
1485
+ return c.json({ ok: true, data: evals });
1486
+ });
1487
+ app6.get("/evals/history", async (c) => {
1488
+ const runtime = c.get("runtime");
1489
+ const history = await runtime.getEvalHistory();
1490
+ return c.json({
1491
+ ok: true,
1492
+ data: redactEvalHistoryList(history, runtime.isRedactEnabled())
1493
+ });
1494
+ });
1495
+ app6.delete("/evals/history/:id", async (c) => {
1496
+ const runtime = c.get("runtime");
1497
+ const id = c.req.param("id");
1498
+ const deleted = await runtime.deleteEvalResult(id);
1499
+ if (!deleted) {
1500
+ return c.json(
1501
+ {
1502
+ ok: false,
1503
+ error: { code: "NOT_FOUND", message: `Eval history entry "${id}" not found` }
1504
+ },
1505
+ 404
1506
+ );
1507
+ }
1508
+ return c.json({ ok: true, data: { id, deleted: true } });
1509
+ });
1510
+ app6.post("/evals/:name/run", async (c) => {
1511
+ if (evalLoader) await evalLoader();
1512
+ const runtime = c.get("runtime");
1513
+ const name = c.req.param("name");
1514
+ const redactOn = runtime.isRedactEnabled();
1515
+ const entry = runtime.getRegisteredEval(name);
1516
+ if (!entry) {
1517
+ return c.json(
1518
+ { ok: false, error: { code: "NOT_FOUND", message: `Eval "${name}" not found` } },
1519
+ 404
1520
+ );
1521
+ }
1522
+ let runs = 1;
1523
+ let stream = false;
1524
+ let captureTraces = false;
1525
+ try {
1526
+ const body = await c.req.json().catch(() => ({}));
1527
+ if (typeof body.runs === "number" && Number.isFinite(body.runs) && body.runs > 1) {
1528
+ runs = Math.min(Math.floor(body.runs), 25);
1529
+ }
1530
+ if (body.stream === true) {
1531
+ stream = true;
1532
+ }
1533
+ if (body.captureTraces === true) {
1534
+ captureTraces = true;
1535
+ }
1536
+ } catch {
1537
+ }
1538
+ if (stream) {
1539
+ const evalRunId = `eval-${randomUUID()}`;
1540
+ const ac = new AbortController();
1541
+ activeRuns.set(evalRunId, ac);
1542
+ (async () => {
1543
+ try {
1544
+ if (runs > 1) {
1545
+ const runGroupId = randomUUID();
1546
+ const results = [];
1547
+ for (let r = 0; r < runs; r++) {
1548
+ if (ac.signal.aborted) break;
1549
+ const result = await runtime.runRegisteredEval(name, {
1550
+ metadata: { runGroupId, runIndex: r },
1551
+ signal: ac.signal,
1552
+ captureTraces,
1553
+ onProgress: (event) => {
1554
+ if (event.type === "run_done") return;
1555
+ connMgr.broadcastWithWildcard(`eval:${evalRunId}`, {
1556
+ ...event,
1557
+ run: r + 1,
1558
+ totalRuns: runs
1559
+ });
1560
+ }
1561
+ });
1562
+ results.push(result);
1563
+ connMgr.broadcastWithWildcard(`eval:${evalRunId}`, {
1564
+ type: "run_done",
1565
+ run: r + 1,
1566
+ totalRuns: runs
1567
+ });
1568
+ }
1569
+ if (results.length > 0) {
1570
+ connMgr.broadcastWithWildcard(`eval:${evalRunId}`, {
1571
+ type: "done",
1572
+ evalResultId: results[0].id,
1573
+ runGroupId
1574
+ });
1575
+ } else {
1576
+ connMgr.broadcastWithWildcard(`eval:${evalRunId}`, {
1577
+ type: "error",
1578
+ message: "All runs were cancelled"
1579
+ });
1580
+ }
1581
+ } else {
1582
+ const result = await runtime.runRegisteredEval(name, {
1583
+ signal: ac.signal,
1584
+ captureTraces,
1585
+ onProgress: (event) => {
1586
+ if (event.type === "run_done") return;
1587
+ connMgr.broadcastWithWildcard(`eval:${evalRunId}`, event);
1588
+ }
1589
+ });
1590
+ connMgr.broadcastWithWildcard(`eval:${evalRunId}`, {
1591
+ type: "done",
1592
+ evalResultId: result.id
1593
+ });
1594
+ }
1595
+ } catch (err) {
1596
+ connMgr.broadcastWithWildcard(`eval:${evalRunId}`, {
1597
+ type: "error",
1598
+ message: redactErrorMessage(err, redactOn)
1599
+ });
1600
+ } finally {
1601
+ activeRuns.delete(evalRunId);
1602
+ }
1603
+ })();
1604
+ return c.json({ ok: true, data: { evalRunId } });
1605
+ }
1606
+ try {
1607
+ if (runs > 1) {
1608
+ const { aggregateRuns } = await import("@axlsdk/eval");
1609
+ const runGroupId = randomUUID();
1610
+ const results = [];
1611
+ for (let r = 0; r < runs; r++) {
1612
+ const result2 = await runtime.runRegisteredEval(name, {
1613
+ metadata: { runGroupId, runIndex: r },
1614
+ captureTraces
1615
+ });
1616
+ results.push(result2);
1617
+ }
1618
+ const typedResults = results;
1619
+ const aggregate = aggregateRuns(typedResults);
1620
+ const first = typedResults[0];
1621
+ const result = {
1622
+ ...first,
1623
+ _multiRun: { aggregate, allRuns: typedResults }
1624
+ };
1625
+ return c.json({
1626
+ ok: true,
1627
+ data: redactEvalResult(result, redactOn)
1628
+ });
1629
+ } else {
1630
+ const result = await runtime.runRegisteredEval(name, { captureTraces });
1631
+ return c.json({
1632
+ ok: true,
1633
+ data: redactEvalResult(result, redactOn)
1634
+ });
1635
+ }
1636
+ } catch (err) {
1637
+ return c.json(
1638
+ { ok: false, error: { code: "EVAL_ERROR", message: redactErrorMessage(err, redactOn) } },
1639
+ 400
1640
+ );
1641
+ }
1642
+ });
1643
+ app6.post("/evals/runs/:evalRunId/cancel", (c) => {
1644
+ const evalRunId = c.req.param("evalRunId");
1645
+ const ac = activeRuns.get(evalRunId);
1646
+ if (!ac) {
1647
+ return c.json(
1648
+ { ok: false, error: { code: "NOT_FOUND", message: "No active eval run found" } },
1649
+ 404
1650
+ );
1651
+ }
1652
+ ac.abort();
1653
+ activeRuns.delete(evalRunId);
1654
+ return c.json({ ok: true, data: { cancelled: true } });
1655
+ });
1656
+ app6.post("/evals/:name/rescore", async (c) => {
1657
+ if (evalLoader) await evalLoader();
1658
+ const runtime = c.get("runtime");
1659
+ const redactOn = runtime.isRedactEnabled();
1660
+ const name = c.req.param("name");
1661
+ const body = await c.req.json();
1662
+ if (!body.resultId || typeof body.resultId !== "string") {
1663
+ return c.json(
1664
+ { ok: false, error: { code: "BAD_REQUEST", message: "resultId is required" } },
1665
+ 400
1666
+ );
1667
+ }
1668
+ const entry = runtime.getRegisteredEval(name);
1669
+ if (!entry) {
1670
+ return c.json(
1671
+ { ok: false, error: { code: "NOT_FOUND", message: `Eval "${name}" not found` } },
1672
+ 404
1673
+ );
1674
+ }
1675
+ const history = await runtime.getEvalHistory();
1676
+ const historyEntry = history.find((h) => h.id === body.resultId);
1677
+ if (!historyEntry) {
1678
+ return c.json(
1679
+ { ok: false, error: { code: "NOT_FOUND", message: `Result "${body.resultId}" not found` } },
1680
+ 404
1681
+ );
1682
+ }
1683
+ try {
1684
+ const { rescore } = await import("@axlsdk/eval");
1685
+ const config = entry.config;
1686
+ const result = await rescore(
1687
+ historyEntry.data,
1688
+ config.scorers,
1689
+ runtime
1690
+ );
1691
+ await runtime.saveEvalResult({
1692
+ id: result.id,
1693
+ eval: name,
1694
+ timestamp: Date.now(),
1695
+ data: result
1696
+ });
1697
+ return c.json({
1698
+ ok: true,
1699
+ data: redactEvalResult(result, redactOn)
1700
+ });
1701
+ } catch (err) {
1702
+ return c.json(
1703
+ { ok: false, error: { code: "EVAL_ERROR", message: redactErrorMessage(err, redactOn) } },
1704
+ 400
1705
+ );
1706
+ }
1707
+ });
1708
+ app6.post("/evals/compare", async (c) => {
1709
+ const runtime = c.get("runtime");
1710
+ const redactOn = runtime.isRedactEnabled();
1711
+ const body = await c.req.json();
1712
+ const validateIdParam = (v, name) => {
1713
+ if (typeof v === "string") return v === "" ? `${name} must be non-empty` : null;
1714
+ if (Array.isArray(v)) {
1715
+ if (v.length === 0) return `${name} must be a non-empty array`;
1716
+ for (const elem of v) {
1717
+ if (typeof elem !== "string" || elem === "") {
1718
+ return `${name} array must contain only non-empty strings`;
1719
+ }
1720
+ }
1721
+ return null;
1722
+ }
1723
+ return `${name} is required (string or string[])`;
1724
+ };
1725
+ const baselineErr = validateIdParam(body.baselineId, "baselineId");
1726
+ const candidateErr = validateIdParam(body.candidateId, "candidateId");
1727
+ if (baselineErr || candidateErr) {
1728
+ return c.json(
1729
+ {
1730
+ ok: false,
1731
+ error: {
1732
+ code: "BAD_REQUEST",
1733
+ message: [baselineErr, candidateErr].filter(Boolean).join("; ")
1734
+ }
1735
+ },
1736
+ 400
1737
+ );
1738
+ }
1739
+ const history = await runtime.getEvalHistory();
1740
+ const byId = new Map(history.map((h) => [h.id, h.data]));
1741
+ const missing = [];
1742
+ const resolveOne = (id) => {
1743
+ const data = byId.get(id);
1744
+ if (!data) missing.push(id);
1745
+ return data;
1746
+ };
1747
+ const resolveSelection = (idOrIds) => {
1748
+ if (Array.isArray(idOrIds)) {
1749
+ const unique = Array.from(new Set(idOrIds));
1750
+ if (unique.length === 1) return resolveOne(unique[0]);
1751
+ const results = [];
1752
+ for (const id of unique) {
1753
+ const data = resolveOne(id);
1754
+ if (data) results.push(data);
1755
+ }
1756
+ return results;
1757
+ }
1758
+ return resolveOne(idOrIds);
1759
+ };
1760
+ const baseline = resolveSelection(body.baselineId);
1761
+ const candidate = resolveSelection(body.candidateId);
1762
+ if (missing.length > 0) {
1763
+ return c.json(
1764
+ {
1765
+ ok: false,
1766
+ error: {
1767
+ code: "NOT_FOUND",
1768
+ message: `Eval result(s) not found in history: ${missing.join(", ")}`
1769
+ }
1770
+ },
1771
+ 404
1772
+ );
1773
+ }
1774
+ try {
1775
+ const result = await runtime.evalCompare(baseline, candidate, body.options);
1776
+ return c.json({ ok: true, data: result });
1777
+ } catch (err) {
1778
+ return c.json(
1779
+ {
1780
+ ok: false,
1781
+ error: { code: "COMPARE_FAILED", message: redactErrorMessage(err, redactOn) }
1782
+ },
1783
+ 400
1784
+ );
1785
+ }
1786
+ });
1787
+ app6.post("/evals/import", async (c) => {
1788
+ const runtime = c.get("runtime");
1789
+ const body = await c.req.json();
1790
+ const bad = (message) => c.json({ ok: false, error: { code: "BAD_REQUEST", message } }, 400);
1791
+ if (!body.result || typeof body.result !== "object") {
1792
+ return bad("result is required");
1793
+ }
1794
+ const result = body.result;
1795
+ if (!Array.isArray(result.items)) {
1796
+ return bad("result.items must be an array");
1797
+ }
1798
+ if (typeof result.summary !== "object" || result.summary == null) {
1799
+ return bad("result.summary must be an object");
1800
+ }
1801
+ if (typeof result.dataset !== "string" || !result.dataset) {
1802
+ return bad("result.dataset must be a non-empty string (required for compare)");
1803
+ }
1804
+ const summary = result.summary;
1805
+ if (typeof summary.scorers !== "object" || summary.scorers == null) {
1806
+ return bad("result.summary.scorers must be an object");
1807
+ }
1808
+ const summaryScorerNames = Object.keys(summary.scorers);
1809
+ const items = result.items;
1810
+ const summaryScorerSet = new Set(summaryScorerNames);
1811
+ const uncoveredAcrossItems = /* @__PURE__ */ new Set();
1812
+ for (const item of items) {
1813
+ const itemScores = item?.scores;
1814
+ if (itemScores && typeof itemScores === "object") {
1815
+ for (const name of Object.keys(itemScores)) {
1816
+ if (!summaryScorerSet.has(name)) uncoveredAcrossItems.add(name);
1817
+ }
1818
+ }
1819
+ }
1820
+ if (uncoveredAcrossItems.size > 0) {
1821
+ return bad(
1822
+ `item scores reference scorer(s) not in summary.scorers: ${[...uncoveredAcrossItems].join(", ")}`
1823
+ );
1824
+ }
1825
+ const trim = (v) => typeof v === "string" && v.trim() !== "" ? v.trim() : void 0;
1826
+ const metadataObj = typeof result.metadata === "object" && result.metadata != null ? result.metadata : {};
1827
+ const workflowsFromMeta = Array.isArray(metadataObj.workflows) ? metadataObj.workflows : [];
1828
+ const primaryWorkflow = workflowsFromMeta.find((w) => typeof w === "string");
1829
+ const evalName = trim(body.eval) ?? trim(primaryWorkflow) ?? // Legacy fallback: pre-0.14 CLI artifacts had workflow at the top level.
1830
+ trim(result.workflow) ?? "imported";
1831
+ const id = randomUUID();
1832
+ const timestamp = Date.now();
1833
+ const imported = {
1834
+ ...result,
1835
+ id,
1836
+ metadata: typeof result.metadata === "object" && result.metadata != null ? result.metadata : {}
1837
+ };
1838
+ await runtime.saveEvalResult({
1839
+ id,
1840
+ eval: evalName,
1841
+ timestamp,
1842
+ data: imported
1843
+ });
1844
+ return c.json({ ok: true, data: { id, eval: evalName, timestamp } });
1845
+ });
1846
+ function closeActiveRuns() {
1847
+ for (const ac of activeRuns.values()) ac.abort();
1848
+ activeRuns.clear();
1849
+ }
1850
+ return { app: app6, closeActiveRuns };
1851
+ }
1852
+
1853
+ // src/server/routes/playground.ts
1854
+ import { Hono as Hono11 } from "hono";
1855
+ function createPlaygroundRoutes(connMgr) {
1856
+ const app6 = new Hono11();
1857
+ app6.post("/playground/chat", async (c) => {
1858
+ const runtime = c.get("runtime");
1859
+ const body = await c.req.json();
1860
+ if (!body.message || typeof body.message !== "string" || !body.message.trim()) {
1861
+ return c.json(
1862
+ {
1863
+ ok: false,
1864
+ error: {
1865
+ code: "INVALID_INPUT",
1866
+ message: "message is required and must be a non-empty string"
1867
+ }
1868
+ },
1869
+ 400
1870
+ );
1871
+ }
1872
+ const agents = runtime.getAgents();
1873
+ const agent = body.agent ? agents.find((a) => a._name === body.agent) : agents[0];
1874
+ if (!agent) {
1875
+ return c.json(
1876
+ {
1877
+ ok: false,
1878
+ error: { code: "NO_AGENT", message: `Agent "${body.agent ?? ""}" not found` }
1879
+ },
1880
+ 400
1881
+ );
1882
+ }
1883
+ const sessionId = body.sessionId ?? `playground-${Date.now()}`;
1884
+ const executionId = `playground-${sessionId}-${Date.now()}`;
1885
+ const store = runtime.getStateStore();
1886
+ const history = await store.getSession(sessionId);
1887
+ history.push({ role: "user", content: body.message });
1888
+ const redactOn = runtime.isRedactEnabled();
1889
+ const broadcast = (event) => {
1890
+ connMgr.broadcastWithWildcard(`execution:${executionId}`, redactStreamEvent(event, redactOn));
1891
+ };
1892
+ const ctx = runtime.createContext({
1893
+ sessionHistory: history,
1894
+ onToken: (token) => {
1895
+ broadcast({ type: "token", data: token });
1896
+ }
1897
+ });
1898
+ (async () => {
1899
+ try {
1900
+ const result = await ctx.ask(agent, body.message);
1901
+ const resultText = typeof result === "string" ? result : JSON.stringify(result);
1902
+ history.push({ role: "assistant", content: resultText });
1903
+ await store.saveSession(sessionId, history);
1904
+ broadcast({ type: "done", data: resultText });
1905
+ } catch (err) {
1906
+ broadcast({
1907
+ type: "error",
1908
+ message: err instanceof Error ? err.message : String(err)
1909
+ });
1910
+ }
1911
+ })();
1912
+ return c.json({
1913
+ ok: true,
1914
+ data: { sessionId, executionId, streaming: true }
1915
+ });
1916
+ });
1917
+ return app6;
1918
+ }
1919
+
1920
+ // src/server/routes/eval-trends.ts
1921
+ import { Hono as Hono12 } from "hono";
1922
+ function createEvalTrendsRoutes(aggregator) {
1923
+ const app6 = new Hono12();
1924
+ app6.get("/eval-trends", (c) => {
1925
+ const window = parseWindowParam(c.req.query("window"));
1926
+ return c.json({ ok: true, data: aggregator.getSnapshot(window) });
1927
+ });
1928
+ return app6;
1929
+ }
1930
+
1931
+ // src/server/routes/workflow-stats.ts
1932
+ import { Hono as Hono13 } from "hono";
1933
+ function createWorkflowStatsRoutes(aggregator) {
1934
+ const app6 = new Hono13();
1935
+ app6.get("/workflow-stats", (c) => {
1936
+ const window = parseWindowParam(c.req.query("window"));
1937
+ return c.json({ ok: true, data: enrichWorkflowStats(aggregator.getSnapshot(window)) });
1938
+ });
1939
+ return app6;
1940
+ }
1941
+
1942
+ // src/server/routes/trace-stats.ts
1943
+ import { Hono as Hono14 } from "hono";
1944
+ function createTraceStatsRoutes(aggregator) {
1945
+ const app6 = new Hono14();
1946
+ app6.get("/trace-stats", (c) => {
1947
+ const window = parseWindowParam(c.req.query("window"));
1948
+ return c.json({ ok: true, data: aggregator.getSnapshot(window) });
1949
+ });
1950
+ return app6;
1951
+ }
1952
+
1953
+ // src/server/index.ts
1954
+ function createServer(options) {
1955
+ const { runtime, staticRoot, basePath = "", readOnly = false } = options;
1956
+ const app6 = new Hono15();
1957
+ const connMgr = new ConnectionManager();
1958
+ const windows = ["24h", "7d", "30d", "all"];
1959
+ const costAggregator = new TraceAggregator({
1960
+ runtime,
1961
+ connMgr,
1962
+ channel: "costs",
1963
+ reducer: reduceCost,
1964
+ emptyState: emptyCostData,
1965
+ windows
1966
+ });
1967
+ const workflowStatsAggregator = new ExecutionAggregator({
1968
+ runtime,
1969
+ connMgr,
1970
+ channel: "workflow-stats",
1971
+ reducer: reduceWorkflowStats,
1972
+ emptyState: emptyWorkflowStatsData,
1973
+ windows,
1974
+ broadcastTransform: enrichWorkflowStats
1975
+ });
1976
+ const traceStatsAggregator = new TraceAggregator({
1977
+ runtime,
1978
+ connMgr,
1979
+ channel: "trace-stats",
1980
+ reducer: reduceTraceStats,
1981
+ emptyState: emptyTraceStatsData,
1982
+ windows
1983
+ });
1984
+ const evalTrendsAggregator = new EvalAggregator({
1985
+ runtime,
1986
+ connMgr,
1987
+ channel: "eval-trends",
1988
+ reducer: reduceEvalTrends,
1989
+ emptyState: emptyEvalTrendData,
1990
+ windows
1991
+ });
1992
+ if (options.cors !== false) {
1993
+ app6.use("*", cors());
1994
+ }
1995
+ app6.use("*", errorHandler);
1996
+ app6.use("*", async (c, next) => {
1997
+ c.set("runtime", runtime);
1998
+ await next();
1999
+ });
2000
+ if (readOnly) {
2001
+ const blocked = [
2002
+ /^POST \/api\/workflows(\/|$)/,
2003
+ /^POST \/api\/executions(\/|$)/,
2004
+ /^POST \/api\/sessions(\/|$)/,
2005
+ /^DELETE \/api\/sessions(\/|$)/,
2006
+ /^PUT \/api\/memory(\/|$)/,
2007
+ /^DELETE \/api\/memory(\/|$)/,
2008
+ /^POST \/api\/decisions(\/|$)/,
2009
+ /^POST \/api\/tools(\/|$)/,
2010
+ /^POST \/api\/evals\/import$/,
2011
+ /^POST \/api\/evals\/[^/]+\/run$/,
2012
+ /^POST \/api\/evals\/[^/]+\/rescore$/,
2013
+ /^POST \/api\/evals\/runs\/[^/]+\/cancel$/,
2014
+ /^DELETE \/api\/evals\/history\/[^/]+$/,
2015
+ /^POST \/api\/playground(\/|$)/
2016
+ ];
2017
+ app6.use("/api/*", async (c, next) => {
2018
+ const apiIdx = c.req.path.indexOf("/api/");
2019
+ const apiPath = apiIdx >= 0 ? c.req.path.slice(apiIdx) : c.req.path;
2020
+ const key = `${c.req.method} ${apiPath}`;
2021
+ if (blocked.some((re) => re.test(key))) {
2022
+ return c.json(
2023
+ {
2024
+ ok: false,
2025
+ error: { code: "READ_ONLY", message: "Studio is mounted in read-only mode" }
2026
+ },
2027
+ 405
2028
+ );
2029
+ }
2030
+ await next();
2031
+ });
2032
+ }
2033
+ const api = new Hono15();
2034
+ api.route("/", createHealthRoutes(readOnly));
2035
+ api.route("/", createWorkflowRoutes(connMgr));
2036
+ api.route("/", executions_default);
2037
+ api.route("/", createSessionRoutes(connMgr));
2038
+ api.route("/", agents_default);
2039
+ api.route("/", tools_default);
2040
+ api.route("/", memory_default);
2041
+ api.route("/", decisions_default);
2042
+ api.route("/", createCostRoutes(costAggregator));
2043
+ api.route("/", createEvalTrendsRoutes(evalTrendsAggregator));
2044
+ api.route("/", createWorkflowStatsRoutes(workflowStatsAggregator));
2045
+ api.route("/", createTraceStatsRoutes(traceStatsAggregator));
2046
+ const { app: evalApp, closeActiveRuns } = createEvalRoutes(connMgr, options.evalLoader);
2047
+ api.route("/", evalApp);
2048
+ api.route("/", createPlaygroundRoutes(connMgr));
2049
+ app6.route("/api", api);
2050
+ const traceListener = (event) => {
2051
+ const traceEvent = event;
2052
+ if (traceEvent.executionId) {
2053
+ connMgr.broadcastWithWildcard(`trace:${traceEvent.executionId}`, traceEvent);
2054
+ }
2055
+ if (traceEvent.type === "await_human") {
2056
+ connMgr.broadcast("decisions", traceEvent);
2057
+ }
2058
+ };
2059
+ runtime.on("trace", traceListener);
2060
+ const aggregatorStartPromise = Promise.all([
2061
+ costAggregator.start(),
2062
+ workflowStatsAggregator.start(),
2063
+ traceStatsAggregator.start(),
2064
+ evalTrendsAggregator.start()
2065
+ ]).catch((err) => console.error("[axl-studio] aggregator start failed:", err));
2066
+ if (staticRoot) {
2067
+ const indexPath = resolve(staticRoot, "index.html");
2068
+ let spaHtml;
2069
+ if (!existsSync(indexPath)) {
2070
+ console.warn(`[axl-studio] index.html not found at ${indexPath}`);
2071
+ } else {
2072
+ const rawHtml = readFileSync(indexPath, "utf-8");
2073
+ if (basePath) {
2074
+ const safeBasePath = JSON.stringify(basePath).replace(/</g, "\\u003c");
2075
+ const injected = rawHtml.replace(
2076
+ "<head>",
2077
+ `<head>
2078
+ <base href="${basePath}/">
2079
+ <script>window.__AXL_STUDIO_BASE__=${safeBasePath}</script>`
2080
+ );
2081
+ if (injected === rawHtml) {
2082
+ console.warn(
2083
+ "[axl-studio] Could not inject basePath into index.html \u2014 <head> tag not found. The SPA may not route correctly."
2084
+ );
2085
+ }
2086
+ spaHtml = injected;
2087
+ } else {
2088
+ spaHtml = rawHtml;
2089
+ }
2090
+ }
2091
+ const staticHandler = serveStatic({
2092
+ root: staticRoot,
2093
+ rewriteRequestPath: basePath ? (path) => path.startsWith(basePath) ? path.slice(basePath.length) || "/" : path : void 0
2094
+ });
2095
+ app6.use("/*", async (c, next) => {
2096
+ const reqPath = c.req.path;
2097
+ const resolved = basePath && reqPath.startsWith(basePath) ? reqPath.slice(basePath.length) || "/" : reqPath;
2098
+ if (resolved === "/" || resolved === "/index.html" || resolved === "/ws") {
2099
+ return next();
2100
+ }
2101
+ return staticHandler(c, next);
2102
+ });
2103
+ if (spaHtml) {
2104
+ app6.get("*", async (c, next) => {
2105
+ const resolved = basePath && c.req.path.startsWith(basePath) ? c.req.path.slice(basePath.length) || "/" : c.req.path;
2106
+ if (resolved === "/ws") return next();
2107
+ return c.html(spaHtml);
2108
+ });
2109
+ }
2110
+ }
2111
+ return {
2112
+ app: app6,
2113
+ connMgr,
2114
+ costAggregator,
2115
+ workflowStatsAggregator,
2116
+ traceStatsAggregator,
2117
+ evalTrendsAggregator,
2118
+ aggregatorStartPromise,
2119
+ /** Create WS handlers. Call before registering static/SPA routes are reached. */
2120
+ createWsHandlers: () => createWsHandlers(connMgr),
2121
+ traceListener,
2122
+ /** Abort all active streaming eval runs. */
2123
+ closeActiveRuns,
2124
+ /** Close all aggregators (clear intervals and unsubscribe listeners). */
2125
+ closeAggregators: () => {
2126
+ costAggregator.close();
2127
+ workflowStatsAggregator.close();
2128
+ traceStatsAggregator.close();
2129
+ evalTrendsAggregator.close();
2130
+ }
2131
+ };
2132
+ }
2133
+
2134
+ export {
2135
+ ConnectionManager,
2136
+ handleWsMessage,
2137
+ TraceAggregator,
2138
+ ExecutionAggregator,
2139
+ EvalAggregator,
2140
+ createServer
2141
+ };
2142
+ //# sourceMappingURL=chunk-IPDMFFTQ.js.map