@axlsdk/studio 0.15.0 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/dist/{chunk-IPDMFFTQ.js → chunk-RE6VPUXA.js} +274 -203
- package/dist/chunk-RE6VPUXA.js.map +1 -0
- package/dist/cli.cjs +281 -210
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/client/assets/index-BzQe3w-R.js +313 -0
- package/dist/client/assets/index-C2nTRFWX.css +1 -0
- package/dist/client/index.html +20 -2
- package/dist/{connection-manager-BMPahDuY.d.cts → connection-manager-DAuqk9lM.d.cts} +24 -1
- package/dist/{connection-manager-BMPahDuY.d.ts → connection-manager-DAuqk9lM.d.ts} +24 -1
- package/dist/middleware.cjs +283 -211
- package/dist/middleware.cjs.map +1 -1
- package/dist/middleware.d.cts +32 -9
- package/dist/middleware.d.ts +32 -9
- package/dist/middleware.js +3 -2
- package/dist/middleware.js.map +1 -1
- package/dist/server/index.cjs +281 -210
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +12 -6
- package/dist/server/index.d.ts +12 -6
- package/dist/server/index.js +1 -1
- package/package.json +7 -7
- package/dist/chunk-IPDMFFTQ.js.map +0 -1
- package/dist/client/assets/index-CLKKOaE2.css +0 -1
- package/dist/client/assets/index-rvds50cZ.js +0 -278
package/dist/middleware.cjs
CHANGED
|
@@ -48,6 +48,7 @@ var import_cors = require("hono/cors");
|
|
|
48
48
|
var import_serve_static = require("@hono/node-server/serve-static");
|
|
49
49
|
|
|
50
50
|
// src/server/redact.ts
|
|
51
|
+
var import_axl = require("@axlsdk/axl");
|
|
51
52
|
var REDACTED = "[redacted]";
|
|
52
53
|
var SAFE_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
53
54
|
"QuorumNotMet",
|
|
@@ -72,7 +73,8 @@ function redactExecutionInfo(info, redact) {
|
|
|
72
73
|
return {
|
|
73
74
|
...info,
|
|
74
75
|
...info.result !== void 0 ? { result: REDACTED } : {},
|
|
75
|
-
...info.error !== void 0 ? { error: REDACTED } : {}
|
|
76
|
+
...info.error !== void 0 ? { error: REDACTED } : {},
|
|
77
|
+
events: info.events.map((e) => redactStreamEvent(e, true))
|
|
76
78
|
};
|
|
77
79
|
}
|
|
78
80
|
function redactExecutionList(infos, redact) {
|
|
@@ -113,30 +115,7 @@ function redactSessionHistory(history, redact) {
|
|
|
113
115
|
}
|
|
114
116
|
function redactStreamEvent(event, redact) {
|
|
115
117
|
if (!redact) return event;
|
|
116
|
-
|
|
117
|
-
case "token":
|
|
118
|
-
return { type: "token", data: REDACTED };
|
|
119
|
-
case "tool_call":
|
|
120
|
-
return { ...event, args: REDACTED };
|
|
121
|
-
case "tool_result":
|
|
122
|
-
return { ...event, result: REDACTED };
|
|
123
|
-
case "tool_approval":
|
|
124
|
-
return {
|
|
125
|
-
...event,
|
|
126
|
-
args: REDACTED,
|
|
127
|
-
...event.reason !== void 0 ? { reason: REDACTED } : {}
|
|
128
|
-
};
|
|
129
|
-
case "done":
|
|
130
|
-
return { type: "done", data: REDACTED };
|
|
131
|
-
case "error":
|
|
132
|
-
return { type: "error", message: REDACTED };
|
|
133
|
-
// Structural events have no user content to scrub.
|
|
134
|
-
case "agent_start":
|
|
135
|
-
case "agent_end":
|
|
136
|
-
case "handoff":
|
|
137
|
-
case "step":
|
|
138
|
-
return event;
|
|
139
|
-
}
|
|
118
|
+
return (0, import_axl.redactEvent)(event);
|
|
140
119
|
}
|
|
141
120
|
function redactEvalItem(item) {
|
|
142
121
|
const scrubbed = {
|
|
@@ -222,13 +201,17 @@ async function errorHandler(c, next) {
|
|
|
222
201
|
|
|
223
202
|
// src/server/ws/connection-manager.ts
|
|
224
203
|
var BUFFER_TTL_MS = 3e4;
|
|
225
|
-
var
|
|
204
|
+
var DEFAULT_MAX_BUFFER_EVENTS = 1e3;
|
|
205
|
+
var DEFAULT_MAX_BUFFER_BYTES = 4 * 1024 * 1024;
|
|
206
|
+
var DEFAULT_MAX_ACTIVE_BUFFERS = 256;
|
|
207
|
+
var UNBUFFERED_EVENT_TYPES = /* @__PURE__ */ new Set(["token", "partial_object"]);
|
|
226
208
|
var MAX_WS_FRAME_BYTES = 65536;
|
|
227
209
|
function isBufferedChannel(channel) {
|
|
228
210
|
return channel.startsWith("execution:") || channel.startsWith("eval:");
|
|
229
211
|
}
|
|
230
212
|
function truncateIfOversized(msg, channel, data) {
|
|
231
|
-
|
|
213
|
+
const msgBytes = Buffer.byteLength(msg, "utf8");
|
|
214
|
+
if (msgBytes <= MAX_WS_FRAME_BYTES) return msg;
|
|
232
215
|
const event = data ?? {};
|
|
233
216
|
const truncated = {
|
|
234
217
|
type: "event",
|
|
@@ -237,7 +220,7 @@ function truncateIfOversized(msg, channel, data) {
|
|
|
237
220
|
...event,
|
|
238
221
|
data: {
|
|
239
222
|
__truncated: true,
|
|
240
|
-
originalBytes:
|
|
223
|
+
originalBytes: msgBytes,
|
|
241
224
|
maxBytes: MAX_WS_FRAME_BYTES,
|
|
242
225
|
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."
|
|
243
226
|
}
|
|
@@ -254,6 +237,25 @@ var ConnectionManager = class {
|
|
|
254
237
|
buffers = /* @__PURE__ */ new Map();
|
|
255
238
|
maxConnections = 100;
|
|
256
239
|
filter;
|
|
240
|
+
/** Resolved replay-buffer caps. Per-instance so embedders can dial them
|
|
241
|
+
* without monkey-patching module-level constants. */
|
|
242
|
+
maxEventsPerBuffer;
|
|
243
|
+
maxBytesPerBuffer;
|
|
244
|
+
maxActiveBuffers;
|
|
245
|
+
constructor(bufferCaps) {
|
|
246
|
+
const validatePositiveInt = (key, value) => {
|
|
247
|
+
if (value === void 0) return;
|
|
248
|
+
if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1) {
|
|
249
|
+
throw new RangeError(`bufferCaps.${key} must be a positive integer (>= 1); got ${value}`);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
validatePositiveInt("maxEventsPerBuffer", bufferCaps?.maxEventsPerBuffer);
|
|
253
|
+
validatePositiveInt("maxBytesPerBuffer", bufferCaps?.maxBytesPerBuffer);
|
|
254
|
+
validatePositiveInt("maxActiveBuffers", bufferCaps?.maxActiveBuffers);
|
|
255
|
+
this.maxEventsPerBuffer = bufferCaps?.maxEventsPerBuffer ?? DEFAULT_MAX_BUFFER_EVENTS;
|
|
256
|
+
this.maxBytesPerBuffer = bufferCaps?.maxBytesPerBuffer ?? DEFAULT_MAX_BUFFER_BYTES;
|
|
257
|
+
this.maxActiveBuffers = bufferCaps?.maxActiveBuffers ?? DEFAULT_MAX_ACTIVE_BUFFERS;
|
|
258
|
+
}
|
|
257
259
|
/**
|
|
258
260
|
* Register a broadcast filter. Called once at middleware construction.
|
|
259
261
|
* The filter runs on every outbound event and can drop or deliver based
|
|
@@ -336,13 +338,37 @@ var ConnectionManager = class {
|
|
|
336
338
|
if (isBufferedChannel(channel)) {
|
|
337
339
|
let buffer = this.buffers.get(channel);
|
|
338
340
|
if (!buffer) {
|
|
339
|
-
|
|
341
|
+
if (this.buffers.size >= this.maxActiveBuffers) {
|
|
342
|
+
let victim;
|
|
343
|
+
for (const [ch, buf] of this.buffers) {
|
|
344
|
+
if (buf.complete) {
|
|
345
|
+
victim = ch;
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (victim === void 0) {
|
|
350
|
+
victim = this.buffers.keys().next().value;
|
|
351
|
+
}
|
|
352
|
+
if (victim !== void 0) {
|
|
353
|
+
const old = this.buffers.get(victim);
|
|
354
|
+
if (old?.timer) clearTimeout(old.timer);
|
|
355
|
+
this.buffers.delete(victim);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
buffer = { events: [], complete: false, bytes: 0 };
|
|
340
359
|
this.buffers.set(channel, buffer);
|
|
341
360
|
}
|
|
342
361
|
const event = data;
|
|
343
362
|
const isTerminal = event.type === "done" || event.type === "error";
|
|
344
|
-
|
|
345
|
-
|
|
363
|
+
const isUnbuffered = event.type !== void 0 && UNBUFFERED_EVENT_TYPES.has(event.type);
|
|
364
|
+
if (!isUnbuffered) {
|
|
365
|
+
const msgBytes = Buffer.byteLength(msg, "utf8");
|
|
366
|
+
const atCountCap = buffer.events.length >= this.maxEventsPerBuffer;
|
|
367
|
+
const atByteCap = buffer.bytes + msgBytes > this.maxBytesPerBuffer;
|
|
368
|
+
if (isTerminal || !atCountCap && !atByteCap) {
|
|
369
|
+
buffer.events.push({ msg, data });
|
|
370
|
+
buffer.bytes += msgBytes;
|
|
371
|
+
}
|
|
346
372
|
}
|
|
347
373
|
if (isTerminal) {
|
|
348
374
|
buffer.complete = true;
|
|
@@ -427,7 +453,7 @@ var VALID_CHANNEL_PREFIXES = ["execution:", "trace:", "eval:"];
|
|
|
427
453
|
var VALID_EXACT_CHANNELS = ["costs", "decisions", "eval-trends", "workflow-stats", "trace-stats"];
|
|
428
454
|
var MAX_CHANNEL_LENGTH = 256;
|
|
429
455
|
function handleWsMessage(raw, socket, connMgr) {
|
|
430
|
-
if (raw
|
|
456
|
+
if (Buffer.byteLength(raw, "utf8") > MAX_WS_FRAME_BYTES) {
|
|
431
457
|
return JSON.stringify({ type: "error", message: "Message too large" });
|
|
432
458
|
}
|
|
433
459
|
let msg;
|
|
@@ -583,7 +609,7 @@ var TraceAggregator = class {
|
|
|
583
609
|
this.options.windows.map((w) => [w, this.options.emptyState()])
|
|
584
610
|
);
|
|
585
611
|
for (const exec of capped) {
|
|
586
|
-
for (const event of exec.
|
|
612
|
+
for (const event of exec.events) {
|
|
587
613
|
for (const window of this.options.windows) {
|
|
588
614
|
if (withinWindow(event.timestamp, window, now)) {
|
|
589
615
|
fresh.set(window, this.options.reducer(fresh.get(window), event));
|
|
@@ -605,15 +631,131 @@ var TraceAggregator = class {
|
|
|
605
631
|
}
|
|
606
632
|
};
|
|
607
633
|
|
|
634
|
+
// src/server/aggregates/execution-aggregator.ts
|
|
635
|
+
var ExecutionAggregator = class {
|
|
636
|
+
snaps;
|
|
637
|
+
interval;
|
|
638
|
+
listener;
|
|
639
|
+
options;
|
|
640
|
+
/** Generation counter to prevent stale async fold after rebuild. */
|
|
641
|
+
generation = 0;
|
|
642
|
+
constructor(options) {
|
|
643
|
+
this.options = options;
|
|
644
|
+
this.snaps = new AggregateSnapshots(
|
|
645
|
+
options.windows,
|
|
646
|
+
options.emptyState,
|
|
647
|
+
options.connMgr,
|
|
648
|
+
options.channel,
|
|
649
|
+
options.broadcastTransform
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
async start() {
|
|
653
|
+
await this.rebuild();
|
|
654
|
+
this.listener = (event) => {
|
|
655
|
+
if (event.type !== "workflow_end") return;
|
|
656
|
+
const gen = this.generation;
|
|
657
|
+
this.options.runtime.getExecution(event.executionId).then((exec) => {
|
|
658
|
+
if (this.generation !== gen) return;
|
|
659
|
+
if (exec) {
|
|
660
|
+
this.snaps.fold(exec.startedAt, (prev) => this.options.reducer(prev, exec));
|
|
661
|
+
}
|
|
662
|
+
}).catch((err) => console.error("[axl-studio] execution fold failed:", err));
|
|
663
|
+
};
|
|
664
|
+
this.options.runtime.on("trace", this.listener);
|
|
665
|
+
this.interval = setInterval(
|
|
666
|
+
() => this.rebuild().catch((err) => console.error("[axl-studio] rebuild failed:", err)),
|
|
667
|
+
REBUILD_INTERVAL_MS
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
async rebuild() {
|
|
671
|
+
this.generation++;
|
|
672
|
+
const executions = await this.options.runtime.getExecutions();
|
|
673
|
+
const cap = this.options.executionCap ?? 2e3;
|
|
674
|
+
const capped = executions.slice(0, cap);
|
|
675
|
+
const now = Date.now();
|
|
676
|
+
const fresh = new Map(
|
|
677
|
+
this.options.windows.map((w) => [w, this.options.emptyState()])
|
|
678
|
+
);
|
|
679
|
+
for (const exec of capped) {
|
|
680
|
+
for (const window of this.options.windows) {
|
|
681
|
+
if (withinWindow(exec.startedAt, window, now)) {
|
|
682
|
+
fresh.set(window, this.options.reducer(fresh.get(window), exec));
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
this.snaps.replace(fresh);
|
|
687
|
+
}
|
|
688
|
+
getSnapshot(window) {
|
|
689
|
+
return this.snaps.get(window);
|
|
690
|
+
}
|
|
691
|
+
getAllSnapshots() {
|
|
692
|
+
return this.snaps.getAll();
|
|
693
|
+
}
|
|
694
|
+
close() {
|
|
695
|
+
if (this.listener) this.options.runtime.off("trace", this.listener);
|
|
696
|
+
if (this.interval) clearInterval(this.interval);
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
// src/server/aggregates/eval-aggregator.ts
|
|
701
|
+
var EvalAggregator = class {
|
|
702
|
+
snaps;
|
|
703
|
+
interval;
|
|
704
|
+
listener;
|
|
705
|
+
options;
|
|
706
|
+
constructor(options) {
|
|
707
|
+
this.options = options;
|
|
708
|
+
this.snaps = new AggregateSnapshots(
|
|
709
|
+
options.windows,
|
|
710
|
+
options.emptyState,
|
|
711
|
+
options.connMgr,
|
|
712
|
+
options.channel,
|
|
713
|
+
options.broadcastTransform
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
async start() {
|
|
717
|
+
await this.rebuild();
|
|
718
|
+
this.listener = (entry) => {
|
|
719
|
+
this.snaps.fold(entry.timestamp, (prev) => this.options.reducer(prev, entry));
|
|
720
|
+
};
|
|
721
|
+
this.options.runtime.on("eval_result", this.listener);
|
|
722
|
+
this.interval = setInterval(
|
|
723
|
+
() => this.rebuild().catch((err) => console.error("[axl-studio] rebuild failed:", err)),
|
|
724
|
+
REBUILD_INTERVAL_MS
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
async rebuild() {
|
|
728
|
+
const history = await this.options.runtime.getEvalHistory();
|
|
729
|
+
const cap = this.options.entryCap ?? 500;
|
|
730
|
+
const capped = history.slice(0, cap);
|
|
731
|
+
const now = Date.now();
|
|
732
|
+
const fresh = new Map(
|
|
733
|
+
this.options.windows.map((w) => [w, this.options.emptyState()])
|
|
734
|
+
);
|
|
735
|
+
for (const entry of capped) {
|
|
736
|
+
for (const window of this.options.windows) {
|
|
737
|
+
if (withinWindow(entry.timestamp, window, now)) {
|
|
738
|
+
fresh.set(window, this.options.reducer(fresh.get(window), entry));
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
this.snaps.replace(fresh);
|
|
743
|
+
}
|
|
744
|
+
getSnapshot(window) {
|
|
745
|
+
return this.snaps.get(window);
|
|
746
|
+
}
|
|
747
|
+
getAllSnapshots() {
|
|
748
|
+
return this.snaps.getAll();
|
|
749
|
+
}
|
|
750
|
+
close() {
|
|
751
|
+
if (this.listener) this.options.runtime.off("eval_result", this.listener);
|
|
752
|
+
if (this.interval) clearInterval(this.interval);
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
|
|
608
756
|
// src/server/aggregates/reducers.ts
|
|
757
|
+
var import_axl2 = require("@axlsdk/axl");
|
|
609
758
|
var finite = (v) => Number.isFinite(v) ? v : 0;
|
|
610
|
-
function isLogEvent(event, eventName) {
|
|
611
|
-
if (event.type === eventName) return true;
|
|
612
|
-
if (event.type === "log" && event.data != null && typeof event.data === "object") {
|
|
613
|
-
return event.data.event === eventName;
|
|
614
|
-
}
|
|
615
|
-
return false;
|
|
616
|
-
}
|
|
617
759
|
function emptyRetry() {
|
|
618
760
|
return {
|
|
619
761
|
primary: 0,
|
|
@@ -639,7 +781,7 @@ function emptyCostData() {
|
|
|
639
781
|
};
|
|
640
782
|
}
|
|
641
783
|
function reduceCost(acc, event) {
|
|
642
|
-
const isWorkflowStart =
|
|
784
|
+
const isWorkflowStart = event.type === "workflow_start";
|
|
643
785
|
if (isWorkflowStart && event.workflow) {
|
|
644
786
|
const byWorkflow2 = { ...acc.byWorkflow };
|
|
645
787
|
const prev = byWorkflow2[event.workflow] ?? { cost: 0, executions: 0 };
|
|
@@ -647,9 +789,10 @@ function reduceCost(acc, event) {
|
|
|
647
789
|
return { ...acc, byWorkflow: byWorkflow2 };
|
|
648
790
|
}
|
|
649
791
|
if (event.cost == null && !event.tokens) return acc;
|
|
650
|
-
const cost =
|
|
792
|
+
const cost = (0, import_axl2.eventCostContribution)(event);
|
|
793
|
+
if (event.type === "ask_end") return acc;
|
|
651
794
|
const tokens = event.tokens ?? {};
|
|
652
|
-
const totalTokens = event.type === "
|
|
795
|
+
const totalTokens = event.type === "agent_call_end" ? {
|
|
653
796
|
input: acc.totalTokens.input + finite(tokens.input),
|
|
654
797
|
output: acc.totalTokens.output + finite(tokens.output),
|
|
655
798
|
reasoning: acc.totalTokens.reasoning + finite(tokens.reasoning)
|
|
@@ -680,7 +823,7 @@ function reduceCost(acc, event) {
|
|
|
680
823
|
};
|
|
681
824
|
}
|
|
682
825
|
let retry = acc.retry;
|
|
683
|
-
if (event.type === "
|
|
826
|
+
if (event.type === "agent_call_end") {
|
|
684
827
|
const d = event.data ?? {};
|
|
685
828
|
const reason = d.retryReason;
|
|
686
829
|
retry = { ...acc.retry };
|
|
@@ -702,19 +845,17 @@ function reduceCost(acc, event) {
|
|
|
702
845
|
}
|
|
703
846
|
}
|
|
704
847
|
let byEmbedder = acc.byEmbedder;
|
|
705
|
-
if (event.type === "
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
};
|
|
717
|
-
}
|
|
848
|
+
if (event.type === "memory_remember" || event.type === "memory_recall") {
|
|
849
|
+
const usage = event.data.usage;
|
|
850
|
+
byEmbedder = { ...acc.byEmbedder };
|
|
851
|
+
const modelKey = usage?.model ?? "unknown";
|
|
852
|
+
const embedTokens = typeof usage?.tokens === "number" ? finite(usage.tokens) : 0;
|
|
853
|
+
const prev = byEmbedder[modelKey] ?? { cost: 0, calls: 0, tokens: 0 };
|
|
854
|
+
byEmbedder[modelKey] = {
|
|
855
|
+
cost: prev.cost + cost,
|
|
856
|
+
calls: prev.calls + 1,
|
|
857
|
+
tokens: prev.tokens + embedTokens
|
|
858
|
+
};
|
|
718
859
|
}
|
|
719
860
|
return {
|
|
720
861
|
totalCost: acc.totalCost + cost,
|
|
@@ -909,7 +1050,7 @@ function reduceTraceStats(acc, event) {
|
|
|
909
1050
|
const eventTypeCounts = { ...acc.eventTypeCounts };
|
|
910
1051
|
eventTypeCounts[event.type] = (eventTypeCounts[event.type] ?? 0) + 1;
|
|
911
1052
|
const byTool = { ...acc.byTool };
|
|
912
|
-
if (event.type === "
|
|
1053
|
+
if (event.type === "tool_call_end" || event.type === "tool_denied" || event.type === "tool_approval") {
|
|
913
1054
|
const toolName = event.tool;
|
|
914
1055
|
const prev = byTool[toolName] ?? { calls: 0, denied: 0, approved: 0 };
|
|
915
1056
|
const isDeniedEvent = event.type === "tool_denied";
|
|
@@ -918,13 +1059,13 @@ function reduceTraceStats(acc, event) {
|
|
|
918
1059
|
const isApproved = isDeniedEvent && eventData?.approved === true || isApprovalEvent && eventData?.approved === true;
|
|
919
1060
|
const isDenied = isDeniedEvent && !eventData?.approved || isApprovalEvent && eventData?.approved === false;
|
|
920
1061
|
byTool[toolName] = {
|
|
921
|
-
calls: prev.calls + (event.type === "
|
|
1062
|
+
calls: prev.calls + (event.type === "tool_call_end" ? 1 : 0),
|
|
922
1063
|
denied: prev.denied + (isDenied ? 1 : 0),
|
|
923
1064
|
approved: prev.approved + (isApproved ? 1 : 0)
|
|
924
1065
|
};
|
|
925
1066
|
}
|
|
926
1067
|
const retryByAgent = { ...acc.retryByAgent };
|
|
927
|
-
if (event.agent && event.type === "
|
|
1068
|
+
if (event.agent && event.type === "agent_call_end") {
|
|
928
1069
|
const data = event.data;
|
|
929
1070
|
if (data?.retryReason) {
|
|
930
1071
|
const prev = retryByAgent[event.agent] ?? { schema: 0, validate: 0, guardrail: 0 };
|
|
@@ -942,128 +1083,6 @@ function reduceTraceStats(acc, event) {
|
|
|
942
1083
|
};
|
|
943
1084
|
}
|
|
944
1085
|
|
|
945
|
-
// src/server/aggregates/execution-aggregator.ts
|
|
946
|
-
var ExecutionAggregator = class {
|
|
947
|
-
snaps;
|
|
948
|
-
interval;
|
|
949
|
-
listener;
|
|
950
|
-
options;
|
|
951
|
-
/** Generation counter to prevent stale async fold after rebuild. */
|
|
952
|
-
generation = 0;
|
|
953
|
-
constructor(options) {
|
|
954
|
-
this.options = options;
|
|
955
|
-
this.snaps = new AggregateSnapshots(
|
|
956
|
-
options.windows,
|
|
957
|
-
options.emptyState,
|
|
958
|
-
options.connMgr,
|
|
959
|
-
options.channel,
|
|
960
|
-
options.broadcastTransform
|
|
961
|
-
);
|
|
962
|
-
}
|
|
963
|
-
async start() {
|
|
964
|
-
await this.rebuild();
|
|
965
|
-
this.listener = (event) => {
|
|
966
|
-
if (!isLogEvent(event, "workflow_end")) return;
|
|
967
|
-
const gen = this.generation;
|
|
968
|
-
this.options.runtime.getExecution(event.executionId).then((exec) => {
|
|
969
|
-
if (this.generation !== gen) return;
|
|
970
|
-
if (exec) {
|
|
971
|
-
this.snaps.fold(exec.startedAt, (prev) => this.options.reducer(prev, exec));
|
|
972
|
-
}
|
|
973
|
-
}).catch((err) => console.error("[axl-studio] execution fold failed:", err));
|
|
974
|
-
};
|
|
975
|
-
this.options.runtime.on("trace", this.listener);
|
|
976
|
-
this.interval = setInterval(
|
|
977
|
-
() => this.rebuild().catch((err) => console.error("[axl-studio] rebuild failed:", err)),
|
|
978
|
-
REBUILD_INTERVAL_MS
|
|
979
|
-
);
|
|
980
|
-
}
|
|
981
|
-
async rebuild() {
|
|
982
|
-
this.generation++;
|
|
983
|
-
const executions = await this.options.runtime.getExecutions();
|
|
984
|
-
const cap = this.options.executionCap ?? 2e3;
|
|
985
|
-
const capped = executions.slice(0, cap);
|
|
986
|
-
const now = Date.now();
|
|
987
|
-
const fresh = new Map(
|
|
988
|
-
this.options.windows.map((w) => [w, this.options.emptyState()])
|
|
989
|
-
);
|
|
990
|
-
for (const exec of capped) {
|
|
991
|
-
for (const window of this.options.windows) {
|
|
992
|
-
if (withinWindow(exec.startedAt, window, now)) {
|
|
993
|
-
fresh.set(window, this.options.reducer(fresh.get(window), exec));
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
this.snaps.replace(fresh);
|
|
998
|
-
}
|
|
999
|
-
getSnapshot(window) {
|
|
1000
|
-
return this.snaps.get(window);
|
|
1001
|
-
}
|
|
1002
|
-
getAllSnapshots() {
|
|
1003
|
-
return this.snaps.getAll();
|
|
1004
|
-
}
|
|
1005
|
-
close() {
|
|
1006
|
-
if (this.listener) this.options.runtime.off("trace", this.listener);
|
|
1007
|
-
if (this.interval) clearInterval(this.interval);
|
|
1008
|
-
}
|
|
1009
|
-
};
|
|
1010
|
-
|
|
1011
|
-
// src/server/aggregates/eval-aggregator.ts
|
|
1012
|
-
var EvalAggregator = class {
|
|
1013
|
-
snaps;
|
|
1014
|
-
interval;
|
|
1015
|
-
listener;
|
|
1016
|
-
options;
|
|
1017
|
-
constructor(options) {
|
|
1018
|
-
this.options = options;
|
|
1019
|
-
this.snaps = new AggregateSnapshots(
|
|
1020
|
-
options.windows,
|
|
1021
|
-
options.emptyState,
|
|
1022
|
-
options.connMgr,
|
|
1023
|
-
options.channel,
|
|
1024
|
-
options.broadcastTransform
|
|
1025
|
-
);
|
|
1026
|
-
}
|
|
1027
|
-
async start() {
|
|
1028
|
-
await this.rebuild();
|
|
1029
|
-
this.listener = (entry) => {
|
|
1030
|
-
this.snaps.fold(entry.timestamp, (prev) => this.options.reducer(prev, entry));
|
|
1031
|
-
};
|
|
1032
|
-
this.options.runtime.on("eval_result", this.listener);
|
|
1033
|
-
this.interval = setInterval(
|
|
1034
|
-
() => this.rebuild().catch((err) => console.error("[axl-studio] rebuild failed:", err)),
|
|
1035
|
-
REBUILD_INTERVAL_MS
|
|
1036
|
-
);
|
|
1037
|
-
}
|
|
1038
|
-
async rebuild() {
|
|
1039
|
-
const history = await this.options.runtime.getEvalHistory();
|
|
1040
|
-
const cap = this.options.entryCap ?? 500;
|
|
1041
|
-
const capped = history.slice(0, cap);
|
|
1042
|
-
const now = Date.now();
|
|
1043
|
-
const fresh = new Map(
|
|
1044
|
-
this.options.windows.map((w) => [w, this.options.emptyState()])
|
|
1045
|
-
);
|
|
1046
|
-
for (const entry of capped) {
|
|
1047
|
-
for (const window of this.options.windows) {
|
|
1048
|
-
if (withinWindow(entry.timestamp, window, now)) {
|
|
1049
|
-
fresh.set(window, this.options.reducer(fresh.get(window), entry));
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
this.snaps.replace(fresh);
|
|
1054
|
-
}
|
|
1055
|
-
getSnapshot(window) {
|
|
1056
|
-
return this.snaps.get(window);
|
|
1057
|
-
}
|
|
1058
|
-
getAllSnapshots() {
|
|
1059
|
-
return this.snaps.getAll();
|
|
1060
|
-
}
|
|
1061
|
-
close() {
|
|
1062
|
-
if (this.listener) this.options.runtime.off("eval_result", this.listener);
|
|
1063
|
-
if (this.interval) clearInterval(this.interval);
|
|
1064
|
-
}
|
|
1065
|
-
};
|
|
1066
|
-
|
|
1067
1086
|
// src/server/routes/health.ts
|
|
1068
1087
|
var import_hono = require("hono");
|
|
1069
1088
|
function createHealthRoutes(readOnly) {
|
|
@@ -1086,7 +1105,7 @@ function createHealthRoutes(readOnly) {
|
|
|
1086
1105
|
|
|
1087
1106
|
// src/server/routes/workflows.ts
|
|
1088
1107
|
var import_hono2 = require("hono");
|
|
1089
|
-
var
|
|
1108
|
+
var import_axl3 = require("@axlsdk/axl");
|
|
1090
1109
|
function createWorkflowRoutes(connMgr) {
|
|
1091
1110
|
const app6 = new import_hono2.Hono();
|
|
1092
1111
|
app6.get("/workflows", (c) => {
|
|
@@ -1112,8 +1131,8 @@ function createWorkflowRoutes(connMgr) {
|
|
|
1112
1131
|
ok: true,
|
|
1113
1132
|
data: {
|
|
1114
1133
|
name: workflow.name,
|
|
1115
|
-
inputSchema: workflow.inputSchema ? (0,
|
|
1116
|
-
outputSchema: workflow.outputSchema ? (0,
|
|
1134
|
+
inputSchema: workflow.inputSchema ? (0, import_axl3.zodToJsonSchema)(workflow.inputSchema) : null,
|
|
1135
|
+
outputSchema: workflow.outputSchema ? (0, import_axl3.zodToJsonSchema)(workflow.outputSchema) : null
|
|
1117
1136
|
}
|
|
1118
1137
|
});
|
|
1119
1138
|
});
|
|
@@ -1172,9 +1191,31 @@ app.get("/executions/:id", async (c) => {
|
|
|
1172
1191
|
404
|
|
1173
1192
|
);
|
|
1174
1193
|
}
|
|
1194
|
+
const sinceParam = c.req.query("since");
|
|
1195
|
+
let paged = execution;
|
|
1196
|
+
if (sinceParam !== void 0) {
|
|
1197
|
+
const since = Number(sinceParam);
|
|
1198
|
+
if (!Number.isFinite(since) || !Number.isInteger(since)) {
|
|
1199
|
+
return c.json(
|
|
1200
|
+
{
|
|
1201
|
+
ok: false,
|
|
1202
|
+
error: {
|
|
1203
|
+
code: "INVALID_PARAM",
|
|
1204
|
+
message: `\`since\` must be a finite integer (got "${sinceParam}")`,
|
|
1205
|
+
param: "since"
|
|
1206
|
+
}
|
|
1207
|
+
},
|
|
1208
|
+
400
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
paged = {
|
|
1212
|
+
...execution,
|
|
1213
|
+
events: execution.events.filter((e) => e.step > since)
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1175
1216
|
return c.json({
|
|
1176
1217
|
ok: true,
|
|
1177
|
-
data: redactExecutionInfo(
|
|
1218
|
+
data: redactExecutionInfo(paged, runtime.isRedactEnabled())
|
|
1178
1219
|
});
|
|
1179
1220
|
});
|
|
1180
1221
|
app.post("/executions/:id/abort", (c) => {
|
|
@@ -1254,7 +1295,7 @@ function createSessionRoutes(connMgr) {
|
|
|
1254
1295
|
|
|
1255
1296
|
// src/server/routes/agents.ts
|
|
1256
1297
|
var import_hono5 = require("hono");
|
|
1257
|
-
var
|
|
1298
|
+
var import_axl4 = require("@axlsdk/axl");
|
|
1258
1299
|
var app2 = new import_hono5.Hono();
|
|
1259
1300
|
app2.get("/agents", (c) => {
|
|
1260
1301
|
const runtime = c.get("runtime");
|
|
@@ -1295,7 +1336,7 @@ app2.get("/agents/:name", (c) => {
|
|
|
1295
1336
|
tools: cfg.tools?.map((t) => ({
|
|
1296
1337
|
name: t.name,
|
|
1297
1338
|
description: t.description,
|
|
1298
|
-
inputSchema: (0,
|
|
1339
|
+
inputSchema: (0, import_axl4.zodToJsonSchema)(t.inputSchema)
|
|
1299
1340
|
})) ?? [],
|
|
1300
1341
|
handoffs: typeof cfg.handoffs === "function" ? [
|
|
1301
1342
|
{
|
|
@@ -1335,14 +1376,14 @@ var agents_default = app2;
|
|
|
1335
1376
|
|
|
1336
1377
|
// src/server/routes/tools.ts
|
|
1337
1378
|
var import_hono6 = require("hono");
|
|
1338
|
-
var
|
|
1379
|
+
var import_axl5 = require("@axlsdk/axl");
|
|
1339
1380
|
var app3 = new import_hono6.Hono();
|
|
1340
1381
|
app3.get("/tools", (c) => {
|
|
1341
1382
|
const runtime = c.get("runtime");
|
|
1342
1383
|
const tools = runtime.getTools().map((t) => ({
|
|
1343
1384
|
name: t.name,
|
|
1344
1385
|
description: t.description,
|
|
1345
|
-
inputSchema: t.inputSchema ? (0,
|
|
1386
|
+
inputSchema: t.inputSchema ? (0, import_axl5.zodToJsonSchema)(t.inputSchema) : {},
|
|
1346
1387
|
sensitive: t.sensitive ?? false,
|
|
1347
1388
|
requireApproval: t.requireApproval ?? false
|
|
1348
1389
|
}));
|
|
@@ -1363,7 +1404,7 @@ app3.get("/tools/:name", (c) => {
|
|
|
1363
1404
|
data: {
|
|
1364
1405
|
name: tool.name,
|
|
1365
1406
|
description: tool.description,
|
|
1366
|
-
inputSchema: tool.inputSchema ? (0,
|
|
1407
|
+
inputSchema: tool.inputSchema ? (0, import_axl5.zodToJsonSchema)(tool.inputSchema) : {},
|
|
1367
1408
|
sensitive: tool.sensitive,
|
|
1368
1409
|
requireApproval: tool.requireApproval,
|
|
1369
1410
|
retry: tool.retry,
|
|
@@ -1751,10 +1792,14 @@ function createEvalRoutes(connMgr, evalLoader) {
|
|
|
1751
1792
|
const runtime = c.get("runtime");
|
|
1752
1793
|
const redactOn = runtime.isRedactEnabled();
|
|
1753
1794
|
const body = await c.req.json();
|
|
1795
|
+
const MAX_POOLED_RUNS = 25;
|
|
1754
1796
|
const validateIdParam = (v, name) => {
|
|
1755
1797
|
if (typeof v === "string") return v === "" ? `${name} must be non-empty` : null;
|
|
1756
1798
|
if (Array.isArray(v)) {
|
|
1757
1799
|
if (v.length === 0) return `${name} must be a non-empty array`;
|
|
1800
|
+
if (v.length > MAX_POOLED_RUNS) {
|
|
1801
|
+
return `${name} may contain at most ${MAX_POOLED_RUNS} ids (pooled comparison)`;
|
|
1802
|
+
}
|
|
1758
1803
|
for (const elem of v) {
|
|
1759
1804
|
if (typeof elem !== "string" || elem === "") {
|
|
1760
1805
|
return `${name} array must contain only non-empty strings`;
|
|
@@ -1923,32 +1968,50 @@ function createPlaygroundRoutes(connMgr) {
|
|
|
1923
1968
|
);
|
|
1924
1969
|
}
|
|
1925
1970
|
const sessionId = body.sessionId ?? `playground-${Date.now()}`;
|
|
1926
|
-
const executionId = `playground-${sessionId}-${Date.now()}`;
|
|
1927
1971
|
const store = runtime.getStateStore();
|
|
1928
1972
|
const history = await store.getSession(sessionId);
|
|
1929
1973
|
history.push({ role: "user", content: body.message });
|
|
1930
1974
|
const redactOn = runtime.isRedactEnabled();
|
|
1931
|
-
const
|
|
1975
|
+
const ctx = runtime.createContext({ sessionHistory: history });
|
|
1976
|
+
const executionId = ctx.executionId;
|
|
1977
|
+
const traceListener = (event) => {
|
|
1978
|
+
if (event.executionId !== executionId) return;
|
|
1932
1979
|
connMgr.broadcastWithWildcard(`execution:${executionId}`, redactStreamEvent(event, redactOn));
|
|
1933
1980
|
};
|
|
1934
|
-
|
|
1935
|
-
sessionHistory: history,
|
|
1936
|
-
onToken: (token) => {
|
|
1937
|
-
broadcast({ type: "token", data: token });
|
|
1938
|
-
}
|
|
1939
|
-
});
|
|
1981
|
+
runtime.on("trace", traceListener);
|
|
1940
1982
|
(async () => {
|
|
1983
|
+
let stepCounter = Number.MAX_SAFE_INTEGER - 1;
|
|
1984
|
+
const terminalFields = () => ({
|
|
1985
|
+
executionId,
|
|
1986
|
+
step: stepCounter++,
|
|
1987
|
+
timestamp: Date.now()
|
|
1988
|
+
});
|
|
1941
1989
|
try {
|
|
1942
1990
|
const result = await ctx.ask(agent, body.message);
|
|
1943
1991
|
const resultText = typeof result === "string" ? result : JSON.stringify(result);
|
|
1944
1992
|
history.push({ role: "assistant", content: resultText });
|
|
1945
1993
|
await store.saveSession(sessionId, history);
|
|
1946
|
-
|
|
1994
|
+
const doneEvent = {
|
|
1995
|
+
...terminalFields(),
|
|
1996
|
+
type: "done",
|
|
1997
|
+
data: { result: resultText }
|
|
1998
|
+
};
|
|
1999
|
+
connMgr.broadcastWithWildcard(
|
|
2000
|
+
`execution:${executionId}`,
|
|
2001
|
+
redactStreamEvent(doneEvent, redactOn)
|
|
2002
|
+
);
|
|
1947
2003
|
} catch (err) {
|
|
1948
|
-
|
|
2004
|
+
const errorEvent = {
|
|
2005
|
+
...terminalFields(),
|
|
1949
2006
|
type: "error",
|
|
1950
|
-
message: err instanceof Error ? err.message : String(err)
|
|
1951
|
-
}
|
|
2007
|
+
data: { message: err instanceof Error ? err.message : String(err) }
|
|
2008
|
+
};
|
|
2009
|
+
connMgr.broadcastWithWildcard(
|
|
2010
|
+
`execution:${executionId}`,
|
|
2011
|
+
redactStreamEvent(errorEvent, redactOn)
|
|
2012
|
+
);
|
|
2013
|
+
} finally {
|
|
2014
|
+
runtime.off("trace", traceListener);
|
|
1952
2015
|
}
|
|
1953
2016
|
})();
|
|
1954
2017
|
return c.json({
|
|
@@ -1996,7 +2059,7 @@ function createTraceStatsRoutes(aggregator) {
|
|
|
1996
2059
|
function createServer(options) {
|
|
1997
2060
|
const { runtime, staticRoot, basePath = "", readOnly = false } = options;
|
|
1998
2061
|
const app6 = new import_hono15.Hono();
|
|
1999
|
-
const connMgr = new ConnectionManager();
|
|
2062
|
+
const connMgr = new ConnectionManager(options.bufferCaps);
|
|
2000
2063
|
const windows = ["24h", "7d", "30d", "all"];
|
|
2001
2064
|
const costAggregator = new TraceAggregator({
|
|
2002
2065
|
runtime,
|
|
@@ -2090,12 +2153,20 @@ function createServer(options) {
|
|
|
2090
2153
|
api.route("/", createPlaygroundRoutes(connMgr));
|
|
2091
2154
|
app6.route("/api", api);
|
|
2092
2155
|
const traceListener = (event) => {
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2156
|
+
try {
|
|
2157
|
+
const traceEvent = event;
|
|
2158
|
+
const redacted = redactStreamEvent(traceEvent, runtime.isRedactEnabled());
|
|
2159
|
+
if (traceEvent.executionId) {
|
|
2160
|
+
connMgr.broadcastWithWildcard(`trace:${traceEvent.executionId}`, redacted);
|
|
2161
|
+
}
|
|
2162
|
+
if (traceEvent.type === "await_human") {
|
|
2163
|
+
connMgr.broadcast("decisions", redacted);
|
|
2164
|
+
}
|
|
2165
|
+
} catch (err) {
|
|
2166
|
+
console.error(
|
|
2167
|
+
"[axl-studio] trace listener threw; event dropped:",
|
|
2168
|
+
err instanceof Error ? err.message : String(err)
|
|
2169
|
+
);
|
|
2099
2170
|
}
|
|
2100
2171
|
};
|
|
2101
2172
|
runtime.on("trace", traceListener);
|
|
@@ -2374,7 +2445,8 @@ function createStudioMiddleware(options) {
|
|
|
2374
2445
|
readOnly,
|
|
2375
2446
|
cors: false,
|
|
2376
2447
|
// Host framework owns CORS policy
|
|
2377
|
-
evalLoader
|
|
2448
|
+
evalLoader,
|
|
2449
|
+
bufferCaps: options.bufferCaps
|
|
2378
2450
|
});
|
|
2379
2451
|
if (filterTraceEvent) {
|
|
2380
2452
|
connMgr.setFilter(filterTraceEvent);
|