Skip to content

Commit c083df0

Browse files
feat: add clientSideNoiseCancellationEnabled option to start() and reconnect() (#195)
* feat: add noiseCancellationEnabled option to start() and reconnect() (#194) Default true, so current callers still get Krisp. reconnect() reuses the value from the last start() unless told otherwise. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs: say noiseCancellationEnabled is client-side only Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * refactor: rename the option to clientSideNoiseCancellationEnabled Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * refactor: skip the audio processing stage when noise cancellation is off With it off, start() and reconnect() no longer report an audio-processing-setup stage that never ran. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs: reconnect option defaults to the last start() or reconnect() value Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 2f278f8 commit c083df0

2 files changed

Lines changed: 250 additions & 102 deletions

File tree

‎__tests__/vapi.test.ts‎

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,115 @@ describe("Vapi local audio level observer", () => {
577577
});
578578
});
579579

580+
describe("Vapi clientSideNoiseCancellationEnabled", () => {
581+
const webCall = {
582+
id: "call_test",
583+
webCallUrl: "https://example.daily.co/test",
584+
};
585+
const noiseCancellation = {
586+
audio: { processor: { type: "noise-cancellation" } },
587+
};
588+
589+
afterEach(() => {
590+
mockDailyCall = null;
591+
});
592+
593+
function startCall(vapi: Vapi, options?: { clientSideNoiseCancellationEnabled?: boolean }) {
594+
return vapi.start(
595+
"dummy_assistant_id",
596+
undefined,
597+
undefined,
598+
undefined,
599+
undefined,
600+
options
601+
);
602+
}
603+
604+
it("turns noise cancellation on by default", async () => {
605+
mockDailyCall = createMockDailyCall(jest.fn().mockResolvedValue(undefined));
606+
const vapi = new Vapi("dummy_token");
607+
608+
await startCall(vapi);
609+
610+
expect(mockDailyCall.updateInputSettings).toHaveBeenCalledWith(
611+
noiseCancellation
612+
);
613+
});
614+
615+
it("skips noise cancellation when start() is told not to", async () => {
616+
mockDailyCall = createMockDailyCall(jest.fn().mockResolvedValue(undefined));
617+
const vapi = new Vapi("dummy_token");
618+
vapi.on("local-volume-level", () => {});
619+
620+
const call = await startCall(vapi, { clientSideNoiseCancellationEnabled: false });
621+
await flushRejections();
622+
623+
expect(call).not.toBeNull();
624+
expect(mockDailyCall.updateInputSettings).not.toHaveBeenCalled();
625+
// Nothing swaps the track, so the observer starts without waiting.
626+
expect(mockDailyCall.startLocalAudioLevelObserver).toHaveBeenCalledWith(100);
627+
});
628+
629+
it("reports no audio processing stage when start() skips it", async () => {
630+
mockDailyCall = createMockDailyCall(jest.fn().mockResolvedValue(undefined));
631+
const vapi = new Vapi("dummy_token");
632+
const stages: string[] = [];
633+
vapi.on("call-start-progress", (event) => {
634+
stages.push(event.stage);
635+
});
636+
637+
await startCall(vapi, { clientSideNoiseCancellationEnabled: false });
638+
639+
expect(stages).not.toContain("audio-processing-setup");
640+
});
641+
642+
it("keeps the value from start() on reconnect()", async () => {
643+
mockDailyCall = createMockDailyCall(jest.fn().mockResolvedValue(undefined));
644+
const vapi = new Vapi("dummy_token");
645+
await startCall(vapi, { clientSideNoiseCancellationEnabled: false });
646+
await vapi.stop();
647+
648+
mockDailyCall = createMockDailyCall(jest.fn().mockResolvedValue(undefined));
649+
await vapi.reconnect(webCall);
650+
651+
expect(mockDailyCall.updateInputSettings).not.toHaveBeenCalled();
652+
});
653+
654+
it("lets reconnect() override the value from start()", async () => {
655+
mockDailyCall = createMockDailyCall(jest.fn().mockResolvedValue(undefined));
656+
const vapi = new Vapi("dummy_token");
657+
await startCall(vapi, { clientSideNoiseCancellationEnabled: false });
658+
await vapi.stop();
659+
660+
mockDailyCall = createMockDailyCall(jest.fn().mockResolvedValue(undefined));
661+
await vapi.reconnect(webCall, { clientSideNoiseCancellationEnabled: true });
662+
663+
expect(mockDailyCall.updateInputSettings).toHaveBeenCalledWith(
664+
noiseCancellation
665+
);
666+
});
667+
668+
it("turns noise cancellation on for reconnect() without a prior start()", async () => {
669+
mockDailyCall = createMockDailyCall(jest.fn().mockResolvedValue(undefined));
670+
const vapi = new Vapi("dummy_token");
671+
672+
await vapi.reconnect(webCall);
673+
674+
expect(mockDailyCall.updateInputSettings).toHaveBeenCalledWith(
675+
noiseCancellation
676+
);
677+
});
678+
679+
it("skips noise cancellation when reconnect() is told not to", async () => {
680+
mockDailyCall = createMockDailyCall(jest.fn().mockResolvedValue(undefined));
681+
const vapi = new Vapi("dummy_token");
682+
683+
await vapi.reconnect(webCall, { clientSideNoiseCancellationEnabled: false });
684+
685+
expect(mockDailyCall.updateInputSettings).not.toHaveBeenCalled();
686+
});
687+
});
688+
580689
// The SDK never starts a recording itself: a token (or Vapi's servers) does,
581690
// and a missing token or a failed server-side start becomes recording-error.
582691
describe("Vapi video recording start", () => {

0 commit comments

Comments
 (0)