@axlsdk/studio 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/dist/{chunk-IPDMFFTQ.js → chunk-RE6VPUXA.js} +274 -203
- package/dist/chunk-RE6VPUXA.js.map +1 -0
- package/dist/cli.cjs +281 -210
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/client/assets/index-ClajLxib.js +288 -0
- package/dist/client/assets/index-DnHL_gtF.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/{connection-manager-BMPahDuY.d.cts → connection-manager-DAuqk9lM.d.cts} +24 -1
- package/dist/{connection-manager-BMPahDuY.d.ts → connection-manager-DAuqk9lM.d.ts} +24 -1
- package/dist/middleware.cjs +283 -211
- package/dist/middleware.cjs.map +1 -1
- package/dist/middleware.d.cts +32 -9
- package/dist/middleware.d.ts +32 -9
- package/dist/middleware.js +3 -2
- package/dist/middleware.js.map +1 -1
- package/dist/server/index.cjs +281 -210
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +12 -6
- package/dist/server/index.d.ts +12 -6
- package/dist/server/index.js +1 -1
- package/package.json +7 -7
- package/dist/chunk-IPDMFFTQ.js.map +0 -1
- package/dist/client/assets/index-CLKKOaE2.css +0 -1
- package/dist/client/assets/index-rvds50cZ.js +0 -278
package/dist/middleware.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AxlRuntime } from '@axlsdk/axl';
|
|
2
|
-
import { B as BroadcastTarget, C as ConnectionManager, S as StudioEnv } from './connection-manager-
|
|
2
|
+
import { B as BroadcastTarget, C as ConnectionManager, a as BufferCaps, S as StudioEnv } from './connection-manager-DAuqk9lM.cjs';
|
|
3
3
|
import * as hono_types from 'hono/types';
|
|
4
4
|
import * as hono from 'hono';
|
|
5
5
|
import { IncomingMessage, ServerResponse, Server } from 'node:http';
|
|
@@ -95,9 +95,10 @@ type StudioMiddlewareOptions = {
|
|
|
95
95
|
* `true` to deliver to this connection, `false` to drop.
|
|
96
96
|
*
|
|
97
97
|
* `event` is the parsed event payload — for the `trace:*` channel this is
|
|
98
|
-
*
|
|
99
|
-
* for `costs` it's a `CostData`; for `execution:*` / `eval:*` it's
|
|
100
|
-
*
|
|
98
|
+
* an `AxlEvent` from `@axlsdk/axl` (narrow via the discriminated union);
|
|
99
|
+
* for `costs` it's a `CostData`; for `execution:*` / `eval:*` it's the
|
|
100
|
+
* legacy stream-event shape (until PR 3 collapses the wire to `AxlEvent`).
|
|
101
|
+
* Typed `unknown` because the filter runs for every channel.
|
|
101
102
|
* `metadata` is the per-connection metadata attached by `verifyUpgrade`,
|
|
102
103
|
* or `undefined` if `verifyUpgrade` returned a bare boolean.
|
|
103
104
|
*
|
|
@@ -105,15 +106,15 @@ type StudioMiddlewareOptions = {
|
|
|
105
106
|
* cannot accidentally leak events cross-tenant.
|
|
106
107
|
*
|
|
107
108
|
* @example Scope trace events by tenant id stored on agent metadata
|
|
108
|
-
* import type {
|
|
109
|
+
* import type { AxlEvent } from '@axlsdk/axl';
|
|
109
110
|
*
|
|
110
111
|
* filterTraceEvent: (event, meta) => {
|
|
111
112
|
* const m = meta as { tenantId?: string } | undefined;
|
|
112
113
|
* if (!m?.tenantId) return false;
|
|
113
|
-
* const e = event as
|
|
114
|
-
* // Only narrow event.type === '
|
|
114
|
+
* const e = event as AxlEvent;
|
|
115
|
+
* // Only narrow event.type === 'agent_call_end' events; pass structural
|
|
115
116
|
* // events (workflow_start/end, cost updates) through unconditionally.
|
|
116
|
-
* if (e.type !== '
|
|
117
|
+
* if (e.type !== 'agent_call_end') return true;
|
|
117
118
|
* return e.workflow?.startsWith(`${m.tenantId}:`) ?? false;
|
|
118
119
|
* }
|
|
119
120
|
*/
|
|
@@ -157,6 +158,28 @@ type StudioMiddlewareOptions = {
|
|
|
157
158
|
* }
|
|
158
159
|
*/
|
|
159
160
|
evals?: EvalLoaderConfig;
|
|
161
|
+
/**
|
|
162
|
+
* Override the default WebSocket replay-buffer resource caps for
|
|
163
|
+
* production deployments under sustained execution churn.
|
|
164
|
+
*
|
|
165
|
+
* Defaults: `maxEventsPerBuffer: 1000`, `maxBytesPerBuffer: 4 MiB`,
|
|
166
|
+
* `maxActiveBuffers: 256`. Worst-case memory is roughly
|
|
167
|
+
* `maxActiveBuffers × maxBytesPerBuffer` (≈1 GiB at defaults). Tighten
|
|
168
|
+
* either dimension to lower the ceiling at the cost of late-subscriber
|
|
169
|
+
* replay coverage; raise them if you need more event headroom on long
|
|
170
|
+
* verbose-mode streams.
|
|
171
|
+
*
|
|
172
|
+
* Terminal `done` / `error` events are always buffered regardless of
|
|
173
|
+
* caps, so a late subscriber to a completed stream still sees the
|
|
174
|
+
* outcome — only intermediate non-terminal events are dropped.
|
|
175
|
+
*
|
|
176
|
+
* All fields are optional; passing `{}` is a no-op.
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* // Halve memory ceiling on a high-churn deployment
|
|
180
|
+
* bufferCaps: { maxActiveBuffers: 128, maxBytesPerBuffer: 2 * 1024 * 1024 }
|
|
181
|
+
*/
|
|
182
|
+
bufferCaps?: BufferCaps;
|
|
160
183
|
};
|
|
161
184
|
/**
|
|
162
185
|
* Minimal contract a WebSocket connection must satisfy.
|
|
@@ -180,4 +203,4 @@ declare function createStudioMiddleware(options: StudioMiddlewareOptions): {
|
|
|
180
203
|
close: () => void;
|
|
181
204
|
};
|
|
182
205
|
|
|
183
|
-
export { type EvalLoaderConfig, type StudioMiddleware, type StudioMiddlewareOptions, type StudioWebSocket, createStudioMiddleware, handleWsMessage };
|
|
206
|
+
export { BufferCaps, type EvalLoaderConfig, type StudioMiddleware, type StudioMiddlewareOptions, type StudioWebSocket, createStudioMiddleware, handleWsMessage };
|
package/dist/middleware.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AxlRuntime } from '@axlsdk/axl';
|
|
2
|
-
import { B as BroadcastTarget, C as ConnectionManager, S as StudioEnv } from './connection-manager-
|
|
2
|
+
import { B as BroadcastTarget, C as ConnectionManager, a as BufferCaps, S as StudioEnv } from './connection-manager-DAuqk9lM.js';
|
|
3
3
|
import * as hono_types from 'hono/types';
|
|
4
4
|
import * as hono from 'hono';
|
|
5
5
|
import { IncomingMessage, ServerResponse, Server } from 'node:http';
|
|
@@ -95,9 +95,10 @@ type StudioMiddlewareOptions = {
|
|
|
95
95
|
* `true` to deliver to this connection, `false` to drop.
|
|
96
96
|
*
|
|
97
97
|
* `event` is the parsed event payload — for the `trace:*` channel this is
|
|
98
|
-
*
|
|
99
|
-
* for `costs` it's a `CostData`; for `execution:*` / `eval:*` it's
|
|
100
|
-
*
|
|
98
|
+
* an `AxlEvent` from `@axlsdk/axl` (narrow via the discriminated union);
|
|
99
|
+
* for `costs` it's a `CostData`; for `execution:*` / `eval:*` it's the
|
|
100
|
+
* legacy stream-event shape (until PR 3 collapses the wire to `AxlEvent`).
|
|
101
|
+
* Typed `unknown` because the filter runs for every channel.
|
|
101
102
|
* `metadata` is the per-connection metadata attached by `verifyUpgrade`,
|
|
102
103
|
* or `undefined` if `verifyUpgrade` returned a bare boolean.
|
|
103
104
|
*
|
|
@@ -105,15 +106,15 @@ type StudioMiddlewareOptions = {
|
|
|
105
106
|
* cannot accidentally leak events cross-tenant.
|
|
106
107
|
*
|
|
107
108
|
* @example Scope trace events by tenant id stored on agent metadata
|
|
108
|
-
* import type {
|
|
109
|
+
* import type { AxlEvent } from '@axlsdk/axl';
|
|
109
110
|
*
|
|
110
111
|
* filterTraceEvent: (event, meta) => {
|
|
111
112
|
* const m = meta as { tenantId?: string } | undefined;
|
|
112
113
|
* if (!m?.tenantId) return false;
|
|
113
|
-
* const e = event as
|
|
114
|
-
* // Only narrow event.type === '
|
|
114
|
+
* const e = event as AxlEvent;
|
|
115
|
+
* // Only narrow event.type === 'agent_call_end' events; pass structural
|
|
115
116
|
* // events (workflow_start/end, cost updates) through unconditionally.
|
|
116
|
-
* if (e.type !== '
|
|
117
|
+
* if (e.type !== 'agent_call_end') return true;
|
|
117
118
|
* return e.workflow?.startsWith(`${m.tenantId}:`) ?? false;
|
|
118
119
|
* }
|
|
119
120
|
*/
|
|
@@ -157,6 +158,28 @@ type StudioMiddlewareOptions = {
|
|
|
157
158
|
* }
|
|
158
159
|
*/
|
|
159
160
|
evals?: EvalLoaderConfig;
|
|
161
|
+
/**
|
|
162
|
+
* Override the default WebSocket replay-buffer resource caps for
|
|
163
|
+
* production deployments under sustained execution churn.
|
|
164
|
+
*
|
|
165
|
+
* Defaults: `maxEventsPerBuffer: 1000`, `maxBytesPerBuffer: 4 MiB`,
|
|
166
|
+
* `maxActiveBuffers: 256`. Worst-case memory is roughly
|
|
167
|
+
* `maxActiveBuffers × maxBytesPerBuffer` (≈1 GiB at defaults). Tighten
|
|
168
|
+
* either dimension to lower the ceiling at the cost of late-subscriber
|
|
169
|
+
* replay coverage; raise them if you need more event headroom on long
|
|
170
|
+
* verbose-mode streams.
|
|
171
|
+
*
|
|
172
|
+
* Terminal `done` / `error` events are always buffered regardless of
|
|
173
|
+
* caps, so a late subscriber to a completed stream still sees the
|
|
174
|
+
* outcome — only intermediate non-terminal events are dropped.
|
|
175
|
+
*
|
|
176
|
+
* All fields are optional; passing `{}` is a no-op.
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* // Halve memory ceiling on a high-churn deployment
|
|
180
|
+
* bufferCaps: { maxActiveBuffers: 128, maxBytesPerBuffer: 2 * 1024 * 1024 }
|
|
181
|
+
*/
|
|
182
|
+
bufferCaps?: BufferCaps;
|
|
160
183
|
};
|
|
161
184
|
/**
|
|
162
185
|
* Minimal contract a WebSocket connection must satisfy.
|
|
@@ -180,4 +203,4 @@ declare function createStudioMiddleware(options: StudioMiddlewareOptions): {
|
|
|
180
203
|
close: () => void;
|
|
181
204
|
};
|
|
182
205
|
|
|
183
|
-
export { type EvalLoaderConfig, type StudioMiddleware, type StudioMiddlewareOptions, type StudioWebSocket, createStudioMiddleware, handleWsMessage };
|
|
206
|
+
export { BufferCaps, type EvalLoaderConfig, type StudioMiddleware, type StudioMiddlewareOptions, type StudioWebSocket, createStudioMiddleware, handleWsMessage };
|
package/dist/middleware.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
createServer,
|
|
6
6
|
handleWsMessage
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-RE6VPUXA.js";
|
|
8
8
|
|
|
9
9
|
// src/middleware.ts
|
|
10
10
|
import { resolve as resolve2, dirname as dirname2 } from "path";
|
|
@@ -182,7 +182,8 @@ function createStudioMiddleware(options) {
|
|
|
182
182
|
readOnly,
|
|
183
183
|
cors: false,
|
|
184
184
|
// Host framework owns CORS policy
|
|
185
|
-
evalLoader
|
|
185
|
+
evalLoader,
|
|
186
|
+
bufferCaps: options.bufferCaps
|
|
186
187
|
});
|
|
187
188
|
if (filterTraceEvent) {
|
|
188
189
|
connMgr.setFilter(filterTraceEvent);
|
package/dist/middleware.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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 (Express `app.use()`,\n * Fastify `register()`, Hono `app.route()`, etc.). The framework is\n * expected to strip the prefix from `req.url` before calling the handler.\n *\n * Do not set basePath when using a raw `http.Server` as the root handler —\n * leave it empty and mount at root instead.\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 *\n * Return either a boolean (`true` allow, `false` reject) or an object\n * `{ allowed: boolean, metadata?: unknown }` to ALSO attach per-connection\n * metadata that `filterTraceEvent` can later read — typically the auth\n * subject (userId, tenantId, role). 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 * @example Multi-tenant: extract tenant from JWT, attach as metadata\n * verifyUpgrade: async (req) => {\n * const token = req.headers.authorization?.slice(7);\n * const claims = await verifyJwt(token);\n * if (!claims) return false;\n * return { allowed: true, metadata: { userId: claims.sub, tenantId: claims.tid } };\n * }\n */\n verifyUpgrade?: (\n req: IncomingMessage,\n ) =>\n | boolean\n | { allowed: boolean; metadata?: unknown }\n | Promise<boolean | { allowed: boolean; metadata?: unknown }>;\n\n /**\n * Optional per-event filter used by multi-tenant deployments to scope the\n * trace firehose. Called on every outbound WebSocket broadcast; return\n * `true` to deliver to this connection, `false` to drop.\n *\n * `event` is the parsed event payload — for the `trace:*` channel this is\n * a `TraceEvent` from `@axlsdk/axl` (narrow via the discriminated union);\n * for `costs` it's a `CostData`; for `execution:*` / `eval:*` it's a\n * `StreamEvent`. Typed `unknown` because the filter runs for every channel.\n * `metadata` is the per-connection metadata attached by `verifyUpgrade`,\n * or `undefined` if `verifyUpgrade` returned a bare boolean.\n *\n * Filter errors are treated as `drop` (fail-closed) so a buggy predicate\n * cannot accidentally leak events cross-tenant.\n *\n * @example Scope trace events by tenant id stored on agent metadata\n * import type { TraceEvent } from '@axlsdk/axl';\n *\n * filterTraceEvent: (event, meta) => {\n * const m = meta as { tenantId?: string } | undefined;\n * if (!m?.tenantId) return false;\n * const e = event as TraceEvent;\n * // Only narrow event.type === 'agent_call' events; pass structural\n * // events (workflow_start/end, cost updates) through unconditionally.\n * if (e.type !== 'agent_call') return true;\n * return e.workflow?.startsWith(`${m.tenantId}:`) ?? false;\n * }\n */\n filterTraceEvent?: (event: unknown, metadata: unknown) => 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 {\n runtime,\n serveClient = true,\n verifyUpgrade,\n readOnly = false,\n filterTraceEvent,\n } = 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 =\n import.meta.dirname ??\n (typeof __dirname !== 'undefined' ? __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, closeActiveRuns, closeAggregators } = createServer({\n runtime,\n staticRoot,\n basePath,\n readOnly,\n cors: false, // Host framework owns CORS policy\n evalLoader,\n });\n\n // Install the broadcast filter once at construction time. The connMgr\n // applies it on every outbound event; `metadata` is attached to each\n // connection after a successful `verifyUpgrade`.\n if (filterTraceEvent) {\n connMgr.setFilter(filterTraceEvent);\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 via @hono/node-server.\n // overrideGlobalObjects: false prevents replacing global.Request/Response,\n // 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\n // Express/NestJS/Koa body parsers consume the raw IncomingMessage stream\n // and store the parsed result on req.body. When that happens,\n // @hono/node-server's getRequestListener reads an empty stream and Hono\n // sees no body. To fix this, we re-serialize req.body as req.rawBody —\n // a Buffer that getRequestListener checks before falling back to the\n // stream (see newRequestFromIncoming in @hono/node-server/dist/listener.js).\n //\n // Verified against @hono/node-server@1.19.9. If this breaks after an\n // upgrade, check whether the rawBody instanceof Buffer check still exists\n // in newRequestFromIncoming.\n const reqAny = req as unknown as Record<string, unknown>;\n if (reqAny.body != null && !reqAny.rawBody) {\n try {\n reqAny.rawBody = Buffer.from(JSON.stringify(reqAny.body));\n } catch {\n // Non-serializable body — Hono will see an empty body\n }\n }\n\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 // `metadata` is whatever `verifyUpgrade` attached — passed through so the\n // `filterTraceEvent` callback can read it on every outbound event.\n function handleWebSocket(ws: StudioWebSocket, metadata?: unknown) {\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 if (metadata !== undefined) {\n connMgr.setMetadata(socket, metadata);\n }\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 // Early-reject if close() already started. This is a cheap pre-check —\n // we repeat it AFTER the async verifyUpgrade resolves to close the race\n // where close() runs while verifyUpgrade is still in flight.\n if (closed) {\n socket.destroy();\n return;\n }\n\n // Apply auth verification and capture optional per-connection metadata\n // (e.g. { userId, tenantId }) that filterTraceEvent can later read.\n let connectionMetadata: unknown;\n if (verifyUpgrade) {\n try {\n const result = await verifyUpgrade(req);\n // Normalize both return shapes: plain boolean or { allowed, metadata }\n const allowed = typeof result === 'boolean' ? result : result.allowed;\n if (!allowed) {\n socket.write('HTTP/1.1 401 Unauthorized\\r\\n\\r\\n');\n socket.destroy();\n return;\n }\n if (typeof result === 'object' && result !== null) {\n connectionMetadata = result.metadata;\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() having run during async verifyUpgrade. We check\n // BOTH `closed` (set at the top of `close()`) and `!wss` (set at the\n // end). `closed` is the authoritative signal — checking `!wss` alone\n // wouldn't catch the window where `close()` has marked the middleware\n // as shut down but hasn't yet torn down the WebSocketServer. Failing\n // open here would leak a connection past the middleware's lifetime\n // and leave it holding references the host expected to be released.\n if (closed || !wss) {\n socket.destroy();\n return;\n }\n\n wss.handleUpgrade(req, socket, head, (ws) => {\n handleWebSocket(ws, connectionMetadata);\n });\n };\n\n server.on('upgrade', upgradeHandler);\n }\n\n // Cleanup function for lifecycle management.\n function close() {\n closed = true;\n\n // Abort active streaming eval runs before closing connections\n closeActiveRuns();\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 // Close all aggregators (clear intervals and unsubscribe listeners)\n closeAggregators();\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 =\n import.meta.dirname ??\n (typeof __dirname !== 'undefined' ? __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';\nimport { importModule } from './cli-utils.js';\n\n// In the CJS bundle, tsup stubs import.meta as {} so import.meta.url is\n// undefined. Fall back to __filename (which CJS defines) converted to a\n// file:// URL so tsImport() gets a valid parentURL.\nconst parentURL: string =\n import.meta.url ?? pathToFileURL(typeof __filename !== 'undefined' ? __filename : __dirname).href;\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 for (const file of files) {\n try {\n const mod = await importModule(file, parentURL);\n const evalConfig = mod.default?.default ?? 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\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"]}
|
|
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 { BufferCaps } from './server/ws/connection-manager.js';\nimport type { AxlRuntime } from '@axlsdk/axl';\nimport type { IncomingMessage, ServerResponse, Server } from 'node:http';\n\nexport type { EvalLoaderConfig } from './eval-loader.js';\nexport type { BufferCaps } from './server/ws/connection-manager.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 (Express `app.use()`,\n * Fastify `register()`, Hono `app.route()`, etc.). The framework is\n * expected to strip the prefix from `req.url` before calling the handler.\n *\n * Do not set basePath when using a raw `http.Server` as the root handler —\n * leave it empty and mount at root instead.\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 *\n * Return either a boolean (`true` allow, `false` reject) or an object\n * `{ allowed: boolean, metadata?: unknown }` to ALSO attach per-connection\n * metadata that `filterTraceEvent` can later read — typically the auth\n * subject (userId, tenantId, role). 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 * @example Multi-tenant: extract tenant from JWT, attach as metadata\n * verifyUpgrade: async (req) => {\n * const token = req.headers.authorization?.slice(7);\n * const claims = await verifyJwt(token);\n * if (!claims) return false;\n * return { allowed: true, metadata: { userId: claims.sub, tenantId: claims.tid } };\n * }\n */\n verifyUpgrade?: (\n req: IncomingMessage,\n ) =>\n | boolean\n | { allowed: boolean; metadata?: unknown }\n | Promise<boolean | { allowed: boolean; metadata?: unknown }>;\n\n /**\n * Optional per-event filter used by multi-tenant deployments to scope the\n * trace firehose. Called on every outbound WebSocket broadcast; return\n * `true` to deliver to this connection, `false` to drop.\n *\n * `event` is the parsed event payload — for the `trace:*` channel this is\n * an `AxlEvent` from `@axlsdk/axl` (narrow via the discriminated union);\n * for `costs` it's a `CostData`; for `execution:*` / `eval:*` it's the\n * legacy stream-event shape (until PR 3 collapses the wire to `AxlEvent`).\n * Typed `unknown` because the filter runs for every channel.\n * `metadata` is the per-connection metadata attached by `verifyUpgrade`,\n * or `undefined` if `verifyUpgrade` returned a bare boolean.\n *\n * Filter errors are treated as `drop` (fail-closed) so a buggy predicate\n * cannot accidentally leak events cross-tenant.\n *\n * @example Scope trace events by tenant id stored on agent metadata\n * import type { AxlEvent } from '@axlsdk/axl';\n *\n * filterTraceEvent: (event, meta) => {\n * const m = meta as { tenantId?: string } | undefined;\n * if (!m?.tenantId) return false;\n * const e = event as AxlEvent;\n * // Only narrow event.type === 'agent_call_end' events; pass structural\n * // events (workflow_start/end, cost updates) through unconditionally.\n * if (e.type !== 'agent_call_end') return true;\n * return e.workflow?.startsWith(`${m.tenantId}:`) ?? false;\n * }\n */\n filterTraceEvent?: (event: unknown, metadata: unknown) => 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 * Override the default WebSocket replay-buffer resource caps for\n * production deployments under sustained execution churn.\n *\n * Defaults: `maxEventsPerBuffer: 1000`, `maxBytesPerBuffer: 4 MiB`,\n * `maxActiveBuffers: 256`. Worst-case memory is roughly\n * `maxActiveBuffers × maxBytesPerBuffer` (≈1 GiB at defaults). Tighten\n * either dimension to lower the ceiling at the cost of late-subscriber\n * replay coverage; raise them if you need more event headroom on long\n * verbose-mode streams.\n *\n * Terminal `done` / `error` events are always buffered regardless of\n * caps, so a late subscriber to a completed stream still sees the\n * outcome — only intermediate non-terminal events are dropped.\n *\n * All fields are optional; passing `{}` is a no-op.\n *\n * @example\n * // Halve memory ceiling on a high-churn deployment\n * bufferCaps: { maxActiveBuffers: 128, maxBytesPerBuffer: 2 * 1024 * 1024 }\n */\n bufferCaps?: BufferCaps;\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 {\n runtime,\n serveClient = true,\n verifyUpgrade,\n readOnly = false,\n filterTraceEvent,\n } = 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 =\n import.meta.dirname ??\n (typeof __dirname !== 'undefined' ? __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, closeActiveRuns, closeAggregators } = createServer({\n runtime,\n staticRoot,\n basePath,\n readOnly,\n cors: false, // Host framework owns CORS policy\n evalLoader,\n bufferCaps: options.bufferCaps,\n });\n\n // Install the broadcast filter once at construction time. The connMgr\n // applies it on every outbound event; `metadata` is attached to each\n // connection after a successful `verifyUpgrade`.\n if (filterTraceEvent) {\n connMgr.setFilter(filterTraceEvent);\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 via @hono/node-server.\n // overrideGlobalObjects: false prevents replacing global.Request/Response,\n // 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\n // Express/NestJS/Koa body parsers consume the raw IncomingMessage stream\n // and store the parsed result on req.body. When that happens,\n // @hono/node-server's getRequestListener reads an empty stream and Hono\n // sees no body. To fix this, we re-serialize req.body as req.rawBody —\n // a Buffer that getRequestListener checks before falling back to the\n // stream (see newRequestFromIncoming in @hono/node-server/dist/listener.js).\n //\n // Verified against @hono/node-server@1.19.9. If this breaks after an\n // upgrade, check whether the rawBody instanceof Buffer check still exists\n // in newRequestFromIncoming.\n const reqAny = req as unknown as Record<string, unknown>;\n if (reqAny.body != null && !reqAny.rawBody) {\n try {\n reqAny.rawBody = Buffer.from(JSON.stringify(reqAny.body));\n } catch {\n // Non-serializable body — Hono will see an empty body\n }\n }\n\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 // `metadata` is whatever `verifyUpgrade` attached — passed through so the\n // `filterTraceEvent` callback can read it on every outbound event.\n function handleWebSocket(ws: StudioWebSocket, metadata?: unknown) {\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 if (metadata !== undefined) {\n connMgr.setMetadata(socket, metadata);\n }\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 // Early-reject if close() already started. This is a cheap pre-check —\n // we repeat it AFTER the async verifyUpgrade resolves to close the race\n // where close() runs while verifyUpgrade is still in flight.\n if (closed) {\n socket.destroy();\n return;\n }\n\n // Apply auth verification and capture optional per-connection metadata\n // (e.g. { userId, tenantId }) that filterTraceEvent can later read.\n let connectionMetadata: unknown;\n if (verifyUpgrade) {\n try {\n const result = await verifyUpgrade(req);\n // Normalize both return shapes: plain boolean or { allowed, metadata }\n const allowed = typeof result === 'boolean' ? result : result.allowed;\n if (!allowed) {\n socket.write('HTTP/1.1 401 Unauthorized\\r\\n\\r\\n');\n socket.destroy();\n return;\n }\n if (typeof result === 'object' && result !== null) {\n connectionMetadata = result.metadata;\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() having run during async verifyUpgrade. We check\n // BOTH `closed` (set at the top of `close()`) and `!wss` (set at the\n // end). `closed` is the authoritative signal — checking `!wss` alone\n // wouldn't catch the window where `close()` has marked the middleware\n // as shut down but hasn't yet torn down the WebSocketServer. Failing\n // open here would leak a connection past the middleware's lifetime\n // and leave it holding references the host expected to be released.\n if (closed || !wss) {\n socket.destroy();\n return;\n }\n\n wss.handleUpgrade(req, socket, head, (ws) => {\n handleWebSocket(ws, connectionMetadata);\n });\n };\n\n server.on('upgrade', upgradeHandler);\n }\n\n // Cleanup function for lifecycle management.\n function close() {\n closed = true;\n\n // Abort active streaming eval runs before closing connections\n closeActiveRuns();\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 // Close all aggregators (clear intervals and unsubscribe listeners)\n closeAggregators();\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 =\n import.meta.dirname ??\n (typeof __dirname !== 'undefined' ? __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';\nimport { importModule } from './cli-utils.js';\n\n// In the CJS bundle, tsup stubs import.meta as {} so import.meta.url is\n// undefined. Fall back to __filename (which CJS defines) converted to a\n// file:// URL so tsImport() gets a valid parentURL.\nconst parentURL: string =\n import.meta.url ?? pathToFileURL(typeof __filename !== 'undefined' ? __filename : __dirname).href;\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 for (const file of files) {\n try {\n const mod = await importModule(file, parentURL);\n const evalConfig = mod.default?.default ?? 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\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"]}
|