@kuralle-syrinx/cli 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +152 -0
- package/dist/index.js +1161 -0
- package/dist/index.js.map +7 -0
- package/package.json +53 -0
- package/src/__fixtures__/good-text-agent.mjs +21 -0
- package/src/__fixtures__/no-callable-export.mjs +7 -0
- package/src/__fixtures__/throwing-agent.mjs +8 -0
- package/src/agent-resolve.test.ts +50 -0
- package/src/agent-resolve.ts +80 -0
- package/src/cli.test.ts +154 -0
- package/src/cli.ts +225 -0
- package/src/doctor.test.ts +50 -0
- package/src/doctor.ts +101 -0
- package/src/exit-codes.ts +38 -0
- package/src/fixture.test.ts +100 -0
- package/src/fixture.ts +96 -0
- package/src/help.ts +70 -0
- package/src/index.ts +37 -0
- package/src/text-command.test.ts +32 -0
- package/src/text-command.ts +32 -0
- package/src/text-turn.test.ts +49 -0
- package/src/text-turn.ts +108 -0
- package/src/turn-command.test.ts +80 -0
- package/src/turn-command.ts +81 -0
- package/src/turn-runner.test.ts +111 -0
- package/src/turn-runner.ts +381 -0
- package/src/version.test.ts +69 -0
- package/src/version.ts +92 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/cli.ts", "../src/agent-resolve.ts", "../src/exit-codes.ts", "../src/version.ts", "../package.json", "../src/doctor.ts", "../src/help.ts", "../src/text-turn.ts", "../../core/src/pricing.ts", "../../core/src/audio-envelope.ts", "../../core/src/audio/g722.ts", "../src/text-command.ts", "../src/turn-command.ts", "../src/fixture.ts", "../src/turn-runner.ts", "../src/index.ts"],
|
|
4
|
+
"sourcesContent": ["// SPDX-License-Identifier: MIT\n//\n// Argument parsing and verb dispatch. Never interactive: no prompts, no\n// spinners, no colour unless the stream is a TTY. Results go to stdout,\n// diagnostics to stderr \u2014 always, so --json output stays parseable.\n\nimport { parseArgs } from \"node:util\";\n\nimport { runDoctor } from \"./doctor.js\";\nimport { CliError, EXIT_CODES, EXIT_CODE_TABLE, type ExitCode } from \"./exit-codes.js\";\nimport { HELP_TEXT } from \"./help.js\";\nimport { runTextCommand } from \"./text-command.js\";\nimport { runTurnCommand } from \"./turn-command.js\";\nimport { CLI_VERSION, warnOnVersionSkew } from \"./version.js\";\n\nexport interface CliIO {\n readonly stdout: (line: string) => void;\n readonly stderr: (line: string) => void;\n}\n\nexport const defaultIO: CliIO = {\n stdout: (line) => {\n process.stdout.write(`${line}\\n`);\n },\n stderr: (line) => {\n process.stderr.write(`${line}\\n`);\n },\n};\n\nfunction exitCodeName(code: ExitCode): string {\n return EXIT_CODE_TABLE.find((row) => row.code === code)?.name ?? \"UNKNOWN\";\n}\n\n/** Best-effort pre-parse scan so even a usage error can honour --json, without depending on parseArgs succeeding. */\nfunction looksLikeJsonRequested(args: readonly string[]): boolean {\n return args.includes(\"--json\");\n}\n\nfunction reportSuccess(io: CliIO, json: boolean, verb: string, payload: Record<string, unknown>, prose: string): ExitCode {\n if (json) {\n io.stdout(JSON.stringify({ ok: true, verb, ...payload }));\n } else {\n io.stdout(prose);\n }\n return EXIT_CODES.SUCCESS;\n}\n\nfunction reportError(io: CliIO, json: boolean, verb: string, err: unknown): ExitCode {\n const cliErr =\n err instanceof CliError ? err : new CliError(EXIT_CODES.INTERNAL, err instanceof Error ? err.message : String(err));\n if (cliErr.exitCode === EXIT_CODES.INTERNAL && err instanceof Error && err.stack) {\n io.stderr(err.stack);\n }\n if (json) {\n io.stdout(\n JSON.stringify({\n ok: false,\n verb,\n error: { code: exitCodeName(cliErr.exitCode), message: cliErr.message, ...(cliErr.details ?? {}) },\n }),\n );\n } else {\n io.stderr(`error: ${cliErr.message}`);\n }\n return cliErr.exitCode;\n}\n\nasync function handleTurn(args: string[], io: CliIO): Promise<ExitCode> {\n let values;\n try {\n ({ values } = parseArgs({\n args,\n options: {\n in: { type: \"string\" },\n agent: { type: \"string\" },\n \"session-dir\": { type: \"string\" },\n json: { type: \"boolean\", default: false },\n },\n strict: true,\n allowPositionals: false,\n }));\n } catch (err) {\n return reportError(io, looksLikeJsonRequested(args), \"turn\", new CliError(EXIT_CODES.USAGE, err instanceof Error ? err.message : String(err)));\n }\n\n const json = values.json === true;\n if (!values.in) {\n return reportError(io, json, \"turn\", new CliError(EXIT_CODES.USAGE, \"turn requires --in <fixture.wav|fixture.json>\"));\n }\n if (!values.agent) {\n return reportError(\n io,\n json,\n \"turn\",\n new CliError(EXIT_CODES.USAGE, \"turn requires --agent <module>#<export> \u2014 the CLI brings no providers of its own (see --help)\"),\n );\n }\n\n try {\n const result = await runTurnCommand({\n inputPath: values.in,\n agentSpec: values.agent,\n sessionDir: values[\"session-dir\"],\n });\n const prose = [\n `agent: ${result.agent}`,\n `transcript: ${result.transcript || \"(empty)\"}`,\n `reply: ${result.reply || \"(empty)\"}`,\n `session: ${result.sessionDir}`,\n result.assertion ? `assertion: match (expected \"${result.assertion.expectedTranscript}\")` : \"assertion: none\",\n ].join(\"\\n\");\n return reportSuccess(io, json, \"turn\", { ...result }, prose);\n } catch (err) {\n return reportError(io, json, \"turn\", err);\n }\n}\n\nasync function handleText(args: string[], io: CliIO): Promise<ExitCode> {\n let values, positionals;\n try {\n ({ values, positionals } = parseArgs({\n args,\n options: {\n agent: { type: \"string\" },\n json: { type: \"boolean\", default: false },\n },\n strict: true,\n allowPositionals: true,\n }));\n } catch (err) {\n return reportError(io, looksLikeJsonRequested(args), \"text\", new CliError(EXIT_CODES.USAGE, err instanceof Error ? err.message : String(err)));\n }\n\n const json = values.json === true;\n const message = positionals[0];\n if (!message) {\n return reportError(io, json, \"text\", new CliError(EXIT_CODES.USAGE, 'text requires a message: syrinx text \"<message>\" --agent <module>#<export>'));\n }\n if (!values.agent) {\n return reportError(\n io,\n json,\n \"text\",\n new CliError(EXIT_CODES.USAGE, \"text requires --agent <module>#<export> \u2014 the CLI brings no providers of its own (see --help)\"),\n );\n }\n\n try {\n const result = await runTextCommand({ message, agentSpec: values.agent });\n const prose = `agent: ${result.agent}\\nreply: ${result.reply || \"(empty)\"}\\nttft: ${String(result.ttftMs)}ms total: ${String(result.totalMs)}ms`;\n return reportSuccess(io, json, \"text\", { ...result }, prose);\n } catch (err) {\n return reportError(io, json, \"text\", err);\n }\n}\n\nasync function handleDoctor(args: string[], io: CliIO): Promise<ExitCode> {\n let values;\n try {\n ({ values } = parseArgs({\n args,\n options: { agent: { type: \"string\" }, json: { type: \"boolean\", default: false } },\n strict: true,\n allowPositionals: false,\n }));\n } catch (err) {\n return reportError(io, looksLikeJsonRequested(args), \"doctor\", new CliError(EXIT_CODES.USAGE, err instanceof Error ? err.message : String(err)));\n }\n\n const json = values.json === true;\n const report = await runDoctor({ agentSpec: values.agent });\n const prose = [\n `syrinx cli ${report.cliVersion} node ${report.node.version} (${report.node.platform})`,\n `${report.core.package}: ${report.core.resolved ? `v${String(report.core.version)}` : \"not installed\"}${report.core.majorMismatch ? \" (major mismatch with CLI)\" : \"\"}`,\n \"well-known provider keys (informational only):\",\n ...Object.entries(report.wellKnownProviderKeys).map(([key, present]) => ` ${key}: ${present ? \"present\" : \"missing\"}`),\n ...(report.agent ? [`agent (${report.agent.spec}): ${report.agent.resolved ? `resolved -> ${String(report.agent.label)}` : `NOT resolved (${String(report.agent.error)})`}`] : []),\n report.summary,\n ].join(\"\\n\");\n return reportSuccess(io, json, \"doctor\", { ...report }, prose);\n}\n\nexport async function main(argv: readonly string[], io: CliIO = defaultIO): Promise<ExitCode> {\n try {\n warnOnVersionSkew(process.cwd());\n } catch {\n // Version-skew reporting is best-effort diagnostics; never let it block a command.\n }\n\n const [verb, ...rest] = argv;\n\n if (verb === undefined || verb === \"--help\" || verb === \"-h\") {\n io.stdout(HELP_TEXT);\n return EXIT_CODES.SUCCESS;\n }\n if (verb === \"--version\" || verb === \"-v\") {\n io.stdout(CLI_VERSION);\n return EXIT_CODES.SUCCESS;\n }\n\n switch (verb) {\n case \"turn\":\n return handleTurn(rest, io);\n case \"text\":\n return handleText(rest, io);\n case \"doctor\":\n return handleDoctor(rest, io);\n case \"console\":\n case \"chat\":\n case \"listen\":\n return reportError(\n io,\n looksLikeJsonRequested(rest),\n verb,\n new CliError(EXIT_CODES.USAGE, `\"${verb}\" is not a syrinx command \u2014 this CLI is non-interactive by design (see --help). Use the Studio for that.`),\n );\n default:\n return reportError(\n io,\n looksLikeJsonRequested(rest),\n verb,\n new CliError(EXIT_CODES.USAGE, `unknown command: ${verb} (see --help)`),\n );\n }\n}\n", "// SPDX-License-Identifier: MIT\n//\n// Resolves `--agent <module>[#export]` to a zero-arg VoiceAgentSession factory.\n// Same contract as examples/02-hello-voice-headless/scripts/dev-server.ts's\n// `resolveAgentFactory` (LDT-1) \u2014 the module path is user-owned code that\n// brings its own providers; the CLI itself never constructs an STT/TTS/\n// reasoner plugin. Re-implemented here (not imported from the example) because\n// the dependency direction runs examples \u2192 cli, never the reverse, and\n// dev-server.ts is a script in a private example package, not a library export.\n//\n// Fails loudly and specifically, exactly like dev-server.ts's version: a CLI\n// that silently ran a different agent than the one you asked for would be\n// worse than one that refuses to start.\n\nimport { isAbsolute, resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\nimport type { VoiceAgentSession } from \"@kuralle-syrinx/core\";\n\nimport { CliError, EXIT_CODES } from \"./exit-codes.js\";\nimport type { SessionFactory } from \"./turn-runner.js\";\n\nexport interface ResolvedAgent {\n readonly factory: SessionFactory;\n readonly label: string;\n}\n\n/**\n * `spec` is `<module>[#namedExport]`, resolved relative to `baseDir` (the\n * invoking project's cwd) when not absolute. The export must be a zero-arg\n * factory returning a VoiceAgentSession (or a Promise of one) \u2014 the same\n * shape `--agent` expects in dev-server.ts. Omit `#export` for a default\n * export (or a `createSession` export, checked in that order).\n */\nexport async function resolveAgentFactory(spec: string, baseDir: string): Promise<ResolvedAgent> {\n const [modulePath, exportName] = spec.split(\"#\");\n if (!modulePath) {\n throw new CliError(EXIT_CODES.USAGE, `--agent needs a module path, got: ${spec}`);\n }\n\n const abs = isAbsolute(modulePath) ? modulePath : resolve(baseDir, modulePath);\n const url = pathToFileURL(abs).href;\n\n let mod: Record<string, unknown>;\n try {\n mod = (await import(url)) as Record<string, unknown>;\n } catch (err) {\n throw new CliError(EXIT_CODES.USAGE, `could not load --agent module \"${modulePath}\" (resolved to ${abs}): ${err instanceof Error ? err.message : String(err)}`);\n }\n\n const picked = exportName ? mod[exportName] : (mod[\"default\"] ?? mod[\"createSession\"]);\n if (typeof picked !== \"function\") {\n const available = Object.keys(mod).filter((k) => typeof mod[k] === \"function\");\n throw new CliError(\n EXIT_CODES.USAGE,\n `${abs} has no callable export ${exportName ? `\"${exportName}\"` : \"(default or createSession)\"}. ` +\n `Callable exports: ${available.length > 0 ? available.join(\", \") : \"(none)\"}`,\n );\n }\n\n const factory = picked as SessionFactory;\n const label = `${modulePath}${exportName ? `#${exportName}` : \"\"}`;\n\n return {\n label,\n factory: async (): Promise<VoiceAgentSession> => {\n try {\n return await factory();\n } catch (err) {\n // The module resolved and is callable; it failed to actually build a\n // session \u2014 most plausibly its own missing config (an env var it\n // needs), so this is a CONFIG-class failure rather than USAGE.\n throw new CliError(\n EXIT_CODES.CONFIG,\n `--agent ${label} failed to construct a session: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n },\n };\n}\n", "// SPDX-License-Identifier: MIT\n//\n// The exit-code contract an agent depends on. Every verb resolves to exactly one\n// of these \u2014 never collapsed to a bare 1 for \"something went wrong\". Documented\n// here once; `--help` and the README quote this table verbatim.\n\nexport const EXIT_CODES = {\n SUCCESS: 0,\n INTERNAL: 1,\n USAGE: 2,\n CONFIG: 3,\n BACKEND: 4,\n ASSERTION: 5,\n} as const;\n\nexport type ExitCode = (typeof EXIT_CODES)[keyof typeof EXIT_CODES];\n\nexport const EXIT_CODE_TABLE: ReadonlyArray<{ readonly code: ExitCode; readonly name: string; readonly meaning: string }> = [\n { code: EXIT_CODES.SUCCESS, name: \"SUCCESS\", meaning: \"the command completed and, where applicable, any assertion matched\" },\n { code: EXIT_CODES.INTERNAL, name: \"INTERNAL\", meaning: \"an unexpected error inside the CLI itself (not a usage or backend problem) \u2014 treat as a bug\" },\n { code: EXIT_CODES.USAGE, name: \"USAGE\", meaning: \"bad invocation: unknown verb/flag, a missing required argument, or a fixture the CLI cannot honour (unsupported format, capture-config mismatch)\" },\n { code: EXIT_CODES.CONFIG, name: \"CONFIG\", meaning: \"required provider configuration or API keys are missing\" },\n { code: EXIT_CODES.BACKEND, name: \"BACKEND\", meaning: \"the agent or backend failed while running the turn (provider/network error, timeout, pipeline error)\" },\n { code: EXIT_CODES.ASSERTION, name: \"ASSERTION\", meaning: \"a replayed fixture's transcript drifted from the expected transcript\" },\n];\n\n/** A typed error carrying the exit code it should terminate the process with. */\nexport class CliError extends Error {\n readonly exitCode: ExitCode;\n readonly details: Record<string, unknown> | undefined;\n\n constructor(exitCode: ExitCode, message: string, details?: Record<string, unknown>) {\n super(message);\n this.name = \"CliError\";\n this.exitCode = exitCode;\n this.details = details;\n }\n}\n", "// SPDX-License-Identifier: MIT\n//\n// Version-skew check (LDT-20 decision #2). On start, resolve the *project's*\n// installed @kuralle-syrinx/core from the nearest node_modules relative to cwd \u2014\n// not the CLI's own bundled version. If the major differs from the CLI's own\n// major, warn loudly to stderr (never stdout \u2014 stdout must stay parseable) and\n// keep going. Warn; do not refuse.\n\nimport { createRequire } from \"node:module\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join, resolve as resolvePath } from \"node:path\";\n\nimport cliPackageJson from \"../package.json\" with { type: \"json\" };\n\nexport const CLI_VERSION: string = cliPackageJson.version;\nexport const CORE_PACKAGE_NAME = \"@kuralle-syrinx/core\";\n\nexport interface ResolvedPackageVersion {\n readonly version: string;\n readonly packageJsonPath: string;\n}\n\n/** First path component of a semver string, e.g. \"4.3.0\" -> \"4\". Not validated against full semver \u2014 good enough for a major-version skew check. */\nexport function majorOf(version: string): string {\n return version.split(\".\")[0] ?? version;\n}\n\n/**\n * Resolve `pkgName`'s installed version by walking node_modules resolution from\n * `fromDir` (normally the invoking project's cwd), then walking up from the\n * resolved entry file to the package's own package.json. Returns undefined when\n * the package cannot be resolved at all (not installed in this project).\n */\nexport function resolveInstalledPackageVersion(pkgName: string, fromDir: string): ResolvedPackageVersion | undefined {\n const anchor = join(resolvePath(fromDir), \"package.json\");\n const require = createRequire(anchor);\n\n let entryPath: string;\n try {\n entryPath = require.resolve(pkgName);\n } catch {\n return undefined;\n }\n\n let dir = dirname(entryPath);\n for (let depth = 0; depth < 20; depth += 1) {\n const candidate = join(dir, \"package.json\");\n if (existsSync(candidate)) {\n try {\n const parsed = JSON.parse(readFileSync(candidate, \"utf8\")) as { name?: unknown; version?: unknown };\n if (parsed.name === pkgName && typeof parsed.version === \"string\") {\n return { version: parsed.version, packageJsonPath: candidate };\n }\n } catch {\n // Malformed package.json on the walk \u2014 keep climbing.\n }\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return undefined;\n}\n\nexport interface VersionSkewCheck {\n readonly cliVersion: string;\n readonly coreVersion: string | undefined;\n readonly coreResolved: boolean;\n readonly mismatch: boolean;\n}\n\nexport function checkCoreVersionSkew(fromDir: string): VersionSkewCheck {\n const resolved = resolveInstalledPackageVersion(CORE_PACKAGE_NAME, fromDir);\n if (!resolved) {\n return { cliVersion: CLI_VERSION, coreVersion: undefined, coreResolved: false, mismatch: false };\n }\n const mismatch = majorOf(resolved.version) !== majorOf(CLI_VERSION);\n return { cliVersion: CLI_VERSION, coreVersion: resolved.version, coreResolved: true, mismatch };\n}\n\n/** Prints a loud warning to stderr when the CLI's major and the project's installed core major differ. Never writes to stdout. */\nexport function warnOnVersionSkew(fromDir: string): VersionSkewCheck {\n const check = checkCoreVersionSkew(fromDir);\n if (check.mismatch) {\n console.error(\n `[syrinx] warning: CLI version ${check.cliVersion} (major ${majorOf(check.cliVersion)}) does not match ` +\n `the installed ${CORE_PACKAGE_NAME} version ${String(check.coreVersion)} (major ${majorOf(check.coreVersion ?? \"\")}) ` +\n `in ${fromDir}. Behavior may differ from what this CLI version expects.`,\n );\n }\n return check;\n}\n", "{\n \"name\": \"@kuralle-syrinx/cli\",\n \"version\": \"4.3.0\",\n \"private\": false,\n \"description\": \"Agent-facing CLI for Syrinx \u2014 run a turn or a text exchange with deterministic, machine-readable (--json) output and exit codes. Not a console: no REPL, no chat loop, no microphone.\",\n \"keywords\": [\n \"voice\",\n \"voice-agent\",\n \"speech\",\n \"syrinx\",\n \"cli\",\n \"agent\",\n \"testing\"\n ],\n \"license\": \"MIT\",\n \"homepage\": \"https://github.com/kuralle/syrinx#readme\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/kuralle/syrinx.git\",\n \"directory\": \"packages/cli\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/kuralle/syrinx/issues\"\n },\n \"type\": \"module\",\n \"bin\": {\n \"syrinx\": \"./dist/index.js\"\n },\n \"files\": [\n \"dist\",\n \"src\",\n \"README.md\"\n ],\n \"exports\": {\n \"./turn-runner\": \"./src/turn-runner.ts\"\n },\n \"scripts\": {\n \"build\": \"node scripts/build.mjs\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"vitest run\"\n },\n \"dependencies\": {\n \"@kuralle-syrinx/core\": \"workspace:*\",\n \"wavefile\": \"^11.0.0\"\n },\n \"devDependencies\": {\n \"@kuralle-syrinx/test\": \"workspace:*\",\n \"@types/node\": \"^22.0.0\",\n \"esbuild\": \"^0.28.1\",\n \"typescript\": \"^5.7.0\",\n \"vitest\": \"^3.2.6\"\n }\n}\n", "// SPDX-License-Identifier: MIT\n//\n// `syrinx doctor` \u2014 reports what is configured and what is missing. Never prints\n// key values, only presence. Always exits SUCCESS: this verb diagnoses, it does\n// not assert.\n//\n// This CLI has no fixed provider set \u2014 it never constructs an STT/TTS/reasoner\n// itself (see --agent, agent-resolve.ts). So doctor cannot claim \"ready for\n// turn\"/\"ready for text\" against a hardcoded key list; it reports well-known\n// provider keys as informational only, and \u2014 when given --agent \u2014 reports\n// whether that specific agent module resolves.\n\nimport { resolveAgentFactory } from \"./agent-resolve.js\";\nimport { CliError } from \"./exit-codes.js\";\nimport { CLI_VERSION, CORE_PACKAGE_NAME, checkCoreVersionSkew, majorOf } from \"./version.js\";\n\n/** Commonly used in the Syrinx ecosystem \u2014 shown for convenience only. The CLI itself does not require any of these; whatever --agent resolves to owns its own provider requirements. */\nexport const WELL_KNOWN_PROVIDER_KEYS = [\"DEEPGRAM_API_KEY\", \"OPENAI_API_KEY\", \"CARTESIA_API_KEY\", \"GEMINI_API_KEY\", \"ELEVENLABS_API_KEY\", \"GROK_API_KEY\"] as const;\n\nexport type WellKnownProviderKey = (typeof WELL_KNOWN_PROVIDER_KEYS)[number];\n\nexport interface DoctorAgentReport {\n readonly spec: string;\n readonly resolved: boolean;\n readonly label?: string;\n readonly error?: string;\n}\n\nexport interface DoctorReport {\n readonly cliVersion: string;\n readonly node: { readonly version: string; readonly platform: string };\n readonly core: {\n readonly package: string;\n readonly resolved: boolean;\n readonly version: string | undefined;\n readonly cliMajor: string;\n readonly coreMajor: string | undefined;\n readonly majorMismatch: boolean;\n };\n /** Informational only \u2014 presence, never the value. This CLI does not require any specific set; see `agent`. */\n readonly wellKnownProviderKeys: Readonly<Record<WellKnownProviderKey, boolean>>;\n readonly agent: DoctorAgentReport | null;\n readonly summary: string;\n}\n\nexport interface RunDoctorOptions {\n readonly cwd?: string;\n readonly env?: Readonly<Record<string, string | undefined>>;\n readonly nodeVersion?: string;\n readonly platform?: string;\n /** `--agent <module>#<export>` \u2014 when given, doctor resolves (imports + finds the callable export) but does not invoke it, so checking doesn't require live provider access. */\n readonly agentSpec?: string;\n}\n\nexport async function runDoctor(opts: RunDoctorOptions = {}): Promise<DoctorReport> {\n const cwd = opts.cwd ?? process.cwd();\n const env = opts.env ?? process.env;\n const skew = checkCoreVersionSkew(cwd);\n\n const wellKnownProviderKeys = Object.fromEntries(\n WELL_KNOWN_PROVIDER_KEYS.map((key) => [key, Boolean(env[key]?.trim())]),\n ) as Record<WellKnownProviderKey, boolean>;\n\n let agent: DoctorAgentReport | null = null;\n if (opts.agentSpec) {\n try {\n const resolved = await resolveAgentFactory(opts.agentSpec, cwd);\n agent = { spec: opts.agentSpec, resolved: true, label: resolved.label };\n } catch (err) {\n agent = {\n spec: opts.agentSpec,\n resolved: false,\n error: err instanceof CliError ? err.message : err instanceof Error ? err.message : String(err),\n };\n }\n }\n\n const summary = !skew.coreResolved\n ? `${CORE_PACKAGE_NAME} is not installed in this project`\n : agent\n ? agent.resolved\n ? `agent resolved: ${agent.label}`\n : `agent could not be resolved: ${agent.error}`\n : \"pass --agent <module>#<export> to check whether a specific agent resolves; the provider keys above are informational only \u2014 this CLI does not require any specific set\";\n\n return {\n cliVersion: CLI_VERSION,\n node: { version: opts.nodeVersion ?? process.version, platform: opts.platform ?? process.platform },\n core: {\n package: CORE_PACKAGE_NAME,\n resolved: skew.coreResolved,\n version: skew.coreVersion,\n cliMajor: majorOf(CLI_VERSION),\n coreMajor: skew.coreVersion ? majorOf(skew.coreVersion) : undefined,\n majorMismatch: skew.mismatch,\n },\n wellKnownProviderKeys,\n agent,\n summary,\n };\n}\n", "// SPDX-License-Identifier: MIT\n\nimport { EXIT_CODE_TABLE } from \"./exit-codes.js\";\n\nconst EXIT_CODE_LINES = EXIT_CODE_TABLE.map((row) => ` ${String(row.code)} ${row.name.padEnd(9)} ${row.meaning}`).join(\"\\n\");\n\nexport const HELP_TEXT = `syrinx \u2014 the agent-facing CLI for a Syrinx voice agent.\n\nNot a console: no REPL, no chat loop, no microphone. Every command is a single\ndeterministic invocation with a documented exit code, meant to be run by a\ncoding agent (or a script), not a human at a prompt.\n\nThis CLI brings no providers of its own \u2014 no Deepgram, no Cartesia, no OpenAI.\n--agent points it at YOUR agent module; that module owns its own providers.\n\nUSAGE\n syrinx <command> [options]\n\nAGENT RESOLUTION (--agent, required for turn and text)\n --agent <module>[#namedExport]\n <module> is a path (resolved relative to cwd unless absolute) to your\n own code; the export must be a zero-arg factory returning a\n VoiceAgentSession (or a Promise of one) \u2014 exactly the contract\n examples/02-hello-voice-headless/scripts/dev-server.ts's --agent flag\n uses. Omit #export for a default export (or a \"createSession\" export).\n Resolution failures (module not found, no such export, export not\n callable) exit USAGE and name the callable exports that were found.\n If the export IS callable but throws when invoked, that exits CONFIG \u2014\n most plausibly the agent's own module is missing something it needs\n (an env var, a key file, ...).\n\nCOMMANDS\n turn --in <fixture.wav|fixture.json> --agent <module>#<export> [options]\n Run one turn through the resolved agent and report the transcript, the\n reply, and per-stage timings. Given a fixture.json sidecar (produced by\n the Studio's \"Save as fixture\"), honours its recorded capture config\n and, when the sidecar carries an expected transcript, asserts the\n replay matches it.\n --in <path> required. fixture.wav or fixture.json\n --agent <module>#<export> required.\n --session-dir <dir> where turn artifacts are written (default: a temp dir)\n --json emit one parseable JSON object on stdout\n\n text \"<message>\" --agent <module>#<export> [options]\n Send a typed turn (no STT, no microphone) to the resolved agent and\n report the reply.\n --agent <module>#<export> required.\n --json emit one parseable JSON object on stdout\n\n doctor [--agent <module>#<export>] [--json]\n Report what is configured: the Node runtime, whether\n @kuralle-syrinx/core is installed and its version, and \u2014 informational\n only \u2014 which well-known provider keys are present in the environment\n (never their values). This CLI does not require any specific provider\n key; pass --agent to check whether a SPECIFIC agent module resolves.\n Always exits 0 \u2014 this command diagnoses, it does not assert.\n\n --help, -h show this help\n --version, -v print the CLI version\n\nOUTPUT\n --json is a first-class output mode: every command supports it and emits a\n single parseable JSON object on stdout. Diagnostics (warnings, e.g. a\n version-skew notice) always go to stderr, never stdout, so --json output\n stays parseable. Without --json, commands print a short human-readable\n summary to stdout and errors to stderr.\n\nEXIT CODES\n${EXIT_CODE_LINES}\n`;\n", "// SPDX-License-Identifier: MIT\n//\n// `syrinx text \"<message>\"` \u2014 a typed turn against an already-built\n// VoiceAgentSession (the same `--agent`-supplied session `driveTurn` runs\n// audio through). Pushes the real shipped path \u2014 `user.text_received` \u2014 the\n// same packet examples/02-hello-voice-headless/scripts/run-realtime-gemini-sendtext-smoke.ts\n// drives, so a typed turn exercises exactly the pipeline a spoken one would,\n// minus STT. This module never constructs a reasoner/provider itself; the\n// session (and everything it can do) is entirely the caller's.\n\nimport { randomUUID } from \"node:crypto\";\n\nimport { Route, type VoiceAgentSession, type VoiceAgentSessionEvents } from \"@kuralle-syrinx/core\";\n\nimport { CliError, EXIT_CODES } from \"./exit-codes.js\";\nimport type { SessionFactory } from \"./turn-runner.js\";\n\nconst DEFAULT_TIMEOUT_MS = 60_000;\n\nexport interface DriveTextOptions {\n /** An already-registered VoiceAgentSession, or a factory producing one. Built by the caller \u2014 this module never constructs a reasoner. */\n readonly session: VoiceAgentSession | SessionFactory;\n readonly message: string;\n readonly timeoutMs?: number;\n}\n\nexport interface TextTurnResult {\n readonly reply: string;\n readonly ttftMs: number;\n readonly totalMs: number;\n readonly toolCalls: number;\n}\n\nasync function resolveSession(session: VoiceAgentSession | SessionFactory): Promise<VoiceAgentSession> {\n return typeof session === \"function\" ? session() : session;\n}\n\n/** Send one typed turn to an already-built session and report the reply. */\nexport async function driveText(opts: DriveTextOptions): Promise<TextTurnResult> {\n const session = await resolveSession(opts.session);\n const contextId = randomUUID();\n\n const t0 = performance.now();\n let ttftMs = -1;\n let reply = \"\";\n let toolCalls = 0;\n\n const on = <K extends keyof VoiceAgentSessionEvents>(event: K, handler: VoiceAgentSessionEvents[K]): void => {\n session.on(event, handler);\n };\n\n const finished = new Promise<void>((resolveFinished, reject) => {\n const timeout = setTimeout(() => {\n reject(new CliError(EXIT_CODES.BACKEND, `agent did not finish within ${String(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS)}ms`));\n }, opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);\n\n on(\"agent_text_delta\", (event) => {\n if (event.turnId !== contextId) return;\n if (ttftMs < 0) ttftMs = performance.now() - t0;\n reply += event.delta;\n });\n on(\"agent_tool_call\", (event) => {\n if (event.turnId !== contextId) return;\n toolCalls += 1;\n });\n on(\"agent_finished\", (event) => {\n if (event.turnId !== contextId) return;\n clearTimeout(timeout);\n resolveFinished();\n });\n on(\"error\", (event) => {\n clearTimeout(timeout);\n reject(new CliError(EXIT_CODES.BACKEND, `agent failed: ${event.message} (${event.stage}/${event.category})`));\n });\n });\n\n // `session.start()` below can throw before `await finished` is ever reached \u2014 an\n // init failure raises BOTH. In that case the `error` handler still rejects this\n // promise, and with nothing awaiting it Node reports an unhandled rejection and\n // prints a stack trace AFTER the command has already written its clean `--json`\n // object. Attaching a no-op handler marks it handled; `await finished` below still\n // observes the rejection, because `.catch()` returns a new promise and does not\n // consume this one.\n void finished.catch(() => {});\n\n try {\n await session.start();\n session.bus.push(Route.Main, {\n kind: \"user.text_received\",\n contextId,\n timestampMs: Date.now(),\n text: opts.message,\n });\n await finished;\n } catch (err) {\n if (err instanceof CliError) throw err;\n throw new CliError(EXIT_CODES.BACKEND, `agent failed: ${err instanceof Error ? err.message : String(err)}`);\n } finally {\n await session.close().catch(() => {});\n }\n\n return {\n reply,\n ttftMs: ttftMs < 0 ? 0 : Math.round(ttftMs),\n totalMs: Math.round(performance.now() - t0),\n toolCalls,\n };\n}\n", "// SPDX-License-Identifier: MIT\n//\n// Versioned price catalog: usage.recorded quantities \u2192 USD.\n// Unknown provider/model keys yield unpriced (never silent 0 for real providers).\n// Declared local/self-hosted models are explicitly zero-cost.\n\nimport type { UsageRecordedPacket } from \"./packets.js\";\n\nexport interface SttPrice {\n readonly usdPerAudioSecond: number;\n}\n\nexport interface LlmPrice {\n readonly usdPer1MInputTokens: number;\n readonly usdPer1MOutputTokens: number;\n readonly usdPer1MCachedInputTokens?: number;\n}\n\nexport interface TtsPrice {\n readonly usdPer1MCharacters: number;\n}\n\nexport interface PriceCatalog {\n readonly source: string;\n readonly version: string;\n /** Keys: `provider/model` (e.g. `deepgram/nova-3`). */\n readonly stt: Readonly<Record<string, SttPrice>>;\n readonly llm: Readonly<Record<string, LlmPrice>>;\n readonly tts: Readonly<Record<string, TtsPrice>>;\n}\n\nexport type CostResult =\n | { readonly usd: number; readonly unpriced?: undefined }\n | { readonly usd: null; readonly unpriced: true };\n\nfunction catalogKey(provider: string | undefined, model: string | undefined): string | null {\n if (!provider || !model) return null;\n return `${provider}/${model}`;\n}\n\n/**\n * Public list prices stamped into DEFAULT_PRICE_CATALOG.source.\n * STT $/audio-second from $/min \u00F7 60. TTS is $/1M characters. LLM is $/1M tokens.\n */\nexport const DEFAULT_PRICE_CATALOG: PriceCatalog = {\n source:\n \"voice-prices@3 | deepgram.com/pricing PAYG Nova-3 streaming monolingual $0.0077/min \u2192 $0.000128333/s; \" +\n \"deepgram.com/pricing Aura-2 TTS $0.030/1k chars \u2192 $30/1M; \" +\n \"cartesia.ai/pricing + docs.cartesia.ai/pricing ~1 credit/char, PAYG ~$50/1M chars (cloudtalk.io cartesia-pricing 2026); \" +\n \"openai tts-1 $15/1M chars (openai.com api pricing); gpt-4o-mini-tts text $0.60/1M (community/azure listed text input rate); \" +\n \"openai GPT-4.1-mini $0.40/$1.60 per 1M in/out (developers.openai.com/api/docs/pricing; Azure GPT-4.1-mini-2025-04-14 Global); \" +\n \"cloud.google.com/speech-to-text/pricing V2 standard recognition $0.016/min \u2192 $0.000266667/s (latest_long); \" +\n \"ai.google.dev/pricing Gemini 2.5 Flash TTS preview text $0.50/1M chars (Gemini API TTS text input rate, 2026); \" +\n \"elevenlabs.io/pricing/api Flash/Turbo TTS $0.05/1k chars \u2192 $50/1M; Multilingual v2/v3 $0.10/1k \u2192 $100/1M; \" +\n \"Scribe v2 Realtime $0.39/hour \u2192 $0.000108333/s; \" +\n \"grok/stt + grok/* TTS + epsilon/epsilon-tts intentionally ABSENT (no public list price \u2192 costOf unpriced, never silent $0)\",\n version: \"3\",\n stt: {\n \"deepgram/nova-3\": { usdPerAudioSecond: 0.0077 / 60 },\n \"deepgram/nova-2\": { usdPerAudioSecond: 0.0077 / 60 },\n \"deepgram/flux-general-en\": { usdPerAudioSecond: 0.0077 / 60 },\n // Google Cloud Speech-to-Text V2 standard streaming recognition list rate.\n \"google/latest_long\": { usdPerAudioSecond: 0.016 / 60 },\n \"google/latest_short\": { usdPerAudioSecond: 0.016 / 60 },\n \"google/chirp_2\": { usdPerAudioSecond: 0.016 / 60 },\n // ElevenLabs Scribe v2 Realtime \u2014 public list $0.39/hour.\n \"elevenlabs/scribe_v2_realtime\": { usdPerAudioSecond: 0.39 / 3600 },\n \"local/whisper\": { usdPerAudioSecond: 0 },\n // grok/stt: no public list price \u2014 omit so costOf \u2192 unpriced\n },\n llm: {\n \"openai/gpt-4.1-mini\": {\n usdPer1MInputTokens: 0.4,\n usdPer1MOutputTokens: 1.6,\n usdPer1MCachedInputTokens: 0.1,\n },\n \"local/llm\": {\n usdPer1MInputTokens: 0,\n usdPer1MOutputTokens: 0,\n },\n },\n tts: {\n \"cartesia/sonic-3\": { usdPer1MCharacters: 50 },\n \"openai/gpt-4o-mini-tts\": { usdPer1MCharacters: 0.6 },\n \"openai/tts-1\": { usdPer1MCharacters: 15 },\n \"deepgram/aura-2-thalia-en\": { usdPer1MCharacters: 30 },\n // Gemini TTS preview models \u2014 Gemini API TTS text input list rate.\n \"gemini/gemini-3.1-flash-tts-preview\": { usdPer1MCharacters: 0.5 },\n \"gemini/gemini-2.5-flash-preview-tts\": { usdPer1MCharacters: 0.5 },\n \"gemini/gemini-2.5-pro-preview-tts\": { usdPer1MCharacters: 0.5 },\n // ElevenLabs public list \u2014 Flash/Turbo $0.05/1k, Multilingual $0.10/1k.\n \"elevenlabs/eleven_flash_v2_5\": { usdPer1MCharacters: 50 },\n \"elevenlabs/eleven_flash_v2\": { usdPer1MCharacters: 50 },\n \"elevenlabs/eleven_turbo_v2_5\": { usdPer1MCharacters: 50 },\n \"elevenlabs/eleven_turbo_v2\": { usdPer1MCharacters: 50 },\n \"elevenlabs/eleven_multilingual_v2\": { usdPer1MCharacters: 100 },\n \"local/tts\": { usdPer1MCharacters: 0 },\n // grok/* voice keys + epsilon/epsilon-tts: no public list \u2014 omit \u2192 unpriced\n },\n};\n\nexport function costOf(usage: UsageRecordedPacket, catalog: PriceCatalog): CostResult {\n const key = catalogKey(usage.provider, usage.model);\n if (!key) return { usd: null, unpriced: true };\n\n switch (usage.stage) {\n case \"stt\": {\n const price = catalog.stt[key];\n if (!price) return { usd: null, unpriced: true };\n const seconds = usage.audioSeconds ?? 0;\n return { usd: seconds * price.usdPerAudioSecond };\n }\n case \"tts\": {\n const price = catalog.tts[key];\n if (!price) return { usd: null, unpriced: true };\n const characters = usage.characters ?? 0;\n return { usd: (characters / 1_000_000) * price.usdPer1MCharacters };\n }\n case \"llm\": {\n const price = catalog.llm[key];\n if (!price) return { usd: null, unpriced: true };\n const input = usage.inputTokens ?? 0;\n const output = usage.outputTokens ?? 0;\n const cached = usage.cachedInputTokens ?? 0;\n const cachedRate = price.usdPer1MCachedInputTokens ?? price.usdPer1MInputTokens;\n // Prefer charging cached tokens at the cached rate when present; non-cached input is the remainder.\n const nonCachedInput = Math.max(0, input - cached);\n const usd =\n (nonCachedInput / 1_000_000) * price.usdPer1MInputTokens +\n (cached / 1_000_000) * cachedRate +\n (output / 1_000_000) * price.usdPer1MOutputTokens;\n return { usd };\n }\n default:\n return { usd: null, unpriced: true };\n }\n}\n", "// SPDX-License-Identifier: MIT\n\nimport type { AudioFormat } from \"./packets.js\";\n\nexport const SYRINX_AUDIO_ENVELOPE_NAME = \"syrinx.audio.v1\" as const;\nexport const SYRINX_AUDIO_ENVELOPE_MAGIC = new Uint8Array([83, 89, 82, 88, 65, 49, 10]);\n\nexport interface SyrinxAudioEnvelopeHeader {\n readonly type: \"audio\";\n readonly contextId?: string;\n readonly sampleRateHz: number;\n readonly sequence?: number;\n readonly encoding?: \"pcm_s16le\" | \"opus\";\n readonly channels?: 1;\n readonly byteLength?: number;\n readonly durationMs?: number;\n}\n\nexport interface SyrinxAudioEnvelope {\n readonly header: SyrinxAudioEnvelopeHeader;\n readonly audio: Uint8Array;\n}\n\nexport function assertAudioFormat(format: AudioFormat): void {\n if (format.channels !== 1) throw new Error(\"audio must be mono\");\n if (!Number.isInteger(format.sampleRateHz) || format.sampleRateHz <= 0) {\n throw new Error(\"sampleRateHz must be a positive integer\");\n }\n}\n\nexport function assertAudioPayload(format: AudioFormat, audio: Uint8Array): void {\n if (format.encoding === \"opus\") {\n if (audio.byteLength === 0) {\n throw new Error(\"opus payload must not be empty\");\n }\n return;\n }\n if (format.encoding === \"mulaw\") {\n if (audio.byteLength === 0) {\n throw new Error(\"mulaw payload must not be empty\");\n }\n return;\n }\n if (audio.byteLength % 2 !== 0) {\n throw new Error(\"PCM16 payload must contain an even number of bytes\");\n }\n}\n\nexport function encodeSyrinxAudioEnvelope(header: SyrinxAudioEnvelopeHeader, audio: Uint8Array): Uint8Array {\n validateSyrinxAudioEnvelope(header, audio);\n const headerBytes = new TextEncoder().encode(JSON.stringify(header));\n const output = new Uint8Array(SYRINX_AUDIO_ENVELOPE_MAGIC.byteLength + 4 + headerBytes.byteLength + audio.byteLength);\n output.set(SYRINX_AUDIO_ENVELOPE_MAGIC, 0);\n new DataView(output.buffer, output.byteOffset, output.byteLength)\n .setUint32(SYRINX_AUDIO_ENVELOPE_MAGIC.byteLength, headerBytes.byteLength, true);\n output.set(headerBytes, SYRINX_AUDIO_ENVELOPE_MAGIC.byteLength + 4);\n output.set(audio, SYRINX_AUDIO_ENVELOPE_MAGIC.byteLength + 4 + headerBytes.byteLength);\n return output;\n}\n\nexport function decodeSyrinxAudioEnvelope(data: Uint8Array): SyrinxAudioEnvelope {\n if (!hasSyrinxAudioEnvelope(data)) {\n throw new Error(\"Syrinx binary audio envelope magic is missing\");\n }\n\n const headerLengthOffset = SYRINX_AUDIO_ENVELOPE_MAGIC.byteLength;\n if (data.byteLength < headerLengthOffset + 4) {\n throw new Error(\"Syrinx binary audio envelope is truncated\");\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n const headerLength = view.getUint32(headerLengthOffset, true);\n const headerStart = headerLengthOffset + 4;\n const headerEnd = headerStart + headerLength;\n if (headerLength <= 0 || headerEnd > data.byteLength) {\n throw new Error(\"Syrinx binary audio envelope has an invalid header length\");\n }\n\n const parsed = JSON.parse(new TextDecoder().decode(data.subarray(headerStart, headerEnd))) as unknown;\n if (!parsed || typeof parsed !== \"object\") {\n throw new Error(\"Syrinx binary audio envelope header must be an object\");\n }\n const header = parsed as SyrinxAudioEnvelopeHeader;\n const audio = data.subarray(headerEnd);\n validateSyrinxAudioEnvelope(header, audio);\n\n return { header, audio };\n}\n\nexport function hasSyrinxAudioEnvelope(data: Uint8Array): boolean {\n if (data.byteLength < SYRINX_AUDIO_ENVELOPE_MAGIC.byteLength) return false;\n for (let i = 0; i < SYRINX_AUDIO_ENVELOPE_MAGIC.byteLength; i += 1) {\n if (data[i] !== SYRINX_AUDIO_ENVELOPE_MAGIC[i]) return false;\n }\n return true;\n}\n\nfunction validateSyrinxAudioEnvelope(header: SyrinxAudioEnvelopeHeader, audio: Uint8Array): void {\n if (header.type !== \"audio\") {\n throw new Error(\"Syrinx binary audio envelope type must be audio\");\n }\n if (!isPositiveInteger(header.sampleRateHz)) {\n throw new Error(\"Syrinx binary audio envelope sampleRateHz must be a positive integer\");\n }\n if (header.encoding && header.encoding !== \"pcm_s16le\" && header.encoding !== \"opus\") {\n throw new Error(`Unsupported Syrinx binary audio encoding: ${header.encoding}`);\n }\n if (header.sequence !== undefined && !isNonNegativeInteger(header.sequence)) {\n throw new Error(\"Syrinx binary audio envelope sequence must be a non-negative integer\");\n }\n if (header.durationMs !== undefined && !isNonNegativeInteger(header.durationMs)) {\n throw new Error(\"Syrinx binary audio envelope durationMs must be a non-negative integer\");\n }\n if (header.byteLength !== undefined && !isNonNegativeInteger(header.byteLength)) {\n throw new Error(\"Syrinx binary audio envelope byteLength must be a non-negative integer\");\n }\n if (header.byteLength !== undefined && header.byteLength !== audio.byteLength) {\n throw new Error(\"Syrinx binary audio envelope byteLength does not match payload\");\n }\n\n const format: AudioFormat = {\n encoding: header.encoding === \"opus\" ? \"opus\" : \"pcm_s16le\",\n sampleRateHz: header.sampleRateHz,\n channels: header.channels ?? 1,\n };\n assertAudioFormat(format);\n assertAudioPayload(format, audio);\n\n if (format.encoding !== \"opus\" && header.durationMs !== undefined) {\n const expectedDurationMs = Math.round((audio.byteLength / 2 / header.sampleRateHz) * 1000);\n if (Math.abs(header.durationMs - expectedDurationMs) > 1) {\n throw new Error(\"Syrinx binary audio envelope durationMs does not match payload and sampleRateHz\");\n }\n }\n}\n\nfunction isPositiveInteger(value: unknown): value is number {\n return typeof value === \"number\" && Number.isInteger(value) && value > 0;\n}\n\nfunction isNonNegativeInteger(value: unknown): value is number {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 0;\n}\n", "// SPDX-License-Identifier: MIT\n//\n// ITU-T G.722 64 kbit/s sub-band ADPCM (16 kHz PCM16 \u2194 G.722).\n// Pure TypedArray \u2014 Workers-safe (no Buffer / node: / process).\n//\n// HONESTY: spec-implemented (ITU-T G.722 algorithm tables + QMF + dual-band\n// ADPCM), round-trip-tested on speech-like signals. NOT ITU-vector-certified\n// (authoritative ITU G.722 test vectors were not embedded). Unverified against\n// a live carrier trunk negotiation.\n\n/** 16 kHz wideband PCM sample rate for G.722. */\nexport const G722_SAMPLE_RATE_HZ = 16_000;\n\n/** 64 kbit/s = 1 byte per pair of 16 kHz samples. */\nexport const G722_BITRATE = 64_000;\n\n// \u2500\u2500 ITU-T G.722 tables \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst QMF_FWD = Object.freeze([\n 3, -11, 12, 32, -210, 951, 3876, -805, 362, -156, 53, -11,\n]);\nconst QMF_REV = Object.freeze([\n -11, 53, -156, 362, -805, 3876, 951, -210, 32, 12, -11, 3,\n]);\n\nconst QM2 = Object.freeze([-7408, -1616, 7408, 1616]);\nconst QM4 = Object.freeze([\n 0, -20456, -12896, -8968, -6288, -4240, -2584, -1200, 20456, 12896, 8968, 6288, 4240, 2584, 1200, 0,\n]);\nconst QM6 = Object.freeze([\n -136, -136, -136, -136, -24808, -21904, -19008, -16704, -14984, -13512, -12280, -11192, -10232, -9360, -8576, -7856,\n -7192, -6576, -6000, -5456, -4944, -4464, -4008, -3576, -3168, -2776, -2400, -2032, -1688, -1360, -1040, -728,\n 24808, 21904, 19008, 16704, 14984, 13512, 12280, 11192, 10232, 9360, 8576, 7856, 7192, 6576, 6000, 5456,\n 4944, 4464, 4008, 3576, 3168, 2776, 2400, 2032, 1688, 1360, 1040, 728, 432, 136, -432, -136,\n]);\nconst Q6 = Object.freeze([\n 0, 35, 72, 110, 150, 190, 233, 276, 323, 370, 422, 473, 530, 587, 650, 714, 786, 858, 940, 1023, 1121, 1219, 1339, 1458,\n 1612, 1765, 1980, 2195, 2557, 2919, 0, 0,\n]);\nconst ILB = Object.freeze([\n 2048, 2093, 2139, 2186, 2233, 2282, 2332, 2383, 2435, 2489, 2543, 2599, 2656, 2714, 2774, 2834, 2896, 2960, 3025, 3091,\n 3158, 3228, 3298, 3371, 3444, 3520, 3597, 3676, 3756, 3838, 3922, 4008,\n]);\nconst ILN = Object.freeze([0, 63, 62, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 0]);\nconst ILP = Object.freeze([0, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 0]);\nconst IHN = Object.freeze([0, 1, 0]);\nconst IHP = Object.freeze([0, 3, 2]);\nconst WL = Object.freeze([-60, -30, 58, 172, 334, 538, 1198, 3042]);\nconst RL42 = Object.freeze([0, 7, 6, 5, 4, 3, 2, 1, 7, 6, 5, 4, 3, 2, 1, 0]);\nconst WH = Object.freeze([0, -214, 798]);\nconst RH2 = Object.freeze([2, 1, 2, 1]);\n\n// \u2500\u2500 helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction saturate16(amp: number): number {\n if (amp > 32767) return 32767;\n if (amp < -32768) return -32768;\n return amp | 0;\n}\n\nfunction saturate15(amp: number): number {\n if (amp > 16383) return 16383;\n if (amp < -16384) return -16384;\n return amp | 0;\n}\n\nfunction satAdd16(a: number, b: number): number {\n return saturate16(a + b);\n}\n\nfunction satSub16(a: number, b: number): number {\n return saturate16(a - b);\n}\n\nfunction circularDot(hist: Int16Array, coeffs: readonly number[], n: number, pos: number): number {\n let z = 0;\n for (let i = 0; i < n - pos; i += 1) z += hist[pos + i]! * coeffs[i]!;\n for (let i = 0; i < pos; i += 1) z += hist[i]! * coeffs[n - pos + i]!;\n return z;\n}\n\ninterface BandState {\n nb: number;\n det: number;\n s: number;\n sz: number;\n r: number;\n p: [number, number];\n a: [number, number];\n b: [number, number, number, number, number, number];\n d: [number, number, number, number, number, number, number];\n}\n\nfunction createBand(det: number): BandState {\n return {\n nb: 0,\n det,\n s: 0,\n sz: 0,\n r: 0,\n p: [0, 0],\n a: [0, 0],\n b: [0, 0, 0, 0, 0, 0],\n d: [0, 0, 0, 0, 0, 0, 0],\n };\n}\n\nfunction block4(band: BandState, dx: number): void {\n const r = satAdd16(band.s, dx);\n const p = satAdd16(band.sz, dx);\n\n // UPPOL2\n let wd1 = saturate16(band.a[0] << 2);\n let wd32 = (p ^ band.p[0]) & 0x8000 ? wd1 : -wd1;\n if (wd32 > 32767) wd32 = 32767;\n let wd3 =\n (((p ^ band.p[1]) & 0x8000 ? -128 : 128) +\n (wd32 >> 7) +\n ((band.a[1] * 32512) >> 15)) |\n 0;\n if (Math.abs(wd3) > 12288) wd3 = wd3 < 0 ? -12288 : 12288;\n const ap1 = wd3;\n\n // UPPOL1\n wd1 = (p ^ band.p[0]) & 0x8000 ? -192 : 192;\n let wd2 = ((band.a[0] * 32640) >> 15) | 0;\n let ap0 = satAdd16(wd1, wd2);\n wd3 = satSub16(15360, ap1);\n if (Math.abs(ap0) > wd3) ap0 = ap0 < 0 ? -wd3 : wd3;\n\n // FILTEP\n wd1 = satAdd16(r, r);\n wd1 = ((ap0 * wd1) >> 15) | 0;\n wd2 = satAdd16(band.r, band.r);\n wd2 = ((ap1 * wd2) >> 15) | 0;\n const sp = satAdd16(wd1, wd2);\n band.r = r;\n band.a[1] = ap1;\n band.a[0] = ap0;\n band.p[1] = band.p[0];\n band.p[0] = p;\n\n // UPZERO / DELAYA / FILTEZ\n wd1 = dx === 0 ? 0 : 128;\n band.d[0] = dx;\n let sz = 0;\n for (let i = 5; i >= 0; i -= 1) {\n wd2 = (band.d[i + 1]! ^ dx) & 0x8000 ? -wd1 : wd1;\n wd3 = ((band.b[i]! * 32640) >> 15) | 0;\n band.b[i] = satAdd16(wd2, wd3);\n wd3 = satAdd16(band.d[i]!, band.d[i]!);\n sz += (band.b[i]! * wd3) >> 15;\n band.d[i + 1] = band.d[i]!;\n }\n band.sz = saturate16(sz);\n band.s = satAdd16(sp, band.sz);\n}\n\n// \u2500\u2500 public state + API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface G722EncoderState {\n readonly _kind: \"g722-encoder\";\n ptr: number;\n x: Int16Array;\n y: Int16Array;\n band: [BandState, BandState];\n /** leftover sample when input length is odd */\n pending: number | null;\n}\n\nexport interface G722DecoderState {\n readonly _kind: \"g722-decoder\";\n ptr: number;\n x: Int16Array;\n y: Int16Array;\n band: [BandState, BandState];\n}\n\nexport function createG722EncoderState(): G722EncoderState {\n return {\n _kind: \"g722-encoder\",\n ptr: 0,\n x: new Int16Array(12),\n y: new Int16Array(12),\n band: [createBand(32), createBand(8)],\n pending: null,\n };\n}\n\nexport function createG722DecoderState(): G722DecoderState {\n return {\n _kind: \"g722-decoder\",\n ptr: 0,\n x: new Int16Array(12),\n y: new Int16Array(12),\n band: [createBand(32), createBand(8)],\n };\n}\n\n/** Encode 16 kHz PCM16 \u2192 G.722 64 kbit/s bytes. Mutates `state`. Odd trailing sample is held for next call. */\nexport function encodeG722(state: G722EncoderState, pcm16: Int16Array): Uint8Array {\n let offset = 0;\n let samples = pcm16;\n if (state.pending !== null) {\n const merged = new Int16Array(pcm16.length + 1);\n merged[0] = state.pending;\n merged.set(pcm16, 1);\n samples = merged;\n state.pending = null;\n }\n if (samples.length % 2 !== 0) {\n state.pending = samples[samples.length - 1]!;\n samples = samples.subarray(0, samples.length - 1);\n }\n const out = new Uint8Array(samples.length >> 1);\n let outIdx = 0;\n\n while (offset < samples.length) {\n state.x[state.ptr] = samples[offset++]!;\n state.y[state.ptr] = samples[offset++]!;\n state.ptr += 1;\n if (state.ptr >= 12) state.ptr = 0;\n\n const sumodd = circularDot(state.x, QMF_FWD, 12, state.ptr);\n const sumeven = circularDot(state.y, QMF_REV, 12, state.ptr);\n const xlow = ((sumeven + sumodd) >> 14) | 0;\n const xhigh = ((sumeven - sumodd) >> 14) | 0;\n\n // Low band\n const el = satSub16(xlow, state.band[0].s);\n let wd = el >= 0 ? el : ~el;\n let i = 1;\n for (; i < 30; i += 1) {\n const wd1 = (Q6[i]! * state.band[0].det) >> 12;\n if (wd < wd1) break;\n }\n const ilow = el < 0 ? ILN[i]! : ILP[i]!;\n const ril = ilow >> 2;\n let dlow = ((state.band[0].det * QM4[ril]!) >> 15) | 0;\n const il4 = RL42[ril]!;\n wd = ((state.band[0].nb * 127) >> 7) + WL[il4]!;\n if (wd < 0) wd = 0;\n else if (wd > 18432) wd = 18432;\n state.band[0].nb = wd;\n let wd1 = (state.band[0].nb >> 6) & 31;\n let wd2 = 8 - (state.band[0].nb >> 11);\n let wd3 = wd2 < 0 ? ILB[wd1]! << -wd2 : ILB[wd1]! >> wd2;\n state.band[0].det = (wd3 << 2) | 0;\n block4(state.band[0], dlow);\n\n // High band\n const eh = satSub16(xhigh, state.band[1].s);\n wd = eh >= 0 ? eh : ~eh;\n wd1 = (564 * state.band[1].det) >> 12;\n const mih = wd >= wd1 ? 2 : 1;\n const ihigh = eh < 0 ? IHN[mih]! : IHP[mih]!;\n const dhigh = ((state.band[1].det * QM2[ihigh]!) >> 15) | 0;\n const ih2 = RH2[ihigh]!;\n wd = ((state.band[1].nb * 127) >> 7) + WH[ih2]!;\n if (wd < 0) wd = 0;\n else if (wd > 22528) wd = 22528;\n state.band[1].nb = wd;\n wd1 = (state.band[1].nb >> 6) & 31;\n wd2 = 10 - (state.band[1].nb >> 11);\n wd3 = wd2 < 0 ? ILB[wd1]! << -wd2 : ILB[wd1]! >> wd2;\n state.band[1].det = (wd3 << 2) | 0;\n block4(state.band[1], dhigh);\n\n out[outIdx++] = ((ihigh << 6) | ilow) & 0xff;\n }\n return outIdx === out.length ? out : out.subarray(0, outIdx);\n}\n\n/** Decode G.722 64 kbit/s bytes \u2192 16 kHz PCM16. Mutates `state`. */\nexport function decodeG722(state: G722DecoderState, g722: Uint8Array): Int16Array {\n const out = new Int16Array(g722.byteLength * 2);\n let outIdx = 0;\n\n for (let j = 0; j < g722.byteLength; j += 1) {\n const code = g722[j]!;\n let wd1 = code & 0x3f;\n const ihigh = (code >> 6) & 0x03;\n let wd2 = QM6[wd1]!;\n wd1 >>= 2;\n\n // Low band INVQBL / RECONS / LIMIT\n wd2 = ((state.band[0].det * wd2) >> 15) | 0;\n const rlow = saturate15(state.band[0].s + wd2);\n\n // Low band INVQAL + LOGSCL + SCALEL\n wd2 = QM4[wd1]!;\n const dlow = ((state.band[0].det * wd2) >> 15) | 0;\n wd2 = RL42[wd1]!;\n let nb = ((state.band[0].nb * 127) >> 7) + WL[wd2]!;\n if (nb < 0) nb = 0;\n else if (nb > 18432) nb = 18432;\n state.band[0].nb = nb;\n let s1 = (state.band[0].nb >> 6) & 31;\n let s2 = 8 - (state.band[0].nb >> 11);\n let s3 = s2 < 0 ? ILB[s1]! << -s2 : ILB[s1]! >> s2;\n state.band[0].det = (s3 << 2) | 0;\n block4(state.band[0], dlow);\n\n // High band\n wd2 = QM2[ihigh]!;\n const dhigh = ((state.band[1].det * wd2) >> 15) | 0;\n const rhigh = saturate15(dhigh + state.band[1].s);\n wd2 = RH2[ihigh]!;\n nb = ((state.band[1].nb * 127) >> 7) + WH[wd2]!;\n if (nb < 0) nb = 0;\n else if (nb > 22528) nb = 22528;\n state.band[1].nb = nb;\n s1 = (state.band[1].nb >> 6) & 31;\n s2 = 10 - (state.band[1].nb >> 11);\n s3 = s2 < 0 ? ILB[s1]! << -s2 : ILB[s1]! >> s2;\n state.band[1].det = (s3 << 2) | 0;\n block4(state.band[1], dhigh);\n\n // QMF synthesis\n state.x[state.ptr] = (rlow + rhigh) | 0;\n state.y[state.ptr] = (rlow - rhigh) | 0;\n state.ptr += 1;\n if (state.ptr >= 12) state.ptr = 0;\n // QMF introduces ~22-sample group delay; polarity follows the ITU analysis/synthesis pair.\n out[outIdx++] = (circularDot(state.y, QMF_REV, 12, state.ptr) >> 11) | 0;\n out[outIdx++] = (circularDot(state.x, QMF_FWD, 12, state.ptr) >> 11) | 0;\n }\n return outIdx === out.length ? out : out.subarray(0, outIdx);\n}\n", "// SPDX-License-Identifier: MIT\n//\n// `syrinx text \"<message>\" --agent <module>#<export>` \u2014 resolves the caller's\n// own agent and drives one typed turn through it. The CLI brings no reasoner.\n\nimport { resolveAgentFactory } from \"./agent-resolve.js\";\nimport { CliError, EXIT_CODES } from \"./exit-codes.js\";\nimport { driveText, type TextTurnResult } from \"./text-turn.js\";\n\nexport interface RunTextCommandOptions {\n readonly message: string;\n readonly agentSpec: string;\n readonly cwd?: string;\n}\n\nexport interface TextCommandResult extends TextTurnResult {\n readonly agent: string;\n}\n\nexport async function runTextCommand(opts: RunTextCommandOptions): Promise<TextCommandResult> {\n const resolvedAgent = await resolveAgentFactory(opts.agentSpec, opts.cwd ?? process.cwd());\n\n let result: TextTurnResult;\n try {\n result = await driveText({ session: resolvedAgent.factory, message: opts.message });\n } catch (err) {\n if (err instanceof CliError) throw err;\n throw new CliError(EXIT_CODES.BACKEND, `text turn failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n return { ...result, agent: resolvedAgent.label };\n}\n", "// SPDX-License-Identifier: MIT\n//\n// `syrinx turn --in <fixture.wav|fixture.json> --agent <module>#<export>` \u2014\n// resolves the caller's own agent (the CLI brings no providers) and drives it\n// through the shared, provider-agnostic turn driver, mirroring\n// examples/02-hello-voice-headless/scripts/replay-fixture.ts's fixture-replay\n// contract but as a first-class CLI verb with --json output and typed exit\n// codes instead of a throwaway script's console.log/process.exit.\n\nimport { mkdtempSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { extname, join, resolve } from \"node:path\";\n\nimport { resolveAgentFactory } from \"./agent-resolve.js\";\nimport { CliError, EXIT_CODES } from \"./exit-codes.js\";\nimport { assertTranscript, loadFixture, type TranscriptAssertion } from \"./fixture.js\";\nimport { driveTurn, type PerTurnMetrics } from \"./turn-runner.js\";\n\nexport interface RunTurnCommandOptions {\n readonly inputPath: string;\n readonly agentSpec: string;\n readonly sessionDir?: string;\n readonly cwd?: string;\n}\n\nexport interface TurnCommandResult {\n readonly input: string;\n readonly agent: string;\n readonly sessionDir: string;\n readonly transcript: string;\n readonly reply: string;\n readonly timings: PerTurnMetrics;\n readonly durationMs: number;\n readonly assertion: TranscriptAssertion | null;\n}\n\nexport async function runTurnCommand(opts: RunTurnCommandOptions): Promise<TurnCommandResult> {\n // Validate the input itself before resolving the agent: a malformed/unreplayable\n // fixture is a usage problem an agent should see regardless of whether the\n // --agent module even resolves.\n const inputPath = resolve(opts.inputPath);\n const isFixtureSidecar = extname(inputPath).toLowerCase() === \".json\";\n const fixture = isFixtureSidecar ? loadFixture(inputPath) : undefined;\n const wavPath = fixture ? fixture.wavPath : inputPath;\n\n const resolvedAgent = await resolveAgentFactory(opts.agentSpec, opts.cwd ?? process.cwd());\n const sessionDir = opts.sessionDir ?? mkdtempSync(join(tmpdir(), \"syrinx-turn-\"));\n\n let result;\n try {\n result = await driveTurn({\n session: resolvedAgent.factory,\n inputWavPath: wavPath,\n sessionDir,\n });\n } catch (err) {\n if (err instanceof CliError) throw err;\n throw new CliError(EXIT_CODES.BACKEND, `turn failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n let assertion: TranscriptAssertion | null = null;\n if (fixture?.sidecar.expectedTranscript !== undefined) {\n assertion = assertTranscript(fixture.sidecar.expectedTranscript, result.finalTranscript);\n if (!assertion.match) {\n throw new CliError(EXIT_CODES.ASSERTION, \"replayed fixture's transcript drifted from the expected transcript\", {\n assertion,\n });\n }\n }\n\n return {\n input: inputPath,\n agent: resolvedAgent.label,\n sessionDir: result.sessionDir,\n transcript: result.finalTranscript,\n reply: result.agentReply,\n timings: result.metrics,\n durationMs: result.durationMs,\n assertion,\n };\n}\n", "// SPDX-License-Identifier: MIT\n//\n// Fixture sidecar loading for `syrinx turn --in <fixture.json>`. Mirrors the\n// shape written by apps/studio/src/lib/fixture-export.ts (\"Save as fixture\") and\n// the check already proven in examples/02-hello-voice-headless/scripts/replay-fixture.ts:\n// honour the recorded capture config, refuse to replay when it cannot be\n// satisfied rather than silently changing the answer.\n\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nimport { CliError, EXIT_CODES } from \"./exit-codes.js\";\n\nexport const FIXTURE_FORMAT = \"syrinx.fixture.v1\";\n\nexport interface FixtureSidecar {\n readonly format: string;\n readonly turnId?: string;\n readonly expectedTranscript?: string;\n readonly agentText?: string;\n readonly audioFile: string;\n readonly audio: { readonly sampleRateHz: number; readonly channels: number; readonly encoding: string };\n readonly capture?: { readonly inputSampleRateHz?: number };\n}\n\nexport interface LoadedFixture {\n readonly sidecarPath: string;\n readonly sidecar: FixtureSidecar;\n /** The paired WAV file this fixture replays \u2014 prefers the sibling \"captured.wav\" the Studio actually wrote. */\n readonly wavPath: string;\n}\n\n/**\n * Parse and validate a fixture sidecar JSON file, refusing (CliError, USAGE) on\n * anything this CLI cannot honestly replay: an unrecognised format, or capture\n * conditions (sample rate / channel count) that would silently change the\n * transcript if replayed as-is.\n */\nexport function loadFixture(sidecarPath: string): LoadedFixture {\n let raw: string;\n try {\n raw = readFileSync(sidecarPath, \"utf8\");\n } catch (err) {\n throw new CliError(EXIT_CODES.USAGE, `fixture not found: ${sidecarPath}`, { cause: String(err) });\n }\n\n let sidecar: FixtureSidecar;\n try {\n sidecar = JSON.parse(raw) as FixtureSidecar;\n } catch (err) {\n throw new CliError(EXIT_CODES.USAGE, `fixture is not valid JSON: ${sidecarPath}`, { cause: String(err) });\n }\n\n if (sidecar.format !== FIXTURE_FORMAT) {\n throw new CliError(EXIT_CODES.USAGE, `unsupported fixture format \"${sidecar.format}\" (expected \"${FIXTURE_FORMAT}\")`);\n }\n // The sidecar exists so replay conditions can be checked rather than assumed \u2014\n // replaying 24 kHz audio against a 16 kHz-mono expectation changes the transcript.\n if (sidecar.audio.sampleRateHz !== 16000 || sidecar.audio.channels !== 1) {\n throw new CliError(\n EXIT_CODES.USAGE,\n `this replay path needs mono 16 kHz audio; fixture is ${String(sidecar.audio.channels)}ch @ ${String(sidecar.audio.sampleRateHz)} Hz`,\n );\n }\n\n const dir = dirname(sidecarPath);\n const sibling = join(dir, \"captured.wav\");\n let wavPath: string;\n try {\n readFileSync(sibling);\n wavPath = sibling;\n } catch {\n wavPath = join(dir, sidecar.audioFile);\n }\n\n return { sidecarPath, sidecar, wavPath };\n}\n\n/** Compare on words, not punctuation \u2014 STT capitalises and punctuates inconsistently. */\nexport function normalizeTranscript(text: string): string {\n return text.toLowerCase().replace(/[^a-z0-9\\s]/g, \" \").replace(/\\s+/g, \" \").trim();\n}\n\nexport interface TranscriptAssertion {\n readonly expectedTranscript: string;\n readonly actualTranscript: string;\n readonly match: boolean;\n}\n\nexport function assertTranscript(expectedTranscript: string, actualTranscript: string): TranscriptAssertion {\n return {\n expectedTranscript,\n actualTranscript,\n match: normalizeTranscript(expectedTranscript) === normalizeTranscript(actualTranscript),\n };\n}\n", "// SPDX-License-Identifier: MIT\n//\n// Provider-agnostic turn driver (LDT-20, revised). This module owns exactly one\n// thing: feeding audio into an ALREADY-CONSTRUCTED VoiceAgentSession and\n// capturing the transcript/reply/timings/artifacts \u2014 never how that session's\n// STT/TTS/reasoner plugins got built. The CLI itself must not depend on any\n// provider SDK (Deepgram, Cartesia, OpenAI, ...); the caller (the CLI's\n// --agent seam, or examples/02-hello-voice-headless's own hardcoded default\n// kernel) supplies the session.\n//\n// examples/02-hello-voice-headless/src/run-one-turn.ts is the other caller: it\n// keeps its own `runOneTurn(...)` with the hardcoded Deepgram+OpenAI+Cartesia+\n// Silero default (legitimate there \u2014 it's an example harness, not a shipped\n// CLI) and delegates the actual audio-feed/metrics/event-capture work to\n// `driveTurn` here, so there is exactly one implementation of that part.\n\nimport { randomUUID } from \"node:crypto\";\nimport { readFileSync } from \"node:fs\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { createRequire } from \"node:module\";\nimport { basename, join, resolve } from \"node:path\";\n\nimport {\n Route,\n VoiceAgentSession,\n type RecordUserAudioPacket,\n type TextToSpeechAudioPacket,\n type TextToSpeechEndPacket,\n type VoiceAgentSessionEvents,\n} from \"@kuralle-syrinx/core\";\n\nconst require = createRequire(import.meta.url);\nconst { WaveFile } = require(\"wavefile\") as typeof import(\"wavefile\");\n\nconst SAMPLES_PER_FRAME = 320;\nconst DEFAULT_FIXTURE_PATH =\n \"/Users/mithushancj/Documents/asyncdot/openscoped/voice-media-transport/research/agents/tests/test_realtime/hello_world.wav\";\nconst DEFAULT_TTS_END_TIMEOUT_MS = 120_000;\n\n/** A pre-built session, or a zero-arg factory producing one \u2014 the same contract examples/02-hello-voice-headless/scripts/dev-server.ts's `--agent` seam uses. */\nexport type SessionFactory = () => VoiceAgentSession | Promise<VoiceAgentSession>;\n\nexport interface PerTurnMetrics {\n readonly turnId: string;\n readonly inputAudioMs: number;\n readonly speechEndToFinalTranscriptMs: number;\n readonly speechEndToFirstAudioMs: number;\n readonly endpointingMs: number;\n readonly llmTTFTMs: number;\n readonly ttsTTFBMs: number;\n readonly e2eLatencyMs: number;\n readonly agentTokens: number;\n readonly playedMs: number;\n readonly truncated: boolean;\n readonly toolCalls: number;\n}\n\nexport interface TurnResult {\n readonly sessionDir: string;\n readonly finalTranscript: string;\n readonly agentReply: string;\n readonly agentOutWavPath: string;\n readonly inputWavPath: string;\n readonly eventsJsonlPath: string;\n readonly eventsJsonPath: string;\n readonly transcriptJsonPath: string;\n readonly metricsJsonPath: string;\n readonly metrics: PerTurnMetrics;\n readonly durationMs: number;\n}\n\nexport interface DriveTurnOptions {\n /** An already-registered VoiceAgentSession, or a factory producing one. Built by the caller \u2014 this module never constructs providers. */\n readonly session: VoiceAgentSession | SessionFactory;\n readonly inputWavPath: string;\n readonly sessionDir: string;\n /** Skips WAV read; must be mono 16 kHz PCM decoded samples. */\n readonly syntheticMono16kSamples?: Readonly<Int16Array>;\n readonly realtimePacing?: boolean;\n /** Called once per fed audio frame (including the trailing silence pad), right after it is pushed onto the bus \u2014 for a VAD implementation (or a scripted test fake) that needs an explicit per-frame tick outside the bus event flow. */\n readonly onAudioFrame?: (contextId: string) => void;\n /** Called once after all audio (including the silence pad) has been fed, before waiting for tts.end \u2014 for an STT implementation (or a scripted test fake) that finalizes on an explicit signal. */\n readonly onAudioFed?: (contextId: string) => void | Promise<void>;\n readonly ttsEndTimeoutMs?: number;\n}\n\nexport function readPcm16Mono16kWav(filePath: string): Int16Array {\n const buf = readFileSync(filePath);\n const wav = new WaveFile(Buffer.from(buf));\n const fmt = wav.fmt as {\n sampleRate: number;\n numChannels: number;\n bitsPerSample: number;\n audioFormat: number;\n };\n if (fmt.numChannels !== 1) throw new Error(`expected mono WAV, got ${String(fmt.numChannels)} channels`);\n if (fmt.bitsPerSample !== 16 || fmt.audioFormat !== 1) throw new Error(\"expected 16-bit PCM WAV\");\n const raw = wav.getSamples(false, Int16Array);\n const mono: Int16Array | undefined = Array.isArray(raw) ? raw[0] : raw;\n if (mono === undefined || !(mono instanceof Int16Array)) throw new Error(\"WAV has no mono channel samples\");\n return fmt.sampleRate === 16000 ? mono : resamplePcm16(mono, fmt.sampleRate, 16000);\n}\n\n/** Resolves a WAV path, falling back to the repo's bundled demo fixture for the literal name \"hello.wav\" \u2014 a convenience inherited from the pre-move script, harmless (and inert) outside this monorepo. */\nexport function resolveInputWavPath(path: string): string {\n const resolved = resolve(path);\n try {\n readFileSync(resolved);\n return resolved;\n } catch {\n if (basename(path) === \"hello.wav\") return DEFAULT_FIXTURE_PATH;\n throw new Error(`input WAV not found: ${resolved}`);\n }\n}\n\nfunction resamplePcm16(samples: Int16Array, fromHz: number, toHz: number): Int16Array {\n if (fromHz <= 0 || toHz <= 0) throw new Error(\"invalid WAV sample rate\");\n const outLength = Math.max(1, Math.round((samples.length * toHz) / fromHz));\n const out = new Int16Array(outLength);\n const ratio = fromHz / toHz;\n for (let i = 0; i < out.length; i += 1) {\n const src = i * ratio;\n const lo = Math.floor(src);\n const hi = Math.min(samples.length - 1, lo + 1);\n const frac = src - lo;\n out[i] = Math.round(samples[lo]! * (1 - frac) + samples[hi]! * frac);\n }\n return out;\n}\n\nfunction pcmToBytes(samples: Readonly<Int16Array>): Uint8Array {\n return new Uint8Array(samples.buffer, samples.byteOffset, samples.byteLength);\n}\n\nfunction sliceFramePcm(samples: Readonly<Int16Array>, offset: number): Int16Array {\n const end = Math.min(offset + SAMPLES_PER_FRAME, samples.length);\n const frame = new Int16Array(SAMPLES_PER_FRAME);\n if (end > offset) frame.set(samples.subarray(offset, end));\n return frame;\n}\n\nfunction mergeBytes(chunks: readonly Uint8Array[]): Uint8Array {\n const total = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);\n const merged = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n merged.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return merged;\n}\n\nfunction writePcm16Wav(path: string, chunks: readonly Uint8Array[], sampleRateHz: number): Promise<void> {\n const bytes = mergeBytes(chunks);\n const samples = new Int16Array(bytes.buffer, bytes.byteOffset, Math.floor(bytes.byteLength / 2));\n const wav = new WaveFile();\n wav.fromScratch(1, sampleRateHz, \"16\", samples);\n return writeFile(path, Buffer.from(wav.toBuffer()));\n}\n\nfunction eventLine(kind: string, data: Record<string, unknown>): string {\n return `${JSON.stringify({ tsMs: Date.now(), kind, ...data })}\\n`;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));\n}\n\nasync function resolveSession(session: VoiceAgentSession | SessionFactory): Promise<VoiceAgentSession> {\n return typeof session === \"function\" ? session() : session;\n}\n\n/**\n * Feed one turn's worth of audio into an already-built session and report the\n * transcript, reply, per-stage timings, and written artifacts. This is the one\n * implementation of \"drive a turn\" \u2014 it knows nothing about which STT/TTS/\n * reasoner backends the session was assembled from.\n */\nexport async function driveTurn(opts: DriveTurnOptions): Promise<TurnResult> {\n // Resolve (and, if a factory, construct) the session before touching the\n // filesystem: a caller's agent failing to construct is a different failure\n // class (CONFIG) than a bad input file (USAGE/BACKEND), and should surface\n // as itself rather than being masked by a WAV-read error that never got the\n // chance to matter.\n const session = await resolveSession(opts.session);\n\n const sessionDir = resolve(opts.sessionDir);\n await mkdir(sessionDir, { recursive: true });\n\n const pcm =\n opts.syntheticMono16kSamples !== undefined\n ? Int16Array.from(opts.syntheticMono16kSamples)\n : readPcm16Mono16kWav(resolveInputWavPath(opts.inputWavPath));\n\n const contextId = randomUUID();\n const inputChunks: Uint8Array[] = [];\n const outputChunks: Uint8Array[] = [];\n const eventLines: string[] = [];\n const timeline = {\n feedStartMs: 0,\n speechEndMs: 0,\n finalTranscriptMs: 0,\n firstLlmDeltaMs: 0,\n firstTtsAudioMs: 0,\n ttsEndMs: 0,\n };\n let finalTranscript = \"\";\n let agentReply = \"\";\n let toolCalls = 0;\n\n const offRecordUser = session.bus.on<RecordUserAudioPacket>(\"record.user_audio\", (pkt) => {\n inputChunks.push(pkt.audio);\n });\n const offTtsAudio = session.bus.on<TextToSpeechAudioPacket>(\"tts.audio\", (pkt) => {\n if (timeline.firstTtsAudioMs === 0) timeline.firstTtsAudioMs = pkt.timestampMs;\n outputChunks.push(pkt.audio);\n });\n\n const ttsEnd = new Promise<void>((resolveEnd, reject) => {\n const timeout = setTimeout(() => {\n offTtsEnd();\n reject(new Error(\"tts.end timeout\"));\n }, opts.ttsEndTimeoutMs ?? DEFAULT_TTS_END_TIMEOUT_MS);\n const offTtsEnd = session.bus.on<TextToSpeechEndPacket>(\"tts.end\", (pkt) => {\n if (pkt.contextId !== contextId) return;\n clearTimeout(timeout);\n offTtsEnd();\n timeline.ttsEndMs = pkt.timestampMs;\n resolveEnd();\n });\n });\n\n const on = <K extends keyof VoiceAgentSessionEvents>(event: K, handler: VoiceAgentSessionEvents[K]): void => {\n session.on(event, handler);\n };\n on(\"user_input_final\", (event) => {\n finalTranscript = event.text;\n timeline.finalTranscriptMs = event.tsMs;\n eventLines.push(eventLine(\"user_input_final\", { turnId: event.turnId, text: event.text }));\n });\n on(\"agent_text_delta\", (event) => {\n if (timeline.firstLlmDeltaMs === 0) timeline.firstLlmDeltaMs = event.tsMs;\n agentReply += event.delta;\n eventLines.push(eventLine(\"agent_text_delta\", { turnId: event.turnId, delta: event.delta }));\n });\n on(\"agent_tool_call\", (event) => {\n toolCalls += 1;\n eventLines.push(eventLine(\"agent_tool_call\", { turnId: event.turnId, name: event.name }));\n });\n on(\"agent_finished\", (event) => {\n eventLines.push(eventLine(\"agent_finished\", { turnId: event.turnId }));\n });\n on(\"error\", (event) => {\n eventLines.push(eventLine(\"error\", { stage: event.stage, category: event.category, message: event.message }));\n });\n\n await session.start();\n\n session.bus.push(Route.Main, {\n kind: \"turn.change\",\n contextId,\n previousContextId: \"\",\n reason: \"headless_turn_start\",\n timestampMs: Date.now(),\n });\n\n let offset = 0;\n while (offset < pcm.length) {\n const frame = sliceFramePcm(pcm, offset);\n const audio = pcmToBytes(frame);\n if (timeline.feedStartMs === 0) timeline.feedStartMs = Date.now();\n session.bus.push(Route.Main, {\n kind: \"user.audio_received\",\n contextId,\n timestampMs: Date.now(),\n audio,\n });\n opts.onAudioFrame?.(contextId);\n offset += SAMPLES_PER_FRAME;\n if (opts.realtimePacing === true) await sleep(20);\n }\n timeline.speechEndMs = Date.now();\n\n for (let pad = 0; pad < 40; pad += 1) {\n const frame = new Int16Array(SAMPLES_PER_FRAME);\n session.bus.push(Route.Main, {\n kind: \"user.audio_received\",\n contextId,\n timestampMs: Date.now(),\n audio: pcmToBytes(frame),\n });\n opts.onAudioFrame?.(contextId);\n if (opts.realtimePacing === true) await sleep(20);\n }\n\n await opts.onAudioFed?.(contextId);\n await ttsEnd;\n\n offRecordUser();\n offTtsAudio();\n\n const metrics: PerTurnMetrics = {\n turnId: contextId,\n inputAudioMs: Math.round((pcm.length / 16000) * 1000),\n speechEndToFinalTranscriptMs:\n timeline.speechEndMs > 0 && timeline.finalTranscriptMs > 0\n ? Math.max(0, timeline.finalTranscriptMs - timeline.speechEndMs)\n : 0,\n speechEndToFirstAudioMs:\n timeline.speechEndMs > 0 && timeline.firstTtsAudioMs > 0\n ? Math.max(0, timeline.firstTtsAudioMs - timeline.speechEndMs)\n : 0,\n endpointingMs:\n timeline.feedStartMs > 0 && timeline.finalTranscriptMs > 0\n ? Math.max(0, timeline.finalTranscriptMs - timeline.feedStartMs)\n : 0,\n llmTTFTMs:\n timeline.finalTranscriptMs > 0 && timeline.firstLlmDeltaMs > 0\n ? Math.max(0, timeline.firstLlmDeltaMs - timeline.finalTranscriptMs)\n : 0,\n ttsTTFBMs:\n timeline.firstLlmDeltaMs > 0 && timeline.firstTtsAudioMs > 0\n ? Math.max(0, timeline.firstTtsAudioMs - timeline.firstLlmDeltaMs)\n : 0,\n e2eLatencyMs:\n timeline.feedStartMs > 0 && timeline.firstTtsAudioMs > 0\n ? Math.max(0, timeline.firstTtsAudioMs - timeline.feedStartMs)\n : 0,\n agentTokens: agentReply.trim().length === 0 ? 0 : agentReply.trim().split(/\\s+/).length,\n playedMs: Math.round((mergeBytes(outputChunks).byteLength / 2 / 16000) * 1000),\n truncated: false,\n toolCalls,\n };\n\n const inputWavPath = join(sessionDir, \"audio-in.wav\");\n const agentOutWavPath = join(sessionDir, \"audio-out.wav\");\n const eventsJsonlPath = join(sessionDir, \"events.jsonl\");\n const eventsJsonPath = join(sessionDir, \"events.json\");\n const transcriptJsonPath = join(sessionDir, \"transcript.json\");\n const metricsJsonPath = join(sessionDir, \"metrics.json\");\n const events = eventLines\n .map((line) => line.trim())\n .filter((line) => line.length > 0)\n .map((line) => JSON.parse(line) as Record<string, unknown>);\n const qualityGate = {\n passed: finalTranscript.trim().length > 0 && agentReply.trim().length > 0 && outputChunks.length > 0,\n failures: [\n ...(finalTranscript.trim().length === 0 ? [\"missing final transcript\"] : []),\n ...(agentReply.trim().length === 0 ? [\"missing agent reply\"] : []),\n ...(outputChunks.length === 0 ? [\"missing TTS audio\"] : []),\n ],\n };\n\n await writePcm16Wav(inputWavPath, inputChunks, 16000);\n await writePcm16Wav(agentOutWavPath, outputChunks, 16000);\n await writeFile(eventsJsonlPath, eventLines.join(\"\"), \"utf8\");\n await writeFile(eventsJsonPath, `${JSON.stringify({ events, qualityGate }, null, 2)}\\n`, \"utf8\");\n await writeFile(\n transcriptJsonPath,\n `${JSON.stringify({ finalTranscript, agentReply, turnCount: 1, metrics, qualityGate }, null, 2)}\\n`,\n \"utf8\",\n );\n await writeFile(metricsJsonPath, `${JSON.stringify({ ...metrics, turnCount: 1, qualityGate }, null, 2)}\\n`, \"utf8\");\n\n await session.close();\n\n return {\n sessionDir,\n finalTranscript,\n agentReply,\n agentOutWavPath,\n inputWavPath,\n eventsJsonlPath,\n eventsJsonPath,\n transcriptJsonPath,\n metricsJsonPath,\n metrics,\n durationMs:\n timeline.feedStartMs > 0 && timeline.ttsEndMs > 0 ? Math.max(0, timeline.ttsEndMs - timeline.feedStartMs) : 0,\n };\n}\n", "// SPDX-License-Identifier: MIT\n//\n// Bin entry point. The shebang is injected by the esbuild banner at build time\n// (see scripts/build.mjs) so the raw TypeScript source stays a plain module.\n\nimport { main } from \"./cli.js\";\nimport { CliError, EXIT_CODES } from \"./exit-codes.js\";\n\n/**\n * Report a failure the way the contract promises: one line on stderr, the\n * documented code on exit. Never a stack trace \u2014 stdout may already hold a\n * `--json` object, and a stack after it corrupts what a caller parses.\n * A stack is only useful for an unexpected internal fault, which is what\n * INTERNAL means.\n */\nfunction fail(err: unknown): number {\n if (err instanceof CliError) {\n console.error(err.message);\n return err.exitCode;\n }\n console.error(err instanceof Error ? (err.stack ?? err.message) : String(err));\n return EXIT_CODES.INTERNAL;\n}\n\n// A rejection that escapes the command \u2014 e.g. a listener that settles after the\n// command already returned \u2014 must not print a stack over a written JSON object.\nprocess.on(\"unhandledRejection\", (reason) => {\n process.exitCode = fail(reason);\n});\n\nmain(process.argv.slice(2))\n .then((code) => {\n process.exitCode = code;\n })\n .catch((err: unknown) => {\n process.exitCode = fail(err);\n });\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["require", "join", "resolve", "readFileSync", "dirname", "join", "readFileSync", "dirname", "join", "randomUUID", "readFileSync", "createRequire", "join", "resolve", "require", "createRequire", "readFileSync", "resamplePcm16", "resolve", "resolveSession", "randomUUID", "join", "resolve", "join"]
|
|
7
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kuralle-syrinx/cli",
|
|
3
|
+
"version": "4.4.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Agent-facing CLI for Syrinx — run a turn or a text exchange with deterministic, machine-readable (--json) output and exit codes. Not a console: no REPL, no chat loop, no microphone.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"voice",
|
|
8
|
+
"voice-agent",
|
|
9
|
+
"speech",
|
|
10
|
+
"syrinx",
|
|
11
|
+
"cli",
|
|
12
|
+
"agent",
|
|
13
|
+
"testing"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"homepage": "https://github.com/kuralle/syrinx#readme",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/kuralle/syrinx.git",
|
|
20
|
+
"directory": "packages/cli"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/kuralle/syrinx/issues"
|
|
24
|
+
},
|
|
25
|
+
"type": "module",
|
|
26
|
+
"bin": {
|
|
27
|
+
"syrinx": "./dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"src",
|
|
32
|
+
"README.md"
|
|
33
|
+
],
|
|
34
|
+
"exports": {
|
|
35
|
+
"./turn-runner": "./src/turn-runner.ts"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"wavefile": "^11.0.0",
|
|
39
|
+
"@kuralle-syrinx/core": "4.4.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^22.0.0",
|
|
43
|
+
"esbuild": "^0.28.1",
|
|
44
|
+
"typescript": "^5.7.0",
|
|
45
|
+
"vitest": "^3.2.6",
|
|
46
|
+
"@kuralle-syrinx/test": "4.4.0"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "node scripts/build.mjs",
|
|
50
|
+
"typecheck": "tsc --noEmit",
|
|
51
|
+
"test": "vitest run"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
//
|
|
3
|
+
// Test-only --agent module: a session whose "bridge" plugin is the scripted
|
|
4
|
+
// FakeBridge, autonomous (reacts to eos.turn_complete on its own, no manual
|
|
5
|
+
// poking needed) — so this proves the CLI's real --agent resolution + driveText
|
|
6
|
+
// path end to end without a live provider.
|
|
7
|
+
|
|
8
|
+
import { VoiceAgentSession } from "@kuralle-syrinx/core";
|
|
9
|
+
import { FakeBridge } from "@kuralle-syrinx/test";
|
|
10
|
+
|
|
11
|
+
export function createSession() {
|
|
12
|
+
const session = new VoiceAgentSession({
|
|
13
|
+
plugins: {
|
|
14
|
+
bridge: {
|
|
15
|
+
scriptedEvents: [{ kind: "text", delta: "Hello from the fake agent." }, { kind: "done" }],
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
session.registerPlugin("bridge", new FakeBridge());
|
|
20
|
+
return session;
|
|
21
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
//
|
|
3
|
+
// Test-only --agent module whose export resolves and is callable, but throws
|
|
4
|
+
// when invoked — simulates an agent module missing its own configuration.
|
|
5
|
+
|
|
6
|
+
export default function createSession() {
|
|
7
|
+
throw new Error("intentional test failure: missing FAKE_AGENT_API_KEY");
|
|
8
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
|
|
8
|
+
import { CliError, EXIT_CODES } from "./exit-codes.js";
|
|
9
|
+
import { resolveAgentFactory } from "./agent-resolve.js";
|
|
10
|
+
|
|
11
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const FIXTURES = join(HERE, "__fixtures__");
|
|
13
|
+
|
|
14
|
+
describe("resolveAgentFactory", () => {
|
|
15
|
+
it("resolves a named export and the factory builds a real session", async () => {
|
|
16
|
+
const resolved = await resolveAgentFactory(`${join(FIXTURES, "good-text-agent.mjs")}#createSession`, HERE);
|
|
17
|
+
expect(resolved.label).toContain("createSession");
|
|
18
|
+
const session = await resolved.factory();
|
|
19
|
+
expect(typeof session.start).toBe("function");
|
|
20
|
+
await session.close();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("refuses a module that does not exist, as USAGE", async () => {
|
|
24
|
+
const err = await resolveAgentFactory(join(FIXTURES, "does-not-exist.mjs"), HERE).catch((e) => e);
|
|
25
|
+
expect(err).toBeInstanceOf(CliError);
|
|
26
|
+
expect((err as CliError).exitCode).toBe(EXIT_CODES.USAGE);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("refuses when the requested export is not callable, naming the export it looked for", async () => {
|
|
30
|
+
const err = await resolveAgentFactory(`${join(FIXTURES, "no-callable-export.mjs")}#notAFunction`, HERE).catch((e) => e);
|
|
31
|
+
expect(err).toBeInstanceOf(CliError);
|
|
32
|
+
expect((err as CliError).exitCode).toBe(EXIT_CODES.USAGE);
|
|
33
|
+
expect((err as CliError).message).toContain("notAFunction");
|
|
34
|
+
expect((err as CliError).message).toContain("(none)"); // neither export in the fixture is callable
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("refuses an empty --agent spec (no module path) as USAGE", async () => {
|
|
38
|
+
const err = await resolveAgentFactory("#justAnExport", HERE).catch((e) => e);
|
|
39
|
+
expect(err).toBeInstanceOf(CliError);
|
|
40
|
+
expect((err as CliError).exitCode).toBe(EXIT_CODES.USAGE);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("resolves fine but classifies a construction-time throw as CONFIG, not USAGE", async () => {
|
|
44
|
+
const resolved = await resolveAgentFactory(join(FIXTURES, "throwing-agent.mjs"), HERE);
|
|
45
|
+
const err = await Promise.resolve(resolved.factory()).catch((e: unknown) => e);
|
|
46
|
+
expect(err).toBeInstanceOf(CliError);
|
|
47
|
+
expect((err as CliError).exitCode).toBe(EXIT_CODES.CONFIG);
|
|
48
|
+
expect((err as CliError).message).toContain("FAKE_AGENT_API_KEY");
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
//
|
|
3
|
+
// Resolves `--agent <module>[#export]` to a zero-arg VoiceAgentSession factory.
|
|
4
|
+
// Same contract as examples/02-hello-voice-headless/scripts/dev-server.ts's
|
|
5
|
+
// `resolveAgentFactory` (LDT-1) — the module path is user-owned code that
|
|
6
|
+
// brings its own providers; the CLI itself never constructs an STT/TTS/
|
|
7
|
+
// reasoner plugin. Re-implemented here (not imported from the example) because
|
|
8
|
+
// the dependency direction runs examples → cli, never the reverse, and
|
|
9
|
+
// dev-server.ts is a script in a private example package, not a library export.
|
|
10
|
+
//
|
|
11
|
+
// Fails loudly and specifically, exactly like dev-server.ts's version: a CLI
|
|
12
|
+
// that silently ran a different agent than the one you asked for would be
|
|
13
|
+
// worse than one that refuses to start.
|
|
14
|
+
|
|
15
|
+
import { isAbsolute, resolve } from "node:path";
|
|
16
|
+
import { pathToFileURL } from "node:url";
|
|
17
|
+
|
|
18
|
+
import type { VoiceAgentSession } from "@kuralle-syrinx/core";
|
|
19
|
+
|
|
20
|
+
import { CliError, EXIT_CODES } from "./exit-codes.js";
|
|
21
|
+
import type { SessionFactory } from "./turn-runner.js";
|
|
22
|
+
|
|
23
|
+
export interface ResolvedAgent {
|
|
24
|
+
readonly factory: SessionFactory;
|
|
25
|
+
readonly label: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* `spec` is `<module>[#namedExport]`, resolved relative to `baseDir` (the
|
|
30
|
+
* invoking project's cwd) when not absolute. The export must be a zero-arg
|
|
31
|
+
* factory returning a VoiceAgentSession (or a Promise of one) — the same
|
|
32
|
+
* shape `--agent` expects in dev-server.ts. Omit `#export` for a default
|
|
33
|
+
* export (or a `createSession` export, checked in that order).
|
|
34
|
+
*/
|
|
35
|
+
export async function resolveAgentFactory(spec: string, baseDir: string): Promise<ResolvedAgent> {
|
|
36
|
+
const [modulePath, exportName] = spec.split("#");
|
|
37
|
+
if (!modulePath) {
|
|
38
|
+
throw new CliError(EXIT_CODES.USAGE, `--agent needs a module path, got: ${spec}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const abs = isAbsolute(modulePath) ? modulePath : resolve(baseDir, modulePath);
|
|
42
|
+
const url = pathToFileURL(abs).href;
|
|
43
|
+
|
|
44
|
+
let mod: Record<string, unknown>;
|
|
45
|
+
try {
|
|
46
|
+
mod = (await import(url)) as Record<string, unknown>;
|
|
47
|
+
} catch (err) {
|
|
48
|
+
throw new CliError(EXIT_CODES.USAGE, `could not load --agent module "${modulePath}" (resolved to ${abs}): ${err instanceof Error ? err.message : String(err)}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const picked = exportName ? mod[exportName] : (mod["default"] ?? mod["createSession"]);
|
|
52
|
+
if (typeof picked !== "function") {
|
|
53
|
+
const available = Object.keys(mod).filter((k) => typeof mod[k] === "function");
|
|
54
|
+
throw new CliError(
|
|
55
|
+
EXIT_CODES.USAGE,
|
|
56
|
+
`${abs} has no callable export ${exportName ? `"${exportName}"` : "(default or createSession)"}. ` +
|
|
57
|
+
`Callable exports: ${available.length > 0 ? available.join(", ") : "(none)"}`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const factory = picked as SessionFactory;
|
|
62
|
+
const label = `${modulePath}${exportName ? `#${exportName}` : ""}`;
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
label,
|
|
66
|
+
factory: async (): Promise<VoiceAgentSession> => {
|
|
67
|
+
try {
|
|
68
|
+
return await factory();
|
|
69
|
+
} catch (err) {
|
|
70
|
+
// The module resolved and is callable; it failed to actually build a
|
|
71
|
+
// session — most plausibly its own missing config (an env var it
|
|
72
|
+
// needs), so this is a CONFIG-class failure rather than USAGE.
|
|
73
|
+
throw new CliError(
|
|
74
|
+
EXIT_CODES.CONFIG,
|
|
75
|
+
`--agent ${label} failed to construct a session: ${err instanceof Error ? err.message : String(err)}`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|