← Back to PRs

#22889: feat(talk): add provider-agnostic talk config contract

by ngutman open 2026-02-21 19:55 View on GitHub →
app: android app: ios app: macos app: web-ui gateway maintainer size: XL
## Summary Describe the problem and fix in 2–5 bullets: - Problem: `talk.*` contract was ElevenLabs-specific (`voiceId/modelId/outputFormat/apiKey`) and blocked clean multi-provider evolution. - Why it matters: iOS/macOS/Android/gateway need a stable provider-agnostic shape while preserving existing deployments during rollout. - What changed: added normalized `talk.provider` + `talk.providers[provider]` contract, read-time normalization/migration, gateway response normalization with temporary legacy fields, and client parsing that prefers normalized then falls back to legacy. - What did NOT change (scope boundary): no removal of legacy fields yet (phase 1 only), no provider behavior switch in runtime TTS selection beyond config parsing compatibility. ## Change Type (select all) - [x] Bug fix - [x] Feature - [x] Refactor - [ ] Docs - [ ] Security hardening - [ ] Chore/infra ## Scope (select all touched areas) - [x] Gateway / orchestration - [ ] Skills / tool execution - [ ] Auth / tokens - [ ] Memory / storage - [ ] Integrations - [x] API / contracts - [x] UI / DX - [ ] CI/CD / infra ## Linked Issue/PR - Closes # - Related #22817 ## User-visible / Behavior Changes - Gateway `talk.config` now returns normalized provider-agnostic contract (`talk.provider`, `talk.providers`) and still includes legacy ElevenLabs keys for compatibility. - iOS/macOS/Android now read normalized talk config first and fallback to legacy fields only when normalized payload is absent. - iOS keychain talk API key storage is provider-routed and keeps ElevenLabs legacy key migration fallback. ## Security Impact (required) - New permissions/capabilities? (`No`) - Secrets/tokens handling changed? (`Yes`) - New/changed network calls? (`No`) - Command/tool execution surface changed? (`No`) - Data access scope changed? (`No`) - If any `Yes`, explain risk + mitigation: - Talk API key routing now supports provider-specific key lookup/write paths. Mitigation: gateway keeps existing secret redaction behavior, legacy key fallback remains during migration, and compatibility tests cover parse order and fallback semantics. ## Repro + Verification ### Environment - OS: macOS - Runtime/container: Node 22 + pnpm - Model/provider: talk config normalization (default provider `elevenlabs`) - Integration/channel (if any): gateway talk.config + iOS/macOS/Android talk clients - Relevant config (redacted): legacy `talk.voiceId/modelId/outputFormat/apiKey` and normalized `talk.provider/providers.*` ### Steps 1. Configure gateway with legacy talk keys only. 2. Call `talk.config` and validate normalized shape is present and legacy fields remain in payload. 3. Configure gateway with normalized talk shape and confirm clients pick normalized provider config before legacy fallback. ### Expected - Existing legacy configs continue to work unchanged. - Normalized provider-agnostic shape is returned and consumed first by clients. ### Actual - Matches expected in e2e/unit parsing tests and targeted config normalization tests. ## Evidence Attach at least one: - [x] Failing test/log before + passing after - [x] Trace/log snippets - [ ] Screenshot/recording - [ ] Perf numbers (if relevant) ## Human Verification (required) What you personally verified (not just CI), and how: - Verified scenarios: - `pnpm test src/config/talk.normalize.test.ts src/config/config-misc.test.ts src/config/schema.hints.test.ts` - `pnpm test:e2e src/gateway/server.talk-config.e2e.test.ts` - `pnpm tsgo` - manual git-history verification that legacy fields predate this PR and are actively consumed on `origin/main`. - Edge cases checked: - legacy-only config normalization - normalized passthrough - active provider env/default merge behavior - client parse precedence (normalized first, legacy fallback) - What you did **not** verify: - full Android/macOS runtime integration tests on device/simulator in this PR flow. ## Compatibility / Migration - Backward compatible? (`Yes`) - Config/env changes? (`Yes`) - Migration needed? (`No`) - If yes, exact upgrade steps: - Optional: start writing normalized `talk.provider` + `talk.providers` now; legacy keys continue to work during phase 1. ## Failure Recovery (if this breaks) - How to disable/revert this change quickly: - Revert commit `00d5f4478` from branch `feat/talk-provider-agnostic-config`. - Files/config to restore: - `src/config/talk.ts`, `src/gateway/server-methods/talk.ts`, and client talk parsing files in `apps/ios`, `apps/macos`, `apps/android`. - Known bad symptoms reviewers should watch for: - Talk clients ignoring configured provider voice/model values. - Missing key resolution when only legacy fields are present. ## Risks and Mitigations List only real risks for this PR. Add/remove entries as needed. If none, write `None`. - Risk: - Divergence between normalized and legacy values when both are present. - Mitigation: - normalization enforces single active provider resolution and response builder mirrors active provider config into legacy compatibility fields during phase 1. - Risk: - Client regressions from payload shape transition. - Mitigation: - explicit normalized-first + legacy-fallback parsing tests on iOS/macOS/Android. <!-- greptile_comment --> <h3>Greptile Summary</h3> This PR adds provider-agnostic talk config structure while maintaining backward compatibility with legacy ElevenLabs-specific fields. The implementation follows a clean migration strategy: **Backend changes:** - Introduced normalized `talk.provider` and `talk.providers[provider]` structure in config schema and types - Added read-time normalization that auto-migrates legacy ElevenLabs fields to normalized structure (defaults to `elevenlabs` provider) - Gateway responses include both normalized fields and legacy compatibility fields (legacy fields mirror active provider config) - Environment variable merging (`ELEVENLABS_API_KEY`) only applies when active provider is `elevenlabs` **Client changes (iOS/macOS/Android):** - Clients parse normalized provider config first, then fall back to legacy fields when normalized shape absent - iOS keychain storage migrated to provider-routed keys with automatic legacy key migration on read - Non-ElevenLabs providers log warnings and fall back to system voice (phase 1 scope boundary) **Test coverage:** - Unit tests cover legacy-to-normalized migration, normalized passthrough, and env variable merging - E2e tests verify gateway response normalization and precedence rules - Client parsing tests confirm normalized-first, legacy-fallback behavior The migration is zero-downtime: existing configs continue working unchanged, and the normalized shape is additive. <h3>Confidence Score: 4/5</h3> - Safe to merge with thorough testing and well-designed migration strategy - This is a well-executed phased migration with comprehensive test coverage and backward compatibility. Score of 4 (not 5) reflects minor edge case: multiple providers without explicit `provider` field lacks test coverage and clear behavior specification. Implementation correctly handles the normalized/legacy precedence rules, environment variable scoping, and keychain migration. The scope boundary is clear (phase 1, no runtime provider switching), and the rollback plan is straightforward. - No files require special attention - the implementation is consistent across backend normalization, gateway responses, and client parsing <sub>Last reviewed commit: 00d5f44</sub> <!-- greptile_other_comments_section --> <!-- /greptile_comment -->

Most Similar PRs