refactor(voice): dynamic-import @picovoice/cobra-node, move to optionalDependencies - #971
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
✅ Single Commit Policy - COMPLIANTStatus: Policy requirements met • 1 commit • Valid format • Ready for merge 📊 View validation details📝 Commit Details
✅ Validation Results
🤖 Automated validation by NeuroLink Single Commit Enforcement |
🤖 AI Review & Build Compliance ✅Status: AI analysis complete • Build rules validated • Ready for review 📊 View detailed analysis results🛡️ Analysis Complete
📋 Ready for Merge When
🤖 AI analysis complete - check individual code comments for specific feedback |
WalkthroughThe Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR makes @picovoice/cobra-node (native voice activity detection) optional by switching to a dynamic import at WebSocket connection time and moving the dependency to optionalDependencies, so non-voice users aren’t blocked by native install/build requirements.
Changes:
- Replaced the static Cobra import with a dynamic
import("@picovoice/cobra-node")inside the WebSocket connection handler. - Moved
@picovoice/cobra-nodefromdependenciestooptionalDependencies. - Updated the lockfile to mark
@picovoice/cobra-nodeas optional.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/lib/server/voice/voiceWebSocketHandler.ts | Dynamically loads Cobra per WS connection and provides an install hint when missing. |
| package.json | Moves @picovoice/cobra-node into optionalDependencies. |
| pnpm-lock.yaml | Updates dependency placement and marks Cobra as optional in snapshots. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let CobraClass: typeof Cobra; | ||
| try { | ||
| ({ Cobra: CobraClass } = await import("@picovoice/cobra-node")); |
There was a problem hiding this comment.
Cobra is imported using import type, but the code uses typeof Cobra in a value-type query (let CobraClass: typeof Cobra;). TypeScript requires a value symbol for typeof, so this won’t type-check. Consider removing the import type and instead defining local structural types for the Cobra instance/constructor (so the file can compile even when the optional dep isn’t installed), or use a different constructor type that doesn’t rely on typeof a type-only import.
| wss.on("connection", async (clientWs) => { | ||
| logger.info("[WS] Client connected"); |
There was a problem hiding this comment.
The ws "connection" listener is now async, but WebSocketServer/EventEmitter won’t await or handle the returned promise. Any synchronous throw before the first await (or any later rejected await added in the future) will become an unhandled promise rejection. To avoid this, keep the listener non-async and invoke an async initializer with an explicit .catch(...) (closing the socket/logging on failure).
a452fb4 to
5eb368a
Compare
🤖 AI Review & Build Compliance ✅Status: AI analysis complete • Build rules validated • Ready for review 📊 View detailed analysis results🛡️ Analysis Complete
📋 Ready for Merge When
🤖 AI analysis complete - check individual code comments for specific feedback |
5eb368a to
27d3495
Compare
🤖 AI Review & Build Compliance ✅Status: AI analysis complete • Build rules validated • Ready for review 📊 View detailed analysis results🛡️ Analysis Complete
📋 Ready for Merge When
🤖 AI analysis complete - check individual code comments for specific feedback |
…alDependencies Voice activity detection (VAD) via Cobra is a niche feature with native bindings. Convert static import to dynamic inside the WebSocket handler init. Clear install hint on failure.
27d3495 to
36905e5
Compare
🤖 AI Review & Build Compliance ✅Status: AI analysis complete • Build rules validated • Ready for review 📊 View detailed analysis results🛡️ Analysis Complete
📋 Ready for Merge When
🤖 AI analysis complete - check individual code comments for specific feedback |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
package.json (1)
271-275: Verify if@picovoice/cobra-nodeactually skips during install.In pnpm 10,
optionalDependenciesare installed by default duringpnpm install—they are only skipped with the--no-optionalflag. If the goal is to avoid compiling the native Cobra package unless explicitly opted in, usepeerDependencieswithpeerDependenciesMetainstead:{ "peerDependencies": { "@picovoice/cobra-node": "^3.0.2" }, "peerDependenciesMeta": { "@picovoice/cobra-node": { "optional": true } } }Users then add it to their own
package.jsonto opt in. The current placement inoptionalDependencieswill not prevent installation/compilation by default.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` around lines 271 - 275, The package.json currently lists "@picovoice/cobra-node" under optionalDependencies which pnpm installs by default; move it to peerDependencies and mark it optional via peerDependenciesMeta so consumers must opt-in and the native package isn't compiled automatically—update package.json to remove "@picovoice/cobra-node" from optionalDependencies, add it to peerDependencies with the same version spec, and add a peerDependenciesMeta entry for "@picovoice/cobra-node" with "optional": true.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/lib/server/voice/voiceWebSocketHandler.ts`:
- Around line 153-189: Attach the client's disconnect/cleanup handler on
clientWs before performing the async Cobra import/initialization so that if the
socket closes during await the cleanup will run and prevent later code (e.g.,
Soniox setup) from operating on a closed socket; specifically, move or add the
clientWs.on("close", ...) cleanup registration to immediately after the
connection handler starts and before the block that imports/instantiates Cobra
(the section that sets cobra, FRAME_LENGTH, FRAME_BYTES and calls new
mod.Cobra(accessKey)), and ensure the cleanup logic checks/clears the cobra
variable and any downstream resources so later awaits (and the Soniox setup
referenced around the Soniox-related code) short-circuit when the socket is
closed.
- Around line 591-607: The message handler currently checks typeof data ===
"string" which never occurs; change the clientWs.on("message", ...) callback to
accept the isBinary parameter and use it to distinguish control vs audio frames:
treat non-binary frames as control messages by decoding the incoming
Buffer/ArrayBuffer to a string and passing it to parseClientControlMessage (so
playback_done triggers the existing playbackResetTimer clearing and
turnManager.reset() immediately), and treat binary frames as audio as before;
update references around parseClientControlMessage, playbackResetTimer, and
turnManager.reset to use the new branching and return early after handling
control messages.
---
Nitpick comments:
In `@package.json`:
- Around line 271-275: The package.json currently lists "@picovoice/cobra-node"
under optionalDependencies which pnpm installs by default; move it to
peerDependencies and mark it optional via peerDependenciesMeta so consumers must
opt-in and the native package isn't compiled automatically—update package.json
to remove "@picovoice/cobra-node" from optionalDependencies, add it to
peerDependencies with the same version spec, and add a peerDependenciesMeta
entry for "@picovoice/cobra-node" with "optional": true.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 03b1327c-2df0-4741-be14-fcf228d5edc4
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (3)
package.jsonsrc/lib/server/voice/voiceWebSocketHandler.tssrc/lib/types/server.ts
| wss.on("connection", (clientWs) => { | ||
| logger.info("[WS] Client connected"); | ||
|
|
||
| // --- Per-session Cobra instance --- | ||
| let cobra: Cobra | null = null; | ||
| let FRAME_LENGTH = 512; | ||
| let FRAME_BYTES = FRAME_LENGTH * 2; | ||
| try { | ||
| cobra = new Cobra(accessKey); | ||
| FRAME_LENGTH = cobra.frameLength; | ||
| FRAME_BYTES = FRAME_LENGTH * 2; | ||
| logger.info(`[VAD] Cobra ready (frameLength=${FRAME_LENGTH})`); | ||
| } catch (err) { | ||
| logger.error("[VAD] Cobra init failed:", err); | ||
| clientWs.close(); | ||
| return; | ||
| } | ||
|
|
||
| // --- Per-session state --- | ||
| const bus = new FrameBus(); | ||
| const turnManager = new TurnManager(bus); | ||
|
|
||
| let sonioxWs: WebSocket | null = null; | ||
| let keepAliveTimer: NodeJS.Timeout | null = null; | ||
|
|
||
| let sessionClosed = false; | ||
| let transcriptBuffer = ""; | ||
| let activeTTS: CartesiaStream | null = null; | ||
| const conversation: ConversationMessage[] = []; | ||
| let currentTurnId = 0; | ||
| let activePipelineTurnId: number | null = null; | ||
| // Safety fallback: if the client never sends playback_done (crash, network drop), | ||
| // auto-reset the turn state after this many ms so the assistant isn't stuck. | ||
| let playbackResetTimer: NodeJS.Timeout | null = null; | ||
| // Timestamp (ms) before which barge-in via Soniox is suppressed. | ||
| // Set when TTS starts playing to prevent TTS echo from triggering immediate re-interrupt. | ||
| // AEC on the browser needs ~300-400ms to characterise the echo signal before suppressing it. | ||
| let bargeInLockedUntil = 0; | ||
|
|
||
| // Cobra VAD state | ||
| let isSpeaking = false; | ||
| let silenceFrameCount = 0; | ||
| let voiceFrameCount = 0; | ||
| let frameRemainder = Buffer.alloc(0); | ||
|
|
||
| /* ======= INTERRUPT ======= */ | ||
|
|
||
| function closeTts(stream: CartesiaStream | null, reason: string) { | ||
| if (!stream) { | ||
| return; | ||
| } | ||
| void (async () => { | ||
| logger.info("[WS] Client connected"); | ||
|
|
||
| // --- Per-session Cobra instance --- | ||
| let cobra: CobraInstance | null = null; | ||
| let FRAME_LENGTH = 512; | ||
| let FRAME_BYTES = FRAME_LENGTH * 2; | ||
| try { | ||
| // Close the WS first so that any pending done/error/close listeners | ||
| // in processTurn() can settle immediately, rather than hanging until | ||
| // the withTimeout fires. | ||
| stream.close(); | ||
| stream.removeAllListeners(); | ||
| } catch (error) { | ||
| logger.warn(reason, error); | ||
| } | ||
| } | ||
|
|
||
| function doInterrupt() { | ||
| logger.info("[INTERRUPT] Cutting TTS"); | ||
| if (playbackResetTimer) { | ||
| clearTimeout(playbackResetTimer); | ||
| playbackResetTimer = null; | ||
| } | ||
| bargeInLockedUntil = 0; | ||
| currentTurnId++; | ||
| activePipelineTurnId = null; | ||
| transcriptBuffer = ""; | ||
| isSpeaking = false; | ||
| silenceFrameCount = 0; | ||
| voiceFrameCount = 0; | ||
| if (activeTTS) { | ||
| closeTts(activeTTS, "[INTERRUPT] Failed to close active TTS stream"); | ||
| activeTTS = null; | ||
| } | ||
| turnManager.reset(); | ||
| if (clientWs.readyState === WebSocket.OPEN) { | ||
| clientWs.send(JSON.stringify({ type: "interrupt" })); | ||
| } | ||
| } | ||
|
|
||
| /* ======= SONIOX ======= */ | ||
|
|
||
| function connectSoniox() { | ||
| const ws = new WebSocket(SONIOX_URL); | ||
| sonioxWs = ws; | ||
|
|
||
| ws.on("open", () => { | ||
| logger.info("[SONIOX] Connected"); | ||
| ws.send( | ||
| JSON.stringify({ | ||
| api_key: getSonioxApiKey(), | ||
| model: "stt-rt-preview", | ||
| audio_format: "auto", | ||
| language_hints: ["en"], | ||
| enable_endpoint_detection: true, | ||
| }), | ||
| ); | ||
| ws.send(makeWavHeader(16000, 1)); | ||
| startKeepAlive(); | ||
| }); | ||
|
|
||
| ws.on("message", handleSonioxMessage); | ||
| ws.on("close", (code, reason) => { | ||
| logger.info( | ||
| `[SONIOX] Closed: code=${code} reason=${reason.toString() || "(none)"}`, | ||
| ); | ||
| stopKeepAlive(); | ||
| if (!sessionClosed) { | ||
| setTimeout(() => { | ||
| connectSoniox(); | ||
| }, 500); | ||
| } | ||
| }); | ||
| ws.on("error", (err) => { | ||
| logger.error("[SONIOX] Error:", err.message); | ||
| }); | ||
| } | ||
|
|
||
| function startKeepAlive() { | ||
| keepAliveTimer = setInterval(() => { | ||
| if (sonioxWs?.readyState === WebSocket.OPEN) { | ||
| sonioxWs.send(JSON.stringify({ type: "keepalive" })); | ||
| } | ||
| }, 8000); | ||
| } | ||
|
|
||
| function stopKeepAlive() { | ||
| if (keepAliveTimer) { | ||
| clearInterval(keepAliveTimer); | ||
| keepAliveTimer = null; | ||
| } | ||
| } | ||
|
|
||
| /* ======= STT HANDLER ======= */ | ||
|
|
||
| async function handleSonioxMessage(msg: WebSocket.RawData) { | ||
| const data = parseSonioxMessage(msg); | ||
| if (!data) { | ||
| return; | ||
| } | ||
|
|
||
| if (!Array.isArray(data.tokens)) { | ||
| if (data.error || data.status || data.type) { | ||
| if (logger.shouldLog("debug")) { | ||
| logger.info("[SONIOX] msg:", JSON.stringify(data)); | ||
| let mod: { Cobra: new (key: string) => CobraInstance }; | ||
| try { | ||
| mod = (await import( | ||
| /* @vite-ignore */ "@picovoice/cobra-node" | ||
| )) as typeof mod; | ||
| } catch (err) { | ||
| const e = | ||
| err instanceof Error ? (err as NodeJS.ErrnoException) : null; | ||
| if ( | ||
| e?.code === "ERR_MODULE_NOT_FOUND" && | ||
| e.message.includes("cobra-node") | ||
| ) { | ||
| throw new Error( | ||
| 'Voice activity detection requires "@picovoice/cobra-node". Install it with:\n pnpm add @picovoice/cobra-node', | ||
| { cause: err }, | ||
| ); | ||
| } | ||
| throw err; | ||
| } | ||
| cobra = new mod.Cobra(accessKey); | ||
| FRAME_LENGTH = cobra.frameLength; | ||
| FRAME_BYTES = FRAME_LENGTH * 2; | ||
| logger.info(`[VAD] Cobra ready (frameLength=${FRAME_LENGTH})`); | ||
| } catch (err) { | ||
| logger.error("[VAD] Cobra init failed:", err); | ||
| clientWs.close(); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Register disconnect cleanup before awaiting Cobra initialization.
The first await import("@picovoice/cobra-node") happens before the close handler is attached. If the client disconnects during that await/native initialization, the later cleanup at Line 666 never fires, but the handler can still continue and open Soniox for a closed client.
🔧 Proposed fix outline
wss.on("connection", (clientWs) => {
void (async () => {
logger.info("[WS] Client connected");
// --- Per-session Cobra instance ---
let cobra: CobraInstance | null = null;
+ let sessionClosed = false;
+ let sonioxWs: WebSocket | null = null;
+ let keepAliveTimer: NodeJS.Timeout | null = null;
+ let activeTTS: CartesiaStream | null = null;
+ let playbackResetTimer: NodeJS.Timeout | null = null;
+
+ const cleanupSession = () => {
+ logger.info("[WS] Client disconnected");
+ sessionClosed = true;
+ if (playbackResetTimer) {
+ clearTimeout(playbackResetTimer);
+ playbackResetTimer = null;
+ }
+ if (cobra) {
+ cobra.release();
+ cobra = null;
+ }
+ closeTts(activeTTS, "[WS] Failed to close active TTS on disconnect");
+ stopKeepAlive();
+ if (sonioxWs) {
+ sonioxWs.close();
+ }
+ };
+
+ clientWs.once("close", cleanupSession);
let FRAME_LENGTH = 512;
let FRAME_BYTES = FRAME_LENGTH * 2;
try {
let mod: { Cobra: new (key: string) => CobraInstance };
try {
@@
}
cobra = new mod.Cobra(accessKey);
+ if (sessionClosed || clientWs.readyState !== WebSocket.OPEN) {
+ cobra.release();
+ cobra = null;
+ return;
+ }
FRAME_LENGTH = cobra.frameLength;
FRAME_BYTES = FRAME_LENGTH * 2;
logger.info(`[VAD] Cobra ready (frameLength=${FRAME_LENGTH})`);
} catch (err) {
logger.error("[VAD] Cobra init failed:", err);
@@
- let sonioxWs: WebSocket | null = null;
- let keepAliveTimer: NodeJS.Timeout | null = null;
-
- let sessionClosed = false;
+ // sessionClosed, sonioxWs, keepAliveTimer, activeTTS, and playbackResetTimer
+ // are declared before Cobra initialization so early disconnects are cleaned up.Also applies to: 666-677
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/server/voice/voiceWebSocketHandler.ts` around lines 153 - 189, Attach
the client's disconnect/cleanup handler on clientWs before performing the async
Cobra import/initialization so that if the socket closes during await the
cleanup will run and prevent later code (e.g., Soniox setup) from operating on a
closed socket; specifically, move or add the clientWs.on("close", ...) cleanup
registration to immediately after the connection handler starts and before the
block that imports/instantiates Cobra (the section that sets cobra,
FRAME_LENGTH, FRAME_BYTES and calls new mod.Cobra(accessKey)), and ensure the
cleanup logic checks/clears the cobra variable and any downstream resources so
later awaits (and the Soniox setup referenced around the Soniox-related code)
short-circuit when the socket is closed.
| clientWs.on("message", (data) => { | ||
| if (typeof data === "string") { | ||
| const msg = parseClientControlMessage(data); | ||
| if (msg?.type === "playback_done") { | ||
| // Client finished playing all audio — now it's safe to listen again. | ||
| if (playbackResetTimer) { | ||
| clearTimeout(playbackResetTimer); | ||
| playbackResetTimer = null; | ||
| } | ||
| turnManager.reset(); | ||
| } | ||
| return; | ||
| } | ||
| pos += FRAME_BYTES; | ||
|
|
||
| // Cobra VAD: | ||
| // Cobra tracks when the user is speaking vs silent. Its output drives | ||
| // TurnManager state (USER_SPEAKING / PROCESSING) but does NOT trigger | ||
| // interrupt — that comes from Soniox non-final tokens so echo can't fool it. | ||
| let voiceProb = 0; | ||
| try { | ||
| if (!cobra) { | ||
| continue; | ||
| } | ||
| voiceProb = cobra.process(frame); | ||
| } catch (err) { | ||
| logger.error("[VAD] Cobra process error:", err); | ||
| if (!(data instanceof Buffer)) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Verify the ws v8 TypeScript message callback shape.
# Expectation: `@types/ws`@8.18.1 exposes a message listener with RawData and isBinary.
curl -fsSL https://unpkg.com/@types/ws@8.18.1/index.d.ts \
| rg -n -C3 'type RawData|event: "message"|isBinary'Repository: juspay/neurolink
Length of output: 3641
🏁 Script executed:
#!/bin/bash
# Check the current implementation in voiceWebSocketHandler.ts
head -n 610 src/lib/server/voice/voiceWebSocketHandler.ts | tail -n 25Repository: juspay/neurolink
Length of output: 809
🏁 Script executed:
#!/bin/bash
# Find and inspect parseClientControlMessage function
fd -e ts -e tsx | xargs grep -l "parseClientControlMessage" | head -5Repository: juspay/neurolink
Length of output: 105
🏁 Script executed:
#!/bin/bash
# Get more context around the message handler (lines 580-620)
wc -l src/lib/server/voice/voiceWebSocketHandler.tsRepository: juspay/neurolink
Length of output: 109
🏁 Script executed:
#!/bin/bash
# Find parseClientControlMessage definition
rg -n "function parseClientControlMessage|const parseClientControlMessage|export.*parseClientControlMessage" -A 5Repository: juspay/neurolink
Length of output: 602
🏁 Script executed:
#!/bin/bash
# Check what type parseClientControlMessage expects
rg -B 2 -A 10 "parseClientControlMessage.*=.*\(" src/lib/server/voice/Repository: juspay/neurolink
Length of output: 42
🏁 Script executed:
#!/bin/bash
# Check the actual message handler to see how it currently processes control messages
sed -n '590,610p' src/lib/server/voice/voiceWebSocketHandler.tsRepository: juspay/neurolink
Length of output: 739
🏁 Script executed:
#!/bin/bash
# Verify if there's any fallback mechanism for playback_done
rg -n "playback_done|playbackResetTimer" src/lib/server/voice/voiceWebSocketHandler.tsRepository: juspay/neurolink
Length of output: 907
🏁 Script executed:
#!/bin/bash
# Check the exact RawData definition and whether string is in the union
cat -n src/lib/server/voice/voiceWebSocketHandler.ts | sed -n '1,100p' | grep -A 5 "RawData\|import.*ws"Repository: juspay/neurolink
Length of output: 716
🏁 Script executed:
#!/bin/bash
# Check how parseSonioxMessage is called and if it shows the correct pattern
rg -B 3 -A 3 "parseSonioxMessage" src/lib/server/voice/voiceWebSocketHandler.ts | head -30Repository: juspay/neurolink
Length of output: 474
🏁 Script executed:
#!/bin/bash
# Verify RawData usage patterns elsewhere in the codebase
rg -n "RawData|\.toString()" src/lib/server/voice/ | head -20Repository: juspay/neurolink
Length of output: 548
🏁 Script executed:
#!/bin/bash
# Check the exact signature of the message handler - does it accept isBinary?
sed -n '589,595p' src/lib/server/voice/voiceWebSocketHandler.tsRepository: juspay/neurolink
Length of output: 376
🏁 Script executed:
#!/bin/bash
# Double-check: RawData type definition and confirm string is NOT part of it
curl -fsSL https://unpkg.com/@types/ws@8.18.1/index.d.ts | sed -n '200,210p'Repository: juspay/neurolink
Length of output: 381
Use isBinary parameter to properly route control messages from audio frames.
The typeof data === "string" check cannot succeed because ws library's RawData type is Buffer | ArrayBuffer | Buffer[] (never string). When the client sends playback_done as a text frame, it arrives as a Buffer, fails the string check, and gets processed as audio data instead of a control message. This delays the turn manager reset by up to 20 seconds until the fallback timer triggers. Use the isBinary parameter (second argument to the message callback) to properly discriminate: non-binary frames are control messages (decode them to string), binary frames are audio.
🔧 Proposed fix
- clientWs.on("message", (data) => {
- if (typeof data === "string") {
- const msg = parseClientControlMessage(data);
+ clientWs.on("message", (data, isBinary) => {
+ if (!isBinary) {
+ const text =
+ typeof data === "string"
+ ? data
+ : Buffer.isBuffer(data)
+ ? data.toString("utf8")
+ : data instanceof ArrayBuffer
+ ? Buffer.from(data).toString("utf8")
+ : Buffer.concat(data).toString("utf8");
+
+ const msg = parseClientControlMessage(text);
if (msg?.type === "playback_done") {
// Client finished playing all audio — now it's safe to listen again.
if (playbackResetTimer) {
clearTimeout(playbackResetTimer);
playbackResetTimer = null;
@@
return;
}
- if (!(data instanceof Buffer)) {
- return;
- }
+ const audioData = Buffer.isBuffer(data)
+ ? data
+ : data instanceof ArrayBuffer
+ ? Buffer.from(data)
+ : Buffer.concat(data);
// Reassemble into exact FRAME_BYTES-sized Cobra frames.
- const combined = Buffer.concat([frameRemainder, data]);
+ const combined = Buffer.concat([frameRemainder, audioData]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/server/voice/voiceWebSocketHandler.ts` around lines 591 - 607, The
message handler currently checks typeof data === "string" which never occurs;
change the clientWs.on("message", ...) callback to accept the isBinary parameter
and use it to distinguish control vs audio frames: treat non-binary frames as
control messages by decoding the incoming Buffer/ArrayBuffer to a string and
passing it to parseClientControlMessage (so playback_done triggers the existing
playbackResetTimer clearing and turnManager.reset() immediately), and treat
binary frames as audio as before; update references around
parseClientControlMessage, playbackResetTimer, and turnManager.reset to use the
new branching and return early after handling control messages.
|
🎉 This PR is included in version 9.55.4 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Summary
@picovoice/cobra-nodeinside WebSocket connection handlerdependenciestooptionalDependenciesImpact
Test plan
pnpm run buildsucceedspnpm run checkpassesSummary by CodeRabbit
Bug Fixes
Chores