Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/azure-stt-cancellation-recovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@livekit/agents-plugin-azure': minor
---

Add Azure streaming STT with cancellation error recovery.
18 changes: 18 additions & 0 deletions plugins/azure/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!--
SPDX-FileCopyrightText: 2026 LiveKit, Inc.

SPDX-License-Identifier: Apache-2.0
-->

# Azure plugin for LiveKit Agents

The Agents Framework is designed for building realtime, programmable
participants that run on servers. Use it to create conversational, multi-modal
voice agents that can see, hear, and understand.

This package contains the Azure plugin, which allows for speech recognition.
Refer to the [documentation](https://docs.livekit.io/agents/overview/) for
information on how to use it, or browse the [API
reference](https://docs.livekit.io/agents-js/). See the
[repository](https://github.com/livekit/agents-js) for more information about
the framework as a whole.
8 changes: 8 additions & 0 deletions plugins/azure/api-extractor.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Config file for API Extractor. For more info, please visit: https://api-extractor.com
*/
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"extends": "../../api-extractor-shared.json",
"mainEntryPointFilePath": "./dist/index.d.ts"
}
155 changes: 155 additions & 0 deletions plugins/azure/etc/agents-plugin-azure.api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
## API Report File for "@livekit/agents-plugin-azure"

> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).

```ts

import { APIConnectOptions } from '@livekit/agents';
import { AudioBuffer as AudioBuffer_2 } from '@livekit/agents';
import * as speechsdk from 'microsoft-cognitiveservices-speech-sdk';
import { stt } from '@livekit/agents';

// @internal (undocumented)
export interface _CanceledEvent {
// (undocumented)
errorCode?: speechsdk.CancellationErrorCode;
// (undocumented)
errorDetails?: string;
// (undocumented)
reason: speechsdk.CancellationReason;
}

export { speechsdk }

// @public (undocumented)
export class SpeechStream extends stt.SpeechStream {
constructor(stt: STT, opts: STTOptions, connOptions: APIConnectOptions);
// @internal (undocumented)
_cancellationError: _CanceledEvent | null;
// (undocumented)
label: string;
// @internal (undocumented)
_onCanceled(evt: _CanceledEvent): void;
// @internal (undocumented)
_onRecognized(evt: speechsdk.SpeechRecognitionEventArgs): void;
// @internal (undocumented)
_onRecognizing(evt: speechsdk.SpeechRecognitionEventArgs): void;
// @internal (undocumented)
_onSessionStarted(_evt: speechsdk.SessionEventArgs): void;
// @internal (undocumented)
_onSessionStopped(_evt: speechsdk.SessionEventArgs): void;
// @internal (undocumented)
_onSpeechEnd(_evt: speechsdk.RecognitionEventArgs): void;
// @internal (undocumented)
_onSpeechStart(_evt: speechsdk.RecognitionEventArgs): void;
// @internal (undocumented)
_opts: STTOptions;
// @internal (undocumented)
_reconnectEvent: _WaitableEvent;
// (undocumented)
protected run(): Promise<void>;
// @internal (undocumented)
_sessionStartedEvent: _WaitableEvent;
// @internal (undocumented)
_sessionStoppedEvent: _WaitableEvent;
// @internal (undocumented)
_speaking: boolean;
// (undocumented)
updateOptions(opts: STTUpdateOptions): void;
}

// @public (undocumented)
export class STT extends stt.STT {
constructor(opts?: STTConstructorOptions);
// (undocumented)
label: string;
// (undocumented)
get model(): string;
// (undocumented)
get provider(): string;
// (undocumented)
_recognize(_frame: AudioBuffer_2): Promise<stt.SpeechEvent>;
// (undocumented)
stream(options?: {
language?: string;
connOptions?: APIConnectOptions;
}): SpeechStream;
// (undocumented)
updateOptions(opts: STTUpdateOptions): void;
}

// @public (undocumented)
export interface STTConstructorOptions extends STTUpdateOptions {
// (undocumented)
explicitPunctuation?: boolean;
// (undocumented)
numChannels?: number;
// (undocumented)
phraseList?: string[] | null;
// (undocumented)
profanity?: speechsdk.ProfanityOption;
// (undocumented)
sampleRate?: number;
// (undocumented)
speechAuthToken?: string;
// (undocumented)
speechEndpoint?: string;
// (undocumented)
speechHost?: string;
// (undocumented)
speechKey?: string;
// (undocumented)
speechRegion?: string;
// (undocumented)
trueTextPostProcessing?: boolean;
}

// @public (undocumented)
export interface STTOptions {
// (undocumented)
explicitPunctuation: boolean;
// (undocumented)
language: string[];
// (undocumented)
numChannels: number;
// (undocumented)
phraseList?: string[] | null;
// (undocumented)
profanity?: speechsdk.ProfanityOption;
// (undocumented)
sampleRate: number;
// (undocumented)
segmentationMaxTimeMs?: number;
// (undocumented)
segmentationSilenceTimeoutMs?: number;
// (undocumented)
segmentationStrategy?: string;
speechAuthToken?: string;
speechEndpoint?: string;
speechHost?: string;
speechKey?: string;
speechRegion?: string;
// (undocumented)
trueTextPostProcessing: boolean;
}

// @public (undocumented)
export type STTUpdateOptions = Partial<Pick<STTOptions, 'segmentationSilenceTimeoutMs' | 'segmentationMaxTimeMs' | 'segmentationStrategy'>> & {
language?: string | string[];
};

// @internal (undocumented)
export interface _WaitableEvent {
// (undocumented)
clear(): void;
// (undocumented)
isSet: boolean;
// (undocumented)
set(): void;
// (undocumented)
wait(): Promise<void>;
}

// (No @packageDocumentation comment for this package)

```
50 changes: 50 additions & 0 deletions plugins/azure/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "@livekit/agents-plugin-azure",
"version": "1.5.0",
"description": "Azure plugin for LiveKit Agents for Node.js",
"main": "dist/index.js",
"require": "dist/index.cjs",
"types": "dist/index.d.ts",
"exports": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
},
"author": "LiveKit",
"type": "module",
"repository": "git@github.com:livekit/agents-js.git",
"license": "Apache-2.0",
"files": [
"dist",
"src",
"README.md"
],
"scripts": {
"build": "tsup --onSuccess \"pnpm build:types\"",
"build:types": "tsc --declaration --emitDeclarationOnly && node ../../scripts/copyDeclarationOutput.js",
"clean": "rm -rf dist",
"clean:build": "pnpm clean && pnpm build",
"lint": "eslint -f unix \"src/**/*.{ts,js}\"",
"api:check": "api-extractor run --typescript-compiler-folder ../../node_modules/typescript",
"api:update": "api-extractor run --local --typescript-compiler-folder ../../node_modules/typescript --verbose"
},
"devDependencies": {
"@livekit/agents": "workspace:*",
"@livekit/rtc-node": "catalog:",
"@microsoft/api-extractor": "^7.35.0",
"tsup": "^8.3.5",
"typescript": "^5.0.0"
},
"dependencies": {
"microsoft-cognitiveservices-speech-sdk": "^1.50.0"
},
"peerDependencies": {
"@livekit/agents": "workspace:*",
"@livekit/rtc-node": "catalog:"
}
}
18 changes: 18 additions & 0 deletions plugins/azure/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: 2026 LiveKit, Inc.
//
// SPDX-License-Identifier: Apache-2.0
import { Plugin } from '@livekit/agents';

