@axlsdk/studio 0.17.3 → 0.17.5

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