create-syrinx-agent 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/src/options.ts ADDED
@@ -0,0 +1,176 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // The flag matrix, verified against packages/* — do not invent provider
4
+ // names here. What's implemented per stage lives in the provider registries
5
+ // in providers/*.ts, not here: this module only parses and validates shape.
6
+
7
+ import { resolve } from "node:path";
8
+
9
+ import { CliError, EXIT_CODES } from "./exit-codes.js";
10
+
11
+ export const STT_PROVIDERS = ["deepgram", "google", "elevenlabs", "grok"] as const;
12
+ export const TTS_PROVIDERS = ["cartesia", "elevenlabs", "gemini", "openai-tts", "grok"] as const;
13
+ export const REALTIME_PROVIDERS = ["realtime", "grok"] as const;
14
+ export const REASONER_PROVIDERS = ["aisdk", "kuralle", "mastra"] as const;
15
+ export const VAD_PROVIDERS = ["silero-vad"] as const;
16
+ export const ENDPOINTING_PROVIDERS = ["pipecat-smart-turn", "vap"] as const;
17
+ export const TRANSPORTS = ["browser", "twilio", "telnyx", "smartpbx"] as const;
18
+ export const RUNTIMES = ["node", "cloudflare"] as const;
19
+
20
+ export type SttProvider = (typeof STT_PROVIDERS)[number];
21
+ export type TtsProvider = (typeof TTS_PROVIDERS)[number];
22
+ export type RealtimeProvider = (typeof REALTIME_PROVIDERS)[number];
23
+ export type ReasonerProvider = (typeof REASONER_PROVIDERS)[number];
24
+ export type VadProvider = (typeof VAD_PROVIDERS)[number];
25
+ export type EndpointingProvider = (typeof ENDPOINTING_PROVIDERS)[number];
26
+ export type Transport = (typeof TRANSPORTS)[number];
27
+ export type Runtime = (typeof RUNTIMES)[number];
28
+
29
+ /** Flags as parsed off the command line — everything optional, everything a string. */
30
+ export interface RawFlags {
31
+ readonly stt?: string | undefined;
32
+ readonly tts?: string | undefined;
33
+ readonly realtime?: string | undefined;
34
+ readonly reasoner?: string | undefined;
35
+ readonly vad?: string | undefined;
36
+ readonly endpointing?: string | undefined;
37
+ readonly transport?: string | undefined;
38
+ readonly runtime?: string | undefined;
39
+ readonly preset?: string | undefined;
40
+ readonly name?: string | undefined;
41
+ readonly yes?: boolean | undefined;
42
+ readonly "no-install"?: boolean | undefined;
43
+ readonly "skip-install"?: boolean | undefined;
44
+ readonly "dry-run"?: boolean | undefined;
45
+ }
46
+
47
+ /** A resolved, valid selection — either cascade (stt+tts+reasoner) or speech-to-speech (realtime). */
48
+ export type ResolvedOptions =
49
+ | {
50
+ readonly mode: "cascade";
51
+ readonly stt: SttProvider;
52
+ readonly tts: TtsProvider;
53
+ readonly reasoner: ReasonerProvider;
54
+ readonly vad: VadProvider | undefined;
55
+ readonly endpointing: EndpointingProvider | undefined;
56
+ readonly transport: Transport;
57
+ readonly runtime: Runtime;
58
+ readonly name: string;
59
+ /** Absolute path the project is written to. NOT `name` — see resolveOptions. */
60
+ readonly targetDir: string;
61
+ readonly skipInstall: boolean;
62
+ readonly dryRun: boolean;
63
+ }
64
+ | {
65
+ readonly mode: "realtime";
66
+ readonly realtime: RealtimeProvider;
67
+ readonly transport: Transport;
68
+ readonly runtime: Runtime;
69
+ readonly name: string;
70
+ /** Absolute path the project is written to. NOT `name` — see resolveOptions. */
71
+ readonly targetDir: string;
72
+ readonly skipInstall: boolean;
73
+ readonly dryRun: boolean;
74
+ };
75
+
76
+ interface Preset {
77
+ readonly stt?: SttProvider;
78
+ readonly tts?: TtsProvider;
79
+ readonly reasoner?: ReasonerProvider;
80
+ readonly transport?: Transport;
81
+ readonly runtime?: Runtime;
82
+ }
83
+
84
+ // Presets are flag bundles, not a separate code path — every field here is
85
+ // exactly a field an explicit flag can also set, and an explicit flag wins.
86
+ const PRESETS: Readonly<Record<string, Preset>> = {
87
+ phone: { stt: "deepgram", tts: "cartesia", reasoner: "aisdk", transport: "twilio", runtime: "node" },
88
+ };
89
+
90
+ function memberOf<T extends readonly string[]>(list: T, value: string, flagName: string): T[number] {
91
+ if (!(list as readonly string[]).includes(value)) {
92
+ throw new CliError(EXIT_CODES.USAGE, `--${flagName} must be one of: ${list.join(", ")} (got "${value}")`);
93
+ }
94
+ return value as T[number];
95
+ }
96
+
97
+ /**
98
+ * Merge a preset under explicit flags (explicit always wins), validate shape,
99
+ * and refuse the one hard conflict: --realtime combined with --stt/--tts.
100
+ * Missing required fields in cascade mode are a USAGE error — this generator
101
+ * never prompts, see help.ts's documented contract.
102
+ */
103
+ export function resolveOptions(raw: RawFlags, positionals: readonly string[]): ResolvedOptions {
104
+ if (raw.preset !== undefined && !(raw.preset in PRESETS)) {
105
+ throw new CliError(EXIT_CODES.USAGE, `--preset must be one of: ${Object.keys(PRESETS).join(", ")} (got "${raw.preset}")`);
106
+ }
107
+ const preset = raw.preset !== undefined ? PRESETS[raw.preset] : undefined;
108
+
109
+ const sttRaw = raw.stt ?? preset?.stt;
110
+ const ttsRaw = raw.tts ?? preset?.tts;
111
+ const reasonerRaw = raw.reasoner ?? preset?.reasoner;
112
+ const realtimeRaw = raw.realtime;
113
+
114
+ if (realtimeRaw !== undefined && (sttRaw !== undefined || ttsRaw !== undefined)) {
115
+ throw new CliError(
116
+ EXIT_CODES.USAGE,
117
+ `--realtime is a speech-to-speech pipeline with no separate STT/TTS stages — it cannot be combined with ${[
118
+ sttRaw !== undefined ? "--stt" : undefined,
119
+ ttsRaw !== undefined ? "--tts" : undefined,
120
+ ]
121
+ .filter((v): v is string => v !== undefined)
122
+ .join(" and ")}.`,
123
+ );
124
+ }
125
+
126
+ const transport = memberOf(TRANSPORTS, raw.transport ?? preset?.transport ?? "browser", "transport");
127
+ const runtime = memberOf(RUNTIMES, raw.runtime ?? preset?.runtime ?? "node", "runtime");
128
+ const skipInstall = raw["no-install"] === true || raw["skip-install"] === true;
129
+ const dryRun = raw["dry-run"] === true;
130
+ const targetDir = positionals[0];
131
+ const name = raw.name ?? (targetDir !== undefined ? targetDir.split("/").filter(Boolean).at(-1) : undefined);
132
+ if (name === undefined || name.length === 0) {
133
+ throw new CliError(EXIT_CODES.USAGE, "a target directory or --name is required, e.g. `create-syrinx-agent my-agent`");
134
+ }
135
+ // Where the files actually go. Distinct from `name`, which is only what the
136
+ // project calls itself: writing to `./<name>` would silently ignore an absolute
137
+ // or nested target and scatter a project into whatever directory you ran from.
138
+ const resolvedTarget = resolve(process.cwd(), targetDir ?? name);
139
+
140
+ if (realtimeRaw !== undefined) {
141
+ const realtime = memberOf(REALTIME_PROVIDERS, realtimeRaw, "realtime");
142
+ return { mode: "realtime", realtime, transport, runtime, name, targetDir: resolvedTarget, skipInstall, dryRun };
143
+ }
144
+
145
+ const missing = [
146
+ sttRaw === undefined ? "--stt" : undefined,
147
+ ttsRaw === undefined ? "--tts" : undefined,
148
+ reasonerRaw === undefined ? "--reasoner" : undefined,
149
+ ].filter((v): v is string => v !== undefined);
150
+ if (missing.length > 0) {
151
+ throw new CliError(
152
+ EXIT_CODES.USAGE,
153
+ `missing required flag(s): ${missing.join(", ")} (or pass --realtime for a speech-to-speech pipeline, or --preset <name>)`,
154
+ );
155
+ }
156
+
157
+ const stt = memberOf(STT_PROVIDERS, sttRaw as string, "stt");
158
+ const tts = memberOf(TTS_PROVIDERS, ttsRaw as string, "tts");
159
+ const reasoner = memberOf(REASONER_PROVIDERS, reasonerRaw as string, "reasoner");
160
+ const vad = raw.vad !== undefined ? memberOf(VAD_PROVIDERS, raw.vad, "vad") : undefined;
161
+ const endpointing = raw.endpointing !== undefined ? memberOf(ENDPOINTING_PROVIDERS, raw.endpointing, "endpointing") : undefined;
162
+
163
+ return { mode: "cascade", stt, tts, reasoner, vad, endpointing, transport, runtime, name, targetDir: resolvedTarget, skipInstall, dryRun };
164
+ }
165
+
166
+ /** Non-fatal warnings for combinations that generate but aren't fully verified. */
167
+ export function warningsFor(opts: ResolvedOptions): readonly string[] {
168
+ const warnings: string[] = [];
169
+ if (opts.runtime === "cloudflare" && opts.transport === "telnyx") {
170
+ warnings.push(
171
+ "runtime=cloudflare + transport=telnyx is generated but not yet bound on the Workers edge — deploy-unverified. " +
172
+ "See @kuralle-syrinx/server-workers for current Cloudflare transport coverage before deploying.",
173
+ );
174
+ }
175
+ return warnings;
176
+ }
@@ -0,0 +1,31 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Speech-to-speech (realtime) registry, verified against
4
+ // packages/realtime/src/from-openai-realtime.ts and
5
+ // packages/grok/src/from-grok-realtime.ts. Mirrors
6
+ // examples/02-hello-voice-headless/scripts/run-realtime-oneturn-smoke.ts:
7
+ // `fromXRealtime({ apiKey, socketFactory })` -> `RealtimeAdapter`, wrapped in
8
+ // `new RealtimeBridge(adapter)` and registered on the "realtime" slot with
9
+ // `endpointingOwner: "timer"`.
10
+
11
+ import type { RealtimeProvider } from "../options.js";
12
+ import type { RealtimeProviderEmission } from "./types.js";
13
+
14
+ const PKG_VERSION = "^4.3.0";
15
+
16
+ export const REALTIME_PROVIDERS: Readonly<Record<RealtimeProvider, RealtimeProviderEmission | undefined>> = {
17
+ realtime: {
18
+ packageName: "@kuralle-syrinx/realtime",
19
+ packageVersion: PKG_VERSION,
20
+ envKeys: [{ key: "OPENAI_API_KEY", required: true }],
21
+ importLines: [`import { fromOpenAIRealtime } from "@kuralle-syrinx/realtime";`],
22
+ adapterExpr: (envRef, socketFactoryIdent) => `fromOpenAIRealtime({ apiKey: ${envRef("OPENAI_API_KEY")}, socketFactory: ${socketFactoryIdent} })`,
23
+ },
24
+ grok: {
25
+ packageName: "@kuralle-syrinx/grok",
26
+ packageVersion: PKG_VERSION,
27
+ envKeys: [{ key: "XAI_API_KEY", required: true }],
28
+ importLines: [`import { fromGrokRealtime } from "@kuralle-syrinx/grok/realtime";`],
29
+ adapterExpr: (envRef, socketFactoryIdent) => `fromGrokRealtime({ apiKey: ${envRef("XAI_API_KEY")}, socketFactory: ${socketFactoryIdent} })`,
30
+ },
31
+ };
@@ -0,0 +1,69 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Reasoner registry. `undefined` for a provider not yet wired into the
4
+ // generator — kuralle needs a real @kuralle-agents/core Runtime instance
5
+ // (fromKuralleRuntime(runtime, opts)), which this generator does not yet
6
+ // scaffold; see the report for why.
7
+ //
8
+ // Every entry's `reasonerExpr` produces a bare `Reasoner` (packages/core's
9
+ // stream seam). The cascade template wraps it in `new ReasoningBridge(...)`
10
+ // for the node runtime; the Cloudflare template passes it straight to
11
+ // `withVoice`'s `reasoner: (env) => ...` — both consume the same expression.
12
+ // `ReasoningBridge` itself is NOT imported here: the cascade agent-module
13
+ // template always imports it from @kuralle-syrinx/aisdk (it is the generic
14
+ // VoicePlugin wrapper around any Reasoner, independent of which provider
15
+ // produced it — mirrors examples/.../university-support-mastra.ts, which
16
+ // depends on @kuralle-syrinx/aisdk purely for this wrapper).
17
+
18
+ import type { ReasonerProvider } from "../options.js";
19
+ import type { ReasonerProviderEmission } from "./types.js";
20
+
21
+ const PKG_VERSION = "^4.3.0";
22
+ const OPENAI_SDK_VERSION = "^3.0.67";
23
+ const AI_SDK_VERSION = "^6.0.0";
24
+ const MASTRA_CORE_VERSION = "^1.41.0";
25
+
26
+ export const SYSTEM_PROMPT_CONST = "SYSTEM_PROMPT";
27
+
28
+ export const REASONER_PROVIDERS: Readonly<Record<ReasonerProvider, ReasonerProviderEmission | undefined>> = {
29
+ // packages/aisdk/src/from-ai-sdk.ts: fromStreamText(config) -> Reasoner.
30
+ // Mirrors examples/02-hello-voice-headless/src/hello-voice-agent.ts.
31
+ aisdk: {
32
+ packageName: "@kuralle-syrinx/aisdk",
33
+ packageVersion: PKG_VERSION,
34
+ extraPackages: { "@ai-sdk/openai": OPENAI_SDK_VERSION, ai: AI_SDK_VERSION },
35
+ envKeys: [{ key: "OPENAI_API_KEY", required: true }],
36
+ importLines: () => [
37
+ `import { fromStreamText } from "@kuralle-syrinx/aisdk";`,
38
+ `import { createOpenAI } from "@ai-sdk/openai";`,
39
+ ],
40
+ preludeLines: (envRef) => [`const openai = createOpenAI({ apiKey: ${envRef("OPENAI_API_KEY")} });`],
41
+ reasonerExpr: `fromStreamText({ model: openai("gpt-4.1-mini"), system: ${SYSTEM_PROMPT_CONST} })`,
42
+ },
43
+ // packages/mastra/src/from-mastra.ts: fromMastraAgent(agent) -> Reasoner.
44
+ // Mirrors examples/02-hello-voice-headless/src/university-support-mastra.ts.
45
+ mastra: {
46
+ packageName: "@kuralle-syrinx/mastra",
47
+ packageVersion: PKG_VERSION,
48
+ extraPackages: { "@ai-sdk/openai": OPENAI_SDK_VERSION, "@mastra/core": MASTRA_CORE_VERSION },
49
+ envKeys: [{ key: "OPENAI_API_KEY", required: true }],
50
+ importLines: () => [
51
+ `import { fromMastraAgent, type MastraAgentLike } from "@kuralle-syrinx/mastra";`,
52
+ `import { createOpenAI } from "@ai-sdk/openai";`,
53
+ `import { Agent as MastraAgent } from "@mastra/core/agent";`,
54
+ ],
55
+ preludeLines: (envRef) => [
56
+ `const openai = createOpenAI({ apiKey: ${envRef("OPENAI_API_KEY")} });`,
57
+ `const mastraAgent = new MastraAgent({`,
58
+ ` id: "syrinx-agent",`,
59
+ ` name: "syrinx-agent",`,
60
+ ` instructions: ${SYSTEM_PROMPT_CONST},`,
61
+ ` model: openai("gpt-4.1-mini"),`,
62
+ `});`,
63
+ ],
64
+ // Structural cast to MastraAgentLike, same pattern the reference agent uses —
65
+ // fromMastraAgent types against a structural interface, not @mastra/core's own type.
66
+ reasonerExpr: `fromMastraAgent(mastraAgent as unknown as MastraAgentLike)`,
67
+ },
68
+ kuralle: undefined,
69
+ };
@@ -0,0 +1,68 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // STT stage registry, verified against packages/{deepgram,google,elevenlabs,grok}/src.
4
+ // `undefined` for a provider not yet wired into the generator.
5
+
6
+ import type { SttProvider } from "../options.js";
7
+ import type { StageProvider } from "./types.js";
8
+
9
+ const PKG_VERSION = "^4.3.0";
10
+
11
+ export const STT_STAGE_PROVIDERS: Readonly<Record<SttProvider, StageProvider | undefined>> = {
12
+ // packages/deepgram/src/stt.ts: DeepgramSTTPlugin, endpointingCapability.owner === "provider_stt".
13
+ // Config fields mirror examples/02-hello-voice-headless/src/hello-voice-agent.ts.
14
+ deepgram: {
15
+ packageName: "@kuralle-syrinx/deepgram",
16
+ packageVersion: PKG_VERSION,
17
+ className: "DeepgramSTTPlugin",
18
+ envKeys: [{ key: "DEEPGRAM_API_KEY", required: true }],
19
+ usesSocketFactory: true,
20
+ ownsEndpointing: true,
21
+ configFields: (envRef) => [
22
+ `api_key: ${envRef("DEEPGRAM_API_KEY")}`,
23
+ `model: "nova-3"`,
24
+ `sample_rate: 16000`,
25
+ `emit_eos_on_final: true`,
26
+ ],
27
+ },
28
+ // packages/google/src/index.ts: GoogleSTTPlugin (Cloud Speech-to-Text v2).
29
+ // api_key + project_id are required config; endpointingCapability.owner === "provider_stt".
30
+ google: {
31
+ packageName: "@kuralle-syrinx/google",
32
+ packageVersion: PKG_VERSION,
33
+ className: "GoogleSTTPlugin",
34
+ envKeys: [
35
+ { key: "GOOGLE_CLOUD_SPEECH_API_KEY", required: true },
36
+ { key: "GOOGLE_CLOUD_PROJECT_ID", required: true },
37
+ ],
38
+ usesSocketFactory: true,
39
+ ownsEndpointing: true,
40
+ configFields: (envRef) => [
41
+ `api_key: ${envRef("GOOGLE_CLOUD_SPEECH_API_KEY")}`,
42
+ `project_id: ${envRef("GOOGLE_CLOUD_PROJECT_ID")}`,
43
+ `sample_rate: 16000`,
44
+ ],
45
+ },
46
+ // packages/elevenlabs/src/stt.ts: ElevenLabsSTTPlugin (Scribe v2 Realtime).
47
+ elevenlabs: {
48
+ packageName: "@kuralle-syrinx/elevenlabs",
49
+ packageVersion: PKG_VERSION,
50
+ importFrom: "@kuralle-syrinx/elevenlabs/stt",
51
+ className: "ElevenLabsSTTPlugin",
52
+ envKeys: [{ key: "ELEVENLABS_API_KEY", required: true }],
53
+ usesSocketFactory: true,
54
+ ownsEndpointing: true,
55
+ configFields: (envRef) => [`api_key: ${envRef("ELEVENLABS_API_KEY")}`],
56
+ },
57
+ // packages/grok/src/stt.ts: GrokSTTPlugin. endpointingCapability.owner === "provider_stt".
58
+ grok: {
59
+ packageName: "@kuralle-syrinx/grok",
60
+ packageVersion: PKG_VERSION,
61
+ importFrom: "@kuralle-syrinx/grok/stt",
62
+ className: "GrokSTTPlugin",
63
+ envKeys: [{ key: "XAI_API_KEY", required: true }],
64
+ usesSocketFactory: true,
65
+ ownsEndpointing: true,
66
+ configFields: (envRef) => [`api_key: ${envRef("XAI_API_KEY")}`, `language: "en"`],
67
+ },
68
+ };
@@ -0,0 +1,69 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // TTS stage registry, verified against packages/{cartesia,elevenlabs,gemini,openai-tts,grok}/src.
4
+
5
+ import type { TtsProvider } from "../options.js";
6
+ import type { StageProvider } from "./types.js";
7
+
8
+ const PKG_VERSION = "^4.3.0";
9
+
10
+ export const TTS_STAGE_PROVIDERS: Readonly<Record<TtsProvider, StageProvider | undefined>> = {
11
+ // packages/cartesia/src/index.ts: CartesiaTTSPlugin. Config mirrors hello-voice-agent.ts.
12
+ cartesia: {
13
+ packageName: "@kuralle-syrinx/cartesia",
14
+ packageVersion: PKG_VERSION,
15
+ className: "CartesiaTTSPlugin",
16
+ envKeys: [
17
+ { key: "CARTESIA_API_KEY", required: true },
18
+ { key: "CARTESIA_VOICE_ID", required: true },
19
+ ],
20
+ usesSocketFactory: true,
21
+ ownsEndpointing: false,
22
+ configFields: (envRef) => [`api_key: ${envRef("CARTESIA_API_KEY")}`, `voice_id: ${envRef("CARTESIA_VOICE_ID")}`],
23
+ },
24
+ // packages/elevenlabs/src/tts.ts: ElevenLabsTTSPlugin.
25
+ elevenlabs: {
26
+ packageName: "@kuralle-syrinx/elevenlabs",
27
+ packageVersion: PKG_VERSION,
28
+ importFrom: "@kuralle-syrinx/elevenlabs/tts",
29
+ className: "ElevenLabsTTSPlugin",
30
+ envKeys: [
31
+ { key: "ELEVENLABS_API_KEY", required: true },
32
+ { key: "ELEVENLABS_VOICE_ID", required: true },
33
+ ],
34
+ usesSocketFactory: true,
35
+ ownsEndpointing: false,
36
+ configFields: (envRef) => [`api_key: ${envRef("ELEVENLABS_API_KEY")}`, `voice_id: ${envRef("ELEVENLABS_VOICE_ID")}`],
37
+ },
38
+ // packages/gemini/src/index.ts: GeminiTTSPlugin. HTTP-based (@google/genai) — no SocketFactory.
39
+ gemini: {
40
+ packageName: "@kuralle-syrinx/gemini",
41
+ packageVersion: PKG_VERSION,
42
+ className: "GeminiTTSPlugin",
43
+ envKeys: [{ key: "GOOGLE_GENERATIVE_AI_API_KEY", required: true }],
44
+ usesSocketFactory: false,
45
+ ownsEndpointing: false,
46
+ configFields: (envRef) => [`api_key: ${envRef("GOOGLE_GENERATIVE_AI_API_KEY")}`],
47
+ },
48
+ // packages/openai-tts/src/index.ts: OpenAICompatibleTTSPlugin. HTTP-based — no SocketFactory.
49
+ "openai-tts": {
50
+ packageName: "@kuralle-syrinx/openai-tts",
51
+ packageVersion: PKG_VERSION,
52
+ className: "OpenAICompatibleTTSPlugin",
53
+ envKeys: [{ key: "OPENAI_API_KEY", required: true }],
54
+ usesSocketFactory: false,
55
+ ownsEndpointing: false,
56
+ configFields: (envRef) => [`api_key: ${envRef("OPENAI_API_KEY")}`],
57
+ },
58
+ // packages/grok/src/tts.ts: GrokTTSPlugin.
59
+ grok: {
60
+ packageName: "@kuralle-syrinx/grok",
61
+ packageVersion: PKG_VERSION,
62
+ importFrom: "@kuralle-syrinx/grok/tts",
63
+ className: "GrokTTSPlugin",
64
+ envKeys: [{ key: "XAI_API_KEY", required: true }],
65
+ usesSocketFactory: true,
66
+ ownsEndpointing: false,
67
+ configFields: (envRef) => [`api_key: ${envRef("XAI_API_KEY")}`, `voice_id: "eve"`],
68
+ },
69
+ };
@@ -0,0 +1,58 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Shared shape for a provider's generated-project footprint: what it depends
4
+ // on, what env keys it needs, and how to emit its plugin construction code.
5
+ // `envRef` is supplied by the caller so the SAME provider entry emits either
6
+ // `process.env["KEY"]` (node runtime) or `env.KEY` (Cloudflare Workers).
7
+
8
+ export interface EnvKeySpec {
9
+ readonly key: string;
10
+ readonly required: boolean;
11
+ readonly note?: string;
12
+ }
13
+
14
+ /** An STT or TTS provider's plugin — the cascade's discrete stages. */
15
+ export interface StageProvider {
16
+ readonly packageName: string;
17
+ readonly packageVersion: string;
18
+ /** Import path, when it differs from packageName (e.g. a "/stt" or "/tts" subpath). */
19
+ readonly importFrom?: string;
20
+ readonly className: string;
21
+ readonly envKeys: readonly EnvKeySpec[];
22
+ /** True when the plugin constructor takes an optional SocketFactory (ws-based providers). */
23
+ readonly usesSocketFactory: boolean;
24
+ /** True for an STT plugin that can own endpointing itself (`endpointingCapability.owner === "provider_stt"`). */
25
+ readonly ownsEndpointing: boolean;
26
+ /** TS source for each field of the plugin's config object literal (no trailing commas). */
27
+ readonly configFields: (envRef: (key: string) => string) => readonly string[];
28
+ }
29
+
30
+ export interface ReasonerProviderEmission {
31
+ readonly packageName: string;
32
+ readonly packageVersion: string;
33
+ readonly extraPackages: Readonly<Record<string, string>>;
34
+ readonly envKeys: readonly EnvKeySpec[];
35
+ readonly importLines: (envRef: (key: string) => string) => readonly string[];
36
+ /** Lines emitted before the session/pipeline is built (e.g. constructing a model client). */
37
+ readonly preludeLines: (envRef: (key: string) => string) => readonly string[];
38
+ /** The `Reasoner`-producing expression, e.g. `fromStreamText({ ... })`. */
39
+ readonly reasonerExpr: string;
40
+ }
41
+
42
+ export interface RealtimeProviderEmission {
43
+ readonly packageName: string;
44
+ readonly packageVersion: string;
45
+ readonly envKeys: readonly EnvKeySpec[];
46
+ readonly importLines: readonly string[];
47
+ /** `socketFactoryIdent` is runtime-dependent ("createNodeWsSocket" | "createWorkersSocket"), supplied by the caller. */
48
+ readonly adapterExpr: (envRef: (key: string) => string, socketFactoryIdent: string) => string;
49
+ }
50
+
51
+ export interface SidecarPluginEmission {
52
+ readonly packageName: string;
53
+ readonly packageVersion: string;
54
+ readonly className: string;
55
+ readonly slot: "vad" | "eos";
56
+ readonly envKeys: readonly EnvKeySpec[];
57
+ readonly configFields: (envRef: (key: string) => string) => readonly string[];
58
+ }
@@ -0,0 +1,36 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Optional VAD / endpointing sidecar plugins. `vap` is not yet wired into the
4
+ // generator — packages/vap exports an InteractionPolicy (VapInteractionPolicy)
5
+ // rather than a drop-in VoicePlugin like PipecatEOSPlugin, which needs
6
+ // InteractionCoordinator wiring this generator doesn't yet scaffold.
7
+
8
+ import type { EndpointingProvider, VadProvider } from "../options.js";
9
+ import type { SidecarPluginEmission } from "./types.js";
10
+
11
+ const PKG_VERSION = "^4.3.0";
12
+
13
+ export const VAD_SIDECAR_PROVIDERS: Readonly<Record<VadProvider, SidecarPluginEmission | undefined>> = {
14
+ // packages/silero-vad/src/index.ts: SileroVADPlugin. No API key — a bundled ONNX model.
15
+ "silero-vad": {
16
+ packageName: "@kuralle-syrinx/silero-vad",
17
+ packageVersion: PKG_VERSION,
18
+ className: "SileroVADPlugin",
19
+ slot: "vad",
20
+ envKeys: [],
21
+ configFields: () => [],
22
+ },
23
+ };
24
+
25
+ export const ENDPOINTING_SIDECAR_PROVIDERS: Readonly<Record<EndpointingProvider, SidecarPluginEmission | undefined>> = {
26
+ // packages/pipecat-smart-turn/src/index.ts: PipecatEOSPlugin. No API key — a bundled ONNX model.
27
+ "pipecat-smart-turn": {
28
+ packageName: "@kuralle-syrinx/pipecat-smart-turn",
29
+ packageVersion: PKG_VERSION,
30
+ className: "PipecatEOSPlugin",
31
+ slot: "eos",
32
+ envKeys: [],
33
+ configFields: () => [],
34
+ },
35
+ vap: undefined,
36
+ };