export * from './stt.js';

class AzurePlugin extends Plugin {
constructor() {
super({
title: 'azure',
version: __PACKAGE_VERSION__,
package: __PACKAGE_NAME__,
});
}
}

Plugin.registerPlugin(new AzurePlugin());
62 changes: 62 additions & 0 deletions plugins/azure/src/stt.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2026 LiveKit, Inc.
//
// SPDX-License-Identifier: Apache-2.0
import { describe, expect, it } from 'vitest';
import { SpeechStream, speechsdk } from './stt.js';

function canceledEvent(
reason: speechsdk.CancellationReason,
errorCode?: speechsdk.CancellationErrorCode,
errorDetails = '',
) {
return { reason, errorCode, errorDetails };
}

describe('Azure STT cancellation handling', () => {
it('unblocks run on canceled error', () => {
const stream = SpeechStream.prototype as SpeechStream;
const testStream = Object.create(stream) as SpeechStream;
testStream._sessionStoppedEvent = {
isSet: false,
set() {
this.isSet = true;
},
clear() {
this.isSet = false;
},
wait: () => Promise.resolve(),
} as SpeechStream['_sessionStoppedEvent'];
testStream._cancellationError = null;

const event = canceledEvent(
speechsdk.CancellationReason.Error,
speechsdk.CancellationErrorCode.ServiceTimeout,
'timeout',
);
testStream._onCanceled(event);

expect(testStream._sessionStoppedEvent.isSet).toBe(true);
expect(testStream._cancellationError).toBe(event);
});

it('ignores cancellations without error', () => {
const stream = SpeechStream.prototype as SpeechStream;
const testStream = Object.create(stream) as SpeechStream;
testStream._sessionStoppedEvent = {
isSet: false,
set() {
this.isSet = true;
},
clear() {
this.isSet = false;
},
wait: () => Promise.resolve(),
} as SpeechStream['_sessionStoppedEvent'];
testStream._cancellationError = null;

testStream._onCanceled(canceledEvent(speechsdk.CancellationReason.EndOfStream));

expect(testStream._sessionStoppedEvent.isSet).toBe(false);
expect(testStream._cancellationError).toBeNull();
});
});
Loading
Loading