Skip to content

feat(react)!: harden lifecycle and expand agent APIs - #23

Merged
GregHolmes merged 9 commits into
deepgram:mainfrom
GregHolmes:gh/agents-0.1.2-react-0.2.0
Sep 10, 2026
Merged

feat(react)!: harden lifecycle and expand agent APIs#23
GregHolmes merged 9 commits into
deepgram:mainfrom
GregHolmes:gh/agents-0.1.2-react-0.2.0

Conversation

@GregHolmes

@GregHolmes GregHolmes commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • align @deepgram/react with published @deepgram/agents 0.1.2 and @deepgram/sdk 5.9
  • expose current Agent events, runtime setting updates, agent-message injection, thinking state, and SDK transport errors through the provider and standalone hook
  • harden start, stop, reconnect, StrictMode, microphone/TTS prop transitions, client-tool ownership, and async function-response lifecycle behavior
  • update examples, API documentation, dependency locks, TypeScript compatibility, linting, and Release Please configuration

Breaking Changes

  • AgentMode now includes "thinking"; exhaustive consumers must handle the new member
  • public context and hook result types add required state and control members

BREAKING CHANGE: AgentMode now includes "thinking" and public context and hook result types add required members.

The breaking conventional title and commit footer intentionally direct Release Please to propose 0.2.0 from 0.1.0.

Lifecycle Safety

  • canceled starts no longer poison or disconnect replacement sessions
  • terminal disconnects stop microphone capture and queued playback
  • microphone disable/re-enable, pending permission, reconnect, and connected-before-resolve races serialize to at most one active startup
  • client-tool results are scoped to the session generation that requested them
  • duplicate dynamic tool registrations clean up by ownership without removing another mounted handler
  • sdk-error is forwarded separately from protocol error

Validation

  • bun install --frozen-lockfile
  • bun run lint
  • bun run typecheck
  • bun run test (64 tests)
  • bun run build
  • packed clean-room consumer typecheck and Vite build
  • ESM and CommonJS import smoke tests
  • resolved dependency chain: React -> Agents 0.1.2 -> SDK 5.9.0

Release Please Handoff

When Release Please opens the 0.2.0 release PR, edit packages/react/CHANGELOG.md to include these release notes:

New APIs

  • Add thinking mode and isThinking to mode state.
  • Add sendAgentMessage with default, queue, and interrupt behavior.
  • Add updateListen, updateThink, updateSpeak, and updatePrompt, plus matching update-confirmation callbacks.
  • Add typed protocol, history, latency, injection-refusal, and SDK transport-error callbacks.

Lifecycle fixes

  • Prevent cancelled starts, reconnects, StrictMode replays, microphone or TTS prop transitions, and async client-tool responses from affecting replacement sessions.
  • Stop microphone capture and queued playback after a terminal disconnect.
  • Report auto-start failures (for example, microphone permission denied) through onSdkError instead of console.error.

Fixes

  • Published type declarations now import from @deepgram/agents instead of a relative sibling path, so agent-derived types resolve in consumer projects (they were any under skipLibCheck and TS2307 without it in 0.1.0).

Breaking changes

  • AgentMode adds "thinking"; exhaustive consumers must handle it.
  • AgentContextValue and public hook results add required state and control members.
  • registerClientTool now returns an unsubscribe function.
  • useDeepgramAgent().start() begins a fresh session and clears conversation.

Corey’s review is the source of the current required follow-ups.

BREAKING CHANGE: AgentMode now includes "thinking" and public context and hook result types add required members.

@dg-coreylweathers dg-coreylweathers left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DevRel review — changes requested, for completeness of the 0.2.0 release rather than code. Gates, semver mechanics, and the live Voice Agent run all pass; the four items under "Should fix before 0.2.0 ships" are docs and changelog gaps on a breaking release, and the four questions at the end need answers before I sign off.

What this PR does
Moves @deepgram/react onto @deepgram/agents 0.1.2 / @deepgram/sdk 5.9.0 and exposes the SDK's newer surface through the provider, the focused hooks, and useDeepgramAgent: a "thinking" mode, sendAgentMessage, updateListen/Think/Speak/Prompt, and seven typed notification callbacks. It also rewrites start/stop/reconnect/StrictMode handling with generation counters so a cancelled start, a microphone failure, or a dropped session cannot leave stale audio running or send a client-tool result into a replacement session, and removes release-as so Release Please proposes 0.2.0 from the feat! commit.

