@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.
- package/README.md +51 -0
- package/dist/{chunk-RBTYI3TW.js → chunk-GKIPF45K.js} +71 -67
- package/dist/chunk-GKIPF45K.js.map +1 -0
- package/dist/cli.cjs +74 -70
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/middleware.cjs +263 -78
- package/dist/middleware.cjs.map +1 -1
- package/dist/middleware.d.cts +56 -1
- package/dist/middleware.d.ts +56 -1
- package/dist/middleware.js +181 -8
- package/dist/middleware.js.map +1 -1
- package/dist/server/index.cjs +70 -66
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +2 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +1 -1
- package/package.json +4 -4
- package/dist/chunk-RBTYI3TW.js.map +0 -1
package/dist/middleware.js
CHANGED
|
@@ -1,31 +1,204 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createServer,
|
|
3
3
|
handleWsMessage
|
|
4
|
-
} from "./chunk-
|
|
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 ??
|
|
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 ${
|
|
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 ??
|
|
166
|
-
const candidate =
|
|
167
|
-
return existsSync(
|
|
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,
|
package/dist/middleware.js.map
CHANGED
|
@@ -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"]}
|
package/dist/server/index.cjs
CHANGED
|
@@ -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
|
|
306
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
400
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
692
|
-
|
|
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
|
-
|
|
695
|
+
app7.post("/costs/reset", (c) => {
|
|
696
696
|
costAggregator.reset();
|
|
697
697
|
return c.json({ ok: true, data: { reset: true } });
|
|
698
698
|
});
|
|
699
|
-
return
|
|
699
|
+
return app7;
|
|
700
700
|
}
|
|
701
701
|
|
|
702
702
|
// src/server/routes/evals.ts
|
|
703
703
|
var import_hono10 = require("hono");
|
|
704
|
-
|
|
705
|
-
app7
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
const
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
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
|
|
745
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
790
|
+
app7.use("*", (0, import_cors.cors)());
|
|
787
791
|
}
|
|
788
|
-
|
|
789
|
-
|
|
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
|
-
|
|
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("/",
|
|
837
|
+
api.route("/", createEvalRoutes(options.evalLoader));
|
|
834
838
|
api.route("/", createPlaygroundRoutes(connMgr));
|
|
835
|
-
|
|
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
|
-
|
|
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
|
-
|
|
877
|
+
app7.get("*", (c) => c.html(injectedHtml));
|
|
874
878
|
}
|
|
875
879
|
} else {
|
|
876
|
-
|
|
880
|
+
app7.get("*", (0, import_serve_static.serveStatic)({ root: staticRoot, path: "/index.html" }));
|
|
877
881
|
}
|
|
878
882
|
}
|
|
879
883
|
return {
|
|
880
|
-
app:
|
|
884
|
+
app: app7,
|
|
881
885
|
connMgr,
|
|
882
886
|
costAggregator,
|
|
883
887
|
createWsHandlers: () => createWsHandlers(connMgr),
|