Skip to content

fix(tts): construct SarvamAIClient, not the non-existent default export - #41

Open
shreyaskarnik wants to merge 1 commit into
mainfrom
fix/sarvam-client-constructor
Open

fix(tts): construct SarvamAIClient, not the non-existent default export#41
shreyaskarnik wants to merge 1 commit into
mainfrom
fix/sarvam-client-constructor

Conversation

@shreyaskarnik

Copy link
Copy Markdown
Owner

Closes #40. Found by @Joilence during review of #36.

The bug

src/tts/engines/sarvam.ts destructured the default export of sarvamai. There isn't one, and SarvamAI is a namespace object rather than a constructor:

$ node --input-type=module -e "const m = await import('sarvamai'); ..."
default:         undefined
SarvamAI:        object      ← namespace
SarvamAIClient:  function    ← the actual client

new SarvamAI(...) threw SarvamAI is not a constructor for every user who had the package correctly installed. The Sarvam engine has never worked.

Why it survived this long

The try/catch wraps only the import() and reports any failure as "requires the 'sarvamai' package". The new call sits outside it, so the real failure surfaced as a bare TypeError at a line that looks unrelated to the import.

That same over-broad catch bit me while writing the test: vitest's mock factory is hoisted above the file's consts, the resulting TDZ ReferenceError was swallowed, and it reported as "package not installed". vi.hoisted() is the fix, and it's commented in the test so the next person doesn't lose the same twenty minutes.

Changes

  1. Destructure SarvamAIClient.
  2. Guard: if the resolved package doesn't export a callable SarvamAIClient, throw a message naming the package and the expected version — instead of letting new undefined() produce another unrelated-looking TypeError on a future rename.
  3. sarvamai added as a devDependency. Not incidental: vitest cannot intercept vi.mock for a module that doesn't resolve, so without it the engine is untestable. Verified directly — the mock silently no-ops and the import throws MODULE_NOT_FOUND.

Relationship to #36

#36 declares sarvamai an optional peer and rewrites this import to go through importOptional(). This doesn't pre-empt either — the devDependency is what #36 adds too, and the constructor fix is orthogonal to how the module is loaded. Expect a trivial conflict in package.json at most.

Not verified

Whether the convert() payload underneath is also wrong. That needs a live Sarvam API key. The tests assert the call shape — that the client is constructed with apiSubscriptionKey, and that text/lang/voice/pace reach convert() — not that Sarvam accepts it. Worth a real end-to-end check by someone with a key before trusting the engine.

Test plan

5 new tests in tests/tts/sarvam.test.ts, written test-first. The mock mirrors the real module shape exactly, default: undefined and all, so a regression back to the default import fails here rather than only in production.

  • constructs SarvamAIClient with the resolved API key
  • forwards text, target_language_code, speaker and pace to convert()pace asserted specifically because Sarvam has native rate control, so speed must not additionally be applied by convertToWav and compound
  • throws on an empty audios array
  • rejects empty text before touching the SDK
  • explains the incompatibility when the client export is missing

Full suite: 672 pass. The 3 failures are pre-existing and environmental (no Playwright browser binary locally); they pass in CI.

Closes #40.

`sarvam.ts` destructured `{ default: SarvamAI }` from `sarvamai`, but the
package has no default export and its `SarvamAI` export is a namespace object,
not a constructor. The client class is `SarvamAIClient`:

  default:         undefined
  SarvamAI:        object
  SarvamAIClient:  function

So `new SarvamAI(...)` threw `SarvamAI is not a constructor` for every user
with the package correctly installed. The engine had never worked.

It went unnoticed because the try/catch wraps only the `import()` and reports
any failure as "requires the 'sarvamai' package", while the `new` call sits
outside it — so the real failure surfaced as a bare TypeError at a line that
looks unrelated to the import. A guard now names the missing export instead of
letting a future rename repeat the same confusion.

`sarvamai` becomes a devDependency so the engine is testable at all: vitest
cannot intercept `vi.mock` for a module that does not resolve. #36 declares it
an optional peer, which this does not pre-empt.

Not verified: the `convert()` payload underneath, which needs a live API key.
The tests assert the call shape, not that Sarvam accepts it.

Reported-by: @Joilence
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sarvam TTS engine is broken: default export is undefined, constructor is SarvamAIClient

1 participant