@axlsdk/studio 0.10.0 → 0.10.2

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.
@@ -1,31 +1,204 @@
1
1
  import {
2
2
  createServer,
3
3
  handleWsMessage
4
- } from "./chunk-RBTYI3TW.js";
4
+ } from "./chunk-GKIPF45K.js";
5
5
 
6
6
  // src/middleware.ts
7
- import { resolve, dirname } from "path";
7
+ import { resolve as resolve2, dirname as dirname2 } from "path";
8
8
  import { existsSync } from "fs";
9
9
  import { fileURLToPath } from "url";
10
10
  import { getRequestListener } from "@hono/node-server";
11
11
  import { WebSocketServer } from "ws";
12
+
13
+ // src/eval-loader.ts
14
+ import { resolve, relative, dirname, basename } from "path";
15
+ import { readdirSync, statSync } from "fs";
16
+ import { pathToFileURL } from "url";
17
+ function createEvalLoader(config, runtime, cwd) {
18
+ let loadPromise;
19
+ const { patterns, conditions } = normalizeConfig(config);
20
+ const baseCwd = cwd ?? process.cwd();
21
+ return () => {
22
+ if (!loadPromise) {
23
+ loadPromise = loadEvalFiles(patterns, conditions, baseCwd, runtime).catch((err) => {
24
+ loadPromise = void 0;
25
+ throw err;
26
+ });
27
+ }
28
+ return loadPromise;
29
+ };
30
+ }
31
+ async function loadEvalFiles(patterns, conditions, cwd, runtime) {
32
+ if (conditions.length > 0) {
33
+ await registerConditions(conditions);
34
+ }
35
+ const files = resolvePatterns(patterns, cwd);
36
+ if (files.length === 0) {
37
+ console.warn(`[axl-studio] No eval files found matching: ${patterns.join(", ")}`);
38
+ return;
39
+ }
40
+ if (files.some((f) => /\.[mc]?tsx?$/.test(f))) {
41
+ await ensureTsxLoader();
42
+ }
43
+ for (const file of files) {
44
+ try {
45
+ const mod = await import(pathToFileURL(file).href);
46
+ const evalConfig = mod.default ?? mod.config ?? mod;
47
+ if (!evalConfig.workflow || !evalConfig.dataset || !evalConfig.scorers) {
48
+ console.warn(
49
+ `[axl-studio] Skipping ${file}: not a valid eval config (missing workflow, dataset, or scorers)`
50
+ );
51
+ continue;
52
+ }
53
+ const name = deriveEvalName(file, cwd);
54
+ if (runtime.getRegisteredEval(name)) {
55
+ console.warn(
56
+ `[axl-studio] Eval name "${name}" from ${file} collides with an already-registered eval \u2014 overwriting`
57
+ );
58
+ }
59
+ runtime.registerEval(name, evalConfig, mod.executeWorkflow);
60
+ } catch (err) {
61
+ const msg = err instanceof Error ? err.message : String(err);
62
+ console.warn(`[axl-studio] Failed to load eval ${file}: ${msg}`);
63
+ }
64
+ }
65
+ }
66
+ function normalizeConfig(config) {
67
+ if (typeof config === "string") {
68
+ return { patterns: [config], conditions: [] };
69
+ }
70
+ if (Array.isArray(config)) {
71
+ return { patterns: config, conditions: [] };
72
+ }
73
+ const files = typeof config.files === "string" ? [config.files] : config.files;
74
+ return { patterns: files, conditions: config.conditions ?? [] };
75
+ }
76
+ function deriveEvalName(filePath, cwd) {
77
+ const rel = relative(cwd, filePath);
78
+ const normalized = rel.replace(/\\/g, "/");
79
+ if (normalized.startsWith("../")) {
80
+ const base = basename(filePath);
81
+ const stripped = base.replace(/\.eval\.[mc]?[jt]sx?$/, "");
82
+ return stripped !== base ? stripped : base.replace(/\.[mc]?[jt]sx?$/, "") || base;
83
+ }
84
+ const withoutEval = normalized.replace(/\.eval\.[mc]?[jt]sx?$/, "");
85
+ if (withoutEval !== normalized) return withoutEval;
86
+ const withoutExt = normalized.replace(/\.[mc]?[jt]sx?$/, "");
87
+ return withoutExt || normalized;
88
+ }
89
+ function resolvePatterns(patterns, cwd) {
90
+ const files = [];
91
+ const seen = /* @__PURE__ */ new Set();
92
+ for (const pattern of patterns) {
93
+ const resolved = pattern.includes("*") ? expandGlob(pattern, cwd) : [resolve(cwd, pattern)];
94
+ for (const file of resolved) {
95
+ if (!seen.has(file)) {
96
+ seen.add(file);
97
+ files.push(file);
98
+ }
99
+ }
100
+ }
101
+ return files;
102
+ }
103
+ function expandGlob(pattern, cwd) {
104
+ if (pattern.includes("**/")) {
105
+ const sepIdx = pattern.indexOf("**/");
106
+ const baseDir = resolve(cwd, pattern.slice(0, sepIdx) || ".");
107
+ const fileGlob2 = pattern.slice(sepIdx + 3) || "*";
108
+ return findFiles(baseDir, fileGlob2, true);
109
+ }
110
+ const dir = resolve(cwd, dirname(pattern));
111
+ const fileGlob = basename(pattern);
112
+ return findFiles(dir, fileGlob, false);
113
+ }
114
+ var MAX_DEPTH = 20;
115
+ function findFiles(dir, fileGlob, recursive, depth = 0) {
116
+ if (depth > MAX_DEPTH) return [];
117
+ const matcher = globToRegex(fileGlob);
118
+ const results = [];
119
+ try {
120
+ const entries = readdirSync(dir);
121
+ for (const entry of entries) {
122
+ const full = resolve(dir, entry);
123
+ try {
124
+ const stat = statSync(full);
125
+ if (stat.isFile() && matcher.test(entry)) {
126
+ results.push(full);
127
+ } else if (stat.isDirectory() && recursive) {
128
+ results.push(...findFiles(full, fileGlob, true, depth + 1));
129
+ }
130
+ } catch {
131
+ }
132
+ }
133
+ } catch {
134
+ }
135
+ return results;
136
+ }
137
+ function globToRegex(glob) {
138
+ const escaped = glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
139
+ return new RegExp(`^${escaped}$`);
140
+ }
141
+ var tsxRegistered = false;
142
+ async function ensureTsxLoader() {
143
+ if (tsxRegistered) return;
144
+ tsxRegistered = true;
145
+ let loaded = false;
146
+ try {
147
+ const tsxEsm = await import("tsx/esm/api");
148
+ tsxEsm.register();
149
+ loaded = true;
150
+ } catch {
151
+ }
152
+ try {
153
+ const tsxCjs = await import("tsx/cjs/api");
154
+ tsxCjs.register();
155
+ loaded = true;
156
+ } catch {
157
+ }
158
+ if (!loaded) {
159
+ console.warn(
160
+ "[axl-studio] Warning: tsx is not installed. TypeScript eval files require tsx.\n Install it with: npm install -D tsx"
161
+ );
162
+ }
163
+ }
164
+ async function registerConditions(conditions) {
165
+ try {
166
+ const nodeModule = await import("module");
167
+ const hookCode = [
168
+ `const extra = ${JSON.stringify(conditions)};`,
169
+ `export async function resolve(specifier, context, nextResolve) {`,
170
+ ` return nextResolve(specifier, {`,
171
+ ` ...context,`,
172
+ ` conditions: [...new Set([...context.conditions, ...extra])],`,
173
+ ` });`,
174
+ `}`
175
+ ].join("\n");
176
+ nodeModule.register(`data:text/javascript,${encodeURIComponent(hookCode)}`);
177
+ } catch {
178
+ console.warn("[axl-studio] Warning: import conditions require Node.js 20.6+");
179
+ }
180
+ }
181
+
182
+ // src/middleware.ts
12
183
  function createStudioMiddleware(options) {
13
184
  const { runtime, serveClient = true, verifyUpgrade, readOnly = false } = options;
14
185
  const basePath = normalizeBasePath(options.basePath);
15
186
  const staticRoot = serveClient ? resolveClientDist() : void 0;
16
187
  if (serveClient && !staticRoot) {
17
- const dir = import.meta.dirname ?? dirname(fileURLToPath(import.meta.url));
188
+ const dir = import.meta.dirname ?? dirname2(fileURLToPath(import.meta.url));
18
189
  console.warn(
19
- `[axl-studio] serveClient is true but no pre-built client found at ${resolve(dir, "client")}. Studio UI will not be available. Set serveClient: false to suppress this warning.`
190
+ `[axl-studio] serveClient is true but no pre-built client found at ${resolve2(dir, "client")}. Studio UI will not be available. Set serveClient: false to suppress this warning.`
20
191
  );
21
192
  }
193
+ const evalLoader = options.evals ? createEvalLoader(options.evals, runtime) : void 0;
22
194
  const { app, connMgr, traceListener } = createServer({
23
195
  runtime,
24
196
  staticRoot,
25
197
  basePath,
26
198
  readOnly,
27
- cors: false
199
+ cors: false,
28
200
  // Host framework owns CORS policy
201
+ evalLoader
29
202
  });
30
203
  if (process.env.NODE_ENV === "production" && !verifyUpgrade) {
31
204
  console.warn(
@@ -162,9 +335,9 @@ function normalizeBasePath(raw) {
162
335
  return normalized;
163
336
  }
164
337
  function resolveClientDist() {
165
- const dir = import.meta.dirname ?? dirname(fileURLToPath(import.meta.url));
166
- const candidate = resolve(dir, "client");
167
- return existsSync(resolve(candidate, "index.html")) ? candidate : void 0;
338
+ const dir = import.meta.dirname ?? dirname2(fileURLToPath(import.meta.url));
339
+ const candidate = resolve2(dir, "client");
340
+ return existsSync(resolve2(candidate, "index.html")) ? candidate : void 0;
168
341
  }
169
342
  export {
170
343
  createStudioMiddleware,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/middleware.ts"],"sourcesContent":["import { resolve, dirname } from 'node:path';\nimport { existsSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { getRequestListener } from '@hono/node-server';\nimport { WebSocketServer } from 'ws';\nimport { createServer } from './server/index.js';\nimport { handleWsMessage } from './server/ws/protocol.js';\nimport type { AxlRuntime } from '@axlsdk/axl';\nimport type { IncomingMessage, ServerResponse, Server } from 'node:http';\n\nexport type StudioMiddlewareOptions = {\n /** The AxlRuntime instance to observe and control. */\n runtime: AxlRuntime;\n\n /**\n * URL path prefix where Studio is mounted.\n * Must match the mount path in your framework.\n *\n * Must start with '/' when non-empty. Trailing slashes are stripped.\n * Only URL-safe characters allowed: [a-zA-Z0-9/_-]\n *\n * @example '/studio'\n * @example '/admin/studio'\n * @example '' — mounted at root (default)\n */\n basePath?: string;\n\n /**\n * Serve the pre-built Studio SPA.\n * Set to false if serving the client from a CDN or separate build.\n * @default true\n */\n serveClient?: boolean;\n\n /**\n * Verify a WebSocket upgrade request before completing the handshake.\n * Return true to allow, false to reject. Throw to reject with an error.\n *\n * IMPORTANT: WebSocket upgrades bypass Express/Fastify/Koa middleware.\n * If your HTTP routes are behind auth middleware, WS connections are NOT\n * automatically protected. Use this callback to enforce authentication\n * on WebSocket connections.\n */\n verifyUpgrade?: (req: IncomingMessage) => boolean | Promise<boolean>;\n\n /**\n * Disable all mutating endpoints (execute, test, send, delete, resolve).\n * When true, Studio is observation-only.\n * @default false\n */\n readOnly?: boolean;\n};\n\n/**\n * Minimal contract a WebSocket connection must satisfy.\n * Matches the `ws` library API (de facto standard in Node.js).\n */\nexport interface StudioWebSocket {\n send(data: string): void;\n close(): void;\n on(event: 'message', fn: (data: string | Buffer) => void): void;\n on(event: 'close', fn: () => void): void;\n on(event: 'error', fn: (err: Error) => void): void;\n}\n\n// Re-export for Hono-in-Hono consumers\nexport { handleWsMessage } from './server/ws/protocol.js';\n\nexport type StudioMiddleware = ReturnType<typeof createStudioMiddleware>;\n\nexport function createStudioMiddleware(options: StudioMiddlewareOptions) {\n const { runtime, serveClient = true, verifyUpgrade, readOnly = false } = options;\n\n // Normalize basePath: strip trailing slashes, validate format\n const basePath = normalizeBasePath(options.basePath);\n\n // Resolve pre-built SPA assets from this package's dist/\n const staticRoot = serveClient ? resolveClientDist() : undefined;\n\n if (serveClient && !staticRoot) {\n const dir = import.meta.dirname ?? dirname(fileURLToPath(import.meta.url));\n console.warn(\n '[axl-studio] serveClient is true but no pre-built client found at ' +\n `${resolve(dir, 'client')}. Studio UI will not be available. ` +\n 'Set serveClient: false to suppress this warning.',\n );\n }\n\n const { app, connMgr, traceListener } = createServer({\n runtime,\n staticRoot,\n basePath,\n readOnly,\n cors: false, // Host framework owns CORS policy\n });\n\n // Log production safety warning\n if (process.env.NODE_ENV === 'production' && !verifyUpgrade) {\n console.warn(\n '[axl-studio] WARNING: Studio middleware mounted in production without verifyUpgrade. ' +\n 'WebSocket connections are not authenticated. All registered workflows, tools, and ' +\n 'agents are accessible. See https://axlsdk.com/docs/studio/security',\n );\n }\n\n // Convert Hono app → Node.js (req, res) handler.\n // overrideGlobalObjects: false prevents replacing global.Request and\n // global.Response, which could break the host application.\n const listener = getRequestListener(app.fetch, {\n overrideGlobalObjects: false,\n });\n\n let closed = false;\n\n function handler(req: IncomingMessage, res: ServerResponse) {\n if (closed) {\n res.writeHead(503, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n ok: false,\n error: { code: 'CLOSED', message: 'Studio middleware has been shut down' },\n }),\n );\n return;\n }\n // listener returns Promise<void>. Catch async errors to prevent\n // unhandled rejections from crashing the host process.\n listener(req, res).catch((err) => {\n console.error('[axl-studio] Unhandled error in request handler:', err);\n if (!res.headersSent) {\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n ok: false,\n error: { code: 'INTERNAL_ERROR', message: 'Internal server error' },\n }),\n );\n }\n });\n }\n\n // Handle an individual WebSocket using the Studio protocol.\n // Adapts any StudioWebSocket to ConnectionManager's internal BroadcastTarget.\n function handleWebSocket(ws: StudioWebSocket) {\n if (closed) {\n ws.close();\n return;\n }\n const socket = {\n send: (data: string) => ws.send(data),\n close: () => ws.close(),\n };\n connMgr.add(socket);\n\n ws.on('message', (raw) => {\n const reply = handleWsMessage(String(raw), socket, connMgr);\n if (reply) ws.send(reply);\n });\n\n ws.on('close', () => connMgr.remove(socket));\n ws.on('error', () => connMgr.remove(socket));\n }\n\n // Internal WebSocketServer — created lazily by upgradeWebSocket()\n let wss: InstanceType<typeof WebSocketServer> | undefined;\n // References for cleanup: the upgrade handler and server it's attached to\n let upgradeHandler: ((...args: any[]) => void) | undefined;\n let serverRef: Server | undefined;\n\n // Convenience: attach WS handling to an http.Server.\n function upgradeWebSocket(server: Server, path?: string) {\n if (wss) {\n throw new Error(\n '[axl-studio] upgradeWebSocket() has already been called. ' +\n 'Call close() first if you need to re-attach.',\n );\n }\n\n const wsPath = path ?? (basePath ? `${basePath}/ws` : '/ws');\n\n wss = new WebSocketServer({ noServer: true });\n serverRef = server;\n\n upgradeHandler = async (req: IncomingMessage, socket: any, head: Buffer) => {\n // Match path, ignoring query string\n const pathname = new URL(req.url!, `http://${req.headers.host}`).pathname;\n if (pathname !== wsPath) return; // Let other upgrade handlers run\n\n // Apply auth verification\n if (verifyUpgrade) {\n try {\n const allowed = await verifyUpgrade(req);\n if (!allowed) {\n socket.write('HTTP/1.1 401 Unauthorized\\r\\n\\r\\n');\n socket.destroy();\n return;\n }\n } catch {\n socket.write('HTTP/1.1 403 Forbidden\\r\\n\\r\\n');\n socket.destroy();\n return;\n }\n }\n\n // Guard against close() being called during async verifyUpgrade\n if (!wss) {\n socket.destroy();\n return;\n }\n\n wss.handleUpgrade(req, socket, head, (ws) => {\n handleWebSocket(ws);\n });\n };\n\n server.on('upgrade', upgradeHandler);\n }\n\n // Cleanup function for lifecycle management.\n function close() {\n closed = true;\n\n // Close all WebSocket connections\n connMgr.closeAll();\n\n // Remove the upgrade listener from the server before closing WSS\n if (upgradeHandler && serverRef) {\n serverRef.removeListener('upgrade', upgradeHandler);\n upgradeHandler = undefined;\n serverRef = undefined;\n }\n\n // Shut down the internal WebSocketServer if one was created\n if (wss) {\n wss.close();\n wss = undefined;\n }\n\n // Remove only our trace event listener from the runtime\n if (traceListener) {\n runtime.removeListener('trace', traceListener);\n }\n }\n\n return {\n handler,\n handleWebSocket,\n upgradeWebSocket,\n app,\n connectionManager: connMgr,\n close,\n };\n}\n\n/**\n * Normalize and validate basePath.\n * - Empty string and undefined → ''\n * - Strip trailing slashes\n * - Validate leading slash when non-empty\n * - Reject unsafe characters\n */\nfunction normalizeBasePath(raw?: string): string {\n if (!raw) return '';\n\n // Strip trailing slashes\n const normalized = raw.replace(/\\/+$/, '');\n if (!normalized) return '';\n\n // Must start with /\n if (!normalized.startsWith('/')) {\n throw new Error(`basePath must start with '/' (got '${raw}'). Example: '/studio'`);\n }\n\n // Reject path traversal, consecutive slashes, and unsafe characters\n if (normalized.includes('..')) {\n throw new Error(`basePath must not contain '..' segments (got '${raw}')`);\n }\n if (normalized.includes('//')) {\n throw new Error(`basePath must not contain consecutive slashes (got '${raw}')`);\n }\n if (!/^\\/[a-zA-Z0-9/_-]*$/.test(normalized)) {\n throw new Error(\n `basePath contains invalid characters (got '${raw}'). ` +\n 'Only alphanumeric characters, /, _, and - are allowed.',\n );\n }\n\n return normalized;\n}\n\nfunction resolveClientDist(): string | undefined {\n // Resolve the directory of this file (dist/ in published package).\n const dir = import.meta.dirname ?? dirname(fileURLToPath(import.meta.url));\n const candidate = resolve(dir, 'client');\n return existsSync(resolve(candidate, 'index.html')) ? candidate : undefined;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/middleware.ts","../src/eval-loader.ts"],"sourcesContent":["import { resolve, dirname } from 'node:path';\nimport { existsSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { getRequestListener } from '@hono/node-server';\nimport { WebSocketServer } from 'ws';\nimport { createServer } from './server/index.js';\nimport { handleWsMessage } from './server/ws/protocol.js';\nimport { createEvalLoader } from './eval-loader.js';\nimport type { EvalLoaderConfig } from './eval-loader.js';\nimport type { AxlRuntime } from '@axlsdk/axl';\nimport type { IncomingMessage, ServerResponse, Server } from 'node:http';\n\nexport type { EvalLoaderConfig } from './eval-loader.js';\n\nexport type StudioMiddlewareOptions = {\n /** The AxlRuntime instance to observe and control. */\n runtime: AxlRuntime;\n\n /**\n * URL path prefix where Studio is mounted.\n * Must match the mount path in your framework.\n *\n * Must start with '/' when non-empty. Trailing slashes are stripped.\n * Only URL-safe characters allowed: [a-zA-Z0-9/_-]\n *\n * @example '/studio'\n * @example '/admin/studio'\n * @example '' — mounted at root (default)\n */\n basePath?: string;\n\n /**\n * Serve the pre-built Studio SPA.\n * Set to false if serving the client from a CDN or separate build.\n * @default true\n */\n serveClient?: boolean;\n\n /**\n * Verify a WebSocket upgrade request before completing the handshake.\n * Return true to allow, false to reject. Throw to reject with an error.\n *\n * IMPORTANT: WebSocket upgrades bypass Express/Fastify/Koa middleware.\n * If your HTTP routes are behind auth middleware, WS connections are NOT\n * automatically protected. Use this callback to enforce authentication\n * on WebSocket connections.\n */\n verifyUpgrade?: (req: IncomingMessage) => boolean | Promise<boolean>;\n\n /**\n * Disable all mutating endpoints (execute, test, send, delete, resolve).\n * When true, Studio is observation-only.\n * @default false\n */\n readOnly?: boolean;\n\n /**\n * Lazy-load eval files for the Eval Runner panel.\n *\n * Eval files are dynamically imported on first access to eval endpoints,\n * not at middleware construction time. This means:\n * - Zero cost during normal API operation\n * - Eval files can import from any module without creating circular deps\n * in the static module graph (they're loaded as standalone entry points)\n * - `@axlsdk/eval` can remain a devDependency — eval files never enter\n * the production bundle since bundlers can't see dynamic imports\n *\n * Accepts glob patterns, explicit file paths, or an object with\n * `conditions` for monorepo source export resolution.\n *\n * Eval files are loaded once and cached for the middleware's lifetime.\n * Changes to eval files require a server restart.\n *\n * @example\n * // Single glob pattern\n * evals: 'evals/*.eval.ts'\n *\n * @example\n * // Multiple patterns\n * evals: ['libs/api/evals/*.eval.ts', 'libs/ai/evals/*.eval.ts']\n *\n * @example\n * // With import conditions for monorepo source exports\n * evals: {\n * files: 'libs/api/evals/*.eval.ts',\n * conditions: ['development'],\n * }\n */\n evals?: EvalLoaderConfig;\n};\n\n/**\n * Minimal contract a WebSocket connection must satisfy.\n * Matches the `ws` library API (de facto standard in Node.js).\n */\nexport interface StudioWebSocket {\n send(data: string): void;\n close(): void;\n on(event: 'message', fn: (data: string | Buffer) => void): void;\n on(event: 'close', fn: () => void): void;\n on(event: 'error', fn: (err: Error) => void): void;\n}\n\n// Re-export for Hono-in-Hono consumers\nexport { handleWsMessage } from './server/ws/protocol.js';\n\nexport type StudioMiddleware = ReturnType<typeof createStudioMiddleware>;\n\nexport function createStudioMiddleware(options: StudioMiddlewareOptions) {\n const { runtime, serveClient = true, verifyUpgrade, readOnly = false } = options;\n\n // Normalize basePath: strip trailing slashes, validate format\n const basePath = normalizeBasePath(options.basePath);\n\n // Resolve pre-built SPA assets from this package's dist/\n const staticRoot = serveClient ? resolveClientDist() : undefined;\n\n if (serveClient && !staticRoot) {\n const dir = import.meta.dirname ?? dirname(fileURLToPath(import.meta.url));\n console.warn(\n '[axl-studio] serveClient is true but no pre-built client found at ' +\n `${resolve(dir, 'client')}. Studio UI will not be available. ` +\n 'Set serveClient: false to suppress this warning.',\n );\n }\n\n // Create lazy eval loader if eval files are configured\n const evalLoader = options.evals ? createEvalLoader(options.evals, runtime) : undefined;\n\n const { app, connMgr, traceListener } = createServer({\n runtime,\n staticRoot,\n basePath,\n readOnly,\n cors: false, // Host framework owns CORS policy\n evalLoader,\n });\n\n // Log production safety warning\n if (process.env.NODE_ENV === 'production' && !verifyUpgrade) {\n console.warn(\n '[axl-studio] WARNING: Studio middleware mounted in production without verifyUpgrade. ' +\n 'WebSocket connections are not authenticated. All registered workflows, tools, and ' +\n 'agents are accessible. See https://axlsdk.com/docs/studio/security',\n );\n }\n\n // Convert Hono app → Node.js (req, res) handler.\n // overrideGlobalObjects: false prevents replacing global.Request and\n // global.Response, which could break the host application.\n const listener = getRequestListener(app.fetch, {\n overrideGlobalObjects: false,\n });\n\n let closed = false;\n\n function handler(req: IncomingMessage, res: ServerResponse) {\n if (closed) {\n res.writeHead(503, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n ok: false,\n error: { code: 'CLOSED', message: 'Studio middleware has been shut down' },\n }),\n );\n return;\n }\n // listener returns Promise<void>. Catch async errors to prevent\n // unhandled rejections from crashing the host process.\n listener(req, res).catch((err) => {\n console.error('[axl-studio] Unhandled error in request handler:', err);\n if (!res.headersSent) {\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n ok: false,\n error: { code: 'INTERNAL_ERROR', message: 'Internal server error' },\n }),\n );\n }\n });\n }\n\n // Handle an individual WebSocket using the Studio protocol.\n // Adapts any StudioWebSocket to ConnectionManager's internal BroadcastTarget.\n function handleWebSocket(ws: StudioWebSocket) {\n if (closed) {\n ws.close();\n return;\n }\n const socket = {\n send: (data: string) => ws.send(data),\n close: () => ws.close(),\n };\n connMgr.add(socket);\n\n ws.on('message', (raw) => {\n const reply = handleWsMessage(String(raw), socket, connMgr);\n if (reply) ws.send(reply);\n });\n\n ws.on('close', () => connMgr.remove(socket));\n ws.on('error', () => connMgr.remove(socket));\n }\n\n // Internal WebSocketServer — created lazily by upgradeWebSocket()\n let wss: InstanceType<typeof WebSocketServer> | undefined;\n // References for cleanup: the upgrade handler and server it's attached to\n let upgradeHandler: ((...args: any[]) => void) | undefined;\n let serverRef: Server | undefined;\n\n // Convenience: attach WS handling to an http.Server.\n function upgradeWebSocket(server: Server, path?: string) {\n if (wss) {\n throw new Error(\n '[axl-studio] upgradeWebSocket() has already been called. ' +\n 'Call close() first if you need to re-attach.',\n );\n }\n\n const wsPath = path ?? (basePath ? `${basePath}/ws` : '/ws');\n\n wss = new WebSocketServer({ noServer: true });\n serverRef = server;\n\n upgradeHandler = async (req: IncomingMessage, socket: any, head: Buffer) => {\n // Match path, ignoring query string\n const pathname = new URL(req.url!, `http://${req.headers.host}`).pathname;\n if (pathname !== wsPath) return; // Let other upgrade handlers run\n\n // Apply auth verification\n if (verifyUpgrade) {\n try {\n const allowed = await verifyUpgrade(req);\n if (!allowed) {\n socket.write('HTTP/1.1 401 Unauthorized\\r\\n\\r\\n');\n socket.destroy();\n return;\n }\n } catch {\n socket.write('HTTP/1.1 403 Forbidden\\r\\n\\r\\n');\n socket.destroy();\n return;\n }\n }\n\n // Guard against close() being called during async verifyUpgrade\n if (!wss) {\n socket.destroy();\n return;\n }\n\n wss.handleUpgrade(req, socket, head, (ws) => {\n handleWebSocket(ws);\n });\n };\n\n server.on('upgrade', upgradeHandler);\n }\n\n // Cleanup function for lifecycle management.\n function close() {\n closed = true;\n\n // Close all WebSocket connections\n connMgr.closeAll();\n\n // Remove the upgrade listener from the server before closing WSS\n if (upgradeHandler && serverRef) {\n serverRef.removeListener('upgrade', upgradeHandler);\n upgradeHandler = undefined;\n serverRef = undefined;\n }\n\n // Shut down the internal WebSocketServer if one was created\n if (wss) {\n wss.close();\n wss = undefined;\n }\n\n // Remove only our trace event listener from the runtime\n if (traceListener) {\n runtime.removeListener('trace', traceListener);\n }\n }\n\n return {\n handler,\n handleWebSocket,\n upgradeWebSocket,\n app,\n connectionManager: connMgr,\n close,\n };\n}\n\n/**\n * Normalize and validate basePath.\n * - Empty string and undefined → ''\n * - Strip trailing slashes\n * - Validate leading slash when non-empty\n * - Reject unsafe characters\n */\nfunction normalizeBasePath(raw?: string): string {\n if (!raw) return '';\n\n // Strip trailing slashes\n const normalized = raw.replace(/\\/+$/, '');\n if (!normalized) return '';\n\n // Must start with /\n if (!normalized.startsWith('/')) {\n throw new Error(`basePath must start with '/' (got '${raw}'). Example: '/studio'`);\n }\n\n // Reject path traversal, consecutive slashes, and unsafe characters\n if (normalized.includes('..')) {\n throw new Error(`basePath must not contain '..' segments (got '${raw}')`);\n }\n if (normalized.includes('//')) {\n throw new Error(`basePath must not contain consecutive slashes (got '${raw}')`);\n }\n if (!/^\\/[a-zA-Z0-9/_-]*$/.test(normalized)) {\n throw new Error(\n `basePath contains invalid characters (got '${raw}'). ` +\n 'Only alphanumeric characters, /, _, and - are allowed.',\n );\n }\n\n return normalized;\n}\n\nfunction resolveClientDist(): string | undefined {\n // Resolve the directory of this file (dist/ in published package).\n const dir = import.meta.dirname ?? dirname(fileURLToPath(import.meta.url));\n const candidate = resolve(dir, 'client');\n return existsSync(resolve(candidate, 'index.html')) ? candidate : undefined;\n}\n","import { resolve, relative, dirname, basename } from 'node:path';\nimport { readdirSync, statSync } from 'node:fs';\nimport { pathToFileURL } from 'node:url';\nimport type { AxlRuntime } from '@axlsdk/axl';\n\n/**\n * Configuration for lazy eval file discovery.\n *\n * - `string` — a glob pattern or explicit file path\n * - `string[]` — multiple patterns/paths\n * - `object` — patterns with optional import conditions\n */\nexport type EvalLoaderConfig =\n | string\n | string[]\n | {\n files: string | string[];\n\n /**\n * Custom Node.js import conditions (e.g., `['development']`).\n *\n * In monorepos, package.json `exports` often use the `development` condition\n * to point at source (`.ts`) instead of built dist. Without this, eval files\n * that import workspace packages resolve to dist files, which may not exist.\n *\n * **WARNING**: Conditions are registered process-wide via `module.register()`.\n * They affect all subsequent imports in the process, not just eval files.\n */\n conditions?: string[];\n };\n\n/**\n * Create a lazy eval loader that resolves file patterns and dynamically imports\n * eval files on first call, registering them with the runtime.\n *\n * The loader is idempotent — subsequent calls return the same promise.\n * Concurrent callers all await the same loading work.\n *\n * Eval files should export a default config with `{ workflow, dataset, scorers }`\n * (the result of `defineEval()` from `@axlsdk/eval`). An optional named export\n * `executeWorkflow` overrides the default `runtime.execute()` behavior.\n *\n * Eval names are the file's path relative to `cwd` (project root), minus the\n * `.eval.*` suffix. This makes names completely stable — a file's name never\n * changes regardless of what other files or patterns exist.\n *\n * @param config Glob patterns, file paths, or object with conditions\n * @param runtime The AxlRuntime to register discovered evals on\n * @param cwd Base directory for resolving patterns and deriving names (default: `process.cwd()`)\n */\nexport function createEvalLoader(\n config: EvalLoaderConfig,\n runtime: AxlRuntime,\n cwd?: string,\n): () => Promise<void> {\n let loadPromise: Promise<void> | undefined;\n const { patterns, conditions } = normalizeConfig(config);\n const baseCwd = cwd ?? process.cwd();\n\n return () => {\n if (!loadPromise) {\n loadPromise = loadEvalFiles(patterns, conditions, baseCwd, runtime).catch((err) => {\n loadPromise = undefined; // Allow retry on next request\n throw err;\n });\n }\n return loadPromise;\n };\n}\n\n// ── Core loading logic ─────────────────────────────────────────────\n\nasync function loadEvalFiles(\n patterns: string[],\n conditions: string[],\n cwd: string,\n runtime: AxlRuntime,\n): Promise<void> {\n if (conditions.length > 0) {\n await registerConditions(conditions);\n }\n\n const files = resolvePatterns(patterns, cwd);\n\n if (files.length === 0) {\n console.warn(`[axl-studio] No eval files found matching: ${patterns.join(', ')}`);\n return;\n }\n\n // Register tsx loader if any files are TypeScript\n if (files.some((f) => /\\.[mc]?tsx?$/.test(f))) {\n await ensureTsxLoader();\n }\n\n for (const file of files) {\n try {\n const mod = await import(pathToFileURL(file).href);\n const evalConfig = mod.default ?? mod.config ?? mod;\n\n if (!evalConfig.workflow || !evalConfig.dataset || !evalConfig.scorers) {\n console.warn(\n `[axl-studio] Skipping ${file}: not a valid eval config ` +\n `(missing workflow, dataset, or scorers)`,\n );\n continue;\n }\n\n const name = deriveEvalName(file, cwd);\n\n if (runtime.getRegisteredEval(name)) {\n console.warn(\n `[axl-studio] Eval name \"${name}\" from ${file} collides with an ` +\n `already-registered eval — overwriting`,\n );\n }\n\n runtime.registerEval(name, evalConfig, mod.executeWorkflow);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[axl-studio] Failed to load eval ${file}: ${msg}`);\n }\n }\n}\n\n// ── Internal helpers ───────────────────────────────────────────────\n\nfunction normalizeConfig(config: EvalLoaderConfig): {\n patterns: string[];\n conditions: string[];\n} {\n if (typeof config === 'string') {\n return { patterns: [config], conditions: [] };\n }\n if (Array.isArray(config)) {\n return { patterns: config, conditions: [] };\n }\n const files = typeof config.files === 'string' ? [config.files] : config.files;\n return { patterns: files, conditions: config.conditions ?? [] };\n}\n\n/**\n * Derive eval name from file path relative to cwd.\n *\n * Examples (cwd = `/project`):\n * - `/project/evals/suggestions.eval.ts` → `\"evals/suggestions\"`\n * - `/project/evals/api/accuracy.eval.ts` → `\"evals/api/accuracy\"`\n */\nfunction deriveEvalName(filePath: string, cwd: string): string {\n const rel = relative(cwd, filePath);\n // Normalize to forward slashes for cross-platform consistency\n const normalized = rel.replace(/\\\\/g, '/');\n // Guard: file outside cwd (symlink, absolute path) — fall back to basename\n if (normalized.startsWith('../')) {\n const base = basename(filePath);\n const stripped = base.replace(/\\.eval\\.[mc]?[jt]sx?$/, '');\n return stripped !== base ? stripped : base.replace(/\\.[mc]?[jt]sx?$/, '') || base;\n }\n // Strip .eval.ts, .eval.mjs, .eval.js, etc.\n const withoutEval = normalized.replace(/\\.eval\\.[mc]?[jt]sx?$/, '');\n if (withoutEval !== normalized) return withoutEval;\n // Fallback: strip extension\n const withoutExt = normalized.replace(/\\.[mc]?[jt]sx?$/, '');\n return withoutExt || normalized;\n}\n\n/**\n * Resolve patterns to absolute file paths.\n *\n * Supports:\n * - Explicit file paths (no wildcards)\n * - Single-directory globs: `dir/*.eval.ts`\n * - Recursive globs: `dir/**\\/*.eval.ts` or `**\\/*.eval.ts`\n *\n * Multi-segment `**` (e.g., `a/**\\/b/**\\/*.ts`) is not supported.\n */\nfunction resolvePatterns(patterns: string[], cwd: string): string[] {\n const files: string[] = [];\n const seen = new Set<string>();\n for (const pattern of patterns) {\n const resolved = pattern.includes('*') ? expandGlob(pattern, cwd) : [resolve(cwd, pattern)];\n for (const file of resolved) {\n if (!seen.has(file)) {\n seen.add(file);\n files.push(file);\n }\n }\n }\n return files;\n}\n\n/**\n * Expand a glob pattern to matching file paths.\n *\n * Supported forms:\n * - `dir/*.eval.ts` — match files in dir/\n * - `dir/**\\/*.eval.ts` — recursively match under dir/\n * - `**\\/*.eval.ts` — recursively match under cwd\n */\nfunction expandGlob(pattern: string, cwd: string): string[] {\n if (pattern.includes('**/')) {\n const sepIdx = pattern.indexOf('**/');\n const baseDir = resolve(cwd, pattern.slice(0, sepIdx) || '.');\n const fileGlob = pattern.slice(sepIdx + 3) || '*';\n return findFiles(baseDir, fileGlob, true);\n }\n\n const dir = resolve(cwd, dirname(pattern));\n const fileGlob = basename(pattern);\n return findFiles(dir, fileGlob, false);\n}\n\nconst MAX_DEPTH = 20;\n\nfunction findFiles(dir: string, fileGlob: string, recursive: boolean, depth = 0): string[] {\n if (depth > MAX_DEPTH) return [];\n const matcher = globToRegex(fileGlob);\n const results: string[] = [];\n\n try {\n const entries = readdirSync(dir);\n for (const entry of entries) {\n const full = resolve(dir, entry);\n try {\n const stat = statSync(full);\n if (stat.isFile() && matcher.test(entry)) {\n results.push(full);\n } else if (stat.isDirectory() && recursive) {\n results.push(...findFiles(full, fileGlob, true, depth + 1));\n }\n } catch {\n // Skip unreadable entries\n }\n }\n } catch {\n // Directory doesn't exist or unreadable\n }\n\n return results;\n}\n\n/** Convert a simple glob pattern (e.g., `*.eval.ts`) to a RegExp. */\nfunction globToRegex(glob: string): RegExp {\n const escaped = glob.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\*/g, '.*');\n return new RegExp(`^${escaped}$`);\n}\n\nlet tsxRegistered = false;\n\nasync function ensureTsxLoader(): Promise<void> {\n if (tsxRegistered) return;\n tsxRegistered = true;\n\n let loaded = false;\n try {\n const tsxEsm = await import('tsx/esm/api');\n tsxEsm.register();\n loaded = true;\n } catch {\n // ESM hook not available\n }\n try {\n const tsxCjs = await import('tsx/cjs/api');\n tsxCjs.register();\n loaded = true;\n } catch {\n // CJS hook not available\n }\n if (!loaded) {\n console.warn(\n '[axl-studio] Warning: tsx is not installed. TypeScript eval files require tsx.\\n' +\n ' Install it with: npm install -D tsx',\n );\n }\n}\n\nasync function registerConditions(conditions: string[]): Promise<void> {\n try {\n const nodeModule = await import('node:module');\n const hookCode = [\n `const extra = ${JSON.stringify(conditions)};`,\n `export async function resolve(specifier, context, nextResolve) {`,\n ` return nextResolve(specifier, {`,\n ` ...context,`,\n ` conditions: [...new Set([...context.conditions, ...extra])],`,\n ` });`,\n `}`,\n ].join('\\n');\n nodeModule.register(`data:text/javascript,${encodeURIComponent(hookCode)}`);\n } catch {\n console.warn('[axl-studio] Warning: import conditions require Node.js 20.6+');\n }\n}\n"],"mappings":"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","names":["resolve","dirname","fileGlob","dirname","resolve"]}
@@ -302,8 +302,8 @@ var health_default = app;
302
302
  var import_hono2 = require("hono");
303
303
  var import_axl = require("@axlsdk/axl");
304
304
  function createWorkflowRoutes(connMgr) {
305
- const app8 = new import_hono2.Hono();
306
- app8.get("/workflows", (c) => {
305
+ const app7 = new import_hono2.Hono();
306
+ app7.get("/workflows", (c) => {
307
307
  const runtime = c.get("runtime");
308
308
  const workflows = runtime.getWorkflows().map((w) => ({
309
309
  name: w.name,
@@ -312,7 +312,7 @@ function createWorkflowRoutes(connMgr) {
312
312
  }));
313
313
  return c.json({ ok: true, data: workflows });
314
314
  });
315
- app8.get("/workflows/:name", (c) => {
315
+ app7.get("/workflows/:name", (c) => {
316
316
  const runtime = c.get("runtime");
317
317
  const name = c.req.param("name");
318
318
  const workflow = runtime.getWorkflow(name);
@@ -331,7 +331,7 @@ function createWorkflowRoutes(connMgr) {
331
331
  }
332
332
  });
333
333
  });
334
- app8.post("/workflows/:name/execute", async (c) => {
334
+ app7.post("/workflows/:name/execute", async (c) => {
335
335
  const runtime = c.get("runtime");
336
336
  const name = c.req.param("name");
337
337
  const workflow = runtime.getWorkflow(name);
@@ -362,7 +362,7 @@ function createWorkflowRoutes(connMgr) {
362
362
  const result = await runtime.execute(name, body.input ?? {}, { metadata: body.metadata });
363
363
  return c.json({ ok: true, data: { result } });
364
364
  });
365
- return app8;
365
+ return app7;
366
366
  }
367
367
 
368
368
  // src/server/routes/executions.ts
@@ -396,8 +396,8 @@ var executions_default = app2;
396
396
  // src/server/routes/sessions.ts
397
397
  var import_hono4 = require("hono");
398
398
  function createSessionRoutes(connMgr) {
399
- const app8 = new import_hono4.Hono();
400
- app8.get("/sessions", async (c) => {
399
+ const app7 = new import_hono4.Hono();
400
+ app7.get("/sessions", async (c) => {
401
401
  const runtime = c.get("runtime");
402
402
  const store = runtime.getStateStore();
403
403
  if (!store.listSessions) {
@@ -411,7 +411,7 @@ function createSessionRoutes(connMgr) {
411
411
  }
412
412
  return c.json({ ok: true, data: sessions });
413
413
  });
414
- app8.get("/sessions/:id", async (c) => {
414
+ app7.get("/sessions/:id", async (c) => {
415
415
  const runtime = c.get("runtime");
416
416
  const store = runtime.getStateStore();
417
417
  const id = c.req.param("id");
@@ -419,7 +419,7 @@ function createSessionRoutes(connMgr) {
419
419
  const handoffHistory = await store.getSessionMeta(id, "handoffHistory");
420
420
  return c.json({ ok: true, data: { id, history, handoffHistory: handoffHistory ?? [] } });
421
421
  });
422
- app8.post("/sessions/:id/send", async (c) => {
422
+ app7.post("/sessions/:id/send", async (c) => {
423
423
  const runtime = c.get("runtime");
424
424
  const id = c.req.param("id");
425
425
  const body = await c.req.json();
@@ -427,7 +427,7 @@ function createSessionRoutes(connMgr) {
427
427
  const result = await session.send(body.workflow, body.message);
428
428
  return c.json({ ok: true, data: { result } });
429
429
  });
430
- app8.post("/sessions/:id/stream", async (c) => {
430
+ app7.post("/sessions/:id/stream", async (c) => {
431
431
  const runtime = c.get("runtime");
432
432
  const id = c.req.param("id");
433
433
  const body = await c.req.json();
@@ -448,14 +448,14 @@ function createSessionRoutes(connMgr) {
448
448
  })();
449
449
  return c.json({ ok: true, data: { executionId, streaming: true } });
450
450
  });
451
- app8.delete("/sessions/:id", async (c) => {
451
+ app7.delete("/sessions/:id", async (c) => {
452
452
  const runtime = c.get("runtime");
453
453
  const store = runtime.getStateStore();
454
454
  const id = c.req.param("id");
455
455
  await store.deleteSession(id);
456
456
  return c.json({ ok: true, data: { deleted: true } });
457
457
  });
458
- return app8;
458
+ return app7;
459
459
  }
460
460
 
461
461
  // src/server/routes/agents.ts
@@ -688,61 +688,65 @@ var decisions_default = app6;
688
688
  // src/server/routes/costs.ts
689
689
  var import_hono9 = require("hono");
690
690
  function createCostRoutes(costAggregator) {
691
- const app8 = new import_hono9.Hono();
692
- app8.get("/costs", (c) => {
691
+ const app7 = new import_hono9.Hono();
692
+ app7.get("/costs", (c) => {
693
693
  return c.json({ ok: true, data: costAggregator.getData() });
694
694
  });
695
- app8.post("/costs/reset", (c) => {
695
+ app7.post("/costs/reset", (c) => {
696
696
  costAggregator.reset();
697
697
  return c.json({ ok: true, data: { reset: true } });
698
698
  });
699
- return app8;
699
+ return app7;
700
700
  }
701
701
 
702
702
  // src/server/routes/evals.ts
703
703
  var import_hono10 = require("hono");
704
- var app7 = new import_hono10.Hono();
705
- app7.get("/evals", async (c) => {
706
- const runtime = c.get("runtime");
707
- const evals = runtime.getRegisteredEvals();
708
- return c.json({ ok: true, data: evals });
709
- });
710
- app7.post("/evals/:name/run", async (c) => {
711
- const runtime = c.get("runtime");
712
- const name = c.req.param("name");
713
- const entry = runtime.getRegisteredEval(name);
714
- if (!entry) {
715
- return c.json(
716
- { ok: false, error: { code: "NOT_FOUND", message: `Eval "${name}" not found` } },
717
- 404
718
- );
719
- }
720
- try {
721
- const result = await runtime.runRegisteredEval(name);
722
- return c.json({ ok: true, data: result });
723
- } catch (err) {
724
- const message = err instanceof Error ? err.message : String(err);
725
- return c.json({ ok: false, error: { code: "EVAL_ERROR", message } }, 400);
726
- }
727
- });
728
- app7.post("/evals/compare", async (c) => {
729
- const runtime = c.get("runtime");
730
- const body = await c.req.json();
731
- try {
732
- const result = await runtime.evalCompare(body.baseline, body.candidate);
733
- return c.json({ ok: true, data: result });
734
- } catch (err) {
735
- const message = err instanceof Error ? err.message : String(err);
736
- return c.json({ ok: false, error: { code: "EVAL_ERROR", message } }, 400);
737
- }
738
- });
739
- var evals_default = app7;
704
+ function createEvalRoutes(evalLoader) {
705
+ const app7 = new import_hono10.Hono();
706
+ app7.get("/evals", async (c) => {
707
+ if (evalLoader) await evalLoader();
708
+ const runtime = c.get("runtime");
709
+ const evals = runtime.getRegisteredEvals();
710
+ return c.json({ ok: true, data: evals });
711
+ });
712
+ app7.post("/evals/:name/run", async (c) => {
713
+ if (evalLoader) await evalLoader();
714
+ const runtime = c.get("runtime");
715
+ const name = c.req.param("name");
716
+ const entry = runtime.getRegisteredEval(name);
717
+ if (!entry) {
718
+ return c.json(
719
+ { ok: false, error: { code: "NOT_FOUND", message: `Eval "${name}" not found` } },
720
+ 404
721
+ );
722
+ }
723
+ try {
724
+ const result = await runtime.runRegisteredEval(name);
725
+ return c.json({ ok: true, data: result });
726
+ } catch (err) {
727
+ const message = err instanceof Error ? err.message : String(err);
728
+ return c.json({ ok: false, error: { code: "EVAL_ERROR", message } }, 400);
729
+ }
730
+ });
731
+ app7.post("/evals/compare", async (c) => {
732
+ const runtime = c.get("runtime");
733
+ const body = await c.req.json();
734
+ try {
735
+ const result = await runtime.evalCompare(body.baseline, body.candidate);
736
+ return c.json({ ok: true, data: result });
737
+ } catch (err) {
738
+ const message = err instanceof Error ? err.message : String(err);
739
+ return c.json({ ok: false, error: { code: "EVAL_ERROR", message } }, 400);
740
+ }
741
+ });
742
+ return app7;
743
+ }
740
744
 
741
745
  // src/server/routes/playground.ts
742
746
  var import_hono11 = require("hono");
743
747
  function createPlaygroundRoutes(connMgr) {
744
- const app8 = new import_hono11.Hono();
745
- app8.post("/playground/chat", async (c) => {
748
+ const app7 = new import_hono11.Hono();
749
+ app7.post("/playground/chat", async (c) => {
746
750
  const runtime = c.get("runtime");
747
751
  const body = await c.req.json();
748
752
  const workflowName = body.workflow ?? runtime.getWorkflowNames()[0];
@@ -773,20 +777,20 @@ function createPlaygroundRoutes(connMgr) {
773
777
  data: { sessionId, executionId, streaming: true }
774
778
  });
775
779
  });
776
- return app8;
780
+ return app7;
777
781
  }
778
782
 
779
783
  // src/server/index.ts
780
784
  function createServer(options) {
781
785
  const { runtime, staticRoot, basePath = "", readOnly = false } = options;
782
- const app8 = new import_hono12.Hono();
786
+ const app7 = new import_hono12.Hono();
783
787
  const connMgr = new ConnectionManager();
784
788
  const costAggregator = new CostAggregator(connMgr);
785
789
  if (options.cors !== false) {
786
- app8.use("*", (0, import_cors.cors)());
790
+ app7.use("*", (0, import_cors.cors)());
787
791
  }
788
- app8.use("*", errorHandler);
789
- app8.use("*", async (c, next) => {
792
+ app7.use("*", errorHandler);
793
+ app7.use("*", async (c, next) => {
790
794
  c.set("runtime", runtime);
791
795
  await next();
792
796
  });
@@ -804,7 +808,7 @@ function createServer(options) {
804
808
  "POST /api/evals",
805
809
  "POST /api/playground"
806
810
  ];
807
- app8.use("/api/*", async (c, next) => {
811
+ app7.use("/api/*", async (c, next) => {
808
812
  const apiIdx = c.req.path.indexOf("/api/");
809
813
  const apiPath = apiIdx >= 0 ? c.req.path.slice(apiIdx) : c.req.path;
810
814
  const key = `${c.req.method} ${apiPath}`;
@@ -830,9 +834,9 @@ function createServer(options) {
830
834
  api.route("/", memory_default);
831
835
  api.route("/", decisions_default);
832
836
  api.route("/", createCostRoutes(costAggregator));
833
- api.route("/", evals_default);
837
+ api.route("/", createEvalRoutes(options.evalLoader));
834
838
  api.route("/", createPlaygroundRoutes(connMgr));
835
- app8.route("/api", api);
839
+ app7.route("/api", api);
836
840
  const traceListener = (event) => {
837
841
  const traceEvent = event;
838
842
  if (traceEvent.executionId) {
@@ -845,7 +849,7 @@ function createServer(options) {
845
849
  };
846
850
  runtime.on("trace", traceListener);
847
851
  if (staticRoot) {
848
- app8.use(
852
+ app7.use(
849
853
  "/*",
850
854
  (0, import_serve_static.serveStatic)({
851
855
  root: staticRoot,
@@ -870,14 +874,14 @@ function createServer(options) {
870
874
  "[axl-studio] Could not inject basePath into index.html \u2014 </head> tag not found. The SPA may not route correctly."
871
875
  );
872
876
  }
873
- app8.get("*", (c) => c.html(injectedHtml));
877
+ app7.get("*", (c) => c.html(injectedHtml));
874
878
  }
875
879
  } else {
876
- app8.get("*", (0, import_serve_static.serveStatic)({ root: staticRoot, path: "/index.html" }));
880
+ app7.get("*", (0, import_serve_static.serveStatic)({ root: staticRoot, path: "/index.html" }));
877
881
  }
878
882
  }
879
883
  return {
880
- app: app8,
884
+ app: app7,
881
885
  connMgr,
882
886
  costAggregator,
883
887
  createWsHandlers: () => createWsHandlers(connMgr),