@axlsdk/studio 0.13.5 → 0.13.7

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/dist/cli.cjs CHANGED
@@ -63,11 +63,18 @@ async function errorHandler(c, next) {
63
63
  }
64
64
 
65
65
  // src/server/ws/connection-manager.ts
66
+ function isBufferedChannel(channel) {
67
+ return channel.startsWith("execution:");
68
+ }
69
+ var BUFFER_TTL_MS = 3e4;
70
+ var MAX_BUFFER_EVENTS = 500;
66
71
  var ConnectionManager = class {
67
72
  /** channel -> set of WS connections */
68
73
  channels = /* @__PURE__ */ new Map();
69
74
  /** ws -> set of subscribed channels (for cleanup) */
70
75
  connections = /* @__PURE__ */ new Map();
76
+ /** channel -> replay buffer for execution streams */
77
+ buffers = /* @__PURE__ */ new Map();
71
78
  maxConnections = 100;
72
79
  /** Register a new WS connection. */
73
80
  add(ws) {
@@ -90,7 +97,7 @@ var ConnectionManager = class {
90
97
  }
91
98
  this.connections.delete(ws);
92
99
  }
93
- /** Subscribe a connection to a channel. No-op if the connection was not added. */
100
+ /** Subscribe a connection to a channel. Replays buffered events for execution channels. */
94
101
  subscribe(ws, channel) {
95
102
  if (!this.connections.has(ws)) return;
96
103
  let subs = this.channels.get(channel);
@@ -100,6 +107,17 @@ var ConnectionManager = class {
100
107
  }
101
108
  subs.add(ws);
102
109
  this.connections.get(ws).add(channel);
110
+ const buffer = this.buffers.get(channel);
111
+ if (buffer) {
112
+ for (const msg of buffer.events) {
113
+ try {
114
+ ws.send(msg);
115
+ } catch {
116
+ this.remove(ws);
117
+ return;
118
+ }
119
+ }
120
+ }
103
121
  }
104
122
  /** Unsubscribe a connection from a channel. */
105
123
  unsubscribe(ws, channel) {
@@ -109,11 +127,30 @@ var ConnectionManager = class {
109
127
  }
110
128
  this.connections.get(ws)?.delete(channel);
111
129
  }
112
- /** Broadcast data to all subscribers of a channel. */
130
+ /** Broadcast data to all subscribers of a channel. Buffers events for execution channels. */
113
131
  broadcast(channel, data) {
132
+ const msg = JSON.stringify({ type: "event", channel, data });
133
+ if (isBufferedChannel(channel)) {
134
+ let buffer = this.buffers.get(channel);
135
+ if (!buffer) {
136
+ buffer = { events: [], complete: false };
137
+ this.buffers.set(channel, buffer);
138
+ }
139
+ const event = data;
140
+ const isTerminal = event.type === "done" || event.type === "error";
141
+ if (buffer.events.length < MAX_BUFFER_EVENTS || isTerminal) {
142
+ buffer.events.push(msg);
143
+ }
144
+ if (isTerminal) {
145
+ buffer.complete = true;
146
+ if (buffer.timer) clearTimeout(buffer.timer);
147
+ buffer.timer = setTimeout(() => {
148
+ this.buffers.delete(channel);
149
+ }, BUFFER_TTL_MS);
150
+ }
151
+ }
114
152
  const subs = this.channels.get(channel);
115
153
  if (!subs || subs.size === 0) return;
116
- const msg = JSON.stringify({ type: "event", channel, data });
117
154
  for (const ws of [...subs]) {
118
155
  try {
119
156
  ws.send(msg);
@@ -140,13 +177,17 @@ var ConnectionManager = class {
140
177
  }
141
178
  }
142
179
  }
143
- /** Close all connections and clear all state. Used during shutdown. */
180
+ /** Close all connections, clear all state and buffers. Used during shutdown. */
144
181
  closeAll() {
145
182
  for (const ws of this.connections.keys()) {
146
183
  ws.close?.();
147
184
  }
185
+ for (const buffer of this.buffers.values()) {
186
+ if (buffer.timer) clearTimeout(buffer.timer);
187
+ }
148
188
  this.connections.clear();
149
189
  this.channels.clear();
190
+ this.buffers.clear();
150
191
  }
151
192
  /** Get the number of active connections. */
152
193
  get connectionCount() {
@@ -351,15 +392,8 @@ function createWorkflowRoutes(connMgr) {
351
392
  const stream = runtime.stream(name, body.input ?? {}, { metadata: body.metadata });
352
393
  const executionId = `stream-${Date.now()}`;
353
394
  (async () => {
354
- try {
355
- for await (const event of stream) {
356
- connMgr.broadcastWithWildcard(`execution:${executionId}`, event);
357
- }
358
- } catch (err) {
359
- connMgr.broadcastWithWildcard(`execution:${executionId}`, {
360
- type: "error",
361
- message: err instanceof Error ? err.message : "Stream error"
362
- });
395
+ for await (const event of stream) {
396
+ connMgr.broadcastWithWildcard(`execution:${executionId}`, event);
363
397
  }
364
398
  })();
365
399
  return c.json({ ok: true, data: { executionId, streaming: true } });
@@ -440,15 +474,8 @@ function createSessionRoutes(connMgr) {
440
474
  const stream = await session.stream(body.workflow, body.message);
441
475
  const executionId = `session-${id}-${Date.now()}`;
442
476
  (async () => {
443
- try {
444
- for await (const event of stream) {
445
- connMgr.broadcastWithWildcard(`execution:${executionId}`, event);
446
- }
447
- } catch (err) {
448
- connMgr.broadcastWithWildcard(`execution:${executionId}`, {
449
- type: "error",
450
- message: err instanceof Error ? err.message : "Stream error"
451
- });
477
+ for await (const event of stream) {
478
+ connMgr.broadcastWithWildcard(`execution:${executionId}`, event);
452
479
  }
453
480
  })();
454
481
  return c.json({ ok: true, data: { executionId, streaming: true } });
@@ -730,8 +757,80 @@ function createEvalRoutes(evalLoader) {
730
757
  404
731
758
  );
732
759
  }
760
+ let runs = 1;
761
+ try {
762
+ const body = await c.req.json().catch(() => ({}));
763
+ if (typeof body.runs === "number" && Number.isFinite(body.runs) && body.runs > 1) {
764
+ runs = Math.min(Math.floor(body.runs), 25);
765
+ }
766
+ } catch {
767
+ }
768
+ try {
769
+ if (runs > 1) {
770
+ const { randomUUID } = await import("crypto");
771
+ const { aggregateRuns } = await import("@axlsdk/eval");
772
+ const runGroupId = randomUUID();
773
+ const results = [];
774
+ for (let r = 0; r < runs; r++) {
775
+ const result2 = await runtime.runRegisteredEval(name, {
776
+ metadata: { runGroupId, runIndex: r }
777
+ });
778
+ results.push(result2);
779
+ }
780
+ const typedResults = results;
781
+ const aggregate = aggregateRuns(typedResults);
782
+ const first = typedResults[0];
783
+ const result = { ...first, _multiRun: { aggregate, allRuns: typedResults } };
784
+ return c.json({ ok: true, data: result });
785
+ } else {
786
+ const result = await runtime.runRegisteredEval(name);
787
+ return c.json({ ok: true, data: result });
788
+ }
789
+ } catch (err) {
790
+ const message = err instanceof Error ? err.message : String(err);
791
+ return c.json({ ok: false, error: { code: "EVAL_ERROR", message } }, 400);
792
+ }
793
+ });
794
+ app7.post("/evals/:name/rescore", async (c) => {
795
+ if (evalLoader) await evalLoader();
796
+ const runtime = c.get("runtime");
797
+ const name = c.req.param("name");
798
+ const body = await c.req.json();
799
+ if (!body.resultId || typeof body.resultId !== "string") {
800
+ return c.json(
801
+ { ok: false, error: { code: "BAD_REQUEST", message: "resultId is required" } },
802
+ 400
803
+ );
804
+ }
805
+ const entry = runtime.getRegisteredEval(name);
806
+ if (!entry) {
807
+ return c.json(
808
+ { ok: false, error: { code: "NOT_FOUND", message: `Eval "${name}" not found` } },
809
+ 404
810
+ );
811
+ }
812
+ const history = await runtime.getEvalHistory();
813
+ const historyEntry = history.find((h) => h.id === body.resultId);
814
+ if (!historyEntry) {
815
+ return c.json(
816
+ { ok: false, error: { code: "NOT_FOUND", message: `Result "${body.resultId}" not found` } },
817
+ 404
818
+ );
819
+ }
733
820
  try {
734
- const result = await runtime.runRegisteredEval(name);
821
+ const { rescore } = await import("@axlsdk/eval");
822
+ const config = entry.config;
823
+ const result = await rescore(
824
+ historyEntry.data,
825
+ config.scorers,
826
+ runtime
827
+ );
828
+ await runtime.saveEvalResult({
829
+ id: result.id,
830
+ eval: name,
831
+ timestamp: Date.now(),
832
+ data: result
833
+ });
735
834
  return c.json({ ok: true, data: result });
736
835
  } catch (err) {
737
836
  const message = err instanceof Error ? err.message : String(err);
@@ -742,7 +841,7 @@ function createEvalRoutes(evalLoader) {
742
841
  const runtime = c.get("runtime");
743
842
  const body = await c.req.json();
744
843
  try {
745
- const result = await runtime.evalCompare(body.baseline, body.candidate);
844
+ const result = await runtime.evalCompare(body.baseline, body.candidate, body.options);
746
845
  return c.json({ ok: true, data: result });
747
846
  } catch (err) {
748
847
  const message = err instanceof Error ? err.message : String(err);
@@ -759,26 +858,57 @@ function createPlaygroundRoutes(connMgr) {
759
858
  app7.post("/playground/chat", async (c) => {
760
859
  const runtime = c.get("runtime");
761
860
  const body = await c.req.json();
762
- const workflowName = body.workflow ?? runtime.getWorkflowNames()[0];
763
- if (!workflowName) {
861
+ if (!body.message || typeof body.message !== "string" || !body.message.trim()) {
862
+ return c.json(
863
+ {
864
+ ok: false,
865
+ error: {
866
+ code: "INVALID_INPUT",
867
+ message: "message is required and must be a non-empty string"
868
+ }
869
+ },
870
+ 400
871
+ );
872
+ }
873
+ const agents = runtime.getAgents();
874
+ const agent = body.agent ? agents.find((a) => a._name === body.agent) : agents[0];
875
+ if (!agent) {
764
876
  return c.json(
765
- { ok: false, error: { code: "NO_WORKFLOW", message: "No workflows registered" } },
877
+ {
878
+ ok: false,
879
+ error: { code: "NO_AGENT", message: `Agent "${body.agent ?? ""}" not found` }
880
+ },
766
881
  400
767
882
  );
768
883
  }
769
884
  const sessionId = body.sessionId ?? `playground-${Date.now()}`;
770
- const session = runtime.session(sessionId);
771
- const stream = await session.stream(workflowName, body.message);
772
885
  const executionId = `playground-${sessionId}-${Date.now()}`;
886
+ const store = runtime.getStateStore();
887
+ const history = await store.getSession(sessionId);
888
+ history.push({ role: "user", content: body.message });
889
+ const ctx = runtime.createContext({
890
+ sessionHistory: history,
891
+ onToken: (token) => {
892
+ connMgr.broadcastWithWildcard(`execution:${executionId}`, {
893
+ type: "token",
894
+ data: token
895
+ });
896
+ }
897
+ });
773
898
  (async () => {
774
899
  try {
775
- for await (const event of stream) {
776
- connMgr.broadcastWithWildcard(`execution:${executionId}`, event);
777
- }
900
+ const result = await ctx.ask(agent, body.message);
901
+ const resultText = typeof result === "string" ? result : JSON.stringify(result);
902
+ history.push({ role: "assistant", content: resultText });
903
+ await store.saveSession(sessionId, history);
904
+ connMgr.broadcastWithWildcard(`execution:${executionId}`, {
905
+ type: "done",
906
+ data: resultText
907
+ });
778
908
  } catch (err) {
779
909
  connMgr.broadcastWithWildcard(`execution:${executionId}`, {
780
910
  type: "error",
781
- message: err instanceof Error ? err.message : "Stream error"
911
+ message: err instanceof Error ? err.message : String(err)
782
912
  });
783
913
  }
784
914
  })();
@@ -890,19 +1020,24 @@ function createServer(options) {
890
1020
  app7.use("/*", async (c, next) => {
891
1021
  const reqPath = c.req.path;
892
1022
  const resolved = basePath && reqPath.startsWith(basePath) ? reqPath.slice(basePath.length) || "/" : reqPath;
893
- if (resolved === "/" || resolved === "/index.html") {
1023
+ if (resolved === "/" || resolved === "/index.html" || resolved === "/ws") {
894
1024
  return next();
895
1025
  }
896
1026
  return staticHandler(c, next);
897
1027
  });
898
1028
  if (spaHtml) {
899
- app7.get("*", (c) => c.html(spaHtml));
1029
+ app7.get("*", async (c, next) => {
1030
+ const resolved = basePath && c.req.path.startsWith(basePath) ? c.req.path.slice(basePath.length) || "/" : c.req.path;
1031
+ if (resolved === "/ws") return next();
1032
+ return c.html(spaHtml);
1033
+ });
900
1034
  }
901
1035
  }
902
1036
  return {
903
1037
  app: app7,
904
1038
  connMgr,
905
1039
  costAggregator,
1040
+ /** Create WS handlers. Call before registering static/SPA routes are reached. */
906
1041
  createWsHandlers: () => createWsHandlers(connMgr),
907
1042
  traceListener
908
1043
  };