{"record":{"id":"8b39ee1f9347f775","repo":"HeyPuter/puter","slug":"bad-request-8b39ee","errorCode":"bad_request","errorMessage":"Speech-to-speech provider not found: ${args.provider}. Available: ${PROVIDERS.join(', ')}","messagePattern":"Speech-to-speech provider not found: (.+?)\\. Available: (.+?)","errorType":"validation","errorClass":"HttpError","httpStatus":400,"severity":"error","filePath":"src/backend/drivers/ai-speech2speech/VoiceChangerDriver.ts","lineNumber":121,"sourceCode":"        this.#defaultModelId =\n            (elevenlabs?.speechToSpeechModelId as string | undefined) ??\n            DEFAULT_MODEL;\n    }\n\n    async convert(\n        args: ConvertArgs,\n    ): Promise<DriverStreamResult | { url: string; content_type: string }> {\n        // Only one provider exists today, but naming a different one should\n        // fail loudly rather than quietly convert with this one.\n        if (\n            args.provider &&\n            !PROVIDERS.includes(\n                args.provider\n                    .trim()\n                    .toLowerCase() as (typeof PROVIDERS)[number],\n            )\n        ) {\n            throw new HttpError(\n                400,\n                `Speech-to-speech provider not found: ${args.provider}. Available: ${PROVIDERS.join(', ')}`,\n                { legacyCode: 'bad_request' },\n            );\n        }\n\n        if (args.test_mode) {\n            return { url: SAMPLE_AUDIO_URL, content_type: 'audio/mpeg' };\n        }\n\n        if (!this.#apiKey) {\n            throw new HttpError(500, 'ElevenLabs API key not configured', {\n                legacyCode: 'internal_error',\n            });\n        }\n\n        const actor = Context.get('actor');\n        if (!actor)","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/drivers/ai-speech2speech/VoiceChangerDriver.ts#L103-L139","documentation":"The voice-changer driver (puter-speech2speech) only ships an ElevenLabs provider. The caller set args.provider to a value that, after trim+lowercase, is not in the single-entry allowlist (['elevenlabs']), so the driver refuses rather than silently route to ElevenLabs. The check exists so that once a second provider lands, a stale provider value fails loud instead of converting with the wrong backend. Thrown as 400 / bad_request.","triggerScenarios":"Calling driver.convert({ audio, provider: 'openai' }) (or 'azure', 'google', any typo) on the puter-speech2speech driver. Also hit by an older SDK bundle that injects a provider name copied from a sibling AI call (TTS/chat).","commonSituations":"Reusing args from a text-to-speech or chat-completion call into the speech-to-speech driver; integration tests stubbing a fake provider name; a puter.js bundle that names 'azure' or 'google'.","solutions":["Pass provider: 'elevenlabs' (case/space tolerant — it is trimmed and lowercased) or simply omit it; the driver defaults to ElevenLabs.","Confirm the driver interface you dispatched to is puter-speech2speech. TTS and STT have their own, different provider lists."],"exampleFix":"// before\ndriver.convert({ audio, provider: 'openai' })\n// after\ndriver.convert({ audio }) // or provider: 'elevenlabs'","handlingStrategy":"validation","validationCode":"const ALLOWED_S2S = ['elevenlabs'];\nif (args.provider && !ALLOWED_S2S.includes(String(args.provider).trim().toLowerCase())) {\n  throw new Error(`provider must be one of: ${ALLOWED_S2S.join(', ')}`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Omit provider to use the driver default; the only valid value is 'elevenlabs'.","Mirror the driver's allowlist client-side before the network call.","Don't forward provider names copied from TTS or chat-completion calls."],"tags":["validation","provider-routing","speech2speech"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}