What I checked

  • Do the gates pass with the lockfile frozen? Yes: bun install --frozen-lockfile, Biome lint (0 diagnostics), tsc against the npm @deepgram/agents 0.1.2 with no ../agent checkout, 64/64 tests, vite build + npm pack (7 files), all in a clean node:24 container.
  • Does every type, event, and method this PR re-exports exist in the published agents 0.1.2? Yes; checked its dist/index.d.ts. fn.arguments is the real field on FunctionCallItem in sdk 5.9.0.
  • Is the semver story honest? Yes. The built index.d.ts diff shows exactly the declared breaks (AgentMode gains "thinking"; required members added to AgentContextValue and the hook result types; registerClientTool now returns an unsubscribe function). No export, prop, or default was removed. Two behavior changes are not declared anywhere: useDeepgramAgent().start() now empties conversation, and microphone/tts prop changes now take effect mid-session.
  • Does it work against the live Voice Agent API? Yes. I drove the built provider in Node with microphone={false} tts={false}: connect in 180 ms, SettingsApplied -> listening, sendUserMessage -> user + assistant entries, updatePrompt -> PromptUpdated, updateListen -> onListenUpdated, sendAgentMessage(..., "interrupt") -> injected assistant turn, a live FunctionCallRequest -> onFunctionCall -> reply "4711.", a registerClientTool override -> reply "9090." with the prop handler never called, 16 latency reports, 0 Error/Warning/sdk-error, stop() -> disconnected/idle, restart -> connected with conversation cleared. The standalone hook connected live, then the microphone failure rolled the session back to disconnected / micActive=false / mode="idle".
  • What did not match the README? For a text-injected turn the server sent no AgentThinking and no AgentStartedSpeaking, and with tts={false} the provider skips the audio-based speaking inference, so mode stayed listening through the whole reply (97 audio chunks). Same as 0.1.0, but this PR is the one documenting four modes and adding isThinking.
  • Not live-verified: tts={true} playback, microphone capture, mode transitions on voice turns, StrictMode replay, and reconnect after a dropped socket (browser audio APIs are unavailable in Node; the mocked unit tests cover these).

Should fix before 0.2.0 ships

  • S1 packages/react/README.md "Mode Tracking": state that speaking is inferred from incoming agent audio and requires tts={true}, and that thinking is set only when the server sends AgentThinking (not observed on sendUserMessage text turns).
  • S2 The generated 0.2.0 changelog will be one Features line plus the BREAKING CHANGE footer. Edit the Release Please PR (or the squash-commit body) to list each new API, each lifecycle fix, and the full breaking list including the registerClientTool return type and useDeepgramAgent().start() clearing conversation.
  • S3 onListenUpdated exists but onPromptUpdated / onSpeakUpdated / onThinkUpdated do not, although updatePrompt/Speak/Think and their message types are exported. Either add the three callbacks (additive) or document that confirmations come from useAgentSession().on(...).
  • S4 Document in the standalone-hook section that start() begins a fresh session and clears conversation.

