@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/server/index.cjs
CHANGED
|
@@ -44,6 +44,7 @@ var import_cors = require("hono/cors");
|
|
|
44
44
|
var import_serve_static = require("@hono/node-server/serve-static");
|
|
45
45
|
|
|
46
46
|
// src/server/redact.ts
|
|
47
|
+
var import_axl = require("@axlsdk/axl");
|
|
47
48
|
var REDACTED = "[redacted]";
|
|
48
49
|
var SAFE_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
49
50
|
"QuorumNotMet",
|
|
@@ -68,7 +69,8 @@ function redactExecutionInfo(info, redact) {
|
|
|
68
69
|
return {
|
|
69
70
|
...info,
|
|
70
71
|
...info.result !== void 0 ? { result: REDACTED } : {},
|
|
71
|
-
...info.error !== void 0 ? { error: REDACTED } : {}
|
|
72
|
+
...info.error !== void 0 ? { error: REDACTED } : {},
|
|
73
|
+
events: info.events.map((e) => redactStreamEvent(e, true))
|
|
72
74
|
};
|
|
73
75
|
}
|
|
74
76
|
function redactExecutionList(infos, redact) {
|
|
@@ -109,30 +111,7 @@ function redactSessionHistory(history, redact) {
|
|
|
109
111
|
}
|
|
110
112
|
function redactStreamEvent(event, redact) {
|
|
111
113
|
if (!redact) return event;
|
|
112
|
-
|
|
113
|
-
case "token":
|
|
114
|
-
return { type: "token", data: REDACTED };
|
|
115
|
-
case "tool_call":
|
|
116
|
-
return { ...event, args: REDACTED };
|
|
117
|
-
case "tool_result":
|
|
118
|
-
return { ...event, result: REDACTED };
|
|
119
|
-
case "tool_approval":
|
|
120
|
-
return {
|
|
121
|
-
...event,
|
|
122
|
-
args: REDACTED,
|
|
123
|
-
...event.reason !== void 0 ? { reason: REDACTED } : {}
|
|
124
|
-
};
|
|
125
|
-
case "done":
|
|
126
|
-
return { type: "done", data: REDACTED };
|
|
127
|
-
case "error":
|
|
128
|
-
return { type: "error", message: REDACTED };
|
|
129
|
-
// Structural events have no user content to scrub.
|
|
130
|
-
case "agent_start":
|
|
131
|
-
case "agent_end":
|
|
132
|
-
case "handoff":
|
|
133
|
-
case "step":
|
|
134
|
-
return event;
|
|
135
|
-
}
|
|
114
|
+
return (0, import_axl.redactEvent)(event);
|
|
136
115
|
}
|
|
137
116
|
function redactEvalItem(item) {
|
|
138
117
|
const scrubbed = {
|
|
@@ -218,13 +197,17 @@ async function errorHandler(c, next) {
|
|
|
218
197
|
|
|
219
198
|
// src/server/ws/connection-manager.ts
|
|
220
199
|
var BUFFER_TTL_MS = 3e4;
|
|
221
|
-
var
|
|
200
|
+
var DEFAULT_MAX_BUFFER_EVENTS = 1e3;
|
|
201
|
+
var DEFAULT_MAX_BUFFER_BYTES = 4 * 1024 * 1024;
|
|
202
|
+
var DEFAULT_MAX_ACTIVE_BUFFERS = 256;
|
|
203
|
+
var UNBUFFERED_EVENT_TYPES = /* @__PURE__ */ new Set(["token", "partial_object"]);
|
|
222
204
|
var MAX_WS_FRAME_BYTES = 65536;
|
|
223
205
|
function isBufferedChannel(channel) {
|
|
224
206
|
return channel.startsWith("execution:") || channel.startsWith("eval:");
|
|
225
207
|
}
|
|
226
208
|
function truncateIfOversized(msg, channel, data) {
|
|
227
|
-
|
|
209
|
+
const msgBytes = Buffer.byteLength(msg, "utf8");
|
|
210
|
+
if (msgBytes <= MAX_WS_FRAME_BYTES) return msg;
|
|
228
211
|
const event = data ?? {};
|
|
229
212
|
const truncated = {
|
|
230
213
|
type: "event",
|
|
@@ -233,7 +216,7 @@ function truncateIfOversized(msg, channel, data) {
|
|
|
233
216
|
...event,
|
|
234
217
|
data: {
|
|
235
218
|
__truncated: true,
|
|
236
|
-
originalBytes:
|
|
219
|
+
originalBytes: msgBytes,
|
|
237
220
|
maxBytes: MAX_WS_FRAME_BYTES,
|
|
238
221
|
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."
|
|
239
222
|
}
|
|
@@ -250,6 +233,25 @@ var ConnectionManager = class {
|
|
|
250
233
|
buffers = /* @__PURE__ */ new Map();
|
|
251
234
|
maxConnections = 100;
|
|
252
235
|
filter;
|
|
236
|
+
/** Resolved replay-buffer caps. Per-instance so embedders can dial them
|
|
237
|
+
* without monkey-patching module-level constants. */
|
|
238
|
+
maxEventsPerBuffer;
|
|
239
|
+
maxBytesPerBuffer;
|
|
240
|
+
maxActiveBuffers;
|
|
241
|
+
constructor(bufferCaps) {
|
|
242
|
+
const validatePositiveInt = (key, value) => {
|
|
243
|
+
if (value === void 0) return;
|
|
244
|
+
if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1) {
|
|
245
|
+
throw new RangeError(`bufferCaps.${key} must be a positive integer (>= 1); got ${value}`);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
validatePositiveInt("maxEventsPerBuffer", bufferCaps?.maxEventsPerBuffer);
|
|
249
|
+
validatePositiveInt("maxBytesPerBuffer", bufferCaps?.maxBytesPerBuffer);
|
|
250
|
+
validatePositiveInt("maxActiveBuffers", bufferCaps?.maxActiveBuffers);
|
|
251
|
+
this.maxEventsPerBuffer = bufferCaps?.maxEventsPerBuffer ?? DEFAULT_MAX_BUFFER_EVENTS;
|
|
252
|
+
this.maxBytesPerBuffer = bufferCaps?.maxBytesPerBuffer ?? DEFAULT_MAX_BUFFER_BYTES;
|
|
253
|
+
this.maxActiveBuffers = bufferCaps?.maxActiveBuffers ?? DEFAULT_MAX_ACTIVE_BUFFERS;
|
|
254
|
+
}
|
|
253
255
|
/**
|
|
254
256
|
* Register a broadcast filter. Called once at middleware construction.
|
|
255
257
|
* The filter runs on every outbound event and can drop or deliver based
|
|
@@ -332,13 +334,37 @@ var ConnectionManager = class {
|
|
|
332
334
|
if (isBufferedChannel(channel)) {
|
|
333
335
|
let buffer = this.buffers.get(channel);
|
|
334
336
|
if (!buffer) {
|
|
335
|
-
|
|
337
|
+
if (this.buffers.size >= this.maxActiveBuffers) {
|
|
338
|
+
let victim;
|
|
339
|
+
for (const [ch, buf] of this.buffers) {
|
|
340
|
+
if (buf.complete) {
|
|
341
|
+
victim = ch;
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (victim === void 0) {
|
|
346
|
+
victim = this.buffers.keys().next().value;
|
|
347
|
+
}
|
|
348
|
+
if (victim !== void 0) {
|
|
349
|
+
const old = this.buffers.get(victim);
|
|
350
|
+
if (old?.timer) clearTimeout(old.timer);
|
|
351
|
+
this.buffers.delete(victim);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
buffer = { events: [], complete: false, bytes: 0 };
|
|
336
355
|
this.buffers.set(channel, buffer);
|
|
337
356
|
}
|
|
338
357
|
const event = data;
|
|
339
358
|
const isTerminal = event.type === "done" || event.type === "error";
|
|
340
|
-
|
|
341
|
-
|
|
359
|
+
const isUnbuffered = event.type !== void 0 && UNBUFFERED_EVENT_TYPES.has(event.type);
|
|
360
|
+
if (!isUnbuffered) {
|
|
361
|
+
const msgBytes = Buffer.byteLength(msg, "utf8");
|
|
362
|
+
const atCountCap = buffer.events.length >= this.maxEventsPerBuffer;
|
|
363
|
+
const atByteCap = buffer.bytes + msgBytes > this.maxBytesPerBuffer;
|
|
364
|
+
if (isTerminal || !atCountCap && !atByteCap) {
|
|
365
|
+
buffer.events.push({ msg, data });
|
|
366
|
+
buffer.bytes += msgBytes;
|
|
367
|
+
}
|
|
342
368
|
}
|
|
343
369
|
if (isTerminal) {
|
|
344
370
|
buffer.complete = true;
|
|
@@ -423,7 +449,7 @@ var VALID_CHANNEL_PREFIXES = ["execution:", "trace:", "eval:"];
|
|
|
423
449
|
var VALID_EXACT_CHANNELS = ["costs", "decisions", "eval-trends", "workflow-stats", "trace-stats"];
|
|
424
450
|
var MAX_CHANNEL_LENGTH = 256;
|
|
425
451
|
function handleWsMessage(raw, socket, connMgr) {
|
|
426
|
-
if (raw
|
|
452
|
+
if (Buffer.byteLength(raw, "utf8") > MAX_WS_FRAME_BYTES) {
|
|
427
453
|
return JSON.stringify({ type: "error", message: "Message too large" });
|
|
428
454
|
}
|
|
429
455
|
let msg;
|
|
@@ -579,7 +605,7 @@ var TraceAggregator = class {
|
|
|
579
605
|
this.options.windows.map((w) => [w, this.options.emptyState()])
|
|
580
606
|
);
|
|
581
607
|
for (const exec of capped) {
|
|
582
|
-
for (const event of exec.
|
|
608
|
+
for (const event of exec.events) {
|
|
583
609
|
for (const window of this.options.windows) {
|
|
584
610
|
if (withinWindow(event.timestamp, window, now)) {
|
|
585
611
|
fresh.set(window, this.options.reducer(fresh.get(window), event));
|
|
@@ -601,15 +627,131 @@ var TraceAggregator = class {
|
|
|
601
627
|
}
|
|
602
628
|
};
|
|
603
629
|
|
|
630
|
+
// src/server/aggregates/execution-aggregator.ts
|
|
631
|
+
var ExecutionAggregator = class {
|
|
632
|
+
snaps;
|
|
633
|
+
interval;
|
|
634
|
+
listener;
|
|
635
|
+
options;
|
|
636
|
+
/** Generation counter to prevent stale async fold after rebuild. */
|
|
637
|
+
generation = 0;
|
|
638
|
+
constructor(options) {
|
|
639
|
+
this.options = options;
|
|
640
|
+
this.snaps = new AggregateSnapshots(
|
|
641
|
+
options.windows,
|
|
642
|
+
options.emptyState,
|
|
643
|
+
options.connMgr,
|
|
644
|
+
options.channel,
|
|
645
|
+
options.broadcastTransform
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
async start() {
|
|
649
|
+
await this.rebuild();
|
|
650
|
+
this.listener = (event) => {
|
|
651
|
+
if (event.type !== "workflow_end") return;
|
|
652
|
+
const gen = this.generation;
|
|
653
|
+
this.options.runtime.getExecution(event.executionId).then((exec) => {
|
|
654
|
+
if (this.generation !== gen) return;
|
|
655
|
+
if (exec) {
|
|
656
|
+
this.snaps.fold(exec.startedAt, (prev) => this.options.reducer(prev, exec));
|
|
657
|
+
}
|
|
658
|
+
}).catch((err) => console.error("[axl-studio] execution fold failed:", err));
|
|
659
|
+
};
|
|
660
|
+
this.options.runtime.on("trace", this.listener);
|
|
661
|
+
this.interval = setInterval(
|
|
662
|
+
() => this.rebuild().catch((err) => console.error("[axl-studio] rebuild failed:", err)),
|
|
663
|
+
REBUILD_INTERVAL_MS
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
async rebuild() {
|
|
667
|
+
this.generation++;
|
|
668
|
+
const executions = await this.options.runtime.getExecutions();
|
|
669
|
+
const cap = this.options.executionCap ?? 2e3;
|
|
670
|
+
const capped = executions.slice(0, cap);
|
|
671
|
+
const now = Date.now();
|
|
672
|
+
const fresh = new Map(
|
|
673
|
+
this.options.windows.map((w) => [w, this.options.emptyState()])
|
|
674
|
+
);
|
|
675
|
+
for (const exec of capped) {
|
|
676
|
+
for (const window of this.options.windows) {
|
|
677
|
+
if (withinWindow(exec.startedAt, window, now)) {
|
|
678
|
+
fresh.set(window, this.options.reducer(fresh.get(window), exec));
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
this.snaps.replace(fresh);
|
|
683
|
+
}
|
|
684
|
+
getSnapshot(window) {
|
|
685
|
+
return this.snaps.get(window);
|
|
686
|
+
}
|
|
687
|
+
getAllSnapshots() {
|
|
688
|
+
return this.snaps.getAll();
|
|
689
|
+
}
|
|
690
|
+
close() {
|
|
691
|
+
if (this.listener) this.options.runtime.off("trace", this.listener);
|
|
692
|
+
if (this.interval) clearInterval(this.interval);
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
// src/server/aggregates/eval-aggregator.ts
|
|
697
|
+
var EvalAggregator = class {
|
|
698
|
+
snaps;
|
|
699
|
+
interval;
|
|
700
|
+
listener;
|
|
701
|
+
options;
|
|
702
|
+
constructor(options) {
|
|
703
|
+
this.options = options;
|
|
704
|
+
this.snaps = new AggregateSnapshots(
|
|
705
|
+
options.windows,
|
|
706
|
+
options.emptyState,
|
|
707
|
+
options.connMgr,
|
|
708
|
+
options.channel,
|
|
709
|
+
options.broadcastTransform
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
async start() {
|
|
713
|
+
await this.rebuild();
|
|
714
|
+
this.listener = (entry) => {
|
|
715
|
+
this.snaps.fold(entry.timestamp, (prev) => this.options.reducer(prev, entry));
|
|
716
|
+
};
|
|
717
|
+
this.options.runtime.on("eval_result", this.listener);
|
|
718
|
+
this.interval = setInterval(
|
|
719
|
+
() => this.rebuild().catch((err) => console.error("[axl-studio] rebuild failed:", err)),
|
|
720
|
+
REBUILD_INTERVAL_MS
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
async rebuild() {
|
|
724
|
+
const history = await this.options.runtime.getEvalHistory();
|
|
725
|
+
const cap = this.options.entryCap ?? 500;
|
|
726
|
+
const capped = history.slice(0, cap);
|
|
727
|
+
const now = Date.now();
|
|
728
|
+
const fresh = new Map(
|
|
729
|
+
this.options.windows.map((w) => [w, this.options.emptyState()])
|
|
730
|
+
);
|
|
731
|
+
for (const entry of capped) {
|
|
732
|
+
for (const window of this.options.windows) {
|
|
733
|
+
if (withinWindow(entry.timestamp, window, now)) {
|
|
734
|
+
fresh.set(window, this.options.reducer(fresh.get(window), entry));
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
this.snaps.replace(fresh);
|
|
739
|
+
}
|
|
740
|
+
getSnapshot(window) {
|
|
741
|
+
return this.snaps.get(window);
|
|
742
|
+
}
|
|
743
|
+
getAllSnapshots() {
|
|
744
|
+
return this.snaps.getAll();
|
|
745
|
+
}
|
|
746
|
+
close() {
|
|
747
|
+
if (this.listener) this.options.runtime.off("eval_result", this.listener);
|
|
748
|
+
if (this.interval) clearInterval(this.interval);
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
|
|
604
752
|
// src/server/aggregates/reducers.ts
|
|
753
|
+
var import_axl2 = require("@axlsdk/axl");
|
|
605
754
|
var finite = (v) => Number.isFinite(v) ? v : 0;
|
|
606
|
-
function isLogEvent(event, eventName) {
|
|
607
|
-
if (event.type === eventName) return true;
|
|
608
|
-
if (event.type === "log" && event.data != null && typeof event.data === "object") {
|
|
609
|
-
return event.data.event === eventName;
|
|
610
|
-
}
|
|
611
|
-
return false;
|
|
612
|
-
}
|
|
613
755
|
function emptyRetry() {
|
|
614
756
|
return {
|
|
615
757
|
primary: 0,
|
|
@@ -635,7 +777,7 @@ function emptyCostData() {
|
|
|
635
777
|
};
|
|
636
778
|
}
|
|
637
779
|
function reduceCost(acc, event) {
|
|
638
|
-
const isWorkflowStart =
|
|
780
|
+
const isWorkflowStart = event.type === "workflow_start";
|
|
639
781
|
if (isWorkflowStart && event.workflow) {
|
|
640
782
|
const byWorkflow2 = { ...acc.byWorkflow };
|
|
641
783
|
const prev = byWorkflow2[event.workflow] ?? { cost: 0, executions: 0 };
|
|
@@ -643,9 +785,10 @@ function reduceCost(acc, event) {
|
|
|
643
785
|
return { ...acc, byWorkflow: byWorkflow2 };
|
|
644
786
|
}
|
|
645
787
|
if (event.cost == null && !event.tokens) return acc;
|
|
646
|
-
const cost =
|
|
788
|
+
const cost = (0, import_axl2.eventCostContribution)(event);
|
|
789
|
+
if (event.type === "ask_end") return acc;
|
|
647
790
|
const tokens = event.tokens ?? {};
|
|
648
|
-
const totalTokens = event.type === "
|
|
791
|
+
const totalTokens = event.type === "agent_call_end" ? {
|
|
649
792
|
input: acc.totalTokens.input + finite(tokens.input),
|
|
650
793
|
output: acc.totalTokens.output + finite(tokens.output),
|
|
651
794
|
reasoning: acc.totalTokens.reasoning + finite(tokens.reasoning)
|
|
@@ -676,7 +819,7 @@ function reduceCost(acc, event) {
|
|
|
676
819
|
};
|
|
677
820
|
}
|
|
678
821
|
let retry = acc.retry;
|
|
679
|
-
if (event.type === "
|
|
822
|
+
if (event.type === "agent_call_end") {
|
|
680
823
|
const d = event.data ?? {};
|
|
681
824
|
const reason = d.retryReason;
|
|
682
825
|
retry = { ...acc.retry };
|
|
@@ -698,19 +841,17 @@ function reduceCost(acc, event) {
|
|
|
698
841
|
}
|
|
699
842
|
}
|
|
700
843
|
let byEmbedder = acc.byEmbedder;
|
|
701
|
-
if (event.type === "
|
|
702
|
-
const
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
};
|
|
713
|
-
}
|
|
844
|
+
if (event.type === "memory_remember" || event.type === "memory_recall") {
|
|
845
|
+
const usage = event.data.usage;
|
|
846
|
+
byEmbedder = { ...acc.byEmbedder };
|
|
847
|
+
const modelKey = usage?.model ?? "unknown";
|
|
848
|
+
const embedTokens = typeof usage?.tokens === "number" ? finite(usage.tokens) : 0;
|
|
849
|
+
const prev = byEmbedder[modelKey] ?? { cost: 0, calls: 0, tokens: 0 };
|
|
850
|
+
byEmbedder[modelKey] = {
|
|
851
|
+
cost: prev.cost + cost,
|
|
852
|
+
calls: prev.calls + 1,
|
|
853
|
+
tokens: prev.tokens + embedTokens
|
|
854
|
+
};
|
|
714
855
|
}
|
|
715
856
|
return {
|
|
716
857
|
totalCost: acc.totalCost + cost,
|
|
@@ -905,7 +1046,7 @@ function reduceTraceStats(acc, event) {
|
|
|
905
1046
|
const eventTypeCounts = { ...acc.eventTypeCounts };
|
|
906
1047
|
eventTypeCounts[event.type] = (eventTypeCounts[event.type] ?? 0) + 1;
|
|
907
1048
|
const byTool = { ...acc.byTool };
|
|
908
|
-
if (event.type === "
|
|
1049
|
+
if (event.type === "tool_call_end" || event.type === "tool_denied" || event.type === "tool_approval") {
|
|
909
1050
|
const toolName = event.tool;
|
|
910
1051
|
const prev = byTool[toolName] ?? { calls: 0, denied: 0, approved: 0 };
|
|
911
1052
|
const isDeniedEvent = event.type === "tool_denied";
|
|
@@ -914,13 +1055,13 @@ function reduceTraceStats(acc, event) {
|
|
|
914
1055
|
const isApproved = isDeniedEvent && eventData?.approved === true || isApprovalEvent && eventData?.approved === true;
|
|
915
1056
|
const isDenied = isDeniedEvent && !eventData?.approved || isApprovalEvent && eventData?.approved === false;
|
|
916
1057
|
byTool[toolName] = {
|
|
917
|
-
calls: prev.calls + (event.type === "
|
|
1058
|
+
calls: prev.calls + (event.type === "tool_call_end" ? 1 : 0),
|
|
918
1059
|
denied: prev.denied + (isDenied ? 1 : 0),
|
|
919
1060
|
approved: prev.approved + (isApproved ? 1 : 0)
|
|
920
1061
|
};
|
|
921
1062
|
}
|
|
922
1063
|
const retryByAgent = { ...acc.retryByAgent };
|
|
923
|
-
if (event.agent && event.type === "
|
|
1064
|
+
if (event.agent && event.type === "agent_call_end") {
|
|
924
1065
|
const data = event.data;
|
|
925
1066
|
if (data?.retryReason) {
|
|
926
1067
|
const prev = retryByAgent[event.agent] ?? { schema: 0, validate: 0, guardrail: 0 };
|
|
@@ -938,128 +1079,6 @@ function reduceTraceStats(acc, event) {
|
|
|
938
1079
|
};
|
|
939
1080
|
}
|
|
940
1081
|
|
|
941
|
-
// src/server/aggregates/execution-aggregator.ts
|
|
942
|
-
var ExecutionAggregator = class {
|
|
943
|
-
snaps;
|
|
944
|
-
interval;
|
|
945
|
-
listener;
|
|
946
|
-
options;
|
|
947
|
-
/** Generation counter to prevent stale async fold after rebuild. */
|
|
948
|
-
generation = 0;
|
|
949
|
-
constructor(options) {
|
|
950
|
-
this.options = options;
|
|
951
|
-
this.snaps = new AggregateSnapshots(
|
|
952
|
-
options.windows,
|
|
953
|
-
options.emptyState,
|
|
954
|
-
options.connMgr,
|
|
955
|
-
options.channel,
|
|
956
|
-
options.broadcastTransform
|
|
957
|
-
);
|
|
958
|
-
}
|
|
959
|
-
async start() {
|
|
960
|
-
await this.rebuild();
|
|
961
|
-
this.listener = (event) => {
|
|
962
|
-
if (!isLogEvent(event, "workflow_end")) return;
|
|
963
|
-
const gen = this.generation;
|
|
964
|
-
this.options.runtime.getExecution(event.executionId).then((exec) => {
|
|
965
|
-
if (this.generation !== gen) return;
|
|
966
|
-
if (exec) {
|
|
967
|
-
this.snaps.fold(exec.startedAt, (prev) => this.options.reducer(prev, exec));
|
|
968
|
-
}
|
|
969
|
-
}).catch((err) => console.error("[axl-studio] execution fold failed:", err));
|
|
970
|
-
};
|
|
971
|
-
this.options.runtime.on("trace", this.listener);
|
|
972
|
-
this.interval = setInterval(
|
|
973
|
-
() => this.rebuild().catch((err) => console.error("[axl-studio] rebuild failed:", err)),
|
|
974
|
-
REBUILD_INTERVAL_MS
|
|
975
|
-
);
|
|
976
|
-
}
|
|
977
|
-
async rebuild() {
|
|
978
|
-
this.generation++;
|
|
979
|
-
const executions = await this.options.runtime.getExecutions();
|
|
980
|
-
const cap = this.options.executionCap ?? 2e3;
|
|
981
|
-
const capped = executions.slice(0, cap);
|
|
982
|
-
const now = Date.now();
|
|
983
|
-
const fresh = new Map(
|
|
984
|
-
this.options.windows.map((w) => [w, this.options.emptyState()])
|
|
985
|
-
);
|
|
986
|
-
for (const exec of capped) {
|
|
987
|
-
for (const window of this.options.windows) {
|
|
988
|
-
if (withinWindow(exec.startedAt, window, now)) {
|
|
989
|
-
fresh.set(window, this.options.reducer(fresh.get(window), exec));
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
this.snaps.replace(fresh);
|
|
994
|
-
}
|
|
995
|
-
getSnapshot(window) {
|
|
996
|
-
return this.snaps.get(window);
|
|
997
|
-
}
|
|
998
|
-
getAllSnapshots() {
|
|
999
|
-
return this.snaps.getAll();
|
|
1000
|
-
}
|
|
1001
|
-
close() {
|
|
1002
|
-
if (this.listener) this.options.runtime.off("trace", this.listener);
|
|
1003
|
-
if (this.interval) clearInterval(this.interval);
|
|
1004
|
-
}
|
|
1005
|
-
};
|
|
1006
|
-
|
|
1007
|
-
// src/server/aggregates/eval-aggregator.ts
|
|
1008
|
-
var EvalAggregator = class {
|
|
1009
|
-
snaps;
|
|
1010
|
-
interval;
|
|
1011
|
-
listener;
|
|
1012
|
-
options;
|
|
1013
|
-
constructor(options) {
|
|
1014
|
-
this.options = options;
|
|
1015
|
-
this.snaps = new AggregateSnapshots(
|
|
1016
|
-
options.windows,
|
|
1017
|
-
options.emptyState,
|
|
1018
|
-
options.connMgr,
|
|
1019
|
-
options.channel,
|
|
1020
|
-
options.broadcastTransform
|
|
1021
|
-
);
|
|
1022
|
-
}
|
|
1023
|
-
async start() {
|
|
1024
|
-
await this.rebuild();
|
|
1025
|
-
this.listener = (entry) => {
|
|
1026
|
-
this.snaps.fold(entry.timestamp, (prev) => this.options.reducer(prev, entry));
|
|
1027
|
-
};
|
|
1028
|
-
this.options.runtime.on("eval_result", this.listener);
|
|
1029
|
-
this.interval = setInterval(
|
|
1030
|
-
() => this.rebuild().catch((err) => console.error("[axl-studio] rebuild failed:", err)),
|
|
1031
|
-
REBUILD_INTERVAL_MS
|
|
1032
|
-
);
|
|
1033
|
-
}
|
|
1034
|
-
async rebuild() {
|
|
1035
|
-
const history = await this.options.runtime.getEvalHistory();
|
|
1036
|
-
const cap = this.options.entryCap ?? 500;
|
|
1037
|
-
const capped = history.slice(0, cap);
|
|
1038
|
-
const now = Date.now();
|
|
1039
|
-
const fresh = new Map(
|
|
1040
|
-
this.options.windows.map((w) => [w, this.options.emptyState()])
|
|
1041
|
-
);
|
|
1042
|
-
for (const entry of capped) {
|
|
1043
|
-
for (const window of this.options.windows) {
|
|
1044
|
-
if (withinWindow(entry.timestamp, window, now)) {
|
|
1045
|
-
fresh.set(window, this.options.reducer(fresh.get(window), entry));
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
this.snaps.replace(fresh);
|
|
1050
|
-
}
|
|
1051
|
-
getSnapshot(window) {
|
|
1052
|
-
return this.snaps.get(window);
|
|
1053
|
-
}
|
|
1054
|
-
getAllSnapshots() {
|
|
1055
|
-
return this.snaps.getAll();
|
|
1056
|
-
}
|
|
1057
|
-
close() {
|
|
1058
|
-
if (this.listener) this.options.runtime.off("eval_result", this.listener);
|
|
1059
|
-
if (this.interval) clearInterval(this.interval);
|
|
1060
|
-
}
|
|
1061
|
-
};
|
|
1062
|
-
|
|
1063
1082
|
// src/server/routes/health.ts
|
|
1064
1083
|
var import_hono = require("hono");
|
|
1065
1084
|
function createHealthRoutes(readOnly) {
|
|
@@ -1082,7 +1101,7 @@ function createHealthRoutes(readOnly) {
|
|
|
1082
1101
|
|
|
1083
1102
|
// src/server/routes/workflows.ts
|
|
1084
1103
|
var import_hono2 = require("hono");
|
|
1085
|
-
var
|
|
1104
|
+
var import_axl3 = require("@axlsdk/axl");
|
|
1086
1105
|
function createWorkflowRoutes(connMgr) {
|
|
1087
1106
|
const app6 = new import_hono2.Hono();
|
|
1088
1107
|
app6.get("/workflows", (c) => {
|
|
@@ -1108,8 +1127,8 @@ function createWorkflowRoutes(connMgr) {
|
|
|
1108
1127
|
ok: true,
|
|
1109
1128
|
data: {
|
|
1110
1129
|
name: workflow.name,
|
|
1111
|
-
inputSchema: workflow.inputSchema ? (0,
|
|
1112
|
-
outputSchema: workflow.outputSchema ? (0,
|
|
1130
|
+
inputSchema: workflow.inputSchema ? (0, import_axl3.zodToJsonSchema)(workflow.inputSchema) : null,
|
|
1131
|
+
outputSchema: workflow.outputSchema ? (0, import_axl3.zodToJsonSchema)(workflow.outputSchema) : null
|
|
1113
1132
|
}
|
|
1114
1133
|
});
|
|
1115
1134
|
});
|
|
@@ -1168,9 +1187,31 @@ app.get("/executions/:id", async (c) => {
|
|
|
1168
1187
|
404
|
|
1169
1188
|
);
|
|
1170
1189
|
}
|
|
1190
|
+
const sinceParam = c.req.query("since");
|
|
1191
|
+
let paged = execution;
|
|
1192
|
+
if (sinceParam !== void 0) {
|
|
1193
|
+
const since = Number(sinceParam);
|
|
1194
|
+
if (!Number.isFinite(since) || !Number.isInteger(since)) {
|
|
1195
|
+
return c.json(
|
|
1196
|
+
{
|
|
1197
|
+
ok: false,
|
|
1198
|
+
error: {
|
|
1199
|
+
code: "INVALID_PARAM",
|
|
1200
|
+
message: `\`since\` must be a finite integer (got "${sinceParam}")`,
|
|
1201
|
+
param: "since"
|
|
1202
|
+
}
|
|
1203
|
+
},
|
|
1204
|
+
400
|
|
1205
|
+
);
|
|
1206
|
+
}
|
|
1207
|
+
paged = {
|
|
1208
|
+
...execution,
|
|
1209
|
+
events: execution.events.filter((e) => e.step > since)
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1171
1212
|
return c.json({
|
|
1172
1213
|
ok: true,
|
|
1173
|
-
data: redactExecutionInfo(
|
|
1214
|
+
data: redactExecutionInfo(paged, runtime.isRedactEnabled())
|
|
1174
1215
|
});
|
|
1175
1216
|
});
|
|
1176
1217
|
app.post("/executions/:id/abort", (c) => {
|
|
@@ -1250,7 +1291,7 @@ function createSessionRoutes(connMgr) {
|
|
|
1250
1291
|
|
|
1251
1292
|
// src/server/routes/agents.ts
|
|
1252
1293
|
var import_hono5 = require("hono");
|
|
1253
|
-
var
|
|
1294
|
+
var import_axl4 = require("@axlsdk/axl");
|
|
1254
1295
|
var app2 = new import_hono5.Hono();
|
|
1255
1296
|
app2.get("/agents", (c) => {
|
|
1256
1297
|
const runtime = c.get("runtime");
|
|
@@ -1291,7 +1332,7 @@ app2.get("/agents/:name", (c) => {
|
|
|
1291
1332
|
tools: cfg.tools?.map((t) => ({
|
|
1292
1333
|
name: t.name,
|
|
1293
1334
|
description: t.description,
|
|
1294
|
-
inputSchema: (0,
|
|
1335
|
+
inputSchema: (0, import_axl4.zodToJsonSchema)(t.inputSchema)
|
|
1295
1336
|
})) ?? [],
|
|
1296
1337
|
handoffs: typeof cfg.handoffs === "function" ? [
|
|
1297
1338
|
{
|
|
@@ -1331,14 +1372,14 @@ var agents_default = app2;
|
|
|
1331
1372
|
|
|
1332
1373
|
// src/server/routes/tools.ts
|
|
1333
1374
|
var import_hono6 = require("hono");
|
|
1334
|
-
var
|
|
1375
|
+
var import_axl5 = require("@axlsdk/axl");
|
|
1335
1376
|
var app3 = new import_hono6.Hono();
|
|
1336
1377
|
app3.get("/tools", (c) => {
|
|
1337
1378
|
const runtime = c.get("runtime");
|
|
1338
1379
|
const tools = runtime.getTools().map((t) => ({
|
|
1339
1380
|
name: t.name,
|
|
1340
1381
|
description: t.description,
|
|
1341
|
-
inputSchema: t.inputSchema ? (0,
|
|
1382
|
+
inputSchema: t.inputSchema ? (0, import_axl5.zodToJsonSchema)(t.inputSchema) : {},
|
|
1342
1383
|
sensitive: t.sensitive ?? false,
|
|
1343
1384
|
requireApproval: t.requireApproval ?? false
|
|
1344
1385
|
}));
|
|
@@ -1359,7 +1400,7 @@ app3.get("/tools/:name", (c) => {
|
|
|
1359
1400
|
data: {
|
|
1360
1401
|
name: tool.name,
|
|
1361
1402
|
description: tool.description,
|
|
1362
|
-
inputSchema: tool.inputSchema ? (0,
|
|
1403
|
+
inputSchema: tool.inputSchema ? (0, import_axl5.zodToJsonSchema)(tool.inputSchema) : {},
|
|
1363
1404
|
sensitive: tool.sensitive,
|
|
1364
1405
|
requireApproval: tool.requireApproval,
|
|
1365
1406
|
retry: tool.retry,
|
|
@@ -1747,10 +1788,14 @@ function createEvalRoutes(connMgr, evalLoader) {
|
|
|
1747
1788
|
const runtime = c.get("runtime");
|
|
1748
1789
|
const redactOn = runtime.isRedactEnabled();
|
|
1749
1790
|
const body = await c.req.json();
|
|
1791
|
+
const MAX_POOLED_RUNS = 25;
|
|
1750
1792
|
const validateIdParam = (v, name) => {
|
|
1751
1793
|
if (typeof v === "string") return v === "" ? `${name} must be non-empty` : null;
|
|
1752
1794
|
if (Array.isArray(v)) {
|
|
1753
1795
|
if (v.length === 0) return `${name} must be a non-empty array`;
|
|
1796
|
+
if (v.length > MAX_POOLED_RUNS) {
|
|
1797
|
+
return `${name} may contain at most ${MAX_POOLED_RUNS} ids (pooled comparison)`;
|
|
1798
|
+
}
|
|
1754
1799
|
for (const elem of v) {
|
|
1755
1800
|
if (typeof elem !== "string" || elem === "") {
|
|
1756
1801
|
return `${name} array must contain only non-empty strings`;
|
|
@@ -1919,32 +1964,50 @@ function createPlaygroundRoutes(connMgr) {
|
|
|
1919
1964
|
);
|
|
1920
1965
|
}
|
|
1921
1966
|
const sessionId = body.sessionId ?? `playground-${Date.now()}`;
|
|
1922
|
-
const executionId = `playground-${sessionId}-${Date.now()}`;
|
|
1923
1967
|
const store = runtime.getStateStore();
|
|
1924
1968
|
const history = await store.getSession(sessionId);
|
|
1925
1969
|
history.push({ role: "user", content: body.message });
|
|
1926
1970
|
const redactOn = runtime.isRedactEnabled();
|
|
1927
|
-
const
|
|
1971
|
+
const ctx = runtime.createContext({ sessionHistory: history });
|
|
1972
|
+
const executionId = ctx.executionId;
|
|
1973
|
+
const traceListener = (event) => {
|
|
1974
|
+
if (event.executionId !== executionId) return;
|
|
1928
1975
|
connMgr.broadcastWithWildcard(`execution:${executionId}`, redactStreamEvent(event, redactOn));
|
|
1929
1976
|
};
|
|
1930
|
-
|
|
1931
|
-
sessionHistory: history,
|
|
1932
|
-
onToken: (token) => {
|
|
1933
|
-
broadcast({ type: "token", data: token });
|
|
1934
|
-
}
|
|
1935
|
-
});
|
|
1977
|
+
runtime.on("trace", traceListener);
|
|
1936
1978
|
(async () => {
|
|
1979
|
+
let stepCounter = Number.MAX_SAFE_INTEGER - 1;
|
|
1980
|
+
const terminalFields = () => ({
|
|
1981
|
+
executionId,
|
|
1982
|
+
step: stepCounter++,
|
|
1983
|
+
timestamp: Date.now()
|
|
1984
|
+
});
|
|
1937
1985
|
try {
|
|
1938
1986
|
const result = await ctx.ask(agent, body.message);
|
|
1939
1987
|
const resultText = typeof result === "string" ? result : JSON.stringify(result);
|
|
1940
1988
|
history.push({ role: "assistant", content: resultText });
|
|
1941
1989
|
await store.saveSession(sessionId, history);
|
|
1942
|
-
|
|
1990
|
+
const doneEvent = {
|
|
1991
|
+
...terminalFields(),
|
|
1992
|
+
type: "done",
|
|
1993
|
+
data: { result: resultText }
|
|
1994
|
+
};
|
|
1995
|
+
connMgr.broadcastWithWildcard(
|
|
1996
|
+
`execution:${executionId}`,
|
|
1997
|
+
redactStreamEvent(doneEvent, redactOn)
|
|
1998
|
+
);
|
|
1943
1999
|
} catch (err) {
|
|
1944
|
-
|
|
2000
|
+
const errorEvent = {
|
|
2001
|
+
...terminalFields(),
|
|
1945
2002
|
type: "error",
|
|
1946
|
-
message: err instanceof Error ? err.message : String(err)
|
|
1947
|
-
}
|
|
2003
|
+
data: { message: err instanceof Error ? err.message : String(err) }
|
|
2004
|
+
};
|
|
2005
|
+
connMgr.broadcastWithWildcard(
|
|
2006
|
+
`execution:${executionId}`,
|
|
2007
|
+
redactStreamEvent(errorEvent, redactOn)
|
|
2008
|
+
);
|
|
2009
|
+
} finally {
|
|
2010
|
+
runtime.off("trace", traceListener);
|
|
1948
2011
|
}
|
|
1949
2012
|
})();
|
|
1950
2013
|
return c.json({
|
|
@@ -1992,7 +2055,7 @@ function createTraceStatsRoutes(aggregator) {
|
|
|
1992
2055
|
function createServer(options) {
|
|
1993
2056
|
const { runtime, staticRoot, basePath = "", readOnly = false } = options;
|
|
1994
2057
|
const app6 = new import_hono15.Hono();
|
|
1995
|
-
const connMgr = new ConnectionManager();
|
|
2058
|
+
const connMgr = new ConnectionManager(options.bufferCaps);
|
|
1996
2059
|
const windows = ["24h", "7d", "30d", "all"];
|
|
1997
2060
|
const costAggregator = new TraceAggregator({
|
|
1998
2061
|
runtime,
|
|
@@ -2086,12 +2149,20 @@ function createServer(options) {
|
|
|
2086
2149
|
api.route("/", createPlaygroundRoutes(connMgr));
|
|
2087
2150
|
app6.route("/api", api);
|
|
2088
2151
|
const traceListener = (event) => {
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2152
|
+
try {
|
|
2153
|
+
const traceEvent = event;
|
|
2154
|
+
const redacted = redactStreamEvent(traceEvent, runtime.isRedactEnabled());
|
|
2155
|
+
if (traceEvent.executionId) {
|
|
2156
|
+
connMgr.broadcastWithWildcard(`trace:${traceEvent.executionId}`, redacted);
|
|
2157
|
+
}
|
|
2158
|
+
if (traceEvent.type === "await_human") {
|
|
2159
|
+
connMgr.broadcast("decisions", redacted);
|
|
2160
|
+
}
|
|
2161
|
+
} catch (err) {
|
|
2162
|
+
console.error(
|
|
2163
|
+
"[axl-studio] trace listener threw; event dropped:",
|
|
2164
|
+
err instanceof Error ? err.message : String(err)
|
|
2165
|
+
);
|
|
2095
2166
|
}
|
|
2096
2167
|
};
|
|
2097
2168
|
runtime.on("trace", traceListener);
|