Skip to content

H-6763: Speak finalized Brunch responses with OpenAI - #9357

Open
kostandinang wants to merge 7 commits into
kostandin/h-6763-openai-realtime-inputfrom
kostandin/h-6763-openai-canonical-speech
Open

H-6763: Speak finalized Brunch responses with OpenAI#9357
kostandinang wants to merge 7 commits into
kostandin/h-6763-openai-realtime-inputfrom
kostandin/h-6763-openai-canonical-speech

Conversation

@kostandinang

@kostandinang kostandinang commented Aug 26, 2026

Copy link
Copy Markdown

🌟 What is the purpose of this PR?

Speak finalized Brunch responses through OpenAI's dedicated Speech API. The exact canonical text already visible in Petrinaut—including a validated pending brunch_ask question—is selected once, sent unchanged for synthesis, and played while the microphone remains closed.

OpenAI Realtime continues to perform transcription only; it never generates assistant responses.

🔗 Related links

🚫 Blocked by

  • #9356 and its downstack prerequisites

🔍 What does this change?

  • Selects only finalized canonical assistant text and schema-validated brunch_ask.input.question values.
    • Reasoning, partial text, tool output, malformed asks, other tools, and non-assistant roles are excluded.
    • Questions come from structured AI SDK parts, never DOM scraping.
    • The real Brunch ask integration test proves an awaiting question is emitted without duplicate visible assistant text.
  • Assigns every speakable segment stable message/part identity and an exact-text fingerprint.
    • The browser sends { segmentId, text }.
    • The server recomputes the fingerprint and rejects any mismatched or malformed request.
  • Adds the app-owned /api/voice/speech edge.
    • It validates same-origin, content type, body size, text length, and segment identity.
    • It sends the exact selected text to OpenAI gpt-4o-mini-tts with server-fixed marin voice and MP3 output.
    • It accepts only audio/mpeg, disables caching, sanitizes upstream errors, and propagates timeouts/client cancellation.
    • It sends no content-changing instruction, “say exactly” prompt, or Realtime response.create event.
  • Adds cancelable, stale-safe browser playback.
    • New generations cancel old fetch/audio work.
    • Audio elements, response streams, blobs, and object URLs are cleaned up on success, failure, cancellation, and disposal.
  • Extends the half-duplex controller so the microphone stays closed throughout synthesis and playback and reopens only after speech drains and Brunch is ready.
  • Keeps canonical text visible if speech fails; there is no generated or alternate speech fallback.
  • Adds accessible synthesis/playback status and the required disclosure: spoken responses use an AI-generated OpenAI voice.
  • Updates ADR-0009, the website README, and the Petrinaut AI-assistant guide. No provider abstraction, package dependency, lockfile change, or ElevenLabs path is added.

Stack record: direct base 7c042206e919b80ef27ce46649bfe5202005b27c; PR head b98ec4772a9702e1cba096e5604e7dc77f46706d.

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

  • does not modify any publishable blocks or libraries, or modifications do not need publishing

📜 Does this require a change to the docs?

The changes in this PR:

  • require changes to docs which are made as part of this PR

No existing documentation screenshot covers this voice surface.

🕸️ Does this require a change to the Turbo Graph?

The changes in this PR:

  • do not affect the execution graph

⚠️ Known issues

🐾 Next steps

Voice PR4 will add delivery caching, authoritative reload reconciliation, stale-ask recovery, authentication, distributed quotas, telemetry, retention cleanup, a kill switch, and real completion/projection evidence. It must not start until the listed platform contracts are available.

🛡 What tests cover this?

  • Canonical-selection tests prove exact finalized assistant/ask selection and exclusion of reasoning, tool output, partial, malformed, and duplicate content.
  • Speech-handler tests prove request bounds, exact-text forwarding, fixed server policy, media-type enforcement, sanitized errors, timeout, disconnect, and stream cancellation.
  • Playback-controller tests prove cancellation, stale-generation rejection, and resource cleanup.
  • Turn-controller/component tests prove queued speech ordering, microphone suppression, failure fallback, disclosure, and accessible status.
  • The Brunch ask integration test proves the structured question reaches Petrinaut without duplicate assistant text.
