@axlsdk/studio 0.9.0 → 0.10.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 +174 -7
- package/dist/{chunk-EG74VI3M.js → chunk-RBTYI3TW.js} +142 -41
- package/dist/chunk-RBTYI3TW.js.map +1 -0
- package/dist/cli.cjs +152 -52
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/client/assets/{index-DDlRZgfC.js → index-jeUeToM_.js} +13 -13
- package/dist/client/index.html +2 -2
- package/dist/connection-manager-DbOgO_gK.d.cts +75 -0
- package/dist/connection-manager-DbOgO_gK.d.ts +75 -0
- package/dist/middleware.cjs +1058 -0
- package/dist/middleware.cjs.map +1 -0
- package/dist/middleware.d.cts +76 -0
- package/dist/middleware.d.ts +76 -0
- package/dist/middleware.js +173 -0
- package/dist/middleware.js.map +1 -0
- package/dist/server/index.cjs +140 -40
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +10 -62
- package/dist/server/index.d.ts +10 -62
- package/dist/server/index.js +1 -1
- package/package.json +16 -4
- package/dist/chunk-EG74VI3M.js.map +0 -1
package/dist/cli.cjs
CHANGED
|
@@ -24,13 +24,15 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/cli.ts
|
|
27
|
-
var
|
|
28
|
-
var
|
|
27
|
+
var import_node_path3 = require("path");
|
|
28
|
+
var import_node_fs3 = require("fs");
|
|
29
29
|
var import_node_url = require("url");
|
|
30
30
|
var import_node_server = require("@hono/node-server");
|
|
31
31
|
var import_node_ws = require("@hono/node-ws");
|
|
32
32
|
|
|
33
33
|
// src/server/index.ts
|
|
34
|
+
var import_node_fs = require("fs");
|
|
35
|
+
var import_node_path = require("path");
|
|
34
36
|
var import_hono12 = require("hono");
|
|
35
37
|
var import_cors = require("hono/cors");
|
|
36
38
|
var import_serve_static = require("@hono/node-server/serve-static");
|
|
@@ -67,8 +69,13 @@ var ConnectionManager = class {
|
|
|
67
69
|
channels = /* @__PURE__ */ new Map();
|
|
68
70
|
/** ws -> set of subscribed channels (for cleanup) */
|
|
69
71
|
connections = /* @__PURE__ */ new Map();
|
|
72
|
+
maxConnections = 100;
|
|
70
73
|
/** Register a new WS connection. */
|
|
71
74
|
add(ws) {
|
|
75
|
+
if (this.connections.size >= this.maxConnections) {
|
|
76
|
+
ws.close?.();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
72
79
|
this.connections.set(ws, /* @__PURE__ */ new Set());
|
|
73
80
|
}
|
|
74
81
|
/** Remove a WS connection and all its subscriptions. */
|
|
@@ -84,15 +91,16 @@ var ConnectionManager = class {
|
|
|
84
91
|
}
|
|
85
92
|
this.connections.delete(ws);
|
|
86
93
|
}
|
|
87
|
-
/** Subscribe a connection to a channel. */
|
|
94
|
+
/** Subscribe a connection to a channel. No-op if the connection was not added. */
|
|
88
95
|
subscribe(ws, channel) {
|
|
96
|
+
if (!this.connections.has(ws)) return;
|
|
89
97
|
let subs = this.channels.get(channel);
|
|
90
98
|
if (!subs) {
|
|
91
99
|
subs = /* @__PURE__ */ new Set();
|
|
92
100
|
this.channels.set(channel, subs);
|
|
93
101
|
}
|
|
94
102
|
subs.add(ws);
|
|
95
|
-
this.connections.get(ws)
|
|
103
|
+
this.connections.get(ws).add(channel);
|
|
96
104
|
}
|
|
97
105
|
/** Unsubscribe a connection from a channel. */
|
|
98
106
|
unsubscribe(ws, channel) {
|
|
@@ -133,6 +141,14 @@ var ConnectionManager = class {
|
|
|
133
141
|
}
|
|
134
142
|
}
|
|
135
143
|
}
|
|
144
|
+
/** Close all connections and clear all state. Used during shutdown. */
|
|
145
|
+
closeAll() {
|
|
146
|
+
for (const ws of this.connections.keys()) {
|
|
147
|
+
ws.close?.();
|
|
148
|
+
}
|
|
149
|
+
this.connections.clear();
|
|
150
|
+
this.channels.clear();
|
|
151
|
+
}
|
|
136
152
|
/** Get the number of active connections. */
|
|
137
153
|
get connectionCount() {
|
|
138
154
|
return this.connections.size;
|
|
@@ -143,6 +159,52 @@ var ConnectionManager = class {
|
|
|
143
159
|
}
|
|
144
160
|
};
|
|
145
161
|
|
|
162
|
+
// src/server/ws/protocol.ts
|
|
163
|
+
var VALID_CHANNEL_PREFIXES = ["execution:", "trace:"];
|
|
164
|
+
var VALID_EXACT_CHANNELS = ["costs", "decisions"];
|
|
165
|
+
var MAX_CHANNEL_LENGTH = 256;
|
|
166
|
+
function handleWsMessage(raw, socket, connMgr) {
|
|
167
|
+
if (raw.length > 65536) {
|
|
168
|
+
return JSON.stringify({ type: "error", message: "Message too large" });
|
|
169
|
+
}
|
|
170
|
+
let msg;
|
|
171
|
+
try {
|
|
172
|
+
msg = JSON.parse(raw);
|
|
173
|
+
} catch {
|
|
174
|
+
return JSON.stringify({ type: "error", message: "Invalid JSON" });
|
|
175
|
+
}
|
|
176
|
+
switch (msg.type) {
|
|
177
|
+
case "subscribe": {
|
|
178
|
+
const error = validateChannel(msg.channel);
|
|
179
|
+
if (error) return JSON.stringify({ type: "error", message: error });
|
|
180
|
+
connMgr.subscribe(socket, msg.channel);
|
|
181
|
+
return JSON.stringify({ type: "subscribed", channel: msg.channel });
|
|
182
|
+
}
|
|
183
|
+
case "unsubscribe": {
|
|
184
|
+
const error = validateChannel(msg.channel);
|
|
185
|
+
if (error) return JSON.stringify({ type: "error", message: error });
|
|
186
|
+
connMgr.unsubscribe(socket, msg.channel);
|
|
187
|
+
return JSON.stringify({ type: "unsubscribed", channel: msg.channel });
|
|
188
|
+
}
|
|
189
|
+
case "ping":
|
|
190
|
+
return JSON.stringify({ type: "pong" });
|
|
191
|
+
default:
|
|
192
|
+
return JSON.stringify({ type: "error", message: "Unknown message type" });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function validateChannel(channel) {
|
|
196
|
+
if (typeof channel !== "string" || !channel) {
|
|
197
|
+
return "Missing or invalid channel";
|
|
198
|
+
}
|
|
199
|
+
if (channel.length > MAX_CHANNEL_LENGTH) {
|
|
200
|
+
return `Channel name exceeds ${MAX_CHANNEL_LENGTH} characters`;
|
|
201
|
+
}
|
|
202
|
+
if (!VALID_EXACT_CHANNELS.includes(channel) && !VALID_CHANNEL_PREFIXES.some((p) => channel.startsWith(p))) {
|
|
203
|
+
return `Invalid channel: ${channel}`;
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
146
208
|
// src/server/ws/handler.ts
|
|
147
209
|
function createWsHandlers(connMgr) {
|
|
148
210
|
return {
|
|
@@ -150,37 +212,8 @@ function createWsHandlers(connMgr) {
|
|
|
150
212
|
connMgr.add(ws);
|
|
151
213
|
},
|
|
152
214
|
onMessage(event, ws) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
msg = JSON.parse(String(event.data));
|
|
156
|
-
} catch {
|
|
157
|
-
const err = { type: "error", message: "Invalid JSON" };
|
|
158
|
-
ws.send(JSON.stringify(err));
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
switch (msg.type) {
|
|
162
|
-
case "subscribe": {
|
|
163
|
-
connMgr.subscribe(ws, msg.channel);
|
|
164
|
-
const reply = { type: "subscribed", channel: msg.channel };
|
|
165
|
-
ws.send(JSON.stringify(reply));
|
|
166
|
-
break;
|
|
167
|
-
}
|
|
168
|
-
case "unsubscribe": {
|
|
169
|
-
connMgr.unsubscribe(ws, msg.channel);
|
|
170
|
-
const reply = { type: "unsubscribed", channel: msg.channel };
|
|
171
|
-
ws.send(JSON.stringify(reply));
|
|
172
|
-
break;
|
|
173
|
-
}
|
|
174
|
-
case "ping": {
|
|
175
|
-
const reply = { type: "pong" };
|
|
176
|
-
ws.send(JSON.stringify(reply));
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
default: {
|
|
180
|
-
const err = { type: "error", message: `Unknown message type` };
|
|
181
|
-
ws.send(JSON.stringify(err));
|
|
182
|
-
}
|
|
183
|
-
}
|
|
215
|
+
const reply = handleWsMessage(String(event.data), ws, connMgr);
|
|
216
|
+
if (reply) ws.send(reply);
|
|
184
217
|
},
|
|
185
218
|
onClose(_event, ws) {
|
|
186
219
|
connMgr.remove(ws);
|
|
@@ -751,16 +784,48 @@ function createPlaygroundRoutes(connMgr) {
|
|
|
751
784
|
|
|
752
785
|
// src/server/index.ts
|
|
753
786
|
function createServer(options) {
|
|
754
|
-
const { runtime, staticRoot } = options;
|
|
787
|
+
const { runtime, staticRoot, basePath = "", readOnly = false } = options;
|
|
755
788
|
const app8 = new import_hono12.Hono();
|
|
756
789
|
const connMgr = new ConnectionManager();
|
|
757
790
|
const costAggregator = new CostAggregator(connMgr);
|
|
758
|
-
|
|
791
|
+
if (options.cors !== false) {
|
|
792
|
+
app8.use("*", (0, import_cors.cors)());
|
|
793
|
+
}
|
|
759
794
|
app8.use("*", errorHandler);
|
|
760
795
|
app8.use("*", async (c, next) => {
|
|
761
796
|
c.set("runtime", runtime);
|
|
762
797
|
await next();
|
|
763
798
|
});
|
|
799
|
+
if (readOnly) {
|
|
800
|
+
const blocked = [
|
|
801
|
+
"POST /api/workflows",
|
|
802
|
+
"POST /api/executions",
|
|
803
|
+
"POST /api/sessions",
|
|
804
|
+
"DELETE /api/sessions",
|
|
805
|
+
"PUT /api/memory",
|
|
806
|
+
"DELETE /api/memory",
|
|
807
|
+
"POST /api/decisions",
|
|
808
|
+
"POST /api/costs",
|
|
809
|
+
"POST /api/tools",
|
|
810
|
+
"POST /api/evals",
|
|
811
|
+
"POST /api/playground"
|
|
812
|
+
];
|
|
813
|
+
app8.use("/api/*", async (c, next) => {
|
|
814
|
+
const apiIdx = c.req.path.indexOf("/api/");
|
|
815
|
+
const apiPath = apiIdx >= 0 ? c.req.path.slice(apiIdx) : c.req.path;
|
|
816
|
+
const key = `${c.req.method} ${apiPath}`;
|
|
817
|
+
if (blocked.some((b) => key.startsWith(b))) {
|
|
818
|
+
return c.json(
|
|
819
|
+
{
|
|
820
|
+
ok: false,
|
|
821
|
+
error: { code: "READ_ONLY", message: "Studio is mounted in read-only mode" }
|
|
822
|
+
},
|
|
823
|
+
405
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
await next();
|
|
827
|
+
});
|
|
828
|
+
}
|
|
764
829
|
const api = new import_hono12.Hono();
|
|
765
830
|
api.route("/", health_default);
|
|
766
831
|
api.route("/", createWorkflowRoutes(connMgr));
|
|
@@ -774,7 +839,7 @@ function createServer(options) {
|
|
|
774
839
|
api.route("/", evals_default);
|
|
775
840
|
api.route("/", createPlaygroundRoutes(connMgr));
|
|
776
841
|
app8.route("/api", api);
|
|
777
|
-
|
|
842
|
+
const traceListener = (event) => {
|
|
778
843
|
const traceEvent = event;
|
|
779
844
|
if (traceEvent.executionId) {
|
|
780
845
|
connMgr.broadcastWithWildcard(`trace:${traceEvent.executionId}`, traceEvent);
|
|
@@ -783,12 +848,47 @@ function createServer(options) {
|
|
|
783
848
|
if (traceEvent.type === "await_human") {
|
|
784
849
|
connMgr.broadcast("decisions", traceEvent);
|
|
785
850
|
}
|
|
786
|
-
}
|
|
851
|
+
};
|
|
852
|
+
runtime.on("trace", traceListener);
|
|
787
853
|
if (staticRoot) {
|
|
788
|
-
app8.use(
|
|
789
|
-
|
|
854
|
+
app8.use(
|
|
855
|
+
"/*",
|
|
856
|
+
(0, import_serve_static.serveStatic)({
|
|
857
|
+
root: staticRoot,
|
|
858
|
+
rewriteRequestPath: basePath ? (path) => path.startsWith(basePath) ? path.slice(basePath.length) || "/" : path : void 0
|
|
859
|
+
})
|
|
860
|
+
);
|
|
861
|
+
if (basePath) {
|
|
862
|
+
const indexPath = (0, import_node_path.resolve)(staticRoot, "index.html");
|
|
863
|
+
if (!(0, import_node_fs.existsSync)(indexPath)) {
|
|
864
|
+
console.warn(`[axl-studio] index.html not found at ${indexPath}`);
|
|
865
|
+
} else {
|
|
866
|
+
const indexHtml = (0, import_node_fs.readFileSync)(indexPath, "utf-8");
|
|
867
|
+
const safeBasePath = JSON.stringify(basePath).replace(/</g, "\\u003c");
|
|
868
|
+
const injectedHtml = indexHtml.replace(
|
|
869
|
+
"</head>",
|
|
870
|
+
`<base href="${basePath}/">
|
|
871
|
+
<script>window.__AXL_STUDIO_BASE__=${safeBasePath}</script>
|
|
872
|
+
</head>`
|
|
873
|
+
);
|
|
874
|
+
if (injectedHtml === indexHtml) {
|
|
875
|
+
console.warn(
|
|
876
|
+
"[axl-studio] Could not inject basePath into index.html \u2014 </head> tag not found. The SPA may not route correctly."
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
app8.get("*", (c) => c.html(injectedHtml));
|
|
880
|
+
}
|
|
881
|
+
} else {
|
|
882
|
+
app8.get("*", (0, import_serve_static.serveStatic)({ root: staticRoot, path: "/index.html" }));
|
|
883
|
+
}
|
|
790
884
|
}
|
|
791
|
-
return {
|
|
885
|
+
return {
|
|
886
|
+
app: app8,
|
|
887
|
+
connMgr,
|
|
888
|
+
costAggregator,
|
|
889
|
+
createWsHandlers: () => createWsHandlers(connMgr),
|
|
890
|
+
traceListener
|
|
891
|
+
};
|
|
792
892
|
}
|
|
793
893
|
|
|
794
894
|
// src/resolve-runtime.ts
|
|
@@ -798,8 +898,8 @@ function resolveRuntime(mod) {
|
|
|
798
898
|
}
|
|
799
899
|
|
|
800
900
|
// src/cli-utils.ts
|
|
801
|
-
var
|
|
802
|
-
var
|
|
901
|
+
var import_node_path2 = require("path");
|
|
902
|
+
var import_node_fs2 = require("fs");
|
|
803
903
|
var CONFIG_CANDIDATES = [
|
|
804
904
|
"axl.config.mts",
|
|
805
905
|
"axl.config.ts",
|
|
@@ -808,8 +908,8 @@ var CONFIG_CANDIDATES = [
|
|
|
808
908
|
];
|
|
809
909
|
function findConfig(cwd) {
|
|
810
910
|
for (const name of CONFIG_CANDIDATES) {
|
|
811
|
-
const p = (0,
|
|
812
|
-
if ((0,
|
|
911
|
+
const p = (0, import_node_path2.resolve)(cwd, name);
|
|
912
|
+
if ((0, import_node_fs2.existsSync)(p)) return p;
|
|
813
913
|
}
|
|
814
914
|
return void 0;
|
|
815
915
|
}
|
|
@@ -843,7 +943,7 @@ function parseArgs(argv) {
|
|
|
843
943
|
return result;
|
|
844
944
|
}
|
|
845
945
|
function needsEsmForcing(configPath) {
|
|
846
|
-
const ext = (0,
|
|
946
|
+
const ext = (0, import_node_path2.extname)(configPath);
|
|
847
947
|
return ext === ".ts" || ext === ".tsx";
|
|
848
948
|
}
|
|
849
949
|
function needsTsxLoader(configPath) {
|
|
@@ -881,8 +981,8 @@ Tip: Use .mts for configs with top-level await or in projects without "type": "m
|
|
|
881
981
|
}
|
|
882
982
|
let configPath;
|
|
883
983
|
if (args.config) {
|
|
884
|
-
configPath = (0,
|
|
885
|
-
if (!(0,
|
|
984
|
+
configPath = (0, import_node_path3.resolve)(process.cwd(), args.config);
|
|
985
|
+
if (!(0, import_node_fs3.existsSync)(configPath)) {
|
|
886
986
|
console.error(`Config file not found: ${configPath}`);
|
|
887
987
|
process.exit(1);
|
|
888
988
|
}
|
|
@@ -950,7 +1050,7 @@ Tip: Use .mts for configs with top-level await or in projects without "type": "m
|
|
|
950
1050
|
}
|
|
951
1051
|
console.log(`[axl-studio] Loading config from ${configPath}`);
|
|
952
1052
|
let runtime;
|
|
953
|
-
const ext = (0,
|
|
1053
|
+
const ext = (0, import_node_path3.extname)(configPath);
|
|
954
1054
|
try {
|
|
955
1055
|
const mod = await import((0, import_node_url.pathToFileURL)(configPath).href);
|
|
956
1056
|
runtime = resolveRuntime(mod);
|
|
@@ -987,8 +1087,8 @@ Tip: Use .mts for configs with top-level await or in projects without "type": "m
|
|
|
987
1087
|
console.error(`Failed to load config:`, err);
|
|
988
1088
|
process.exit(1);
|
|
989
1089
|
}
|
|
990
|
-
const staticRoot = (0,
|
|
991
|
-
const hasStaticAssets = (0,
|
|
1090
|
+
const staticRoot = (0, import_node_path3.resolve)(import_meta.dirname ?? __dirname, "client");
|
|
1091
|
+
const hasStaticAssets = (0, import_node_fs3.existsSync)((0, import_node_path3.resolve)(staticRoot, "index.html"));
|
|
992
1092
|
const { app: app8, createWsHandlers: createWsHandlers2 } = createServer({
|
|
993
1093
|
runtime,
|
|
994
1094
|
staticRoot: hasStaticAssets ? staticRoot : void 0
|