@axlsdk/studio 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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-ClajLxib.js +288 -0
- package/dist/client/assets/index-DnHL_gtF.css +1 -0
- package/dist/client/index.html +2 -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/cli.cjs
CHANGED
|
@@ -37,6 +37,7 @@ var import_cors = require("hono/cors");
|
|
|
37
37
|
var import_serve_static = require("@hono/node-server/serve-static");
|
|
38
38
|
|
|
39
39
|
// src/server/redact.ts
|
|
40
|
+
var import_axl = require("@axlsdk/axl");
|
|
40
41
|
var REDACTED = "[redacted]";
|
|
41
42
|
var SAFE_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
42
43
|
"QuorumNotMet",
|
|
@@ -61,7 +62,8 @@ function redactExecutionInfo(info, redact) {
|
|
|
61
62
|
return {
|
|
62
63
|
...info,
|
|
63
64
|
...info.result !== void 0 ? { result: REDACTED } : {},
|
|
64
|
-
...info.error !== void 0 ? { error: REDACTED } : {}
|
|
65
|
+
...info.error !== void 0 ? { error: REDACTED } : {},
|
|
66
|
+
events: info.events.map((e) => redactStreamEvent(e, true))
|
|
65
67
|
};
|
|
66
68
|
}
|
|
67
69
|
function redactExecutionList(infos, redact) {
|
|
@@ -102,30 +104,7 @@ function redactSessionHistory(history, redact) {
|
|
|
102
104
|
}
|
|
103
105
|
function redactStreamEvent(event, redact) {
|
|
104
106
|
if (!redact) return event;
|
|
105
|
-
|
|
106
|
-
case "token":
|
|
107
|
-
return { type: "token", data: REDACTED };
|
|
108
|
-
case "tool_call":
|
|
109
|
-
return { ...event, args: REDACTED };
|
|
110
|
-
case "tool_result":
|
|
111
|
-
return { ...event, result: REDACTED };
|
|
112
|
-
case "tool_approval":
|
|
113
|
-
return {
|
|
114
|
-
...event,
|
|
115
|
-
args: REDACTED,
|
|
116
|
-
...event.reason !== void 0 ? { reason: REDACTED } : {}
|
|
117
|
-
};
|
|
118
|
-
case "done":
|
|
119
|
-
return { type: "done", data: REDACTED };
|
|
120
|
-
case "error":
|
|
121
|
-
return { type: "error", message: REDACTED };
|
|
122
|
-
// Structural events have no user content to scrub.
|
|
123
|
-
case "agent_start":
|
|
124
|
-
case "agent_end":
|
|
125
|
-
case "handoff":
|
|
126
|
-
case "step":
|
|
127
|
-
return event;
|
|
128
|
-
}
|
|
107
|
+
return (0, import_axl.redactEvent)(event);
|
|
129
108
|
}
|
|
130
109
|
function redactEvalItem(item) {
|
|
131
110
|
const scrubbed = {
|
|
@@ -211,13 +190,17 @@ async function errorHandler(c, next) {
|
|
|
211
190
|
|
|
212
191
|
// src/server/ws/connection-manager.ts
|
|
213
192
|
var BUFFER_TTL_MS = 3e4;
|
|
214
|
-
var
|
|
193
|
+
var DEFAULT_MAX_BUFFER_EVENTS = 1e3;
|
|
194
|
+
var DEFAULT_MAX_BUFFER_BYTES = 4 * 1024 * 1024;
|
|
195
|
+
var DEFAULT_MAX_ACTIVE_BUFFERS = 256;
|
|
196
|
+
var UNBUFFERED_EVENT_TYPES = /* @__PURE__ */ new Set(["token", "partial_object"]);
|
|
215
197
|
var MAX_WS_FRAME_BYTES = 65536;
|
|
216
198
|
function isBufferedChannel(channel) {
|
|
217
199
|
return channel.startsWith("execution:") || channel.startsWith("eval:");
|
|
218
200
|
}
|
|
219
201
|
function truncateIfOversized(msg, channel, data) {
|
|
220
|
-
|
|
202
|
+
const msgBytes = Buffer.byteLength(msg, "utf8");
|
|
203
|
+
if (msgBytes <= MAX_WS_FRAME_BYTES) return msg;
|
|
221
204
|
const event = data ?? {};
|
|
222
205
|
const truncated = {
|
|
223
206
|
type: "event",
|
|
@@ -226,7 +209,7 @@ function truncateIfOversized(msg, channel, data) {
|
|
|
226
209
|
...event,
|
|
227
210
|
data: {
|
|
228
211
|
__truncated: true,
|
|
229
|
-
originalBytes:
|
|
212
|
+
originalBytes: msgBytes,
|
|
230
213
|
maxBytes: MAX_WS_FRAME_BYTES,
|
|
231
214
|
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."
|
|
232
215
|
}
|
|
@@ -243,6 +226,25 @@ var ConnectionManager = class {
|
|
|
243
226
|
buffers = /* @__PURE__ */ new Map();
|
|
244
227
|
maxConnections = 100;
|
|
245
228
|
filter;
|
|
229
|
+
/** Resolved replay-buffer caps. Per-instance so embedders can dial them
|
|
230
|
+
* without monkey-patching module-level constants. */
|
|
231
|
+
maxEventsPerBuffer;
|
|
232
|
+
maxBytesPerBuffer;
|
|
233
|
+
maxActiveBuffers;
|
|
234
|
+
constructor(bufferCaps) {
|
|
235
|
+
const validatePositiveInt = (key, value) => {
|
|
236
|
+
if (value === void 0) return;
|
|
237
|
+
if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1) {
|
|
238
|
+
throw new RangeError(`bufferCaps.${key} must be a positive integer (>= 1); got ${value}`);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
validatePositiveInt("maxEventsPerBuffer", bufferCaps?.maxEventsPerBuffer);
|
|
242
|
+
validatePositiveInt("maxBytesPerBuffer", bufferCaps?.maxBytesPerBuffer);
|
|
243
|
+
validatePositiveInt("maxActiveBuffers", bufferCaps?.maxActiveBuffers);
|
|
244
|
+
this.maxEventsPerBuffer = bufferCaps?.maxEventsPerBuffer ?? DEFAULT_MAX_BUFFER_EVENTS;
|
|
245
|
+
this.maxBytesPerBuffer = bufferCaps?.maxBytesPerBuffer ?? DEFAULT_MAX_BUFFER_BYTES;
|
|
246
|
+
this.maxActiveBuffers = bufferCaps?.maxActiveBuffers ?? DEFAULT_MAX_ACTIVE_BUFFERS;
|
|
247
|
+
}
|
|
246
248
|
/**
|
|
247
249
|
* Register a broadcast filter. Called once at middleware construction.
|
|
248
250
|
* The filter runs on every outbound event and can drop or deliver based
|
|
@@ -325,13 +327,37 @@ var ConnectionManager = class {
|
|
|
325
327
|
if (isBufferedChannel(channel)) {
|
|
326
328
|
let buffer = this.buffers.get(channel);
|
|
327
329
|
if (!buffer) {
|
|
328
|
-
|
|
330
|
+
if (this.buffers.size >= this.maxActiveBuffers) {
|
|
331
|
+
let victim;
|
|
332
|
+
for (const [ch, buf] of this.buffers) {
|
|
333
|
+
if (buf.complete) {
|
|
334
|
+
victim = ch;
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (victim === void 0) {
|
|
339
|
+
victim = this.buffers.keys().next().value;
|
|
340
|
+
}
|
|
341
|
+
if (victim !== void 0) {
|
|
342
|
+
const old = this.buffers.get(victim);
|
|
343
|
+
if (old?.timer) clearTimeout(old.timer);
|
|
344
|
+
this.buffers.delete(victim);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
buffer = { events: [], complete: false, bytes: 0 };
|
|
329
348
|
this.buffers.set(channel, buffer);
|
|
330
349
|
}
|
|
331
350
|
const event = data;
|
|
332
351
|
const isTerminal = event.type === "done" || event.type === "error";
|
|
333
|
-
|
|
334
|
-
|
|
352
|
+
const isUnbuffered = event.type !== void 0 && UNBUFFERED_EVENT_TYPES.has(event.type);
|
|
353
|
+
if (!isUnbuffered) {
|
|
354
|
+
const msgBytes = Buffer.byteLength(msg, "utf8");
|
|
355
|
+
const atCountCap = buffer.events.length >= this.maxEventsPerBuffer;
|
|
356
|
+
const atByteCap = buffer.bytes + msgBytes > this.maxBytesPerBuffer;
|
|
357
|
+
if (isTerminal || !atCountCap && !atByteCap) {
|
|
358
|
+
buffer.events.push({ msg, data });
|
|
359
|
+
buffer.bytes += msgBytes;
|
|
360
|
+
}
|
|
335
361
|
}
|
|
336
362
|
if (isTerminal) {
|
|
337
363
|
buffer.complete = true;
|
|
@@ -416,7 +442,7 @@ var VALID_CHANNEL_PREFIXES = ["execution:", "trace:", "eval:"];
|
|
|
416
442
|
var VALID_EXACT_CHANNELS = ["costs", "decisions", "eval-trends", "workflow-stats", "trace-stats"];
|
|
417
443
|
var MAX_CHANNEL_LENGTH = 256;
|
|
418
444
|
function handleWsMessage(raw, socket, connMgr) {
|
|
419
|
-
if (raw
|
|
445
|
+
if (Buffer.byteLength(raw, "utf8") > MAX_WS_FRAME_BYTES) {
|
|
420
446
|
return JSON.stringify({ type: "error", message: "Message too large" });
|
|
421
447
|
}
|
|
422
448
|
let msg;
|
|
@@ -572,7 +598,7 @@ var TraceAggregator = class {
|
|
|
572
598
|
this.options.windows.map((w) => [w, this.options.emptyState()])
|
|
573
599
|
);
|
|
574
600
|
for (const exec of capped) {
|
|
575
|
-
for (const event of exec.
|
|
601
|
+
for (const event of exec.events) {
|
|
576
602
|
for (const window of this.options.windows) {
|
|
577
603
|
if (withinWindow(event.timestamp, window, now)) {
|
|
578
604
|
fresh.set(window, this.options.reducer(fresh.get(window), event));
|
|
@@ -594,15 +620,131 @@ var TraceAggregator = class {
|
|
|
594
620
|
}
|
|
595
621
|
};
|
|
596
622
|
|
|
623
|
+
// src/server/aggregates/execution-aggregator.ts
|
|
624
|
+
var ExecutionAggregator = class {
|
|
625
|
+
snaps;
|
|
626
|
+
interval;
|
|
627
|
+
listener;
|
|
628
|
+
options;
|
|
629
|
+
/** Generation counter to prevent stale async fold after rebuild. */
|
|
630
|
+
generation = 0;
|
|
631
|
+
constructor(options) {
|
|
632
|
+
this.options = options;
|
|
633
|
+
this.snaps = new AggregateSnapshots(
|
|
634
|
+
options.windows,
|
|
635
|
+
options.emptyState,
|
|
636
|
+
options.connMgr,
|
|
637
|
+
options.channel,
|
|
638
|
+
options.broadcastTransform
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
async start() {
|
|
642
|
+
await this.rebuild();
|
|
643
|
+
this.listener = (event) => {
|
|
644
|
+
if (event.type !== "workflow_end") return;
|
|
645
|
+
const gen = this.generation;
|
|
646
|
+
this.options.runtime.getExecution(event.executionId).then((exec) => {
|
|
647
|
+
if (this.generation !== gen) return;
|
|
648
|
+
if (exec) {
|
|
649
|
+
this.snaps.fold(exec.startedAt, (prev) => this.options.reducer(prev, exec));
|
|
650
|
+
}
|
|
651
|
+
}).catch((err) => console.error("[axl-studio] execution fold failed:", err));
|
|
652
|
+
};
|
|
653
|
+
this.options.runtime.on("trace", this.listener);
|
|
654
|
+
this.interval = setInterval(
|
|
655
|
+
() => this.rebuild().catch((err) => console.error("[axl-studio] rebuild failed:", err)),
|
|
656
|
+
REBUILD_INTERVAL_MS
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
async rebuild() {
|
|
660
|
+
this.generation++;
|
|
661
|
+
const executions = await this.options.runtime.getExecutions();
|
|
662
|
+
const cap = this.options.executionCap ?? 2e3;
|
|
663
|
+
const capped = executions.slice(0, cap);
|
|
664
|
+
const now = Date.now();
|
|
665
|
+
const fresh = new Map(
|
|
666
|
+
this.options.windows.map((w) => [w, this.options.emptyState()])
|
|
667
|
+
);
|
|
668
|
+
for (const exec of capped) {
|
|
669
|
+
for (const window of this.options.windows) {
|
|
670
|
+
if (withinWindow(exec.startedAt, window, now)) {
|
|
671
|
+
fresh.set(window, this.options.reducer(fresh.get(window), exec));
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
this.snaps.replace(fresh);
|
|
676
|
+
}
|
|
677
|
+
getSnapshot(window) {
|
|
678
|
+
return this.snaps.get(window);
|
|
679
|
+
}
|
|
680
|
+
getAllSnapshots() {
|
|
681
|
+
return this.snaps.getAll();
|
|
682
|
+
}
|
|
683
|
+
close() {
|
|
684
|
+
if (this.listener) this.options.runtime.off("trace", this.listener);
|
|
685
|
+
if (this.interval) clearInterval(this.interval);
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
// src/server/aggregates/eval-aggregator.ts
|
|
690
|
+
var EvalAggregator = class {
|
|
691
|
+
snaps;
|
|
692
|
+
interval;
|
|
693
|
+
listener;
|
|
694
|
+
options;
|
|
695
|
+
constructor(options) {
|
|
696
|
+
this.options = options;
|
|
697
|
+
this.snaps = new AggregateSnapshots(
|
|
698
|
+
options.windows,
|
|
699
|
+
options.emptyState,
|
|
700
|
+
options.connMgr,
|
|
701
|
+
options.channel,
|
|
702
|
+
options.broadcastTransform
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
async start() {
|
|
706
|
+
await this.rebuild();
|
|
707
|
+
this.listener = (entry) => {
|
|
708
|
+
this.snaps.fold(entry.timestamp, (prev) => this.options.reducer(prev, entry));
|
|
709
|
+
};
|
|
710
|
+
this.options.runtime.on("eval_result", this.listener);
|
|
711
|
+
this.interval = setInterval(
|
|
712
|
+
() => this.rebuild().catch((err) => console.error("[axl-studio] rebuild failed:", err)),
|
|
713
|
+
REBUILD_INTERVAL_MS
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
async rebuild() {
|
|
717
|
+
const history = await this.options.runtime.getEvalHistory();
|
|
718
|
+
const cap = this.options.entryCap ?? 500;
|
|
719
|
+
const capped = history.slice(0, cap);
|
|
720
|
+
const now = Date.now();
|
|
721
|
+
const fresh = new Map(
|
|
722
|
+
this.options.windows.map((w) => [w, this.options.emptyState()])
|
|
723
|
+
);
|
|
724
|
+
for (const entry of capped) {
|
|
725
|
+
for (const window of this.options.windows) {
|
|
726
|
+
if (withinWindow(entry.timestamp, window, now)) {
|
|
727
|
+
fresh.set(window, this.options.reducer(fresh.get(window), entry));
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
this.snaps.replace(fresh);
|
|
732
|
+
}
|
|
733
|
+
getSnapshot(window) {
|
|
734
|
+
return this.snaps.get(window);
|
|
735
|
+
}
|
|
736
|
+
getAllSnapshots() {
|
|
737
|
+
return this.snaps.getAll();
|
|
738
|
+
}
|
|
739
|
+
close() {
|
|
740
|
+
if (this.listener) this.options.runtime.off("eval_result", this.listener);
|
|
741
|
+
if (this.interval) clearInterval(this.interval);
|
|
742
|
+
}
|
|
743
|
+
};
|
|
744
|
+
|
|
597
745
|
// src/server/aggregates/reducers.ts
|
|
746
|
+
var import_axl2 = require("@axlsdk/axl");
|
|
598
747
|
var finite = (v) => Number.isFinite(v) ? v : 0;
|
|
599
|
-
function isLogEvent(event, eventName) {
|
|
600
|
-
if (event.type === eventName) return true;
|
|
601
|
-
if (event.type === "log" && event.data != null && typeof event.data === "object") {
|
|
602
|
-
return event.data.event === eventName;
|
|
603
|
-
}
|
|
604
|
-
return false;
|
|
605
|
-
}
|
|
606
748
|
function emptyRetry() {
|
|
607
749
|
return {
|
|
608
750
|
primary: 0,
|
|
@@ -628,7 +770,7 @@ function emptyCostData() {
|
|
|
628
770
|
};
|
|
629
771
|
}
|
|
630
772
|
function reduceCost(acc, event) {
|
|
631
|
-
const isWorkflowStart =
|
|
773
|
+
const isWorkflowStart = event.type === "workflow_start";
|
|
632
774
|
if (isWorkflowStart && event.workflow) {
|
|
633
775
|
const byWorkflow2 = { ...acc.byWorkflow };
|
|
634
776
|
const prev = byWorkflow2[event.workflow] ?? { cost: 0, executions: 0 };
|
|
@@ -636,9 +778,10 @@ function reduceCost(acc, event) {
|
|
|
636
778
|
return { ...acc, byWorkflow: byWorkflow2 };
|
|
637
779
|
}
|
|
638
780
|
if (event.cost == null && !event.tokens) return acc;
|
|
639
|
-
const cost =
|
|
781
|
+
const cost = (0, import_axl2.eventCostContribution)(event);
|
|
782
|
+
if (event.type === "ask_end") return acc;
|
|
640
783
|
const tokens = event.tokens ?? {};
|
|
641
|
-
const totalTokens = event.type === "
|
|
784
|
+
const totalTokens = event.type === "agent_call_end" ? {
|
|
642
785
|
input: acc.totalTokens.input + finite(tokens.input),
|
|
643
786
|
output: acc.totalTokens.output + finite(tokens.output),
|
|
644
787
|
reasoning: acc.totalTokens.reasoning + finite(tokens.reasoning)
|
|
@@ -669,7 +812,7 @@ function reduceCost(acc, event) {
|
|
|
669
812
|
};
|
|
670
813
|
}
|
|
671
814
|
let retry = acc.retry;
|
|
672
|
-
if (event.type === "
|
|
815
|
+
if (event.type === "agent_call_end") {
|
|
673
816
|
const d = event.data ?? {};
|
|
674
817
|
const reason = d.retryReason;
|
|
675
818
|
retry = { ...acc.retry };
|
|
@@ -691,19 +834,17 @@ function reduceCost(acc, event) {
|
|
|
691
834
|
}
|
|
692
835
|
}
|
|
693
836
|
let byEmbedder = acc.byEmbedder;
|
|
694
|
-
if (event.type === "
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
};
|
|
706
|
-
}
|
|
837
|
+
if (event.type === "memory_remember" || event.type === "memory_recall") {
|
|
838
|
+
const usage = event.data.usage;
|
|
839
|
+
byEmbedder = { ...acc.byEmbedder };
|
|
840
|
+
const modelKey = usage?.model ?? "unknown";
|
|
841
|
+
const embedTokens = typeof usage?.tokens === "number" ? finite(usage.tokens) : 0;
|
|
842
|
+
const prev = byEmbedder[modelKey] ?? { cost: 0, calls: 0, tokens: 0 };
|
|
843
|
+
byEmbedder[modelKey] = {
|
|
844
|
+
cost: prev.cost + cost,
|
|
845
|
+
calls: prev.calls + 1,
|
|
846
|
+
tokens: prev.tokens + embedTokens
|
|
847
|
+
};
|
|
707
848
|
}
|
|
708
849
|
return {
|
|
709
850
|
totalCost: acc.totalCost + cost,
|
|
@@ -898,7 +1039,7 @@ function reduceTraceStats(acc, event) {
|
|
|
898
1039
|
const eventTypeCounts = { ...acc.eventTypeCounts };
|
|
899
1040
|
eventTypeCounts[event.type] = (eventTypeCounts[event.type] ?? 0) + 1;
|
|
900
1041
|
const byTool = { ...acc.byTool };
|
|
901
|
-
if (event.type === "
|
|
1042
|
+
if (event.type === "tool_call_end" || event.type === "tool_denied" || event.type === "tool_approval") {
|
|
902
1043
|
const toolName = event.tool;
|
|
903
1044
|
const prev = byTool[toolName] ?? { calls: 0, denied: 0, approved: 0 };
|
|
904
1045
|
const isDeniedEvent = event.type === "tool_denied";
|
|
@@ -907,13 +1048,13 @@ function reduceTraceStats(acc, event) {
|
|
|
907
1048
|
const isApproved = isDeniedEvent && eventData?.approved === true || isApprovalEvent && eventData?.approved === true;
|
|
908
1049
|
const isDenied = isDeniedEvent && !eventData?.approved || isApprovalEvent && eventData?.approved === false;
|
|
909
1050
|
byTool[toolName] = {
|
|
910
|
-
calls: prev.calls + (event.type === "
|
|
1051
|
+
calls: prev.calls + (event.type === "tool_call_end" ? 1 : 0),
|
|
911
1052
|
denied: prev.denied + (isDenied ? 1 : 0),
|
|
912
1053
|
approved: prev.approved + (isApproved ? 1 : 0)
|
|
913
1054
|
};
|
|
914
1055
|
}
|
|
915
1056
|
const retryByAgent = { ...acc.retryByAgent };
|
|
916
|
-
if (event.agent && event.type === "
|
|
1057
|
+
if (event.agent && event.type === "agent_call_end") {
|
|
917
1058
|
const data = event.data;
|
|
918
1059
|
if (data?.retryReason) {
|
|
919
1060
|
const prev = retryByAgent[event.agent] ?? { schema: 0, validate: 0, guardrail: 0 };
|
|
@@ -931,128 +1072,6 @@ function reduceTraceStats(acc, event) {
|
|
|
931
1072
|
};
|
|
932
1073
|
}
|
|
933
1074
|
|
|
934
|
-
// src/server/aggregates/execution-aggregator.ts
|
|
935
|
-
var ExecutionAggregator = class {
|
|
936
|
-
snaps;
|
|
937
|
-
interval;
|
|
938
|
-
listener;
|
|
939
|
-
options;
|
|
940
|
-
/** Generation counter to prevent stale async fold after rebuild. */
|
|
941
|
-
generation = 0;
|
|
942
|
-
constructor(options) {
|
|
943
|
-
this.options = options;
|
|
944
|
-
this.snaps = new AggregateSnapshots(
|
|
945
|
-
options.windows,
|
|
946
|
-
options.emptyState,
|
|
947
|
-
options.connMgr,
|
|
948
|
-
options.channel,
|
|
949
|
-
options.broadcastTransform
|
|
950
|
-
);
|
|
951
|
-
}
|
|
952
|
-
async start() {
|
|
953
|
-
await this.rebuild();
|
|
954
|
-
this.listener = (event) => {
|
|
955
|
-
if (!isLogEvent(event, "workflow_end")) return;
|
|
956
|
-
const gen = this.generation;
|
|
957
|
-
this.options.runtime.getExecution(event.executionId).then((exec) => {
|
|
958
|
-
if (this.generation !== gen) return;
|
|
959
|
-
if (exec) {
|
|
960
|
-
this.snaps.fold(exec.startedAt, (prev) => this.options.reducer(prev, exec));
|
|
961
|
-
}
|
|
962
|
-
}).catch((err) => console.error("[axl-studio] execution fold failed:", err));
|
|
963
|
-
};
|
|
964
|
-
this.options.runtime.on("trace", this.listener);
|
|
965
|
-
this.interval = setInterval(
|
|
966
|
-
() => this.rebuild().catch((err) => console.error("[axl-studio] rebuild failed:", err)),
|
|
967
|
-
REBUILD_INTERVAL_MS
|
|
968
|
-
);
|
|
969
|
-
}
|
|
970
|
-
async rebuild() {
|
|
971
|
-
this.generation++;
|
|
972
|
-
const executions = await this.options.runtime.getExecutions();
|
|
973
|
-
const cap = this.options.executionCap ?? 2e3;
|
|
974
|
-
const capped = executions.slice(0, cap);
|
|
975
|
-
const now = Date.now();
|
|
976
|
-
const fresh = new Map(
|
|
977
|
-
this.options.windows.map((w) => [w, this.options.emptyState()])
|
|
978
|
-
);
|
|
979
|
-
for (const exec of capped) {
|
|
980
|
-
for (const window of this.options.windows) {
|
|
981
|
-
if (withinWindow(exec.startedAt, window, now)) {
|
|
982
|
-
fresh.set(window, this.options.reducer(fresh.get(window), exec));
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
this.snaps.replace(fresh);
|
|
987
|
-
}
|
|
988
|
-
getSnapshot(window) {
|
|
989
|
-
return this.snaps.get(window);
|
|
990
|
-
}
|
|
991
|
-
getAllSnapshots() {
|
|
992
|
-
return this.snaps.getAll();
|
|
993
|
-
}
|
|
994
|
-
close() {
|
|
995
|
-
if (this.listener) this.options.runtime.off("trace", this.listener);
|
|
996
|
-
if (this.interval) clearInterval(this.interval);
|
|
997
|
-
}
|
|
998
|
-
};
|
|
999
|
-
|
|
1000
|
-
// src/server/aggregates/eval-aggregator.ts
|
|
1001
|
-
var EvalAggregator = class {
|
|
1002
|
-
snaps;
|
|
1003
|
-
interval;
|
|
1004
|
-
listener;
|
|
1005
|
-
options;
|
|
1006
|
-
constructor(options) {
|
|
1007
|
-
this.options = options;
|
|
1008
|
-
this.snaps = new AggregateSnapshots(
|
|
1009
|
-
options.windows,
|
|
1010
|
-
options.emptyState,
|
|
1011
|
-
options.connMgr,
|
|
1012
|
-
options.channel,
|
|
1013
|
-
options.broadcastTransform
|
|
1014
|
-
);
|
|
1015
|
-
}
|
|
1016
|
-
async start() {
|
|
1017
|
-
await this.rebuild();
|
|
1018
|
-
this.listener = (entry) => {
|
|
1019
|
-
this.snaps.fold(entry.timestamp, (prev) => this.options.reducer(prev, entry));
|
|
1020
|
-
};
|
|
1021
|
-
this.options.runtime.on("eval_result", this.listener);
|
|
1022
|
-
this.interval = setInterval(
|
|
1023
|
-
() => this.rebuild().catch((err) => console.error("[axl-studio] rebuild failed:", err)),
|
|
1024
|
-
REBUILD_INTERVAL_MS
|
|
1025
|
-
);
|
|
1026
|
-
}
|
|
1027
|
-
async rebuild() {
|
|
1028
|
-
const history = await this.options.runtime.getEvalHistory();
|
|
1029
|
-
const cap = this.options.entryCap ?? 500;
|
|
1030
|
-
const capped = history.slice(0, cap);
|
|
1031
|
-
const now = Date.now();
|
|
1032
|
-
const fresh = new Map(
|
|
1033
|
-
this.options.windows.map((w) => [w, this.options.emptyState()])
|
|
1034
|
-
);
|
|
1035
|
-
for (const entry of capped) {
|
|
1036
|
-
for (const window of this.options.windows) {
|
|
1037
|
-
if (withinWindow(entry.timestamp, window, now)) {
|
|
1038
|
-
fresh.set(window, this.options.reducer(fresh.get(window), entry));
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
this.snaps.replace(fresh);
|
|
1043
|
-
}
|
|
1044
|
-
getSnapshot(window) {
|
|
1045
|
-
return this.snaps.get(window);
|
|
1046
|
-
}
|
|
1047
|
-
getAllSnapshots() {
|
|
1048
|
-
return this.snaps.getAll();
|
|
1049
|
-
}
|
|
1050
|
-
close() {
|
|
1051
|
-
if (this.listener) this.options.runtime.off("eval_result", this.listener);
|
|
1052
|
-
if (this.interval) clearInterval(this.interval);
|
|
1053
|
-
}
|
|
1054
|
-
};
|
|
1055
|
-
|
|
1056
1075
|
// src/server/routes/health.ts
|
|
1057
1076
|
var import_hono = require("hono");
|
|
1058
1077
|
function createHealthRoutes(readOnly) {
|
|
@@ -1075,7 +1094,7 @@ function createHealthRoutes(readOnly) {
|
|
|
1075
1094
|
|
|
1076
1095
|
// src/server/routes/workflows.ts
|
|
1077
1096
|
var import_hono2 = require("hono");
|
|
1078
|
-
var
|
|
1097
|
+
var import_axl3 = require("@axlsdk/axl");
|
|
1079
1098
|
function createWorkflowRoutes(connMgr) {
|
|
1080
1099
|
const app6 = new import_hono2.Hono();
|
|
1081
1100
|
app6.get("/workflows", (c) => {
|
|
@@ -1101,8 +1120,8 @@ function createWorkflowRoutes(connMgr) {
|
|
|
1101
1120
|
ok: true,
|
|
1102
1121
|
data: {
|
|
1103
1122
|
name: workflow.name,
|
|
1104
|
-
inputSchema: workflow.inputSchema ? (0,
|
|
1105
|
-
outputSchema: workflow.outputSchema ? (0,
|
|
1123
|
+
inputSchema: workflow.inputSchema ? (0, import_axl3.zodToJsonSchema)(workflow.inputSchema) : null,
|
|
1124
|
+
outputSchema: workflow.outputSchema ? (0, import_axl3.zodToJsonSchema)(workflow.outputSchema) : null
|
|
1106
1125
|
}
|
|
1107
1126
|
});
|
|
1108
1127
|
});
|
|
@@ -1161,9 +1180,31 @@ app.get("/executions/:id", async (c) => {
|
|
|
1161
1180
|
404
|
|
1162
1181
|
);
|
|
1163
1182
|
}
|
|
1183
|
+
const sinceParam = c.req.query("since");
|
|
1184
|
+
let paged = execution;
|
|
1185
|
+
if (sinceParam !== void 0) {
|
|
1186
|
+
const since = Number(sinceParam);
|
|
1187
|
+
if (!Number.isFinite(since) || !Number.isInteger(since)) {
|
|
1188
|
+
return c.json(
|
|
1189
|
+
{
|
|
1190
|
+
ok: false,
|
|
1191
|
+
error: {
|
|
1192
|
+
code: "INVALID_PARAM",
|
|
1193
|
+
message: `\`since\` must be a finite integer (got "${sinceParam}")`,
|
|
1194
|
+
param: "since"
|
|
1195
|
+
}
|
|
1196
|
+
},
|
|
1197
|
+
400
|
|
1198
|
+
);
|
|
1199
|
+
}
|
|
1200
|
+
paged = {
|
|
1201
|
+
...execution,
|
|
1202
|
+
events: execution.events.filter((e) => e.step > since)
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1164
1205
|
return c.json({
|
|
1165
1206
|
ok: true,
|
|
1166
|
-
data: redactExecutionInfo(
|
|
1207
|
+
data: redactExecutionInfo(paged, runtime.isRedactEnabled())
|
|
1167
1208
|
});
|
|
1168
1209
|
});
|
|
1169
1210
|
app.post("/executions/:id/abort", (c) => {
|
|
@@ -1243,7 +1284,7 @@ function createSessionRoutes(connMgr) {
|
|
|
1243
1284
|
|
|
1244
1285
|
// src/server/routes/agents.ts
|
|
1245
1286
|
var import_hono5 = require("hono");
|
|
1246
|
-
var
|
|
1287
|
+
var import_axl4 = require("@axlsdk/axl");
|
|
1247
1288
|
var app2 = new import_hono5.Hono();
|
|
1248
1289
|
app2.get("/agents", (c) => {
|
|
1249
1290
|
const runtime = c.get("runtime");
|
|
@@ -1284,7 +1325,7 @@ app2.get("/agents/:name", (c) => {
|
|
|
1284
1325
|
tools: cfg.tools?.map((t) => ({
|
|
1285
1326
|
name: t.name,
|
|
1286
1327
|
description: t.description,
|
|
1287
|
-
inputSchema: (0,
|
|
1328
|
+
inputSchema: (0, import_axl4.zodToJsonSchema)(t.inputSchema)
|
|
1288
1329
|
})) ?? [],
|
|
1289
1330
|
handoffs: typeof cfg.handoffs === "function" ? [
|
|
1290
1331
|
{
|
|
@@ -1324,14 +1365,14 @@ var agents_default = app2;
|
|
|
1324
1365
|
|
|
1325
1366
|
// src/server/routes/tools.ts
|
|
1326
1367
|
var import_hono6 = require("hono");
|
|
1327
|
-
var
|
|
1368
|
+
var import_axl5 = require("@axlsdk/axl");
|
|
1328
1369
|
var app3 = new import_hono6.Hono();
|
|
1329
1370
|
app3.get("/tools", (c) => {
|
|
1330
1371
|
const runtime = c.get("runtime");
|
|
1331
1372
|
const tools = runtime.getTools().map((t) => ({
|
|
1332
1373
|
name: t.name,
|
|
1333
1374
|
description: t.description,
|
|
1334
|
-
inputSchema: t.inputSchema ? (0,
|
|
1375
|
+
inputSchema: t.inputSchema ? (0, import_axl5.zodToJsonSchema)(t.inputSchema) : {},
|
|
1335
1376
|
sensitive: t.sensitive ?? false,
|
|
1336
1377
|
requireApproval: t.requireApproval ?? false
|
|
1337
1378
|
}));
|
|
@@ -1352,7 +1393,7 @@ app3.get("/tools/:name", (c) => {
|
|
|
1352
1393
|
data: {
|
|
1353
1394
|
name: tool.name,
|
|
1354
1395
|
description: tool.description,
|
|
1355
|
-
inputSchema: tool.inputSchema ? (0,
|
|
1396
|
+
inputSchema: tool.inputSchema ? (0, import_axl5.zodToJsonSchema)(tool.inputSchema) : {},
|
|
1356
1397
|
sensitive: tool.sensitive,
|
|
1357
1398
|
requireApproval: tool.requireApproval,
|
|
1358
1399
|
retry: tool.retry,
|
|
@@ -1740,10 +1781,14 @@ function createEvalRoutes(connMgr, evalLoader) {
|
|
|
1740
1781
|
const runtime = c.get("runtime");
|
|
1741
1782
|
const redactOn = runtime.isRedactEnabled();
|
|
1742
1783
|
const body = await c.req.json();
|
|
1784
|
+
const MAX_POOLED_RUNS = 25;
|
|
1743
1785
|
const validateIdParam = (v, name) => {
|
|
1744
1786
|
if (typeof v === "string") return v === "" ? `${name} must be non-empty` : null;
|
|
1745
1787
|
if (Array.isArray(v)) {
|
|
1746
1788
|
if (v.length === 0) return `${name} must be a non-empty array`;
|
|
1789
|
+
if (v.length > MAX_POOLED_RUNS) {
|
|
1790
|
+
return `${name} may contain at most ${MAX_POOLED_RUNS} ids (pooled comparison)`;
|
|
1791
|
+
}
|
|
1747
1792
|
for (const elem of v) {
|
|
1748
1793
|
if (typeof elem !== "string" || elem === "") {
|
|
1749
1794
|
return `${name} array must contain only non-empty strings`;
|
|
@@ -1912,32 +1957,50 @@ function createPlaygroundRoutes(connMgr) {
|
|
|
1912
1957
|
);
|
|
1913
1958
|
}
|
|
1914
1959
|
const sessionId = body.sessionId ?? `playground-${Date.now()}`;
|
|
1915
|
-
const executionId = `playground-${sessionId}-${Date.now()}`;
|
|
1916
1960
|
const store = runtime.getStateStore();
|
|
1917
1961
|
const history = await store.getSession(sessionId);
|
|
1918
1962
|
history.push({ role: "user", content: body.message });
|
|
1919
1963
|
const redactOn = runtime.isRedactEnabled();
|
|
1920
|
-
const
|
|
1964
|
+
const ctx = runtime.createContext({ sessionHistory: history });
|
|
1965
|
+
const executionId = ctx.executionId;
|
|
1966
|
+
const traceListener = (event) => {
|
|
1967
|
+
if (event.executionId !== executionId) return;
|
|
1921
1968
|
connMgr.broadcastWithWildcard(`execution:${executionId}`, redactStreamEvent(event, redactOn));
|
|
1922
1969
|
};
|
|
1923
|
-
|
|
1924
|
-
sessionHistory: history,
|
|
1925
|
-
onToken: (token) => {
|
|
1926
|
-
broadcast({ type: "token", data: token });
|
|
1927
|
-
}
|
|
1928
|
-
});
|
|
1970
|
+
runtime.on("trace", traceListener);
|
|
1929
1971
|
(async () => {
|
|
1972
|
+
let stepCounter = Number.MAX_SAFE_INTEGER - 1;
|
|
1973
|
+
const terminalFields = () => ({
|
|
1974
|
+
executionId,
|
|
1975
|
+
step: stepCounter++,
|
|
1976
|
+
timestamp: Date.now()
|
|
1977
|
+
});
|
|
1930
1978
|
try {
|
|
1931
1979
|
const result = await ctx.ask(agent, body.message);
|
|
1932
1980
|
const resultText = typeof result === "string" ? result : JSON.stringify(result);
|
|
1933
1981
|
history.push({ role: "assistant", content: resultText });
|
|
1934
1982
|
await store.saveSession(sessionId, history);
|
|
1935
|
-
|
|
1983
|
+
const doneEvent = {
|
|
1984
|
+
...terminalFields(),
|
|
1985
|
+
type: "done",
|
|
1986
|
+
data: { result: resultText }
|
|
1987
|
+
};
|
|
1988
|
+
connMgr.broadcastWithWildcard(
|
|
1989
|
+
`execution:${executionId}`,
|
|
1990
|
+
redactStreamEvent(doneEvent, redactOn)
|
|
1991
|
+
);
|
|
1936
1992
|
} catch (err) {
|
|
1937
|
-
|
|
1993
|
+
const errorEvent = {
|
|
1994
|
+
...terminalFields(),
|
|
1938
1995
|
type: "error",
|
|
1939
|
-
message: err instanceof Error ? err.message : String(err)
|
|
1940
|
-
}
|
|
1996
|
+
data: { message: err instanceof Error ? err.message : String(err) }
|
|
1997
|
+
};
|
|
1998
|
+
connMgr.broadcastWithWildcard(
|
|
1999
|
+
`execution:${executionId}`,
|
|
2000
|
+
redactStreamEvent(errorEvent, redactOn)
|
|
2001
|
+
);
|
|
2002
|
+
} finally {
|
|
2003
|
+
runtime.off("trace", traceListener);
|
|
1941
2004
|
}
|
|
1942
2005
|
})();
|
|
1943
2006
|
return c.json({
|
|
@@ -1985,7 +2048,7 @@ function createTraceStatsRoutes(aggregator) {
|
|
|
1985
2048
|
function createServer(options) {
|
|
1986
2049
|
const { runtime, staticRoot, basePath = "", readOnly = false } = options;
|
|
1987
2050
|
const app6 = new import_hono15.Hono();
|
|
1988
|
-
const connMgr = new ConnectionManager();
|
|
2051
|
+
const connMgr = new ConnectionManager(options.bufferCaps);
|
|
1989
2052
|
const windows = ["24h", "7d", "30d", "all"];
|
|
1990
2053
|
const costAggregator = new TraceAggregator({
|
|
1991
2054
|
runtime,
|
|
@@ -2079,12 +2142,20 @@ function createServer(options) {
|
|
|
2079
2142
|
api.route("/", createPlaygroundRoutes(connMgr));
|
|
2080
2143
|
app6.route("/api", api);
|
|
2081
2144
|
const traceListener = (event) => {
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2145
|
+
try {
|
|
2146
|
+
const traceEvent = event;
|
|
2147
|
+
const redacted = redactStreamEvent(traceEvent, runtime.isRedactEnabled());
|
|
2148
|
+
if (traceEvent.executionId) {
|
|
2149
|
+
connMgr.broadcastWithWildcard(`trace:${traceEvent.executionId}`, redacted);
|
|
2150
|
+
}
|
|
2151
|
+
if (traceEvent.type === "await_human") {
|
|
2152
|
+
connMgr.broadcast("decisions", redacted);
|
|
2153
|
+
}
|
|
2154
|
+
} catch (err) {
|
|
2155
|
+
console.error(
|
|
2156
|
+
"[axl-studio] trace listener threw; event dropped:",
|
|
2157
|
+
err instanceof Error ? err.message : String(err)
|
|
2158
|
+
);
|
|
2088
2159
|
}
|
|
2089
2160
|
};
|
|
2090
2161
|
runtime.on("trace", traceListener);
|