@axlsdk/studio 0.13.4 → 0.13.6

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 } });
@@ -759,26 +786,57 @@ function createPlaygroundRoutes(connMgr) {
759
786
  app7.post("/playground/chat", async (c) => {
760
787
  const runtime = c.get("runtime");
761
788
  const body = await c.req.json();
762
- const workflowName = body.workflow ?? runtime.getWorkflowNames()[0];
763
- if (!workflowName) {
789
+ if (!body.message || typeof body.message !== "string" || !body.message.trim()) {
790
+ return c.json(
791
+ {
792
+ ok: false,
793
+ error: {
794
+ code: "INVALID_INPUT",
795
+ message: "message is required and must be a non-empty string"
796
+ }
797
+ },
798
+ 400
799
+ );
800
+ }
801
+ const agents = runtime.getAgents();
802
+ const agent = body.agent ? agents.find((a) => a._name === body.agent) : agents[0];
803
+ if (!agent) {
764
804
  return c.json(
765
- { ok: false, error: { code: "NO_WORKFLOW", message: "No workflows registered" } },
805
+ {
806
+ ok: false,
807
+ error: { code: "NO_AGENT", message: `Agent "${body.agent ?? ""}" not found` }
808
+ },
766
809
  400
767
810
  );
768
811
  }
769
812
  const sessionId = body.sessionId ?? `playground-${Date.now()}`;
770
- const session = runtime.session(sessionId);
771
- const stream = await session.stream(workflowName, body.message);
772
813
  const executionId = `playground-${sessionId}-${Date.now()}`;
814
+ const store = runtime.getStateStore();
815
+ const history = await store.getSession(sessionId);
816
+ history.push({ role: "user", content: body.message });
817
+ const ctx = runtime.createContext({
818
+ sessionHistory: history,
819
+ onToken: (token) => {
820
+ connMgr.broadcastWithWildcard(`execution:${executionId}`, {
821
+ type: "token",
822
+ data: token
823
+ });
824
+ }
825
+ });
773
826
  (async () => {
774
827
  try {
775
- for await (const event of stream) {
776
- connMgr.broadcastWithWildcard(`execution:${executionId}`, event);
777
- }
828
+ const result = await ctx.ask(agent, body.message);
829
+ const resultText = typeof result === "string" ? result : JSON.stringify(result);
830
+ history.push({ role: "assistant", content: resultText });
831
+ await store.saveSession(sessionId, history);
832
+ connMgr.broadcastWithWildcard(`execution:${executionId}`, {
833
+ type: "done",
834
+ data: resultText
835
+ });
778
836
  } catch (err) {
779
837
  connMgr.broadcastWithWildcard(`execution:${executionId}`, {
780
838
  type: "error",
781
- message: err instanceof Error ? err.message : "Stream error"
839
+ message: err instanceof Error ? err.message : String(err)
782
840
  });
783
841
  }
784
842
  })();
@@ -890,19 +948,24 @@ function createServer(options) {
890
948
  app7.use("/*", async (c, next) => {
891
949
  const reqPath = c.req.path;
892
950
  const resolved = basePath && reqPath.startsWith(basePath) ? reqPath.slice(basePath.length) || "/" : reqPath;
893
- if (resolved === "/" || resolved === "/index.html") {
951
+ if (resolved === "/" || resolved === "/index.html" || resolved === "/ws") {
894
952
  return next();
895
953
  }
896
954
  return staticHandler(c, next);
897
955
  });
898
956
  if (spaHtml) {
899
- app7.get("*", (c) => c.html(spaHtml));
957
+ app7.get("*", async (c, next) => {
958
+ const resolved = basePath && c.req.path.startsWith(basePath) ? c.req.path.slice(basePath.length) || "/" : c.req.path;
959
+ if (resolved === "/ws") return next();
960
+ return c.html(spaHtml);
961
+ });
900
962
  }
901
963
  }
902
964
  return {
903
965
  app: app7,
904
966
  connMgr,
905
967
  costAggregator,
968
+ /** Create WS handlers. Call before registering static/SPA routes are reached. */
906
969
  createWsHandlers: () => createWsHandlers(connMgr),
907
970
  traceListener
908
971
  };