@agentxjs/devtools 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/bdd/cucumber.config.ts","../../src/bdd/playwright.ts","../../src/bdd/dev-server.ts","../../src/bdd/paths.ts","../../src/bdd/agent-ui-tester.ts","../../src/bdd/agent-doc-tester.ts"],"sourcesContent":["/**\n * Shared Cucumber configuration for BDD tests\n *\n * Usage in project's cucumber.js:\n *\n * ```js\n * import { createCucumberConfig } from \"@agentxjs/devtools/bdd\";\n *\n * export default createCucumberConfig({\n * paths: [\"bdd/journeys/** /*.feature\"],\n * import: [\"bdd/steps/** /*.ts\"],\n * });\n * ```\n */\n\nexport interface CucumberConfigOptions {\n /** Feature file paths */\n paths: string[];\n /** Step definition paths */\n import: string[];\n /** Tags to filter (default: exclude @pending and @skip) */\n tags?: string;\n /** Default timeout in ms (default: 30000) */\n timeout?: number;\n /** Format output (default: progress) */\n format?: string[];\n}\n\nexport function createCucumberConfig(options: CucumberConfigOptions) {\n return {\n format: options.format ?? [\"progress\"],\n formatOptions: { snippetInterface: \"async-await\" },\n import: options.import,\n paths: options.paths,\n tags: options.tags ?? \"not @pending and not @skip\",\n worldParameters: {\n defaultTimeout: options.timeout ?? 30000,\n },\n };\n}\n","/**\n * Playwright utilities for BDD testing\n *\n * Uses system Chrome to avoid downloading Chromium.\n * Install: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 bun add -d @playwright/test\n *\n * Browser lifecycle:\n * - Single browser instance for all tests\n * - Single page (tab) reused across scenarios\n * - resetPage() clears state between scenarios\n */\n\nimport { chromium, Browser, Page } from \"@playwright/test\";\n\nexport interface BrowserOptions {\n headless?: boolean;\n slowMo?: number;\n}\n\nlet browser: Browser | null = null;\nlet page: Page | null = null;\n\n/**\n * Launch browser using system Chrome (singleton)\n */\nexport async function launchBrowser(options: BrowserOptions = {}): Promise<Browser> {\n if (browser) return browser;\n\n const headless = options.headless ?? process.env.CI === \"true\";\n\n browser = await chromium.launch({\n channel: \"chrome\",\n headless,\n slowMo: options.slowMo,\n });\n\n return browser;\n}\n\n/**\n * Get or create a page (singleton, reused across scenarios)\n */\nexport async function getPage(): Promise<Page> {\n if (page && !page.isClosed()) return page;\n\n const b = await launchBrowser();\n page = await b.newPage();\n return page;\n}\n\n/**\n * Reset page state between scenarios (without closing)\n * Use this instead of closePage() for faster tests\n */\nexport async function resetPage(): Promise<void> {\n try {\n if (page && !page.isClosed()) {\n const context = page.context();\n await context.clearCookies();\n await page.goto(\"about:blank\");\n }\n } catch {\n // Browser may have crashed, will recreate on next getPage()\n page = null;\n }\n}\n\n/**\n * Close current page\n * @deprecated Use resetPage() for faster tests. Only use closePage() if you need full isolation.\n */\nexport async function closePage(): Promise<void> {\n if (page) {\n await page.close();\n page = null;\n }\n}\n\n/**\n * Close browser and cleanup\n */\nexport async function closeBrowser(): Promise<void> {\n if (page && !page.isClosed()) {\n await page.close();\n page = null;\n }\n if (browser) {\n await browser.close();\n browser = null;\n }\n}\n\n/**\n * Wait for a URL to be accessible\n */\nexport async function waitForUrl(url: string, timeout = 30000): Promise<boolean> {\n const start = Date.now();\n while (Date.now() - start < timeout) {\n try {\n const response = await fetch(url);\n if (response.ok) {\n return true;\n }\n } catch {\n // Not ready yet\n }\n await new Promise((r) => setTimeout(r, 500));\n }\n return false;\n}\n","/**\n * Dev server utilities for BDD testing\n *\n * Start and stop dev servers during test runs.\n */\n\nimport { spawn, ChildProcess } from \"node:child_process\";\nimport { waitForUrl } from \"./playwright\";\n\nexport interface DevServerOptions {\n /** Working directory */\n cwd: string;\n /** Command to run (default: \"bun\") */\n command?: string;\n /** Command arguments (default: [\"run\", \"dev\"]) */\n args?: string[];\n /** Port to wait for */\n port: number;\n /** Startup timeout in ms (default: 30000) */\n timeout?: number;\n /** Show server output (default: false, or true if DEBUG env is set) */\n debug?: boolean;\n}\n\nlet devServer: ChildProcess | null = null;\n\n/**\n * Start a dev server and wait for it to be ready\n */\nexport async function startDevServer(options: DevServerOptions): Promise<void> {\n if (devServer) return;\n\n const {\n cwd,\n command = \"bun\",\n args = [\"run\", \"dev\"],\n port,\n timeout = 30000,\n debug = !!process.env.DEBUG,\n } = options;\n\n devServer = spawn(command, args, {\n cwd,\n stdio: \"pipe\",\n detached: false,\n });\n\n if (debug) {\n devServer.stdout?.on(\"data\", (data) => {\n console.log(\"[dev-server]\", data.toString());\n });\n\n devServer.stderr?.on(\"data\", (data) => {\n console.error(\"[dev-server error]\", data.toString());\n });\n }\n\n const url = `http://localhost:${port}`;\n const ready = await waitForUrl(url, timeout);\n\n if (!ready) {\n stopDevServer();\n throw new Error(`Dev server failed to start on port ${port}`);\n }\n}\n\n/**\n * Stop the dev server\n */\nexport function stopDevServer(): void {\n if (devServer) {\n devServer.kill(\"SIGTERM\");\n devServer = null;\n }\n}\n\n/**\n * Get the dev server process (for advanced use)\n */\nexport function getDevServer(): ChildProcess | null {\n return devServer;\n}\n","/**\n * Unified path utilities for BDD testing\n *\n * Provides consistent path resolution across all packages.\n */\n\nimport { resolve, dirname } from \"node:path\";\nimport { existsSync, mkdtempSync, mkdirSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\n\n// ============================================================================\n// Path Resolution\n// ============================================================================\n\n/**\n * Find the monorepo root by looking for root package.json with workspaces\n */\nexport function findMonorepoRoot(startDir: string = process.cwd()): string {\n let dir = startDir;\n while (dir !== \"/\") {\n const pkgPath = resolve(dir, \"package.json\");\n if (existsSync(pkgPath)) {\n try {\n const pkg = require(pkgPath);\n if (pkg.workspaces || pkg.private === true) {\n // Check if it looks like a monorepo root\n const hasPackages = existsSync(resolve(dir, \"packages\"));\n const hasApps = existsSync(resolve(dir, \"apps\"));\n if (hasPackages || hasApps) {\n return dir;\n }\n }\n } catch {\n // Ignore errors\n }\n }\n dir = dirname(dir);\n }\n return startDir;\n}\n\n/**\n * Get the current package root (where package.json is)\n */\nexport function getPackageRoot(startDir: string = process.cwd()): string {\n let dir = startDir;\n while (dir !== \"/\") {\n if (existsSync(resolve(dir, \"package.json\"))) {\n return dir;\n }\n dir = dirname(dir);\n }\n return startDir;\n}\n\n// ============================================================================\n// Standard Paths\n// ============================================================================\n\nlet _monorepoRoot: string | null = null;\nlet _packageRoot: string | null = null;\nlet _tempDir: string | null = null;\n\n/**\n * Monorepo root directory\n */\nexport function getMonorepoPath(): string {\n if (!_monorepoRoot) {\n _monorepoRoot = findMonorepoRoot();\n }\n return _monorepoRoot;\n}\n\n/**\n * Current package root directory\n */\nexport function getPackagePath(): string {\n if (!_packageRoot) {\n _packageRoot = getPackageRoot();\n }\n return _packageRoot;\n}\n\n/**\n * BDD directory for current package\n */\nexport function getBddPath(): string {\n return resolve(getPackagePath(), \"bdd\");\n}\n\n/**\n * Fixtures directory for current package's BDD tests\n */\nexport function getFixturesPath(subdir?: string): string {\n const base = resolve(getBddPath(), \"fixtures\");\n return subdir ? resolve(base, subdir) : base;\n}\n\n/**\n * Get or create a temporary directory for tests\n */\nexport function getTempPath(prefix: string = \"bdd-\"): string {\n if (!_tempDir) {\n _tempDir = mkdtempSync(resolve(tmpdir(), prefix));\n }\n return _tempDir;\n}\n\n/**\n * Ensure a directory exists, creating it if necessary\n */\nexport function ensureDir(path: string): string {\n if (!existsSync(path)) {\n mkdirSync(path, { recursive: true });\n }\n return path;\n}\n\n/**\n * Reset cached paths (useful for testing)\n */\nexport function resetPaths(): void {\n _monorepoRoot = null;\n _packageRoot = null;\n _tempDir = null;\n}\n\n// ============================================================================\n// Convenience exports\n// ============================================================================\n\nexport const paths = {\n monorepo: getMonorepoPath,\n package: getPackagePath,\n bdd: getBddPath,\n fixtures: getFixturesPath,\n temp: getTempPath,\n ensure: ensureDir,\n reset: resetPaths,\n};\n","import { execFileSync } from \"node:child_process\";\nimport { readFileSync } from \"node:fs\";\nimport { resolve, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nconst SKILL_PATH = resolve(__dirname, \"../../../../.claude/skills/agent-browser/SKILL.md\");\n\nfunction loadSystemPrompt(headed = false): string {\n let skillContent = \"\";\n try {\n skillContent = readFileSync(SKILL_PATH, \"utf-8\");\n } catch {\n // Skill file not found, continue without it\n }\n\n return `You are a UI tester. You test web application scenarios using the agent-browser CLI.\n\nRULES:\n- ONLY use agent-browser commands via Bash tool\n- Use ${headed ? \"--headed \" : \"\"}--executable-path \"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome\" for all commands\n- After each navigation or click, run: agent-browser snapshot -i\n- Refs (@e1, @e2) are invalidated after page changes — always re-snapshot\n- At the end, close the browser with: agent-browser close\n- Output your result as a single line: PASS or FAIL followed by a brief reason\n\n${skillContent ? `AGENT-BROWSER REFERENCE:\\n${skillContent}` : \"\"}`;\n}\n\nexport interface UiTestResult {\n passed: boolean;\n output: string;\n}\n\nexport interface UiTesterOptions {\n model?: string;\n baseUrl?: string;\n timeout?: number;\n /** Show browser window (default: false) */\n headed?: boolean;\n}\n\n/**\n * Run a UI test scenario using Claude Code CLI + agent-browser.\n *\n * BDD scripts must run under Node.js (not Bun) to avoid claude CLI auth bug.\n */\nexport function agentUiTester(prompt: string, options: UiTesterOptions = {}): UiTestResult {\n const { model = \"haiku\", baseUrl, timeout = 300_000, headed = false } = options;\n\n const fullPrompt = baseUrl ? `Base URL: ${baseUrl}\\n\\n${prompt}` : prompt;\n\n const systemPrompt = loadSystemPrompt(headed);\n\n // Filter out CLAUDE* env vars to avoid auth conflicts when spawned from Claude Code\n const cleanEnv = Object.fromEntries(\n Object.entries(process.env).filter(([k]) => !k.startsWith(\"CLAUDE\"))\n );\n\n try {\n const output = execFileSync(\n \"claude\",\n [\n \"-p\",\n fullPrompt,\n \"--model\",\n model,\n \"--append-system-prompt\",\n systemPrompt,\n \"--allowedTools\",\n \"Bash(agent-browser:*)\",\n ],\n {\n encoding: \"utf-8\",\n timeout,\n env: cleanEnv,\n maxBuffer: 10 * 1024 * 1024,\n }\n ).trim();\n\n const passed = /\\*{0,2}PASS\\*{0,2}\\b/m.test(output);\n return { passed, output };\n } catch (error: any) {\n const output = error.stdout || error.stderr || error.message || \"Unknown error\";\n return { passed: false, output: output.trim() };\n }\n}\n","import { readFileSync, existsSync } from \"node:fs\";\nimport { env } from \"../env\";\n\nconst SYSTEM_PROMPT = `You are a documentation reviewer evaluating documents from the reader's experience.\n\nEVALUATION DIMENSIONS:\n1. Completeness — All required information is present. Nothing critical is missing.\n2. Logic — Structure flows naturally. Concepts build on each other without jumps.\n3. Readability — A newcomer can follow without confusion. No unexplained jargon.\n\nRULES:\n- Read the provided document carefully\n- Evaluate each requirement listed in the prompt against ALL three dimensions\n- Be strict but fair — the document should genuinely help the reader achieve the stated goal\n- Output your result as a single line: PASS or FAIL followed by a brief reason\n- If FAIL, list which specific requirements are not met and which dimension they violate`;\n\nexport interface DocTestResult {\n passed: boolean;\n output: string;\n}\n\nexport interface DocTesterOptions {\n /** LLM provider (default: \"anthropic\") */\n provider?: string;\n /** Model name */\n model?: string;\n /** API key (reads from env if not provided) */\n apiKey?: string;\n /** Base URL (reads from env if not provided) */\n baseUrl?: string;\n /** Timeout in ms */\n timeout?: number;\n}\n\n/**\n * Evaluate a document against requirements using AgentX.\n *\n * Uses agentxjs local mode — no subprocess, no CLI, no auth issues.\n * Requires `agentxjs` as a peer dependency.\n */\nexport async function agentDocTester(\n options: {\n files: string[];\n requirements: string;\n },\n testerOptions: DocTesterOptions = {}\n): Promise<DocTestResult> {\n const {\n provider = process.env.AGENTX_PROVIDER || \"anthropic\",\n model = env.model,\n apiKey = env.apiKey || \"\",\n baseUrl = env.baseUrl,\n timeout = 120_000,\n } = testerOptions;\n\n const docContents = options.files\n .map((filePath) => {\n if (!existsSync(filePath)) {\n return `--- ${filePath} ---\\n[FILE NOT FOUND]`;\n }\n return `--- ${filePath} ---\\n${readFileSync(filePath, \"utf-8\")}`;\n })\n .join(\"\\n\\n\");\n\n const userPrompt = [\n \"Evaluate the following document(s) against the requirements below.\",\n \"\",\n \"DOCUMENTS:\",\n docContents,\n \"\",\n \"REQUIREMENTS:\",\n options.requirements,\n \"\",\n \"Evaluate each requirement. Output PASS if all are met, FAIL if any are not.\",\n ].join(\"\\n\");\n\n // Dynamic import to avoid circular dependency (devtools ↔ agentxjs)\n // Use variable to prevent TypeScript DTS from resolving the module\n const moduleName = \"agentxjs\";\n const agentxjs: any = await import(/* @vite-ignore */ moduleName);\n const createAgentX: (...args: any[]) => Promise<any> = agentxjs.createAgentX;\n\n let agentx: any = null;\n\n try {\n agentx = await createAgentX({\n apiKey,\n provider,\n model,\n baseUrl,\n logLevel: \"silent\",\n });\n\n await agentx.containers.create(\"doc-tester\");\n\n const { record: image } = await agentx.images.create({\n containerId: \"doc-tester\",\n systemPrompt: SYSTEM_PROMPT,\n });\n\n const { agentId } = await agentx.agents.create({ imageId: image.imageId });\n\n // Collect response text\n let output = \"\";\n agentx.on(\"text_delta\", (e: any) => {\n output += e.data.text;\n });\n\n // Send prompt and wait for completion\n await Promise.race([\n agentx.sessions.send(agentId, userPrompt),\n new Promise((_, reject) => setTimeout(() => reject(new Error(\"Timeout\")), timeout)),\n ]);\n\n output = output.trim();\n const passed = /\\*{0,2}PASS\\*{0,2}\\b/m.test(output);\n return { passed, output };\n } catch (error: any) {\n return { passed: false, output: error.message || \"Unknown error\" };\n } finally {\n if (agentx) {\n try {\n await agentx.shutdown();\n } catch {\n // ignore shutdown errors\n }\n }\n }\n}\n"],"mappings":"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","names":["resolve","dirname","readFileSync","existsSync","existsSync","readFileSync"]}
1
+ {"version":3,"sources":["../../src/bdd/agent-doc-tester.ts","../../src/bdd/agent-ui-tester.ts","../../src/bdd/cucumber.config.ts","../../src/bdd/dev-server.ts","../../src/bdd/playwright.ts","../../src/bdd/paths.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { env } from \"../env\";\n\nconst SYSTEM_PROMPT = `You are a documentation reviewer evaluating documents from the reader's experience.\n\nEVALUATION DIMENSIONS:\n1. Completeness — All required information is present. Nothing critical is missing.\n2. Logic — Structure flows naturally. Concepts build on each other without jumps.\n3. Readability — A newcomer can follow without confusion. No unexplained jargon.\n\nRULES:\n- Read the provided document carefully\n- Evaluate each requirement listed in the prompt against ALL three dimensions\n- Be strict but fair — the document should genuinely help the reader achieve the stated goal\n- Output your result as a single line: PASS or FAIL followed by a brief reason\n- If FAIL, list which specific requirements are not met and which dimension they violate`;\n\nexport interface DocTestResult {\n passed: boolean;\n output: string;\n}\n\nexport interface DocTesterOptions {\n /** LLM provider (default: \"anthropic\") */\n provider?: string;\n /** Model name */\n model?: string;\n /** API key (reads from env if not provided) */\n apiKey?: string;\n /** Base URL (reads from env if not provided) */\n baseUrl?: string;\n /** Timeout in ms */\n timeout?: number;\n}\n\n/**\n * Evaluate a document against requirements using AgentX.\n *\n * Uses agentxjs local mode — no subprocess, no CLI, no auth issues.\n * Requires `agentxjs` as a peer dependency.\n */\nexport async function agentDocTester(\n options: {\n files: string[];\n requirements: string;\n },\n testerOptions: DocTesterOptions = {}\n): Promise<DocTestResult> {\n const {\n provider = process.env.AGENTX_PROVIDER || \"anthropic\",\n model = env.model,\n apiKey = env.apiKey || \"\",\n baseUrl = env.baseUrl,\n timeout = 120_000,\n } = testerOptions;\n\n const docContents = options.files\n .map((filePath) => {\n if (!existsSync(filePath)) {\n return `--- ${filePath} ---\\n[FILE NOT FOUND]`;\n }\n return `--- ${filePath} ---\\n${readFileSync(filePath, \"utf-8\")}`;\n })\n .join(\"\\n\\n\");\n\n const userPrompt = [\n \"Evaluate the following document(s) against the requirements below.\",\n \"\",\n \"DOCUMENTS:\",\n docContents,\n \"\",\n \"REQUIREMENTS:\",\n options.requirements,\n \"\",\n \"Evaluate each requirement. Output PASS if all are met, FAIL if any are not.\",\n ].join(\"\\n\");\n\n // Dynamic import to avoid circular dependency (devtools ↔ agentxjs)\n // Use variable to prevent TypeScript DTS from resolving the module\n const moduleName = \"agentxjs\";\n const agentxjs: any = await import(/* @vite-ignore */ moduleName);\n const createAgentX: (...args: any[]) => Promise<any> = agentxjs.createAgentX;\n\n let agentx: any = null;\n\n try {\n agentx = await createAgentX({\n apiKey,\n provider,\n model,\n baseUrl,\n logLevel: \"silent\",\n });\n\n await agentx.containers.create(\"doc-tester\");\n\n const { record: image } = await agentx.images.create({\n containerId: \"doc-tester\",\n systemPrompt: SYSTEM_PROMPT,\n });\n\n const { agentId } = await agentx.agents.create({ imageId: image.imageId });\n\n // Collect response text\n let output = \"\";\n agentx.on(\"text_delta\", (e: any) => {\n output += e.data.text;\n });\n\n // Send prompt and wait for completion\n await Promise.race([\n agentx.sessions.send(agentId, userPrompt),\n new Promise((_, reject) => setTimeout(() => reject(new Error(\"Timeout\")), timeout)),\n ]);\n\n output = output.trim();\n const passed = /\\*{0,2}PASS\\*{0,2}\\b/m.test(output);\n return { passed, output };\n } catch (error: any) {\n return { passed: false, output: error.message || \"Unknown error\" };\n } finally {\n if (agentx) {\n try {\n await agentx.shutdown();\n } catch {\n // ignore shutdown errors\n }\n }\n }\n}\n","import { execFileSync } from \"node:child_process\";\nimport { readFileSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nconst SKILL_PATH = resolve(__dirname, \"../../../../.claude/skills/agent-browser/SKILL.md\");\n\nfunction loadSystemPrompt(headed = false): string {\n let skillContent = \"\";\n try {\n skillContent = readFileSync(SKILL_PATH, \"utf-8\");\n } catch {\n // Skill file not found, continue without it\n }\n\n return `You are a UI tester. You test web application scenarios using the agent-browser CLI.\n\nRULES:\n- ONLY use agent-browser commands via Bash tool\n- Use ${headed ? \"--headed \" : \"\"}--executable-path \"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome\" for all commands\n- After each navigation or click, run: agent-browser snapshot -i\n- Refs (@e1, @e2) are invalidated after page changes — always re-snapshot\n- At the end, close the browser with: agent-browser close\n- Output your result as a single line: PASS or FAIL followed by a brief reason\n\n${skillContent ? `AGENT-BROWSER REFERENCE:\\n${skillContent}` : \"\"}`;\n}\n\nexport interface UiTestResult {\n passed: boolean;\n output: string;\n}\n\nexport interface UiTesterOptions {\n model?: string;\n baseUrl?: string;\n timeout?: number;\n /** Show browser window (default: false) */\n headed?: boolean;\n}\n\n/**\n * Run a UI test scenario using Claude Code CLI + agent-browser.\n *\n * BDD scripts must run under Node.js (not Bun) to avoid claude CLI auth bug.\n */\nexport function agentUiTester(prompt: string, options: UiTesterOptions = {}): UiTestResult {\n const { model = \"haiku\", baseUrl, timeout = 300_000, headed = false } = options;\n\n const fullPrompt = baseUrl ? `Base URL: ${baseUrl}\\n\\n${prompt}` : prompt;\n\n const systemPrompt = loadSystemPrompt(headed);\n\n // Filter out CLAUDE* env vars to avoid auth conflicts when spawned from Claude Code\n const cleanEnv = Object.fromEntries(\n Object.entries(process.env).filter(([k]) => !k.startsWith(\"CLAUDE\"))\n );\n\n try {\n const output = execFileSync(\n \"claude\",\n [\n \"-p\",\n fullPrompt,\n \"--model\",\n model,\n \"--append-system-prompt\",\n systemPrompt,\n \"--allowedTools\",\n \"Bash(agent-browser:*)\",\n ],\n {\n encoding: \"utf-8\",\n timeout,\n env: cleanEnv,\n maxBuffer: 10 * 1024 * 1024,\n }\n ).trim();\n\n const passed = /\\*{0,2}PASS\\*{0,2}\\b/m.test(output);\n return { passed, output };\n } catch (error: any) {\n const output = error.stdout || error.stderr || error.message || \"Unknown error\";\n return { passed: false, output: output.trim() };\n }\n}\n","/**\n * Shared Cucumber configuration for BDD tests\n *\n * Usage in project's cucumber.js:\n *\n * ```js\n * import { createCucumberConfig } from \"@agentxjs/devtools/bdd\";\n *\n * export default createCucumberConfig({\n * paths: [\"bdd/journeys/** /*.feature\"],\n * import: [\"bdd/steps/** /*.ts\"],\n * });\n * ```\n */\n\nexport interface CucumberConfigOptions {\n /** Feature file paths */\n paths: string[];\n /** Step definition paths */\n import: string[];\n /** Tags to filter (default: exclude @pending and @skip) */\n tags?: string;\n /** Default timeout in ms (default: 30000) */\n timeout?: number;\n /** Format output (default: progress) */\n format?: string[];\n}\n\nexport function createCucumberConfig(options: CucumberConfigOptions) {\n return {\n format: options.format ?? [\"progress\"],\n formatOptions: { snippetInterface: \"async-await\" },\n import: options.import,\n paths: options.paths,\n tags: options.tags ?? \"not @pending and not @skip\",\n worldParameters: {\n defaultTimeout: options.timeout ?? 30000,\n },\n };\n}\n","/**\n * Dev server utilities for BDD testing\n *\n * Start and stop dev servers during test runs.\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport { waitForUrl } from \"./playwright\";\n\nexport interface DevServerOptions {\n /** Working directory */\n cwd: string;\n /** Command to run (default: \"bun\") */\n command?: string;\n /** Command arguments (default: [\"run\", \"dev\"]) */\n args?: string[];\n /** Port to wait for */\n port: number;\n /** Startup timeout in ms (default: 30000) */\n timeout?: number;\n /** Show server output (default: false, or true if DEBUG env is set) */\n debug?: boolean;\n}\n\nlet devServer: ChildProcess | null = null;\n\n/**\n * Start a dev server and wait for it to be ready\n */\nexport async function startDevServer(options: DevServerOptions): Promise<void> {\n if (devServer) return;\n\n const {\n cwd,\n command = \"bun\",\n args = [\"run\", \"dev\"],\n port,\n timeout = 30000,\n debug = !!process.env.DEBUG,\n } = options;\n\n devServer = spawn(command, args, {\n cwd,\n stdio: \"pipe\",\n detached: false,\n });\n\n if (debug) {\n devServer.stdout?.on(\"data\", (data) => {\n console.log(\"[dev-server]\", data.toString());\n });\n\n devServer.stderr?.on(\"data\", (data) => {\n console.error(\"[dev-server error]\", data.toString());\n });\n }\n\n const url = `http://localhost:${port}`;\n const ready = await waitForUrl(url, timeout);\n\n if (!ready) {\n stopDevServer();\n throw new Error(`Dev server failed to start on port ${port}`);\n }\n}\n\n/**\n * Stop the dev server\n */\nexport function stopDevServer(): void {\n if (devServer) {\n devServer.kill(\"SIGTERM\");\n devServer = null;\n }\n}\n\n/**\n * Get the dev server process (for advanced use)\n */\nexport function getDevServer(): ChildProcess | null {\n return devServer;\n}\n","/**\n * Playwright utilities for BDD testing\n *\n * Uses system Chrome to avoid downloading Chromium.\n * Install: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 bun add -d @playwright/test\n *\n * Browser lifecycle:\n * - Single browser instance for all tests\n * - Single page (tab) reused across scenarios\n * - resetPage() clears state between scenarios\n */\n\nimport { type Browser, chromium, type Page } from \"@playwright/test\";\n\nexport interface BrowserOptions {\n headless?: boolean;\n slowMo?: number;\n}\n\nlet browser: Browser | null = null;\nlet page: Page | null = null;\n\n/**\n * Launch browser using system Chrome (singleton)\n */\nexport async function launchBrowser(options: BrowserOptions = {}): Promise<Browser> {\n if (browser) return browser;\n\n const headless = options.headless ?? process.env.CI === \"true\";\n\n browser = await chromium.launch({\n channel: \"chrome\",\n headless,\n slowMo: options.slowMo,\n });\n\n return browser;\n}\n\n/**\n * Get or create a page (singleton, reused across scenarios)\n */\nexport async function getPage(): Promise<Page> {\n if (page && !page.isClosed()) return page;\n\n const b = await launchBrowser();\n page = await b.newPage();\n return page;\n}\n\n/**\n * Reset page state between scenarios (without closing)\n * Use this instead of closePage() for faster tests\n */\nexport async function resetPage(): Promise<void> {\n try {\n if (page && !page.isClosed()) {\n const context = page.context();\n await context.clearCookies();\n await page.goto(\"about:blank\");\n }\n } catch {\n // Browser may have crashed, will recreate on next getPage()\n page = null;\n }\n}\n\n/**\n * Close current page\n * @deprecated Use resetPage() for faster tests. Only use closePage() if you need full isolation.\n */\nexport async function closePage(): Promise<void> {\n if (page) {\n await page.close();\n page = null;\n }\n}\n\n/**\n * Close browser and cleanup\n */\nexport async function closeBrowser(): Promise<void> {\n if (page && !page.isClosed()) {\n await page.close();\n page = null;\n }\n if (browser) {\n await browser.close();\n browser = null;\n }\n}\n\n/**\n * Wait for a URL to be accessible\n */\nexport async function waitForUrl(url: string, timeout = 30000): Promise<boolean> {\n const start = Date.now();\n while (Date.now() - start < timeout) {\n try {\n const response = await fetch(url);\n if (response.ok) {\n return true;\n }\n } catch {\n // Not ready yet\n }\n await new Promise((r) => setTimeout(r, 500));\n }\n return false;\n}\n","/**\n * Unified path utilities for BDD testing\n *\n * Provides consistent path resolution across all packages.\n */\n\nimport { existsSync, mkdirSync, mkdtempSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, resolve } from \"node:path\";\n\n// ============================================================================\n// Path Resolution\n// ============================================================================\n\n/**\n * Find the monorepo root by looking for root package.json with workspaces\n */\nexport function findMonorepoRoot(startDir: string = process.cwd()): string {\n let dir = startDir;\n while (dir !== \"/\") {\n const pkgPath = resolve(dir, \"package.json\");\n if (existsSync(pkgPath)) {\n try {\n const pkg = require(pkgPath);\n if (pkg.workspaces || pkg.private === true) {\n // Check if it looks like a monorepo root\n const hasPackages = existsSync(resolve(dir, \"packages\"));\n const hasApps = existsSync(resolve(dir, \"apps\"));\n if (hasPackages || hasApps) {\n return dir;\n }\n }\n } catch {\n // Ignore errors\n }\n }\n dir = dirname(dir);\n }\n return startDir;\n}\n\n/**\n * Get the current package root (where package.json is)\n */\nexport function getPackageRoot(startDir: string = process.cwd()): string {\n let dir = startDir;\n while (dir !== \"/\") {\n if (existsSync(resolve(dir, \"package.json\"))) {\n return dir;\n }\n dir = dirname(dir);\n }\n return startDir;\n}\n\n// ============================================================================\n// Standard Paths\n// ============================================================================\n\nlet _monorepoRoot: string | null = null;\nlet _packageRoot: string | null = null;\nlet _tempDir: string | null = null;\n\n/**\n * Monorepo root directory\n */\nexport function getMonorepoPath(): string {\n if (!_monorepoRoot) {\n _monorepoRoot = findMonorepoRoot();\n }\n return _monorepoRoot;\n}\n\n/**\n * Current package root directory\n */\nexport function getPackagePath(): string {\n if (!_packageRoot) {\n _packageRoot = getPackageRoot();\n }\n return _packageRoot;\n}\n\n/**\n * BDD directory for current package\n */\nexport function getBddPath(): string {\n return resolve(getPackagePath(), \"bdd\");\n}\n\n/**\n * Fixtures directory for current package's BDD tests\n */\nexport function getFixturesPath(subdir?: string): string {\n const base = resolve(getBddPath(), \"fixtures\");\n return subdir ? resolve(base, subdir) : base;\n}\n\n/**\n * Get or create a temporary directory for tests\n */\nexport function getTempPath(prefix: string = \"bdd-\"): string {\n if (!_tempDir) {\n _tempDir = mkdtempSync(resolve(tmpdir(), prefix));\n }\n return _tempDir;\n}\n\n/**\n * Ensure a directory exists, creating it if necessary\n */\nexport function ensureDir(path: string): string {\n if (!existsSync(path)) {\n mkdirSync(path, { recursive: true });\n }\n return path;\n}\n\n/**\n * Reset cached paths (useful for testing)\n */\nexport function resetPaths(): void {\n _monorepoRoot = null;\n _packageRoot = null;\n _tempDir = null;\n}\n\n// ============================================================================\n// Convenience exports\n// ============================================================================\n\nexport const paths = {\n monorepo: getMonorepoPath,\n package: getPackagePath,\n bdd: getBddPath,\n fixtures: getFixturesPath,\n temp: getTempPath,\n ensure: ensureDir,\n reset: resetPaths,\n};\n"],"mappings":"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","names":["readFileSync","existsSync","dirname","resolve","resolve","existsSync","dirname"]}
@@ -298,4 +298,4 @@ export {
298
298
  MockDriver,
299
299
  createMockDriver
300
300
  };
301
- //# sourceMappingURL=chunk-J6L73HM5.js.map
301
+ //# sourceMappingURL=chunk-SVPPRUN5.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/mock/MockDriver.ts"],"sourcesContent":["/**\n * MockDriver - Mock Driver for Testing\n *\n * Plays back recorded fixtures when receiving messages.\n * Implements the new Driver interface with receive() returning AsyncIterable.\n *\n * Usage:\n * ```typescript\n * const driver = new MockDriver({\n * fixture: \"simple-reply\",\n * });\n *\n * await driver.initialize();\n *\n * for await (const event of driver.receive({ content: \"Hello\" })) {\n * if (event.type === \"text_delta\") {\n * console.log(event.data.text);\n * }\n * }\n *\n * await driver.dispose();\n * ```\n */\n\nimport type { Driver, DriverConfig, DriverState, DriverStreamEvent } from \"@agentxjs/core/driver\";\nimport type { UserMessage } from \"@agentxjs/core/agent\";\nimport type { Fixture, FixtureEvent, MockDriverOptions } from \"../types\";\nimport { BUILTIN_FIXTURES } from \"../../fixtures\";\nimport { createLogger } from \"commonxjs/logger\";\n\nconst logger = createLogger(\"devtools/MockDriver\");\n\n/**\n * MockDriver - Playback driver for testing\n *\n * Implements the new Driver interface:\n * - receive() returns AsyncIterable<DriverStreamEvent>\n * - Clear input/output boundaries for testing\n */\nexport class MockDriver implements Driver {\n readonly name = \"MockDriver\";\n\n private _sessionId: string | null = null;\n private _state: DriverState = \"idle\";\n\n private readonly config: DriverConfig | null;\n private readonly options: MockDriverOptions;\n private readonly fixtures: Map<string, Fixture>;\n private currentFixture: Fixture;\n\n // For interrupt handling\n private isInterrupted = false;\n\n // Event cursor for multi-turn conversations\n private eventCursor = 0;\n\n /**\n * Create a MockDriver\n *\n * @param options - MockDriverOptions or DriverConfig\n * @param mockOptions - MockDriverOptions if first param is DriverConfig\n */\n constructor(optionsOrConfig: MockDriverOptions | DriverConfig, mockOptions?: MockDriverOptions) {\n // Detect which constructor form is being used\n if (mockOptions !== undefined || \"apiKey\" in optionsOrConfig) {\n // Factory mode: (DriverConfig, MockDriverOptions)\n this.config = optionsOrConfig as DriverConfig;\n const opts = mockOptions || {};\n this.options = {\n defaultDelay: 10,\n speedMultiplier: 0,\n ...opts,\n };\n } else {\n // Simple mode: (MockDriverOptions)\n this.config = null;\n const opts = optionsOrConfig as MockDriverOptions;\n this.options = {\n defaultDelay: 10,\n speedMultiplier: 0,\n ...opts,\n };\n }\n\n // Initialize fixtures\n this.fixtures = new Map(BUILTIN_FIXTURES);\n if (this.options.fixtures) {\n for (const [name, fixture] of this.options.fixtures) {\n this.fixtures.set(name, fixture);\n }\n }\n\n // Set initial fixture\n this.currentFixture = this.resolveFixture(this.options.fixture || \"simple-reply\");\n\n logger.debug(\"MockDriver created\", {\n fixture: this.currentFixture.name,\n agentId: this.config?.agentId,\n });\n }\n\n // ============================================================================\n // Driver Interface Properties\n // ============================================================================\n\n get sessionId(): string | null {\n return this._sessionId;\n }\n\n get state(): DriverState {\n return this._state;\n }\n\n // ============================================================================\n // Lifecycle Methods\n // ============================================================================\n\n /**\n * Initialize the Driver\n */\n async initialize(): Promise<void> {\n if (this._state !== \"idle\") {\n throw new Error(`Cannot initialize: MockDriver is in \"${this._state}\" state`);\n }\n\n // Generate a mock session ID\n this._sessionId = `mock-session-${Date.now()}`;\n\n logger.debug(\"MockDriver initialized\", {\n sessionId: this._sessionId,\n fixture: this.currentFixture.name,\n });\n }\n\n /**\n * Dispose and cleanup resources\n */\n async dispose(): Promise<void> {\n if (this._state === \"disposed\") {\n return;\n }\n\n this._state = \"disposed\";\n this.isInterrupted = true;\n\n logger.debug(\"MockDriver disposed\");\n }\n\n // ============================================================================\n // Core Methods\n // ============================================================================\n\n /**\n * Receive a user message and return stream of events\n *\n * Plays back the current fixture as DriverStreamEvent.\n *\n * @param message - User message (ignored for playback)\n * @returns AsyncIterable of stream events\n */\n async *receive(_message: UserMessage): AsyncIterable<DriverStreamEvent> {\n if (this._state === \"disposed\") {\n throw new Error(\"Cannot receive: MockDriver is disposed\");\n }\n\n if (this._state === \"active\") {\n throw new Error(\"Cannot receive: MockDriver is already processing a message\");\n }\n\n this._state = \"active\";\n this.isInterrupted = false;\n\n const { speedMultiplier = 0, defaultDelay = 10 } = this.options;\n const events = this.currentFixture.events;\n\n try {\n // Start from cursor position and play until message_stop\n while (this.eventCursor < events.length) {\n const fixtureEvent = events[this.eventCursor];\n this.eventCursor++;\n\n // Check for interrupt\n if (this.isInterrupted) {\n yield {\n type: \"interrupted\",\n timestamp: Date.now(),\n data: { reason: \"user\" },\n };\n break;\n }\n\n // Apply delay\n const delay = fixtureEvent.delay || defaultDelay;\n if (delay > 0 && speedMultiplier > 0) {\n await this.sleep(delay * speedMultiplier);\n }\n\n // Convert and yield event\n const event = this.convertFixtureEvent(fixtureEvent);\n if (event) {\n yield event;\n }\n\n // Stop at message_stop (end of one turn)\n if (fixtureEvent.type === \"message_stop\") {\n break;\n }\n }\n } finally {\n this._state = \"idle\";\n }\n }\n\n /**\n * Interrupt current operation\n */\n interrupt(): void {\n if (this._state !== \"active\") {\n logger.debug(\"Interrupt called but no active operation\");\n return;\n }\n\n logger.debug(\"MockDriver interrupted\");\n this.isInterrupted = true;\n }\n\n // ============================================================================\n // Fixture Management\n // ============================================================================\n\n /**\n * Set the fixture to use for next playback\n */\n setFixture(fixture: string | Fixture): void {\n this.currentFixture = this.resolveFixture(fixture);\n this.eventCursor = 0; // Reset cursor when fixture changes\n logger.debug(\"Fixture changed\", { fixture: this.currentFixture.name });\n }\n\n /**\n * Add a custom fixture\n */\n addFixture(fixture: Fixture): void {\n this.fixtures.set(fixture.name, fixture);\n }\n\n /**\n * Get the current fixture\n */\n getFixture(): Fixture {\n return this.currentFixture;\n }\n\n /**\n * Get available fixture names\n */\n getFixtureNames(): string[] {\n return Array.from(this.fixtures.keys());\n }\n\n // ============================================================================\n // Private Methods\n // ============================================================================\n\n /**\n * Resolve fixture from name or Fixture object\n */\n private resolveFixture(fixture: string | Fixture): Fixture {\n if (typeof fixture === \"string\") {\n const found = this.fixtures.get(fixture);\n if (!found) {\n logger.warn(`Fixture \"${fixture}\" not found, using \"simple-reply\"`);\n return this.fixtures.get(\"simple-reply\")!;\n }\n return found;\n }\n return fixture;\n }\n\n /**\n * Convert FixtureEvent to DriverStreamEvent\n */\n private convertFixtureEvent(fixtureEvent: FixtureEvent): DriverStreamEvent | null {\n const timestamp = Date.now();\n const data = fixtureEvent.data as Record<string, unknown>;\n\n switch (fixtureEvent.type) {\n case \"message_start\":\n return {\n type: \"message_start\",\n timestamp,\n data: {\n messageId: (data.messageId as string) || `msg_${timestamp}`,\n model: (data.model as string) || \"mock-model\",\n },\n };\n\n case \"text_delta\":\n return {\n type: \"text_delta\",\n timestamp,\n data: { text: (data.text as string) || \"\" },\n };\n\n case \"tool_use_start\":\n return {\n type: \"tool_use_start\",\n timestamp,\n data: {\n toolCallId: (data.toolCallId as string) || `tool_${timestamp}`,\n toolName: (data.toolName as string) || \"\",\n },\n };\n\n case \"input_json_delta\":\n return {\n type: \"input_json_delta\",\n timestamp,\n data: { partialJson: (data.partialJson as string) || \"\" },\n };\n\n case \"tool_use_stop\":\n return {\n type: \"tool_use_stop\",\n timestamp,\n data: {\n toolCallId: (data.toolCallId as string) || \"\",\n toolName: (data.toolName as string) || \"\",\n input: (data.input as Record<string, unknown>) || {},\n },\n };\n\n case \"tool_result\":\n return {\n type: \"tool_result\",\n timestamp,\n data: {\n toolCallId: (data.toolCallId as string) || \"\",\n result: data.result,\n isError: data.isError as boolean | undefined,\n },\n };\n\n case \"message_delta\":\n return {\n type: \"message_delta\",\n timestamp,\n data: {\n usage: data.usage as { inputTokens: number; outputTokens: number } | undefined,\n },\n };\n\n case \"message_stop\":\n return {\n type: \"message_stop\",\n timestamp,\n data: {\n stopReason:\n (data.stopReason as\n | \"end_turn\"\n | \"tool_use\"\n | \"max_tokens\"\n | \"stop_sequence\"\n | \"other\") || \"end_turn\",\n },\n };\n\n case \"error\":\n return {\n type: \"error\",\n timestamp,\n data: {\n message: (data.message as string) || \"Unknown error\",\n errorCode: (data.errorCode as string) || \"mock_error\",\n },\n };\n\n default:\n // Pass through unknown events with generic structure\n logger.debug(`Unknown fixture event type: ${fixtureEvent.type}`);\n return null;\n }\n }\n\n /**\n * Sleep for specified milliseconds\n */\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n\n/**\n * Create a MockDriver factory function\n *\n * Returns a CreateDriver-compatible function.\n *\n * @param mockOptions - Options for all created drivers\n * @returns CreateDriver function\n */\nexport function createMockDriver(\n mockOptions: MockDriverOptions = {}\n): (config: DriverConfig) => Driver {\n return (config: DriverConfig) => new MockDriver(config, mockOptions);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/mock/MockDriver.ts"],"sourcesContent":["/**\n * MockDriver - Mock Driver for Testing\n *\n * Plays back recorded fixtures when receiving messages.\n * Implements the new Driver interface with receive() returning AsyncIterable.\n *\n * Usage:\n * ```typescript\n * const driver = new MockDriver({\n * fixture: \"simple-reply\",\n * });\n *\n * await driver.initialize();\n *\n * for await (const event of driver.receive({ content: \"Hello\" })) {\n * if (event.type === \"text_delta\") {\n * console.log(event.data.text);\n * }\n * }\n *\n * await driver.dispose();\n * ```\n */\n\nimport type { UserMessage } from \"@agentxjs/core/agent\";\nimport type { Driver, DriverConfig, DriverState, DriverStreamEvent } from \"@agentxjs/core/driver\";\nimport { createLogger } from \"commonxjs/logger\";\nimport { BUILTIN_FIXTURES } from \"../../fixtures\";\nimport type { Fixture, FixtureEvent, MockDriverOptions } from \"../types\";\n\nconst logger = createLogger(\"devtools/MockDriver\");\n\n/**\n * MockDriver - Playback driver for testing\n *\n * Implements the new Driver interface:\n * - receive() returns AsyncIterable<DriverStreamEvent>\n * - Clear input/output boundaries for testing\n */\nexport class MockDriver implements Driver {\n readonly name = \"MockDriver\";\n\n private _sessionId: string | null = null;\n private _state: DriverState = \"idle\";\n\n private readonly config: DriverConfig | null;\n private readonly options: MockDriverOptions;\n private readonly fixtures: Map<string, Fixture>;\n private currentFixture: Fixture;\n\n // For interrupt handling\n private isInterrupted = false;\n\n // Event cursor for multi-turn conversations\n private eventCursor = 0;\n\n /**\n * Create a MockDriver\n *\n * @param options - MockDriverOptions or DriverConfig\n * @param mockOptions - MockDriverOptions if first param is DriverConfig\n */\n constructor(optionsOrConfig: MockDriverOptions | DriverConfig, mockOptions?: MockDriverOptions) {\n // Detect which constructor form is being used\n if (mockOptions !== undefined || \"apiKey\" in optionsOrConfig) {\n // Factory mode: (DriverConfig, MockDriverOptions)\n this.config = optionsOrConfig as DriverConfig;\n const opts = mockOptions || {};\n this.options = {\n defaultDelay: 10,\n speedMultiplier: 0,\n ...opts,\n };\n } else {\n // Simple mode: (MockDriverOptions)\n this.config = null;\n const opts = optionsOrConfig as MockDriverOptions;\n this.options = {\n defaultDelay: 10,\n speedMultiplier: 0,\n ...opts,\n };\n }\n\n // Initialize fixtures\n this.fixtures = new Map(BUILTIN_FIXTURES);\n if (this.options.fixtures) {\n for (const [name, fixture] of this.options.fixtures) {\n this.fixtures.set(name, fixture);\n }\n }\n\n // Set initial fixture\n this.currentFixture = this.resolveFixture(this.options.fixture || \"simple-reply\");\n\n logger.debug(\"MockDriver created\", {\n fixture: this.currentFixture.name,\n agentId: this.config?.agentId,\n });\n }\n\n // ============================================================================\n // Driver Interface Properties\n // ============================================================================\n\n get sessionId(): string | null {\n return this._sessionId;\n }\n\n get state(): DriverState {\n return this._state;\n }\n\n // ============================================================================\n // Lifecycle Methods\n // ============================================================================\n\n /**\n * Initialize the Driver\n */\n async initialize(): Promise<void> {\n if (this._state !== \"idle\") {\n throw new Error(`Cannot initialize: MockDriver is in \"${this._state}\" state`);\n }\n\n // Generate a mock session ID\n this._sessionId = `mock-session-${Date.now()}`;\n\n logger.debug(\"MockDriver initialized\", {\n sessionId: this._sessionId,\n fixture: this.currentFixture.name,\n });\n }\n\n /**\n * Dispose and cleanup resources\n */\n async dispose(): Promise<void> {\n if (this._state === \"disposed\") {\n return;\n }\n\n this._state = \"disposed\";\n this.isInterrupted = true;\n\n logger.debug(\"MockDriver disposed\");\n }\n\n // ============================================================================\n // Core Methods\n // ============================================================================\n\n /**\n * Receive a user message and return stream of events\n *\n * Plays back the current fixture as DriverStreamEvent.\n *\n * @param message - User message (ignored for playback)\n * @returns AsyncIterable of stream events\n */\n async *receive(_message: UserMessage): AsyncIterable<DriverStreamEvent> {\n if (this._state === \"disposed\") {\n throw new Error(\"Cannot receive: MockDriver is disposed\");\n }\n\n if (this._state === \"active\") {\n throw new Error(\"Cannot receive: MockDriver is already processing a message\");\n }\n\n this._state = \"active\";\n this.isInterrupted = false;\n\n const { speedMultiplier = 0, defaultDelay = 10 } = this.options;\n const events = this.currentFixture.events;\n\n try {\n // Start from cursor position and play until message_stop\n while (this.eventCursor < events.length) {\n const fixtureEvent = events[this.eventCursor];\n this.eventCursor++;\n\n // Check for interrupt\n if (this.isInterrupted) {\n yield {\n type: \"interrupted\",\n timestamp: Date.now(),\n data: { reason: \"user\" },\n };\n break;\n }\n\n // Apply delay\n const delay = fixtureEvent.delay || defaultDelay;\n if (delay > 0 && speedMultiplier > 0) {\n await this.sleep(delay * speedMultiplier);\n }\n\n // Convert and yield event\n const event = this.convertFixtureEvent(fixtureEvent);\n if (event) {\n yield event;\n }\n\n // Stop at message_stop (end of one turn)\n if (fixtureEvent.type === \"message_stop\") {\n break;\n }\n }\n } finally {\n this._state = \"idle\";\n }\n }\n\n /**\n * Interrupt current operation\n */\n interrupt(): void {\n if (this._state !== \"active\") {\n logger.debug(\"Interrupt called but no active operation\");\n return;\n }\n\n logger.debug(\"MockDriver interrupted\");\n this.isInterrupted = true;\n }\n\n // ============================================================================\n // Fixture Management\n // ============================================================================\n\n /**\n * Set the fixture to use for next playback\n */\n setFixture(fixture: string | Fixture): void {\n this.currentFixture = this.resolveFixture(fixture);\n this.eventCursor = 0; // Reset cursor when fixture changes\n logger.debug(\"Fixture changed\", { fixture: this.currentFixture.name });\n }\n\n /**\n * Add a custom fixture\n */\n addFixture(fixture: Fixture): void {\n this.fixtures.set(fixture.name, fixture);\n }\n\n /**\n * Get the current fixture\n */\n getFixture(): Fixture {\n return this.currentFixture;\n }\n\n /**\n * Get available fixture names\n */\n getFixtureNames(): string[] {\n return Array.from(this.fixtures.keys());\n }\n\n // ============================================================================\n // Private Methods\n // ============================================================================\n\n /**\n * Resolve fixture from name or Fixture object\n */\n private resolveFixture(fixture: string | Fixture): Fixture {\n if (typeof fixture === \"string\") {\n const found = this.fixtures.get(fixture);\n if (!found) {\n logger.warn(`Fixture \"${fixture}\" not found, using \"simple-reply\"`);\n return this.fixtures.get(\"simple-reply\")!;\n }\n return found;\n }\n return fixture;\n }\n\n /**\n * Convert FixtureEvent to DriverStreamEvent\n */\n private convertFixtureEvent(fixtureEvent: FixtureEvent): DriverStreamEvent | null {\n const timestamp = Date.now();\n const data = fixtureEvent.data as Record<string, unknown>;\n\n switch (fixtureEvent.type) {\n case \"message_start\":\n return {\n type: \"message_start\",\n timestamp,\n data: {\n messageId: (data.messageId as string) || `msg_${timestamp}`,\n model: (data.model as string) || \"mock-model\",\n },\n };\n\n case \"text_delta\":\n return {\n type: \"text_delta\",\n timestamp,\n data: { text: (data.text as string) || \"\" },\n };\n\n case \"tool_use_start\":\n return {\n type: \"tool_use_start\",\n timestamp,\n data: {\n toolCallId: (data.toolCallId as string) || `tool_${timestamp}`,\n toolName: (data.toolName as string) || \"\",\n },\n };\n\n case \"input_json_delta\":\n return {\n type: \"input_json_delta\",\n timestamp,\n data: { partialJson: (data.partialJson as string) || \"\" },\n };\n\n case \"tool_use_stop\":\n return {\n type: \"tool_use_stop\",\n timestamp,\n data: {\n toolCallId: (data.toolCallId as string) || \"\",\n toolName: (data.toolName as string) || \"\",\n input: (data.input as Record<string, unknown>) || {},\n },\n };\n\n case \"tool_result\":\n return {\n type: \"tool_result\",\n timestamp,\n data: {\n toolCallId: (data.toolCallId as string) || \"\",\n result: data.result,\n isError: data.isError as boolean | undefined,\n },\n };\n\n case \"message_delta\":\n return {\n type: \"message_delta\",\n timestamp,\n data: {\n usage: data.usage as { inputTokens: number; outputTokens: number } | undefined,\n },\n };\n\n case \"message_stop\":\n return {\n type: \"message_stop\",\n timestamp,\n data: {\n stopReason:\n (data.stopReason as\n | \"end_turn\"\n | \"tool_use\"\n | \"max_tokens\"\n | \"stop_sequence\"\n | \"other\") || \"end_turn\",\n },\n };\n\n case \"error\":\n return {\n type: \"error\",\n timestamp,\n data: {\n message: (data.message as string) || \"Unknown error\",\n errorCode: (data.errorCode as string) || \"mock_error\",\n },\n };\n\n default:\n // Pass through unknown events with generic structure\n logger.debug(`Unknown fixture event type: ${fixtureEvent.type}`);\n return null;\n }\n }\n\n /**\n * Sleep for specified milliseconds\n */\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n\n/**\n * Create a MockDriver factory function\n *\n * Returns a CreateDriver-compatible function.\n *\n * @param mockOptions - Options for all created drivers\n * @returns CreateDriver function\n */\nexport function createMockDriver(\n mockOptions: MockDriverOptions = {}\n): (config: DriverConfig) => Driver {\n return (config: DriverConfig) => new MockDriver(config, mockOptions);\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  // src/env.ts
2
2
  import { existsSync, readFileSync } from "fs";
3
- import { resolve, dirname } from "path";
3
+ import { dirname, resolve } from "path";
4
4
  function loadEnvFile(filePath) {
5
5
  if (!existsSync(filePath)) return;
6
6
  const content = readFileSync(filePath, "utf-8");
@@ -61,4 +61,4 @@ var env = {
61
61
  export {
62
62
  env
63
63
  };
64
- //# sourceMappingURL=chunk-S7J75AXG.js.map
64
+ //# sourceMappingURL=chunk-Y6RXZINS.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/env.ts"],"sourcesContent":["/**\n * Unified environment configuration for devtools\n *\n * Single source of truth for API credentials and model settings.\n * Automatically loads .env / .env.local from monorepo root on import.\n *\n * All devtools modules (VCR, BDD, Devtools SDK) should use this\n * instead of reading process.env directly.\n *\n * @example\n * ```ts\n * import { env } from \"@agentxjs/devtools\";\n *\n * const driver = createMonoDriver({\n * apiKey: env.apiKey!,\n * baseUrl: env.baseUrl,\n * model: env.model,\n * });\n * ```\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { resolve, dirname } from \"node:path\";\n\n// ============================================================================\n// Auto-load .env files from monorepo root\n// ============================================================================\n\nfunction loadEnvFile(filePath: string): void {\n if (!existsSync(filePath)) return;\n const content = readFileSync(filePath, \"utf-8\");\n for (const line of content.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n const eqIndex = trimmed.indexOf(\"=\");\n if (eqIndex === -1) continue;\n const key = trimmed.slice(0, eqIndex).trim();\n let value = trimmed.slice(eqIndex + 1).trim();\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n if (process.env[key] === undefined) {\n process.env[key] = value;\n }\n }\n}\n\nfunction findMonorepoRoot(): string | null {\n let dir = process.cwd();\n while (true) {\n const pkgPath = resolve(dir, \"package.json\");\n if (existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n if (pkg.workspaces) return dir;\n } catch {}\n }\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n// Load on import — cwd first, then monorepo root\nconst cwd = process.cwd();\nloadEnvFile(resolve(cwd, \".env\"));\nloadEnvFile(resolve(cwd, \".env.local\"));\n\nconst monorepoRoot = findMonorepoRoot();\nif (monorepoRoot && monorepoRoot !== cwd) {\n loadEnvFile(resolve(monorepoRoot, \".env\"));\n loadEnvFile(resolve(monorepoRoot, \".env.local\"));\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\nexport const env = {\n /** Deepractice API key */\n get apiKey(): string | undefined {\n return process.env.DEEPRACTICE_API_KEY;\n },\n\n /** Deepractice API base URL */\n get baseUrl(): string | undefined {\n return process.env.DEEPRACTICE_BASE_URL;\n },\n\n /** Model identifier */\n get model(): string {\n return process.env.DEEPRACTICE_MODEL || \"claude-haiku-4-5-20251001\";\n },\n};\n"],"mappings":";AAqBA,SAAS,YAAY,oBAAoB;AACzC,SAAS,SAAS,eAAe;AAMjC,SAAS,YAAY,UAAwB;AAC3C,MAAI,CAAC,WAAW,QAAQ,EAAG;AAC3B,QAAM,UAAU,aAAa,UAAU,OAAO;AAC9C,aAAW,QAAQ,QAAQ,MAAM,IAAI,GAAG;AACtC,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,EAAG;AACzC,UAAM,UAAU,QAAQ,QAAQ,GAAG;AACnC,QAAI,YAAY,GAAI;AACpB,UAAM,MAAM,QAAQ,MAAM,GAAG,OAAO,EAAE,KAAK;AAC3C,QAAI,QAAQ,QAAQ,MAAM,UAAU,CAAC,EAAE,KAAK;AAC5C,QACG,MAAM,WAAW,GAAG,KAAK,MAAM,SAAS,GAAG,KAC3C,MAAM,WAAW,GAAG,KAAK,MAAM,SAAS,GAAG,GAC5C;AACA,cAAQ,MAAM,MAAM,GAAG,EAAE;AAAA,IAC3B;AACA,QAAI,QAAQ,IAAI,GAAG,MAAM,QAAW;AAClC,cAAQ,IAAI,GAAG,IAAI;AAAA,IACrB;AAAA,EACF;AACF;AAEA,SAAS,mBAAkC;AACzC,MAAI,MAAM,QAAQ,IAAI;AACtB,SAAO,MAAM;AACX,UAAM,UAAU,QAAQ,KAAK,cAAc;AAC3C,QAAI,WAAW,OAAO,GAAG;AACvB,UAAI;AACF,cAAM,MAAM,KAAK,MAAM,aAAa,SAAS,OAAO,CAAC;AACrD,YAAI,IAAI,WAAY,QAAO;AAAA,MAC7B,QAAQ;AAAA,MAAC;AAAA,IACX;AACA,UAAM,SAAS,QAAQ,GAAG;AAC1B,QAAI,WAAW,IAAK,QAAO;AAC3B,UAAM;AAAA,EACR;AACF;AAGA,IAAM,MAAM,QAAQ,IAAI;AACxB,YAAY,QAAQ,KAAK,MAAM,CAAC;AAChC,YAAY,QAAQ,KAAK,YAAY,CAAC;AAEtC,IAAM,eAAe,iBAAiB;AACtC,IAAI,gBAAgB,iBAAiB,KAAK;AACxC,cAAY,QAAQ,cAAc,MAAM,CAAC;AACzC,cAAY,QAAQ,cAAc,YAAY,CAAC;AACjD;AAMO,IAAM,MAAM;AAAA;AAAA,EAEjB,IAAI,SAA6B;AAC/B,WAAO,QAAQ,IAAI;AAAA,EACrB;AAAA;AAAA,EAGA,IAAI,UAA8B;AAChC,WAAO,QAAQ,IAAI;AAAA,EACrB;AAAA;AAAA,EAGA,IAAI,QAAgB;AAClB,WAAO,QAAQ,IAAI,qBAAqB;AAAA,EAC1C;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/env.ts"],"sourcesContent":["/**\n * Unified environment configuration for devtools\n *\n * Single source of truth for API credentials and model settings.\n * Automatically loads .env / .env.local from monorepo root on import.\n *\n * All devtools modules (VCR, BDD, Devtools SDK) should use this\n * instead of reading process.env directly.\n *\n * @example\n * ```ts\n * import { env } from \"@agentxjs/devtools\";\n *\n * const driver = createMonoDriver({\n * apiKey: env.apiKey!,\n * baseUrl: env.baseUrl,\n * model: env.model,\n * });\n * ```\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\n\n// ============================================================================\n// Auto-load .env files from monorepo root\n// ============================================================================\n\nfunction loadEnvFile(filePath: string): void {\n if (!existsSync(filePath)) return;\n const content = readFileSync(filePath, \"utf-8\");\n for (const line of content.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n const eqIndex = trimmed.indexOf(\"=\");\n if (eqIndex === -1) continue;\n const key = trimmed.slice(0, eqIndex).trim();\n let value = trimmed.slice(eqIndex + 1).trim();\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n if (process.env[key] === undefined) {\n process.env[key] = value;\n }\n }\n}\n\nfunction findMonorepoRoot(): string | null {\n let dir = process.cwd();\n while (true) {\n const pkgPath = resolve(dir, \"package.json\");\n if (existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n if (pkg.workspaces) return dir;\n } catch {}\n }\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n// Load on import — cwd first, then monorepo root\nconst cwd = process.cwd();\nloadEnvFile(resolve(cwd, \".env\"));\nloadEnvFile(resolve(cwd, \".env.local\"));\n\nconst monorepoRoot = findMonorepoRoot();\nif (monorepoRoot && monorepoRoot !== cwd) {\n loadEnvFile(resolve(monorepoRoot, \".env\"));\n loadEnvFile(resolve(monorepoRoot, \".env.local\"));\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\nexport const env = {\n /** Deepractice API key */\n get apiKey(): string | undefined {\n return process.env.DEEPRACTICE_API_KEY;\n },\n\n /** Deepractice API base URL */\n get baseUrl(): string | undefined {\n return process.env.DEEPRACTICE_BASE_URL;\n },\n\n /** Model identifier */\n get model(): string {\n return process.env.DEEPRACTICE_MODEL || \"claude-haiku-4-5-20251001\";\n },\n};\n"],"mappings":";AAqBA,SAAS,YAAY,oBAAoB;AACzC,SAAS,SAAS,eAAe;AAMjC,SAAS,YAAY,UAAwB;AAC3C,MAAI,CAAC,WAAW,QAAQ,EAAG;AAC3B,QAAM,UAAU,aAAa,UAAU,OAAO;AAC9C,aAAW,QAAQ,QAAQ,MAAM,IAAI,GAAG;AACtC,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,EAAG;AACzC,UAAM,UAAU,QAAQ,QAAQ,GAAG;AACnC,QAAI,YAAY,GAAI;AACpB,UAAM,MAAM,QAAQ,MAAM,GAAG,OAAO,EAAE,KAAK;AAC3C,QAAI,QAAQ,QAAQ,MAAM,UAAU,CAAC,EAAE,KAAK;AAC5C,QACG,MAAM,WAAW,GAAG,KAAK,MAAM,SAAS,GAAG,KAC3C,MAAM,WAAW,GAAG,KAAK,MAAM,SAAS,GAAG,GAC5C;AACA,cAAQ,MAAM,MAAM,GAAG,EAAE;AAAA,IAC3B;AACA,QAAI,QAAQ,IAAI,GAAG,MAAM,QAAW;AAClC,cAAQ,IAAI,GAAG,IAAI;AAAA,IACrB;AAAA,EACF;AACF;AAEA,SAAS,mBAAkC;AACzC,MAAI,MAAM,QAAQ,IAAI;AACtB,SAAO,MAAM;AACX,UAAM,UAAU,QAAQ,KAAK,cAAc;AAC3C,QAAI,WAAW,OAAO,GAAG;AACvB,UAAI;AACF,cAAM,MAAM,KAAK,MAAM,aAAa,SAAS,OAAO,CAAC;AACrD,YAAI,IAAI,WAAY,QAAO;AAAA,MAC7B,QAAQ;AAAA,MAAC;AAAA,IACX;AACA,UAAM,SAAS,QAAQ,GAAG;AAC1B,QAAI,WAAW,IAAK,QAAO;AAC3B,UAAM;AAAA,EACR;AACF;AAGA,IAAM,MAAM,QAAQ,IAAI;AACxB,YAAY,QAAQ,KAAK,MAAM,CAAC;AAChC,YAAY,QAAQ,KAAK,YAAY,CAAC;AAEtC,IAAM,eAAe,iBAAiB;AACtC,IAAI,gBAAgB,iBAAiB,KAAK;AACxC,cAAY,QAAQ,cAAc,MAAM,CAAC;AACzC,cAAY,QAAQ,cAAc,YAAY,CAAC;AACjD;AAMO,IAAM,MAAM;AAAA;AAAA,EAEjB,IAAI,SAA6B;AAC/B,WAAO,QAAQ,IAAI;AAAA,EACrB;AAAA;AAAA,EAGA,IAAI,UAA8B;AAChC,WAAO,QAAQ,IAAI;AAAA,EACrB;AAAA;AAAA,EAGA,IAAI,QAAgB;AAClB,WAAO,QAAQ,IAAI,qBAAqB;AAAA,EAC1C;AACF;","names":[]}
@@ -149,4 +149,4 @@ export {
149
149
  RecordingDriver,
150
150
  createRecordingDriver
151
151
  };
152
- //# sourceMappingURL=chunk-DR45HEV4.js.map
152
+ //# sourceMappingURL=chunk-YFONF7SD.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/recorder/RecordingDriver.ts"],"sourcesContent":["/**\n * RecordingDriver - Wraps a real driver to record events\n *\n * Used to capture real LLM API responses and save them as fixtures.\n * These fixtures can then be played back by MockDriver for testing.\n *\n * Usage:\n * ```typescript\n * import { createClaudeDriver } from \"@agentxjs/claude-driver\";\n * import { RecordingDriver } from \"@agentxjs/devtools/recorder\";\n *\n * // Create real driver\n * const realDriver = createClaudeDriver(config);\n *\n * // Wrap with recorder\n * const recorder = new RecordingDriver({\n * driver: realDriver,\n * name: \"my-scenario\",\n * description: \"User asks about weather\",\n * });\n *\n * await recorder.initialize();\n *\n * // Use like a normal driver - events are recorded\n * for await (const event of recorder.receive({ content: \"Hello\" })) {\n * console.log(event);\n * }\n *\n * // Save the fixture\n * await recorder.saveFixture(\"./fixtures/my-scenario.json\");\n * ```\n */\n\nimport type { Driver, DriverState, DriverStreamEvent } from \"@agentxjs/core/driver\";\nimport type { UserMessage } from \"@agentxjs/core/agent\";\nimport type { Fixture, FixtureEvent } from \"../types\";\nimport { createLogger } from \"commonxjs/logger\";\n\nconst logger = createLogger(\"devtools/RecordingDriver\");\n\n/**\n * Options for RecordingDriver\n */\nexport interface RecordingDriverOptions {\n /**\n * The real driver to wrap\n */\n driver: Driver;\n\n /**\n * Fixture name for the recording\n */\n name: string;\n\n /**\n * Description for the recording\n */\n description?: string;\n}\n\n/**\n * Recorded event with timing\n */\ninterface RecordedEvent {\n event: DriverStreamEvent;\n timestamp: number;\n}\n\n/**\n * RecordingDriver - Records events from a real driver\n *\n * Implements the new Driver interface by wrapping a real driver\n * and intercepting events from receive().\n */\nexport class RecordingDriver implements Driver {\n readonly name = \"RecordingDriver\";\n\n private readonly realDriver: Driver;\n private readonly fixtureName: string;\n private readonly fixtureDescription?: string;\n\n private recordedEvents: RecordedEvent[] = [];\n private recordingStartTime: number = 0;\n\n constructor(options: RecordingDriverOptions) {\n this.realDriver = options.driver;\n this.fixtureName = options.name;\n this.fixtureDescription = options.description;\n\n logger.info(\"RecordingDriver created\", { name: this.fixtureName });\n }\n\n // ============================================================================\n // Driver Interface Properties (delegate to real driver)\n // ============================================================================\n\n get sessionId(): string | null {\n return this.realDriver.sessionId;\n }\n\n get state(): DriverState {\n return this.realDriver.state;\n }\n\n // ============================================================================\n // Lifecycle Methods (delegate to real driver)\n // ============================================================================\n\n async initialize(): Promise<void> {\n await this.realDriver.initialize();\n this.recordingStartTime = Date.now();\n this.recordedEvents = [];\n logger.info(\"RecordingDriver initialized, recording started\", {\n name: this.fixtureName,\n });\n }\n\n async dispose(): Promise<void> {\n await this.realDriver.dispose();\n logger.info(\"RecordingDriver disposed\", {\n name: this.fixtureName,\n eventsRecorded: this.recordedEvents.length,\n });\n }\n\n // ============================================================================\n // Core Methods\n // ============================================================================\n\n /**\n * Receive a user message and return stream of events\n *\n * Wraps the real driver's receive() and records all events.\n */\n async *receive(message: UserMessage): AsyncIterable<DriverStreamEvent> {\n logger.debug(\"RecordingDriver receiving message\", {\n name: this.fixtureName,\n });\n\n // Call the real driver and intercept events\n for await (const event of this.realDriver.receive(message)) {\n // Record the event\n this.recordEvent(event);\n\n // Pass through to caller\n yield event;\n }\n\n logger.debug(\"RecordingDriver receive completed\", {\n name: this.fixtureName,\n eventsRecorded: this.recordedEvents.length,\n });\n }\n\n /**\n * Interrupt current operation (delegate to real driver)\n */\n interrupt(): void {\n this.realDriver.interrupt();\n }\n\n // ============================================================================\n // Recording Methods\n // ============================================================================\n\n /**\n * Record an event\n */\n private recordEvent(event: DriverStreamEvent): void {\n this.recordedEvents.push({\n event,\n timestamp: Date.now(),\n });\n\n logger.debug(\"Event recorded\", {\n type: event.type,\n totalEvents: this.recordedEvents.length,\n });\n }\n\n /**\n * Get the recorded fixture\n */\n getFixture(): Fixture {\n const events: FixtureEvent[] = [];\n let lastTimestamp = this.recordingStartTime;\n\n for (const recorded of this.recordedEvents) {\n const delay = recorded.timestamp - lastTimestamp;\n lastTimestamp = recorded.timestamp;\n\n events.push({\n type: recorded.event.type,\n delay: Math.max(0, delay),\n data: recorded.event.data,\n });\n }\n\n return {\n name: this.fixtureName,\n description: this.fixtureDescription,\n recordedAt: this.recordingStartTime,\n events,\n };\n }\n\n /**\n * Save the recorded fixture to a JSON file\n */\n async saveFixture(filePath: string): Promise<void> {\n const fixture = this.getFixture();\n const json = JSON.stringify(fixture, null, 2);\n\n // Use dynamic import for Node.js fs\n const { writeFile } = await import(\"node:fs/promises\");\n await writeFile(filePath, json, \"utf-8\");\n\n logger.info(\"Fixture saved\", {\n path: filePath,\n name: fixture.name,\n eventCount: fixture.events.length,\n });\n }\n\n /**\n * Get the number of recorded events\n */\n get eventCount(): number {\n return this.recordedEvents.length;\n }\n\n /**\n * Clear recorded events (start fresh recording)\n */\n clearRecording(): void {\n this.recordedEvents = [];\n this.recordingStartTime = Date.now();\n logger.debug(\"Recording cleared\");\n }\n\n /**\n * Get raw recorded events (for debugging)\n */\n getRawEvents(): RecordedEvent[] {\n return [...this.recordedEvents];\n }\n}\n\n/**\n * Create a RecordingDriver that wraps a real driver\n */\nexport function createRecordingDriver(options: RecordingDriverOptions): RecordingDriver {\n return new RecordingDriver(options);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/recorder/RecordingDriver.ts"],"sourcesContent":["/**\n * RecordingDriver - Wraps a real driver to record events\n *\n * Used to capture real LLM API responses and save them as fixtures.\n * These fixtures can then be played back by MockDriver for testing.\n *\n * Usage:\n * ```typescript\n * import { createClaudeDriver } from \"@agentxjs/claude-driver\";\n * import { RecordingDriver } from \"@agentxjs/devtools/recorder\";\n *\n * // Create real driver\n * const realDriver = createClaudeDriver(config);\n *\n * // Wrap with recorder\n * const recorder = new RecordingDriver({\n * driver: realDriver,\n * name: \"my-scenario\",\n * description: \"User asks about weather\",\n * });\n *\n * await recorder.initialize();\n *\n * // Use like a normal driver - events are recorded\n * for await (const event of recorder.receive({ content: \"Hello\" })) {\n * console.log(event);\n * }\n *\n * // Save the fixture\n * await recorder.saveFixture(\"./fixtures/my-scenario.json\");\n * ```\n */\n\nimport type { UserMessage } from \"@agentxjs/core/agent\";\nimport type { Driver, DriverState, DriverStreamEvent } from \"@agentxjs/core/driver\";\nimport { createLogger } from \"commonxjs/logger\";\nimport type { Fixture, FixtureEvent } from \"../types\";\n\nconst logger = createLogger(\"devtools/RecordingDriver\");\n\n/**\n * Options for RecordingDriver\n */\nexport interface RecordingDriverOptions {\n /**\n * The real driver to wrap\n */\n driver: Driver;\n\n /**\n * Fixture name for the recording\n */\n name: string;\n\n /**\n * Description for the recording\n */\n description?: string;\n}\n\n/**\n * Recorded event with timing\n */\ninterface RecordedEvent {\n event: DriverStreamEvent;\n timestamp: number;\n}\n\n/**\n * RecordingDriver - Records events from a real driver\n *\n * Implements the new Driver interface by wrapping a real driver\n * and intercepting events from receive().\n */\nexport class RecordingDriver implements Driver {\n readonly name = \"RecordingDriver\";\n\n private readonly realDriver: Driver;\n private readonly fixtureName: string;\n private readonly fixtureDescription?: string;\n\n private recordedEvents: RecordedEvent[] = [];\n private recordingStartTime: number = 0;\n\n constructor(options: RecordingDriverOptions) {\n this.realDriver = options.driver;\n this.fixtureName = options.name;\n this.fixtureDescription = options.description;\n\n logger.info(\"RecordingDriver created\", { name: this.fixtureName });\n }\n\n // ============================================================================\n // Driver Interface Properties (delegate to real driver)\n // ============================================================================\n\n get sessionId(): string | null {\n return this.realDriver.sessionId;\n }\n\n get state(): DriverState {\n return this.realDriver.state;\n }\n\n // ============================================================================\n // Lifecycle Methods (delegate to real driver)\n // ============================================================================\n\n async initialize(): Promise<void> {\n await this.realDriver.initialize();\n this.recordingStartTime = Date.now();\n this.recordedEvents = [];\n logger.info(\"RecordingDriver initialized, recording started\", {\n name: this.fixtureName,\n });\n }\n\n async dispose(): Promise<void> {\n await this.realDriver.dispose();\n logger.info(\"RecordingDriver disposed\", {\n name: this.fixtureName,\n eventsRecorded: this.recordedEvents.length,\n });\n }\n\n // ============================================================================\n // Core Methods\n // ============================================================================\n\n /**\n * Receive a user message and return stream of events\n *\n * Wraps the real driver's receive() and records all events.\n */\n async *receive(message: UserMessage): AsyncIterable<DriverStreamEvent> {\n logger.debug(\"RecordingDriver receiving message\", {\n name: this.fixtureName,\n });\n\n // Call the real driver and intercept events\n for await (const event of this.realDriver.receive(message)) {\n // Record the event\n this.recordEvent(event);\n\n // Pass through to caller\n yield event;\n }\n\n logger.debug(\"RecordingDriver receive completed\", {\n name: this.fixtureName,\n eventsRecorded: this.recordedEvents.length,\n });\n }\n\n /**\n * Interrupt current operation (delegate to real driver)\n */\n interrupt(): void {\n this.realDriver.interrupt();\n }\n\n // ============================================================================\n // Recording Methods\n // ============================================================================\n\n /**\n * Record an event\n */\n private recordEvent(event: DriverStreamEvent): void {\n this.recordedEvents.push({\n event,\n timestamp: Date.now(),\n });\n\n logger.debug(\"Event recorded\", {\n type: event.type,\n totalEvents: this.recordedEvents.length,\n });\n }\n\n /**\n * Get the recorded fixture\n */\n getFixture(): Fixture {\n const events: FixtureEvent[] = [];\n let lastTimestamp = this.recordingStartTime;\n\n for (const recorded of this.recordedEvents) {\n const delay = recorded.timestamp - lastTimestamp;\n lastTimestamp = recorded.timestamp;\n\n events.push({\n type: recorded.event.type,\n delay: Math.max(0, delay),\n data: recorded.event.data,\n });\n }\n\n return {\n name: this.fixtureName,\n description: this.fixtureDescription,\n recordedAt: this.recordingStartTime,\n events,\n };\n }\n\n /**\n * Save the recorded fixture to a JSON file\n */\n async saveFixture(filePath: string): Promise<void> {\n const fixture = this.getFixture();\n const json = JSON.stringify(fixture, null, 2);\n\n // Use dynamic import for Node.js fs\n const { writeFile } = await import(\"node:fs/promises\");\n await writeFile(filePath, json, \"utf-8\");\n\n logger.info(\"Fixture saved\", {\n path: filePath,\n name: fixture.name,\n eventCount: fixture.events.length,\n });\n }\n\n /**\n * Get the number of recorded events\n */\n get eventCount(): number {\n return this.recordedEvents.length;\n }\n\n /**\n * Clear recorded events (start fresh recording)\n */\n clearRecording(): void {\n this.recordedEvents = [];\n this.recordingStartTime = Date.now();\n logger.debug(\"Recording cleared\");\n }\n\n /**\n * Get raw recorded events (for debugging)\n */\n getRawEvents(): RecordedEvent[] {\n return [...this.recordedEvents];\n }\n}\n\n/**\n * Create a RecordingDriver that wraps a real driver\n */\nexport function createRecordingDriver(options: RecordingDriverOptions): RecordingDriver {\n return new RecordingDriver(options);\n}\n"],"mappings":";AAmCA,SAAS,oBAAoB;AAG7B,IAAM,SAAS,aAAa,0BAA0B;AAoC/C,IAAM,kBAAN,MAAwC;AAAA,EACpC,OAAO;AAAA,EAEC;AAAA,EACA;AAAA,EACA;AAAA,EAET,iBAAkC,CAAC;AAAA,EACnC,qBAA6B;AAAA,EAErC,YAAY,SAAiC;AAC3C,SAAK,aAAa,QAAQ;AAC1B,SAAK,cAAc,QAAQ;AAC3B,SAAK,qBAAqB,QAAQ;AAElC,WAAO,KAAK,2BAA2B,EAAE,MAAM,KAAK,YAAY,CAAC;AAAA,EACnE;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,YAA2B;AAC7B,WAAO,KAAK,WAAW;AAAA,EACzB;AAAA,EAEA,IAAI,QAAqB;AACvB,WAAO,KAAK,WAAW;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,aAA4B;AAChC,UAAM,KAAK,WAAW,WAAW;AACjC,SAAK,qBAAqB,KAAK,IAAI;AACnC,SAAK,iBAAiB,CAAC;AACvB,WAAO,KAAK,kDAAkD;AAAA,MAC5D,MAAM,KAAK;AAAA,IACb,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UAAyB;AAC7B,UAAM,KAAK,WAAW,QAAQ;AAC9B,WAAO,KAAK,4BAA4B;AAAA,MACtC,MAAM,KAAK;AAAA,MACX,gBAAgB,KAAK,eAAe;AAAA,IACtC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,OAAO,QAAQ,SAAwD;AACrE,WAAO,MAAM,qCAAqC;AAAA,MAChD,MAAM,KAAK;AAAA,IACb,CAAC;AAGD,qBAAiB,SAAS,KAAK,WAAW,QAAQ,OAAO,GAAG;AAE1D,WAAK,YAAY,KAAK;AAGtB,YAAM;AAAA,IACR;AAEA,WAAO,MAAM,qCAAqC;AAAA,MAChD,MAAM,KAAK;AAAA,MACX,gBAAgB,KAAK,eAAe;AAAA,IACtC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,YAAkB;AAChB,SAAK,WAAW,UAAU;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,YAAY,OAAgC;AAClD,SAAK,eAAe,KAAK;AAAA,MACvB;AAAA,MACA,WAAW,KAAK,IAAI;AAAA,IACtB,CAAC;AAED,WAAO,MAAM,kBAAkB;AAAA,MAC7B,MAAM,MAAM;AAAA,MACZ,aAAa,KAAK,eAAe;AAAA,IACnC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,aAAsB;AACpB,UAAM,SAAyB,CAAC;AAChC,QAAI,gBAAgB,KAAK;AAEzB,eAAW,YAAY,KAAK,gBAAgB;AAC1C,YAAM,QAAQ,SAAS,YAAY;AACnC,sBAAgB,SAAS;AAEzB,aAAO,KAAK;AAAA,QACV,MAAM,SAAS,MAAM;AAAA,QACrB,OAAO,KAAK,IAAI,GAAG,KAAK;AAAA,QACxB,MAAM,SAAS,MAAM;AAAA,MACvB,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,MACL,MAAM,KAAK;AAAA,MACX,aAAa,KAAK;AAAA,MAClB,YAAY,KAAK;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,UAAiC;AACjD,UAAM,UAAU,KAAK,WAAW;AAChC,UAAM,OAAO,KAAK,UAAU,SAAS,MAAM,CAAC;AAG5C,UAAM,EAAE,UAAU,IAAI,MAAM,OAAO,aAAkB;AACrD,UAAM,UAAU,UAAU,MAAM,OAAO;AAEvC,WAAO,KAAK,iBAAiB;AAAA,MAC3B,MAAM;AAAA,MACN,MAAM,QAAQ;AAAA,MACd,YAAY,QAAQ,OAAO;AAAA,IAC7B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,aAAqB;AACvB,WAAO,KAAK,eAAe;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAuB;AACrB,SAAK,iBAAiB,CAAC;AACvB,SAAK,qBAAqB,KAAK,IAAI;AACnC,WAAO,MAAM,mBAAmB;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,eAAgC;AAC9B,WAAO,CAAC,GAAG,KAAK,cAAc;AAAA,EAChC;AACF;AAKO,SAAS,sBAAsB,SAAkD;AACtF,SAAO,IAAI,gBAAgB,OAAO;AACpC;","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
+ export { BUILTIN_FIXTURES, EMPTY_RESPONSE, ERROR_RESPONSE, LONG_REPLY, SIMPLE_REPLY, TOOL_CALL, getFixture, listFixtures } from './fixtures/index.js';
1
2
  import { CreateDriver, Driver } from '@agentxjs/core/driver';
2
3
  import { F as Fixture } from './types-C6Lf3vz2.js';
3
4
  export { a as FixtureEvent, M as MockDriverOptions } from './types-C6Lf3vz2.js';
4
5
  export { MockDriver, createMockDriver } from './mock/index.js';
5
6
  export { RecordingDriver, RecordingDriverOptions, createRecordingDriver } from './recorder/index.js';
6
- export { BUILTIN_FIXTURES, EMPTY_RESPONSE, ERROR_RESPONSE, LONG_REPLY, SIMPLE_REPLY, TOOL_CALL, getFixture, listFixtures } from './fixtures/index.js';
7
7
  import '@agentxjs/core/agent';
8
8
 
9
9
  /**
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  MockDriver,
3
3
  createMockDriver
4
- } from "./chunk-J6L73HM5.js";
4
+ } from "./chunk-SVPPRUN5.js";
5
5
  import {
6
6
  RecordingDriver,
7
7
  createRecordingDriver
8
- } from "./chunk-DR45HEV4.js";
8
+ } from "./chunk-YFONF7SD.js";
9
9
  import {
10
10
  BUILTIN_FIXTURES,
11
11
  EMPTY_RESPONSE,
@@ -18,14 +18,14 @@ import {
18
18
  } from "./chunk-6OHXS7LW.js";
19
19
  import {
20
20
  env
21
- } from "./chunk-S7J75AXG.js";
21
+ } from "./chunk-Y6RXZINS.js";
22
22
  import "./chunk-DGUM43GV.js";
23
23
 
24
24
  // src/Devtools.ts
25
- import { createLogger } from "commonxjs/logger";
26
25
  import { existsSync, readFileSync } from "fs";
27
- import { readFile, writeFile, mkdir } from "fs/promises";
28
- import { join, dirname } from "path";
26
+ import { mkdir, readFile, writeFile } from "fs/promises";
27
+ import { dirname, join } from "path";
28
+ import { createLogger } from "commonxjs/logger";
29
29
  var logger = createLogger("devtools/Devtools");
30
30
  var Devtools = class {
31
31
  config;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/Devtools.ts"],"sourcesContent":["/**\n * Devtools SDK - VCR-style fixture management\n *\n * Automatically uses existing fixtures or records new ones on-the-fly.\n *\n * Usage:\n * ```typescript\n * import { createDevtools } from \"@agentxjs/devtools\";\n *\n * import { env } from \"@agentxjs/devtools\";\n *\n * const devtools = createDevtools({\n * fixturesDir: \"./fixtures\",\n * apiKey: env.apiKey,\n * });\n *\n * // Has fixture → playback (MockDriver)\n * // No fixture → call API, record, save, return MockDriver\n * const driver = await devtools.driver(\"hello-test\", {\n * message: \"Hello!\",\n * });\n *\n * await driver.initialize();\n * for await (const event of driver.receive({ content: \"Hello\" })) {\n * console.log(event);\n * }\n * ```\n */\n\nimport type { Driver, CreateDriver, DriverConfig } from \"@agentxjs/core/driver\";\nimport type { Fixture } from \"./types\";\nimport { MockDriver } from \"./mock/MockDriver\";\nimport { RecordingDriver } from \"./recorder/RecordingDriver\";\nimport { createLogger } from \"commonxjs/logger\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { join, dirname } from \"node:path\";\n\nconst logger = createLogger(\"devtools/Devtools\");\n\n/**\n * Devtools configuration\n */\nexport interface DevtoolsConfig {\n /**\n * Directory to store/load fixtures\n */\n fixturesDir: string;\n\n /**\n * API key for recording (required if recording)\n */\n apiKey?: string;\n\n /**\n * API base URL\n */\n baseUrl?: string;\n\n /**\n * Default model\n */\n model?: string;\n\n /**\n * Default system prompt\n */\n systemPrompt?: string;\n\n /**\n * Working directory for tool execution\n */\n cwd?: string;\n\n /**\n * Real driver factory for recording\n * If not provided, will try to use @agentxjs/claude-driver\n */\n createDriver?: CreateDriver;\n}\n\n/**\n * Options for getting a driver\n */\nexport interface DriverOptions {\n /**\n * Message to send if recording is needed\n */\n message: string;\n\n /**\n * Override system prompt\n */\n systemPrompt?: string;\n\n /**\n * Override working directory\n */\n cwd?: string;\n\n /**\n * Force re-record even if fixture exists\n */\n forceRecord?: boolean;\n}\n\n/**\n * Devtools SDK\n */\nexport class Devtools {\n private config: DevtoolsConfig;\n private realCreateDriver: CreateDriver | null = null;\n\n constructor(config: DevtoolsConfig) {\n this.config = config;\n logger.info(\"Devtools initialized\", { fixturesDir: config.fixturesDir });\n }\n\n /**\n * Get a driver for the given fixture name\n *\n * - If fixture exists → returns MockDriver with playback\n * - If fixture doesn't exist → records, saves, returns MockDriver\n */\n async driver(name: string, options: DriverOptions): Promise<Driver> {\n const fixturePath = this.getFixturePath(name);\n\n // Check if fixture exists\n if (!options.forceRecord && existsSync(fixturePath)) {\n logger.info(\"Loading existing fixture\", { name, path: fixturePath });\n const fixture = await this.loadFixture(fixturePath);\n return new MockDriver({ fixture });\n }\n\n // Need to record\n logger.info(\"Recording new fixture\", { name, message: options.message });\n const fixture = await this.record(name, options);\n return new MockDriver({ fixture });\n }\n\n /**\n * Get a CreateDriver function that uses a pre-loaded fixture\n *\n * NOTE: This loads the fixture synchronously, so the fixture must exist.\n * For async loading/recording, use driver() instead.\n */\n createDriverForFixture(fixturePath: string): CreateDriver {\n // Load fixture synchronously (requires existing fixture)\n const content = readFileSync(this.getFixturePath(fixturePath), \"utf-8\");\n const fixture = JSON.parse(content) as Fixture;\n\n return (_config: DriverConfig) => {\n return new MockDriver({ fixture });\n };\n }\n\n /**\n * Record a fixture\n */\n async record(name: string, options: DriverOptions): Promise<Fixture> {\n const createDriver = await this.getRealCreateDriver();\n\n const agentId = `record-${name}`;\n\n // Create driver config\n const driverConfig: DriverConfig = {\n apiKey: this.config.apiKey!,\n baseUrl: this.config.baseUrl,\n agentId,\n model: this.config.model,\n systemPrompt:\n options.systemPrompt || this.config.systemPrompt || \"You are a helpful assistant.\",\n cwd: options.cwd || this.config.cwd || process.cwd(),\n };\n\n // Create real driver\n const realDriver = createDriver(driverConfig);\n\n // Wrap with recorder\n const recorder = new RecordingDriver({\n driver: realDriver,\n name,\n description: `Recording of: \"${options.message}\"`,\n });\n\n // Initialize\n await recorder.initialize();\n\n try {\n // Build user message\n const userMessage = {\n id: `msg_${Date.now()}`,\n role: \"user\" as const,\n subtype: \"user\" as const,\n content: options.message,\n timestamp: Date.now(),\n };\n\n // Send message and collect all events\n for await (const event of recorder.receive(userMessage)) {\n logger.debug(\"Recording event\", { type: event.type });\n\n // Check for completion\n if (event.type === \"message_stop\") {\n break;\n }\n\n // Check for error\n if (event.type === \"error\") {\n const errorData = event.data as { message?: string };\n throw new Error(`Recording error: ${errorData.message}`);\n }\n }\n\n // Get fixture\n const fixture = recorder.getFixture();\n\n // Save fixture\n await this.saveFixture(name, fixture);\n\n return fixture;\n } finally {\n // Cleanup\n await recorder.dispose();\n }\n }\n\n /**\n * Load a fixture by name\n */\n async load(name: string): Promise<Fixture> {\n const fixturePath = this.getFixturePath(name);\n return this.loadFixture(fixturePath);\n }\n\n /**\n * Check if a fixture exists\n */\n exists(name: string): boolean {\n return existsSync(this.getFixturePath(name));\n }\n\n /**\n * Delete a fixture\n */\n async delete(name: string): Promise<void> {\n const fixturePath = this.getFixturePath(name);\n if (existsSync(fixturePath)) {\n const { unlink } = await import(\"node:fs/promises\");\n await unlink(fixturePath);\n logger.info(\"Fixture deleted\", { name });\n }\n }\n\n // ==================== Private ====================\n\n private getFixturePath(name: string): string {\n // If name is already a path, use it directly\n if (name.endsWith(\".json\")) {\n return name;\n }\n return join(this.config.fixturesDir, `${name}.json`);\n }\n\n private async loadFixture(path: string): Promise<Fixture> {\n const content = await readFile(path, \"utf-8\");\n return JSON.parse(content) as Fixture;\n }\n\n private async saveFixture(name: string, fixture: Fixture): Promise<void> {\n const path = this.getFixturePath(name);\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, JSON.stringify(fixture, null, 2), \"utf-8\");\n logger.info(\"Fixture saved\", { name, path, eventCount: fixture.events.length });\n }\n\n private async getRealCreateDriver(): Promise<CreateDriver> {\n if (this.realCreateDriver) {\n return this.realCreateDriver;\n }\n\n if (this.config.createDriver) {\n this.realCreateDriver = this.config.createDriver;\n return this.realCreateDriver;\n }\n\n // Validate API key\n if (!this.config.apiKey) {\n throw new Error(\n \"apiKey is required for recording. Set it in DevtoolsConfig or provide a createDriver.\"\n );\n }\n\n // Try to import claude-driver\n try {\n const { createClaudeDriver } = await import(\"@agentxjs/claude-driver\");\n this.realCreateDriver = createClaudeDriver;\n return this.realCreateDriver;\n } catch {\n throw new Error(\"@agentxjs/claude-driver not found. Install it or provide a createDriver.\");\n }\n }\n}\n\n/**\n * Create a Devtools instance\n */\nexport function createDevtools(config: DevtoolsConfig): Devtools {\n return new Devtools(config);\n}\n\n// ============================================================================\n// VCR CreateDriver Factory\n// ============================================================================\n\n/**\n * Configuration for VCR-aware CreateDriver\n */\nexport interface VcrCreateDriverConfig {\n /**\n * Directory to store/load fixtures\n */\n fixturesDir: string;\n\n /**\n * Get current fixture name. Return null to skip VCR (use real driver).\n * Called when driver is created.\n */\n getFixtureName: () => string | null;\n\n /**\n * API key for recording\n */\n apiKey?: string;\n\n /**\n * API base URL\n */\n baseUrl?: string;\n\n /**\n * Default model\n */\n model?: string;\n\n /**\n * Real driver factory (optional, defaults to @agentxjs/claude-driver)\n */\n createRealDriver?: CreateDriver;\n\n /**\n * Called when playback mode is used\n */\n onPlayback?: (fixtureName: string) => void;\n\n /**\n * Called when recording mode is used\n */\n onRecording?: (fixtureName: string) => void;\n\n /**\n * Called when fixture is saved\n */\n onSaved?: (fixtureName: string, eventCount: number) => void;\n}\n\n/**\n * Create a VCR-aware CreateDriver function\n *\n * VCR logic (hardcoded):\n * - Fixture exists → Playback (MockDriver)\n * - Fixture missing → Recording (RecordingDriver) → Auto-save on dispose\n *\n * @example\n * ```typescript\n * let currentFixture: string | null = null;\n *\n * const vcrCreateDriver = createVcrCreateDriver({\n * fixturesDir: \"./fixtures\",\n * getFixtureName: () => currentFixture,\n * apiKey: process.env.API_KEY,\n * });\n *\n * // Before each test:\n * currentFixture = \"test-scenario-name\";\n *\n * // Use with server:\n * const platform = await createNodePlatform({...});\n * const server = await createServer({\n * platform,\n * createDriver: vcrCreateDriver,\n * });\n * ```\n */\nexport function createVcrCreateDriver(config: VcrCreateDriverConfig): CreateDriver {\n const { fixturesDir, getFixtureName, apiKey, baseUrl, model, onPlayback, onRecording, onSaved } =\n config;\n\n // Real driver factory (must be provided or pre-loaded)\n const realCreateDriver: CreateDriver | null = config.createRealDriver || null;\n\n return (driverConfig: DriverConfig): Driver => {\n const fixtureName = getFixtureName();\n\n // No fixture name → use real driver without VCR\n if (!fixtureName) {\n if (!apiKey) {\n throw new Error(\"No fixture name and no API key. Cannot create driver.\");\n }\n\n // Sync: we need to return immediately, so we create the driver with merged config\n // Note: This path is for non-VCR scenarios\n const createDriver = realCreateDriver || config.createRealDriver;\n if (!createDriver) {\n throw new Error(\"No createRealDriver provided and claude-driver not loaded yet.\");\n }\n\n return createDriver({\n ...driverConfig,\n apiKey,\n baseUrl,\n model,\n });\n }\n\n const fixturePath = join(fixturesDir, `${fixtureName}.json`);\n\n // Fixture exists → Playback (MockDriver)\n if (existsSync(fixturePath)) {\n onPlayback?.(fixtureName);\n logger.info(\"VCR Playback\", { fixtureName });\n\n const fixture = JSON.parse(readFileSync(fixturePath, \"utf-8\")) as Fixture;\n return new MockDriver({ fixture });\n }\n\n // No fixture → Recording (RecordingDriver)\n if (!apiKey) {\n throw new Error(\n `No fixture found for \"${fixtureName}\" and no API key for recording. ` +\n `Either create the fixture or provide an API key.`\n );\n }\n\n onRecording?.(fixtureName);\n logger.info(\"VCR Recording\", { fixtureName });\n\n // Get real driver factory (sync - must be pre-loaded or provided)\n const createDriver = realCreateDriver || config.createRealDriver;\n if (!createDriver) {\n throw new Error(\n \"createRealDriver not available. For async loading, ensure claude-driver is pre-loaded.\"\n );\n }\n\n // Create real driver with merged config\n const realDriver = createDriver({\n ...driverConfig,\n apiKey,\n baseUrl,\n model,\n });\n\n // Wrap with RecordingDriver\n const recorder = new RecordingDriver({\n driver: realDriver,\n name: fixtureName,\n description: `VCR recording: ${fixtureName}`,\n });\n\n // Auto-save fixture on dispose\n let fixtureSaved = false;\n const originalDispose = recorder.dispose.bind(recorder);\n\n recorder.dispose = async () => {\n if (!fixtureSaved && recorder.eventCount > 0) {\n try {\n const fixture = recorder.getFixture();\n\n // Ensure directory exists\n const { mkdir, writeFile } = await import(\"node:fs/promises\");\n await mkdir(dirname(fixturePath), { recursive: true });\n await writeFile(fixturePath, JSON.stringify(fixture, null, 2), \"utf-8\");\n\n fixtureSaved = true;\n onSaved?.(fixtureName, recorder.eventCount);\n logger.info(\"VCR Saved\", { fixtureName, eventCount: recorder.eventCount });\n } catch (e) {\n logger.error(\"VCR Save failed\", { fixtureName, error: e });\n }\n }\n\n return originalDispose();\n };\n\n return recorder;\n };\n}\n"],"mappings":"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","names":["fixture","createDriver","mkdir","writeFile"]}
1
+ {"version":3,"sources":["../src/Devtools.ts"],"sourcesContent":["/**\n * Devtools SDK - VCR-style fixture management\n *\n * Automatically uses existing fixtures or records new ones on-the-fly.\n *\n * Usage:\n * ```typescript\n * import { createDevtools } from \"@agentxjs/devtools\";\n *\n * import { env } from \"@agentxjs/devtools\";\n *\n * const devtools = createDevtools({\n * fixturesDir: \"./fixtures\",\n * apiKey: env.apiKey,\n * });\n *\n * // Has fixture → playback (MockDriver)\n * // No fixture → call API, record, save, return MockDriver\n * const driver = await devtools.driver(\"hello-test\", {\n * message: \"Hello!\",\n * });\n *\n * await driver.initialize();\n * for await (const event of driver.receive({ content: \"Hello\" })) {\n * console.log(event);\n * }\n * ```\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport type { CreateDriver, Driver, DriverConfig } from \"@agentxjs/core/driver\";\nimport { createLogger } from \"commonxjs/logger\";\nimport { MockDriver } from \"./mock/MockDriver\";\nimport { RecordingDriver } from \"./recorder/RecordingDriver\";\nimport type { Fixture } from \"./types\";\n\nconst logger = createLogger(\"devtools/Devtools\");\n\n/**\n * Devtools configuration\n */\nexport interface DevtoolsConfig {\n /**\n * Directory to store/load fixtures\n */\n fixturesDir: string;\n\n /**\n * API key for recording (required if recording)\n */\n apiKey?: string;\n\n /**\n * API base URL\n */\n baseUrl?: string;\n\n /**\n * Default model\n */\n model?: string;\n\n /**\n * Default system prompt\n */\n systemPrompt?: string;\n\n /**\n * Working directory for tool execution\n */\n cwd?: string;\n\n /**\n * Real driver factory for recording\n * If not provided, will try to use @agentxjs/claude-driver\n */\n createDriver?: CreateDriver;\n}\n\n/**\n * Options for getting a driver\n */\nexport interface DriverOptions {\n /**\n * Message to send if recording is needed\n */\n message: string;\n\n /**\n * Override system prompt\n */\n systemPrompt?: string;\n\n /**\n * Override working directory\n */\n cwd?: string;\n\n /**\n * Force re-record even if fixture exists\n */\n forceRecord?: boolean;\n}\n\n/**\n * Devtools SDK\n */\nexport class Devtools {\n private config: DevtoolsConfig;\n private realCreateDriver: CreateDriver | null = null;\n\n constructor(config: DevtoolsConfig) {\n this.config = config;\n logger.info(\"Devtools initialized\", { fixturesDir: config.fixturesDir });\n }\n\n /**\n * Get a driver for the given fixture name\n *\n * - If fixture exists → returns MockDriver with playback\n * - If fixture doesn't exist → records, saves, returns MockDriver\n */\n async driver(name: string, options: DriverOptions): Promise<Driver> {\n const fixturePath = this.getFixturePath(name);\n\n // Check if fixture exists\n if (!options.forceRecord && existsSync(fixturePath)) {\n logger.info(\"Loading existing fixture\", { name, path: fixturePath });\n const fixture = await this.loadFixture(fixturePath);\n return new MockDriver({ fixture });\n }\n\n // Need to record\n logger.info(\"Recording new fixture\", { name, message: options.message });\n const fixture = await this.record(name, options);\n return new MockDriver({ fixture });\n }\n\n /**\n * Get a CreateDriver function that uses a pre-loaded fixture\n *\n * NOTE: This loads the fixture synchronously, so the fixture must exist.\n * For async loading/recording, use driver() instead.\n */\n createDriverForFixture(fixturePath: string): CreateDriver {\n // Load fixture synchronously (requires existing fixture)\n const content = readFileSync(this.getFixturePath(fixturePath), \"utf-8\");\n const fixture = JSON.parse(content) as Fixture;\n\n return (_config: DriverConfig) => {\n return new MockDriver({ fixture });\n };\n }\n\n /**\n * Record a fixture\n */\n async record(name: string, options: DriverOptions): Promise<Fixture> {\n const createDriver = await this.getRealCreateDriver();\n\n const agentId = `record-${name}`;\n\n // Create driver config\n const driverConfig: DriverConfig = {\n apiKey: this.config.apiKey!,\n baseUrl: this.config.baseUrl,\n agentId,\n model: this.config.model,\n systemPrompt:\n options.systemPrompt || this.config.systemPrompt || \"You are a helpful assistant.\",\n cwd: options.cwd || this.config.cwd || process.cwd(),\n };\n\n // Create real driver\n const realDriver = createDriver(driverConfig);\n\n // Wrap with recorder\n const recorder = new RecordingDriver({\n driver: realDriver,\n name,\n description: `Recording of: \"${options.message}\"`,\n });\n\n // Initialize\n await recorder.initialize();\n\n try {\n // Build user message\n const userMessage = {\n id: `msg_${Date.now()}`,\n role: \"user\" as const,\n subtype: \"user\" as const,\n content: options.message,\n timestamp: Date.now(),\n };\n\n // Send message and collect all events\n for await (const event of recorder.receive(userMessage)) {\n logger.debug(\"Recording event\", { type: event.type });\n\n // Check for completion\n if (event.type === \"message_stop\") {\n break;\n }\n\n // Check for error\n if (event.type === \"error\") {\n const errorData = event.data as { message?: string };\n throw new Error(`Recording error: ${errorData.message}`);\n }\n }\n\n // Get fixture\n const fixture = recorder.getFixture();\n\n // Save fixture\n await this.saveFixture(name, fixture);\n\n return fixture;\n } finally {\n // Cleanup\n await recorder.dispose();\n }\n }\n\n /**\n * Load a fixture by name\n */\n async load(name: string): Promise<Fixture> {\n const fixturePath = this.getFixturePath(name);\n return this.loadFixture(fixturePath);\n }\n\n /**\n * Check if a fixture exists\n */\n exists(name: string): boolean {\n return existsSync(this.getFixturePath(name));\n }\n\n /**\n * Delete a fixture\n */\n async delete(name: string): Promise<void> {\n const fixturePath = this.getFixturePath(name);\n if (existsSync(fixturePath)) {\n const { unlink } = await import(\"node:fs/promises\");\n await unlink(fixturePath);\n logger.info(\"Fixture deleted\", { name });\n }\n }\n\n // ==================== Private ====================\n\n private getFixturePath(name: string): string {\n // If name is already a path, use it directly\n if (name.endsWith(\".json\")) {\n return name;\n }\n return join(this.config.fixturesDir, `${name}.json`);\n }\n\n private async loadFixture(path: string): Promise<Fixture> {\n const content = await readFile(path, \"utf-8\");\n return JSON.parse(content) as Fixture;\n }\n\n private async saveFixture(name: string, fixture: Fixture): Promise<void> {\n const path = this.getFixturePath(name);\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, JSON.stringify(fixture, null, 2), \"utf-8\");\n logger.info(\"Fixture saved\", { name, path, eventCount: fixture.events.length });\n }\n\n private async getRealCreateDriver(): Promise<CreateDriver> {\n if (this.realCreateDriver) {\n return this.realCreateDriver;\n }\n\n if (this.config.createDriver) {\n this.realCreateDriver = this.config.createDriver;\n return this.realCreateDriver;\n }\n\n // Validate API key\n if (!this.config.apiKey) {\n throw new Error(\n \"apiKey is required for recording. Set it in DevtoolsConfig or provide a createDriver.\"\n );\n }\n\n // Try to import claude-driver\n try {\n const { createClaudeDriver } = await import(\"@agentxjs/claude-driver\");\n this.realCreateDriver = createClaudeDriver;\n return this.realCreateDriver;\n } catch {\n throw new Error(\"@agentxjs/claude-driver not found. Install it or provide a createDriver.\");\n }\n }\n}\n\n/**\n * Create a Devtools instance\n */\nexport function createDevtools(config: DevtoolsConfig): Devtools {\n return new Devtools(config);\n}\n\n// ============================================================================\n// VCR CreateDriver Factory\n// ============================================================================\n\n/**\n * Configuration for VCR-aware CreateDriver\n */\nexport interface VcrCreateDriverConfig {\n /**\n * Directory to store/load fixtures\n */\n fixturesDir: string;\n\n /**\n * Get current fixture name. Return null to skip VCR (use real driver).\n * Called when driver is created.\n */\n getFixtureName: () => string | null;\n\n /**\n * API key for recording\n */\n apiKey?: string;\n\n /**\n * API base URL\n */\n baseUrl?: string;\n\n /**\n * Default model\n */\n model?: string;\n\n /**\n * Real driver factory (optional, defaults to @agentxjs/claude-driver)\n */\n createRealDriver?: CreateDriver;\n\n /**\n * Called when playback mode is used\n */\n onPlayback?: (fixtureName: string) => void;\n\n /**\n * Called when recording mode is used\n */\n onRecording?: (fixtureName: string) => void;\n\n /**\n * Called when fixture is saved\n */\n onSaved?: (fixtureName: string, eventCount: number) => void;\n}\n\n/**\n * Create a VCR-aware CreateDriver function\n *\n * VCR logic (hardcoded):\n * - Fixture exists → Playback (MockDriver)\n * - Fixture missing → Recording (RecordingDriver) → Auto-save on dispose\n *\n * @example\n * ```typescript\n * let currentFixture: string | null = null;\n *\n * const vcrCreateDriver = createVcrCreateDriver({\n * fixturesDir: \"./fixtures\",\n * getFixtureName: () => currentFixture,\n * apiKey: process.env.API_KEY,\n * });\n *\n * // Before each test:\n * currentFixture = \"test-scenario-name\";\n *\n * // Use with server:\n * const platform = await createNodePlatform({...});\n * const server = await createServer({\n * platform,\n * createDriver: vcrCreateDriver,\n * });\n * ```\n */\nexport function createVcrCreateDriver(config: VcrCreateDriverConfig): CreateDriver {\n const { fixturesDir, getFixtureName, apiKey, baseUrl, model, onPlayback, onRecording, onSaved } =\n config;\n\n // Real driver factory (must be provided or pre-loaded)\n const realCreateDriver: CreateDriver | null = config.createRealDriver || null;\n\n return (driverConfig: DriverConfig): Driver => {\n const fixtureName = getFixtureName();\n\n // No fixture name → use real driver without VCR\n if (!fixtureName) {\n if (!apiKey) {\n throw new Error(\"No fixture name and no API key. Cannot create driver.\");\n }\n\n // Sync: we need to return immediately, so we create the driver with merged config\n // Note: This path is for non-VCR scenarios\n const createDriver = realCreateDriver || config.createRealDriver;\n if (!createDriver) {\n throw new Error(\"No createRealDriver provided and claude-driver not loaded yet.\");\n }\n\n return createDriver({\n ...driverConfig,\n apiKey,\n baseUrl,\n model,\n });\n }\n\n const fixturePath = join(fixturesDir, `${fixtureName}.json`);\n\n // Fixture exists → Playback (MockDriver)\n if (existsSync(fixturePath)) {\n onPlayback?.(fixtureName);\n logger.info(\"VCR Playback\", { fixtureName });\n\n const fixture = JSON.parse(readFileSync(fixturePath, \"utf-8\")) as Fixture;\n return new MockDriver({ fixture });\n }\n\n // No fixture → Recording (RecordingDriver)\n if (!apiKey) {\n throw new Error(\n `No fixture found for \"${fixtureName}\" and no API key for recording. ` +\n `Either create the fixture or provide an API key.`\n );\n }\n\n onRecording?.(fixtureName);\n logger.info(\"VCR Recording\", { fixtureName });\n\n // Get real driver factory (sync - must be pre-loaded or provided)\n const createDriver = realCreateDriver || config.createRealDriver;\n if (!createDriver) {\n throw new Error(\n \"createRealDriver not available. For async loading, ensure claude-driver is pre-loaded.\"\n );\n }\n\n // Create real driver with merged config\n const realDriver = createDriver({\n ...driverConfig,\n apiKey,\n baseUrl,\n model,\n });\n\n // Wrap with RecordingDriver\n const recorder = new RecordingDriver({\n driver: realDriver,\n name: fixtureName,\n description: `VCR recording: ${fixtureName}`,\n });\n\n // Auto-save fixture on dispose\n let fixtureSaved = false;\n const originalDispose = recorder.dispose.bind(recorder);\n\n recorder.dispose = async () => {\n if (!fixtureSaved && recorder.eventCount > 0) {\n try {\n const fixture = recorder.getFixture();\n\n // Ensure directory exists\n const { mkdir, writeFile } = await import(\"node:fs/promises\");\n await mkdir(dirname(fixturePath), { recursive: true });\n await writeFile(fixturePath, JSON.stringify(fixture, null, 2), \"utf-8\");\n\n fixtureSaved = true;\n onSaved?.(fixtureName, recorder.eventCount);\n logger.info(\"VCR Saved\", { fixtureName, eventCount: recorder.eventCount });\n } catch (e) {\n logger.error(\"VCR Save failed\", { fixtureName, error: e });\n }\n }\n\n return originalDispose();\n };\n\n return recorder;\n };\n}\n"],"mappings":"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","names":["fixture","createDriver","mkdir","writeFile"]}
@@ -1,5 +1,5 @@
1
- import { Driver, DriverConfig, DriverState, DriverStreamEvent } from '@agentxjs/core/driver';
2
1
  import { UserMessage } from '@agentxjs/core/agent';
2
+ import { DriverConfig, Driver, DriverState, DriverStreamEvent } from '@agentxjs/core/driver';
3
3
  import { M as MockDriverOptions, F as Fixture } from '../types-C6Lf3vz2.js';
4
4
 
5
5
  /**
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  MockDriver,
3
3
  createMockDriver
4
- } from "../chunk-J6L73HM5.js";
4
+ } from "../chunk-SVPPRUN5.js";
5
5
  import "../chunk-6OHXS7LW.js";
6
6
  import "../chunk-DGUM43GV.js";
7
7
  export {
@@ -1,5 +1,5 @@
1
- import { Driver, DriverState, DriverStreamEvent } from '@agentxjs/core/driver';
2
1
  import { UserMessage } from '@agentxjs/core/agent';
2
+ import { Driver, DriverState, DriverStreamEvent } from '@agentxjs/core/driver';
3
3
  import { F as Fixture } from '../types-C6Lf3vz2.js';
4
4
 
5
5
  /**
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  RecordingDriver,
3
3
  createRecordingDriver
4
- } from "../chunk-DR45HEV4.js";
4
+ } from "../chunk-YFONF7SD.js";
5
5
  import "../chunk-DGUM43GV.js";
6
6
  export {
7
7
  RecordingDriver,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentxjs/devtools",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Development tools for AgentX - MockDriver, RecordingDriver, Fixtures",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -47,17 +47,17 @@
47
47
  "bdd": "bdd"
48
48
  },
49
49
  "dependencies": {
50
- "@agentxjs/core": "^2.0.0",
50
+ "@agentxjs/core": "^2.0.2",
51
51
  "commonxjs": "^0.1.1"
52
52
  },
53
53
  "devDependencies": {
54
- "@agentxjs/claude-driver": "^2.0.0",
55
- "@agentxjs/mono-driver": "^2.0.0",
54
+ "@agentxjs/claude-driver": "^2.0.2",
55
+ "@agentxjs/mono-driver": "^2.0.2",
56
56
  "typescript": "^5.3.3"
57
57
  },
58
58
  "peerDependencies": {
59
- "agentxjs": "^2.0.0",
60
- "@agentxjs/claude-driver": "^2.0.0",
59
+ "agentxjs": "^2.0.2",
60
+ "@agentxjs/claude-driver": "^2.0.2",
61
61
  "@playwright/test": "^1.50.0",
62
62
  "@cucumber/cucumber": "^11.0.0"
63
63
  },
package/src/Devtools.ts CHANGED
@@ -27,14 +27,14 @@
27
27
  * ```
28
28
  */
29
29
 
30
- import type { Driver, CreateDriver, DriverConfig } from "@agentxjs/core/driver";
31
- import type { Fixture } from "./types";
30
+ import { existsSync, readFileSync } from "node:fs";
31
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
32
+ import { dirname, join } from "node:path";
33
+ import type { CreateDriver, Driver, DriverConfig } from "@agentxjs/core/driver";
34
+ import { createLogger } from "commonxjs/logger";
32
35
  import { MockDriver } from "./mock/MockDriver";
33
36
  import { RecordingDriver } from "./recorder/RecordingDriver";
34
- import { createLogger } from "commonxjs/logger";
35
- import { existsSync, readFileSync } from "node:fs";
36
- import { readFile, writeFile, mkdir } from "node:fs/promises";
37
- import { join, dirname } from "node:path";
37
+ import type { Fixture } from "./types";
38
38
 
39
39
  const logger = createLogger("devtools/Devtools");
40
40
 
@@ -1,4 +1,4 @@
1
- import { readFileSync, existsSync } from "node:fs";
1
+ import { existsSync, readFileSync } from "node:fs";
2
2
  import { env } from "../env";
3
3
 
4
4
  const SYSTEM_PROMPT = `You are a documentation reviewer evaluating documents from the reader's experience.
@@ -1,6 +1,6 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { readFileSync } from "node:fs";
3
- import { resolve, dirname } from "node:path";
3
+ import { dirname, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
 
6
6
  const __dirname = dirname(fileURLToPath(import.meta.url));
package/src/bdd/cli.ts CHANGED
@@ -14,8 +14,8 @@
14
14
  */
15
15
 
16
16
  import { spawn } from "node:child_process";
17
- import { resolve, dirname, relative } from "node:path";
18
- import { existsSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
17
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
18
+ import { dirname, relative, resolve } from "node:path";
19
19
  import { fileURLToPath } from "node:url";
20
20
 
21
21
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -4,7 +4,7 @@
4
4
  * Start and stop dev servers during test runs.
5
5
  */
6
6
 
7
- import { spawn, ChildProcess } from "node:child_process";
7
+ import { type ChildProcess, spawn } from "node:child_process";
8
8
  import { waitForUrl } from "./playwright";
9
9
 
10
10
  export interface DevServerOptions {