Nits

  • README.md:56 MicrophoneOptions has no VAD option; list the real fields (sampleRate, echoCancellation, noiseSuppression, autoGainControl).
  • README.md:123 name the behavior values: "default" | "queue" | "interrupt".
  • README.md:62 onSdkError also receives microphone start failures after connect (provider.tsx:365, :381).
  • hooks/useAgentSession.ts:6 JSDoc still lists updatePrompt as an escape-hatch use; it is now on useAgentControls.
  • ci.yml:19 / npm-publish.yml:45 bun install is unfrozen; add --frozen-lockfile (it passes).
  • README.md:86-93 / CONTRIBUTING.md:8 the ../agent sibling reads as required; tsconfig paths falls back to npm when it is absent, so mark it optional.
  • package.json:54 TypeScript pinned back 6.0.2 -> 5.9.3 with no stated reason; Dependabot will propose 6.x again (PR #9 already did).

Questions

  • Where does the "independent DevRel re-review: approve" in the PR body live? Nothing is on GitHub and I could not find a Slack thread.
  • Is "no AgentThinking / AgentStartedSpeaking on text-injected turns" expected server behavior? It decides the exact S1 wording.
  • Add the three *Updated callbacks in this release, or defer?
  • Why the TypeScript downgrade?

@GregHolmes

Copy link
Copy Markdown
Contributor Author

@dg-coreylweathers Re-review requested.

Addressed the should-fix items and nits:

  • documented server-driven thinking/speaking behavior, standalone start() conversation clearing, microphone options, message behaviors, SDK-error scope, and optional sibling checkout
  • added onPromptUpdated, onSpeakUpdated, and onThinkUpdated callbacks to the provider and standalone hook, with tests
  • froze CI and publish installs
  • documented the TypeScript 5.9.3/API Extractor compatibility constraint and configured Dependabot to avoid unsupported 6.x updates
  • added 0.2.0 Release Please changelog handoff to the PR description and removed the unsupported prior re-review claim

A fresh independent DevRel review found and we fixed auto-start connection failures not reaching onSdkError (4525294). It then returned Approve with no findings. Verified with frozen install, lint, typecheck, 66 tests, production build, package checks, ESM/CJS smoke tests, and git diff --check.

@dg-coreylweathers dg-coreylweathers left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DevRel re-review — changes requested, for one new finding, not for anything from the 09-04 list. Every should-fix (S1–S4) and every nit from 09-04 is addressed in 42cc340; S2 is partial only because the changelog handoff does not yet mention the 4525294 behavior change. The blocker is in the type declarations the package publishes, and it predates this PR.

What this PR does
Moves @deepgram/react onto @deepgram/agents 0.1.2 / @deepgram/sdk 5.9.0 and exposes the newer surface (a "thinking" mode, sendAgentMessage, updateListen/Think/Speak/Prompt, typed notification callbacks — now including onPromptUpdated, onSpeakUpdated, onThinkUpdated), and rewrites start/stop/reconnect/StrictMode handling with generation counters. 4525294 routes a failure during autoStart to onSdkError instead of the console.

What I checked

  • Do the 09-04 items land? Yes. README now says speaking is inferred from incoming audio only with tts={true} and thinking only when the server sends AgentThinking (matches the code at provider.tsx:414-417 and :447-449 and what I saw live on 09-04); standalone start() clearing conversation is documented; the three callbacks are on both AgentProviderProps and UseDeepgramAgentOptions in the built index.d.ts; MicrophoneOptions fields and behavior values match agents 0.1.2 / sdk 5.9.0; CI and publish use --frozen-lockfile; the sibling checkout reads as optional; the TypeScript 5.9.3 pin is explained and API Extractor 7.58.7 really does pin 5.9.3; Dependabot ignores TypeScript majors.
  • Do the gates pass? Yes: frozen install, lint, typecheck, 66/66 tests, build, in a oven/bun:1.3.13 container. The two new tests are the standalone-hook callback test and the auto-start failure test.
  • Does 4525294 keep a cancelled start from touching a replacement session? Yes. I ran four extra tests: a cancelled auto-start whose connect later fails never calls onSdkError and never disconnects the replacement; the same after unmount; under StrictMode it fires exactly once; a manual start() failure comes back as a rejected promise and not through onSdkError.
  • Does the published package carry the types it advertises? No — see the blocker.

What to fix

  • [B1] Published type declarations import @deepgram/agents types from a path that exists only next to a maintainer's checkout. dist/index.d.ts has 42 imports of '../../../../agent/packages/sdk/dist/index' instead of '@deepgram/agents', because vite-plugin-dts rewrites the tsconfig paths mapping into relative imports by default. Expected: a developer installing the package gets config typed as AgentSessionConfig and each callback's message typed. Observed in a clean-room consumer: with skipLibCheck on (the Vite default) const a: ListenUpdatedMessage = 42 compiles — every agent-derived type has become any; with it off, six TS2307: Cannot find module errors inside node_modules/@deepgram/react. The 0.1.0 tarball on npm already has 16 of these, so this is not new to the PR, but 0.2.0 would ship 42. Fix, verified by rebuilding: packages/react/vite.config.ts:25dts({ rollupTypes: true, pathsToAliases: false }) gives 0 sibling paths and 33 from '@deepgram/agents' imports (grep -c "agent/packages/sdk" packages/react/dist/index.d.ts prints 0). Please confirm the same output on a machine where ../agent is present, and add a changelog line for it.
  • [S5] The auto-start test rejects connect(), but agents 0.1.2 never rejects connect() on a connection failure (it emits sdk-error and reconnects), so the path that actually reaches the new catch is a microphone failure during auto-start. Add a case with failNextMicrophoneStart + autoStart asserting one onSdkError call and the rolled-back state.
  • [N1] README:67 — add that a manual start() call rejects instead of calling onSdkError.
  • [N2] Changelog handoff: add "Report auto-start failures (for example, microphone permission denied) through onSdkError instead of console.error." under Lifecycle fixes.

Questions

  • Is the absence of AgentThinking / AgentStartedSpeaking on text-injected turns expected server behavior? (Carried from 09-04; the README hedge is fine either way.)
  • The comment says a fresh independent DevRel review returned Approve with no findings — where does it live? Nothing on GitHub carries it.

@dg-coreylweathers dg-coreylweathers left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DevRel re-review — approving. Every item from the 09-07 round is fixed and verified; three optional nits remain.

What this PR does
Moves @deepgram/react onto @deepgram/agents 0.1.2 / @deepgram/sdk 5.9.0, exposes the newer surface (a "thinking" mode, sendAgentMessage, updateListen/Think/Speak/Prompt, typed notification callbacks including onPromptUpdated / onSpeakUpdated / onThinkUpdated), and rewrites start/stop/reconnect/StrictMode handling with generation counters. The six commits since 09-07 make the published type declarations portable, settle what happens when a start fails, and document it.

What I checked

  • Do consumers get real types now? Yes. The built dist/index.d.ts has zero imports from the ../agent sibling path and 33 from @deepgram/agents. In a clean-room project (packed tarball, agents 0.1.2, TypeScript 5.9.3) assigning 42 to a ListenUpdated message or 12345 to config now fails with the real type names, and there are zero "Cannot find module" errors from this package with skipLibCheck off. On 09-07 the same test compiled silently (every borrowed type was any) and produced six such errors.
  • Was there a double onSdkError call? Yes, and it is fixed. At the previous head a microphone failure during autoStart called onSdkError twice — once from the connected handler, once from start()'s rethrow. The microphone-failure test asked for on 09-07 is what caught it; d0309c4 adds an in-progress guard. I ran seven extra lifecycle tests: a cancelled auto-start never reports or touches its replacement; after unmount nothing fires; under StrictMode a failure (connection or microphone) fires exactly once; a manual start() failure (connection or microphone) rejects, rolls back to disconnected / micActive=false / mode="idle", and never calls onSdkError; a reconnect after a completed start still restarts the microphone.
  • Does the README describe that contract? Yes for the provider: onSdkError is "SDK transport or automatic-start failure" and start "rejects on failure and does not call onSdkError"; the quickstarts and the example wrap start() in try/catch.
  • Gates: frozen install, lint, typecheck, 67/67 tests (was 66), build, git diff --check, all green in a oven/bun:1.3.13 container; CI green. No connect or message code path changed, so no live Voice Agent rerun.

Optional nits

  • packages/react/README.md:230 — the standalone start() line should carry the same "rejects on failure and does not call onSdkError" note; the hook behaves identically.
  • Changelog handoff — broaden the Lifecycle line: "Report automatic start failures (auto-start, microphone re-enable, reconnect) through onSdkError, or console.error when no callback is set; manual start() rejects instead." (76fa954 also turns previously silent re-enable/reconnect microphone failures into console.error.)
  • Reconnect test — restore expect(microphones).toHaveLength(2) after the waitFor so a third microphone would still fail it.

Still open, not blocking

  • Confirm on a machine with ../agent present that the build still emits from '@deepgram/agents' (CI builds without the sibling, so the npm artifact is right either way).
  • Is the absence of AgentThinking / AgentStartedSpeaking on text-injected turns expected server behavior?
  • At release time I will paste the four "Release Please Handoff" sections into the release PR's packages/react/CHANGELOG.md; Release Please copies only the title and BREAKING CHANGE: footer.

@GregHolmes
GregHolmes merged commit 2312d4e into deepgram:main Sep 10, 2026
1 check passed
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.

2 participants