Validation run
yarn workspace @hashintel/petrinaut test:unit --run
yarn workspace @hashintel/petrinaut lint:eslint
yarn workspace @hashintel/petrinaut lint:tsc
yarn workspace @hashintel/petrinaut build
yarn workspace @apps/petrinaut-website test:unit
yarn workspace @apps/petrinaut-website lint:eslint
yarn workspace @apps/petrinaut-website lint:tsc
yarn workspace @apps/petrinaut-website build
yarn workspace @hashintel/brunch-agent-transport-aisdk test:unit
yarn workspace @hashintel/brunch-agent-transport-aisdk lint:eslint
yarn workspace @hashintel/brunch-agent-transport-aisdk lint:tsc
yarn workspace @hashintel/brunch-agent-transport-aisdk build
yarn workspace @apps/brunch-agent test:unit
yarn workspace @apps/brunch-agent lint:eslint
yarn workspace @apps/brunch-agent lint:tsc
yarn workspace @apps/brunch-agent build
yarn workspace @local/petrinaut-arch-docs lint:arch-docs
yarn oxfmt --check apps/petrinaut-website apps/brunch-agent libs/@hashintel/petrinaut libs/@hashintel/brunch-agent

Results: 214 Petrinaut tests, 72 website tests, 12 transport tests, and 35 Brunch tests passed locally. Lint, typecheck, build, formatting, and architecture-doc checks passed.

The direct-base no-ElevenLabs audit and git diff --check passed. No package manifest or lockfile changed; provider-owned paths are explicitly OpenAI-named.

Credential-dependent checks not run: real OpenAI synthesis, remote Brunch, microphone/browser permission, exact spoken output in desktop Chrome/Edge/Safari, and deployed cancellation/failure behavior.

❓ How to test this?

  1. Run yarn workspace @apps/petrinaut-website test:unit plus its lint, typecheck, and build commands above.
  2. In a protected preview with OpenAI credentials and a real Brunch endpoint, complete one text turn and one voice turn.
  3. Confirm finalized assistant text and a pending structured question are each spoken once and match the visible text exactly.
  4. Cancel/end voice and induce a speech failure; confirm playback stops, the microphone remains safely closed until Brunch is ready, and visible text remains usable.

📹 Demo

Not included: credentialed browser/provider evidence remains an explicit acceptance requirement.

@vercel

vercel Bot commented Aug 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hash Ready Ready Preview Aug 31, 2026 10:41pm
petrinaut Ready Ready Preview Aug 31, 2026 10:41pm
petrinaut-docs Ready Ready Preview Aug 31, 2026 10:41pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hashdotdesign-tokens Ignored Ignored Preview Aug 31, 2026 10:41pm

Request Review

@github-actions github-actions Bot added area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team area/tests New or updated tests area/apps labels Aug 26, 2026

kostandinang commented Aug 26, 2026

Copy link
Copy Markdown
Author

@kostandinang kostandinang changed the title Speak finalized Brunch responses with OpenAI H-6763: Speak finalized Brunch responses with OpenAI Aug 26, 2026
@kostandinang
kostandinang force-pushed the kostandin/h-6763-openai-canonical-speech branch from d4fe337 to 588eff3 Compare August 26, 2026 22:46
@kostandinang
kostandinang force-pushed the kostandin/h-6763-openai-realtime-input branch from cf09c5c to 7c04220 Compare August 26, 2026 23:04
@kostandinang
kostandinang force-pushed the kostandin/h-6763-openai-canonical-speech branch from 588eff3 to b98ec47 Compare August 26, 2026 23:04
@kostandinang kostandinang self-assigned this Aug 26, 2026
@kostandinang
kostandinang force-pushed the kostandin/h-6763-openai-realtime-input branch from 7c04220 to f27bfec Compare August 27, 2026 10:52
@kostandinang
kostandinang force-pushed the kostandin/h-6763-openai-canonical-speech branch from b98ec47 to 7250b21 Compare August 27, 2026 10:52
@kostandinang
kostandinang force-pushed the kostandin/h-6763-openai-canonical-speech branch from 7250b21 to c8c9656 Compare August 27, 2026 11:19
@kostandinang
kostandinang force-pushed the kostandin/h-6763-openai-realtime-input branch from f27bfec to 20c3818 Compare August 27, 2026 11:19
Comment thread apps/petrinaut-website/src/server/voice/openai-speech.ts Outdated
kostandinang and others added 7 commits September 1, 2026 00:21
Select only finalized canonical assistant text and validated brunch_ask questions, then forward the exact fingerprinted text through the app-owned OpenAI Speech edge.

Queue cancelable MP3 playback behind Brunch, keep the microphone closed until speech drains, reject stale generations, disclose the AI-generated voice, and preserve visible text on failure.

Amp-Thread-ID: https://ampcode.com/threads/T-01a03fb3-fd3d-737f-b4c6-1fc9282950bf
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep ready chat updates from reopening the microphone or starting queued speech before the active transcript is finalized.

Co-authored-by: Cursor <cursoragent@cursor.com>
Keep the early transcribing return authoritative so intermediate stack branches type-check without changing runtime behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit aab58fd. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/apps area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team

Development

Successfully merging this pull request may close these issues.

1 participant