diff --git a/CHANGELOG.md b/CHANGELOG.md index afb1bfb..b33a272 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [4.42.1] + +- Add `action_items` to the speech understanding feature requests and responses (`ActionItemsRequest`, `ActionItem`, `ActionItemsResponse`): `include_decisions` and `effort`. The feature was already accepted by the API and returned in transcripts, but was missing from the SDK types + ## [4.42.0] - Add Text-to-Speech: `client.tts.synthesize(request)` returns one text's complete audio (`TextToSpeechResponse`: `audio` plus the response headers as `requestId`, `region`, `model`, `voiceId`, `encoding`, `sampleRate`, `billedCharacters`, `audioDurationMs`, ...), `client.tts.stream(request)` returns it as the API generates it (`TextToSpeechStream`, an async iterable over a web `ReadableStream`), and `client.tts.session(params)` opens a WebSocket `TextToSpeechSession` for voice agents: `connect()`, `generate()` / `flush()` / `audio()`, `speak()`, `cancel()`, `keepAlive()`, `close()`, with `on("audio" | "flushDone" | "wordBoundaries" | "cancelled" | "termination" | "error" | "close", ...)`. Requests take the API's fields (`text`, `voice_id`, `model_id`, `language`, `sample_rate`, `encoding`, `container`, `bit_rate`); failures throw `TextToSpeechError` (`status`, `errorCode`, `param`, `requestId`, `retryable`, `retryAfter`, `errors`, `closeCode`, `body`). New client option `ttsBaseUrl` (default `https://tts.us.assemblyai.com`); `client.tts.createTemporaryToken()` mints a token for browser sessions. Text sent while `connect()` is pending is held and sent the moment the socket opens, so it need not wait for `Begin` diff --git a/package.json b/package.json index f4507f2..772a97f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "assemblyai", - "version": "4.42.0", + "version": "4.42.1", "description": "The AssemblyAI JavaScript SDK provides an easy-to-use interface for interacting with the AssemblyAI API, which supports async and real-time transcription and text-to-speech.", "engines": { "node": ">=18" diff --git a/src/types/openapi.generated.ts b/src/types/openapi.generated.ts index c7f0b66..f817f40 100644 --- a/src/types/openapi.generated.ts +++ b/src/types/openapi.generated.ts @@ -1151,6 +1151,22 @@ export type SummarizationRequest = { effort?: SpeechUnderstandingEffort; }; +/** + * Action items configuration for speech understanding + */ +export type ActionItemsRequest = { + /** + * Also extract key decisions made during the conversation + */ + include_decisions?: boolean; + /** + * How much effort to spend on this task + * + * @defaultValue "low" + */ + effort?: SpeechUnderstandingEffort; +}; + /** * Speech understanding feature requests grouped together */ @@ -1171,6 +1187,10 @@ export type SpeechUnderstandingFeatureRequests = { * Summarization configuration */ summarization?: SummarizationRequest; + /** + * Action items configuration + */ + action_items?: ActionItemsRequest; }; /** @@ -1281,6 +1301,42 @@ export type SummarizationResponse = { effort?: SpeechUnderstandingEffort; }; +/** + * An action item or decision extracted from the audio + */ +export type ActionItem = { + /** + * The extracted action item or decision + */ + action_item: string; + /** + * The source quote from the audio + */ + quote?: string; + /** + * Timestamp in milliseconds from audio start + */ + timestamp?: number; +}; + +/** + * Action items response containing status and extracted items + */ +export type ActionItemsResponse = { + /** + * Status of the action items feature + */ + status: string; + /** + * The effort level that was used + */ + effort?: SpeechUnderstandingEffort; + /** + * List of extracted action items + */ + items?: ActionItem[]; +}; + /** * Speech understanding feature responses grouped together */ @@ -1301,6 +1357,10 @@ export type SpeechUnderstandingFeatureResponses = { * Summarization results */ summarization?: SummarizationResponse; + /** + * Action items results + */ + action_items?: ActionItemsResponse; }; /** @@ -1433,11 +1493,7 @@ export type SummaryModel = "informative" | "conversational" | "catchy"; * The type of summary */ export type SummaryType = - | "bullets" - | "bullets_verbose" - | "gist" - | "headline" - | "paragraph"; + "bullets" | "bullets_verbose" | "gist" | "headline" | "paragraph"; /** * Timestamp containing a start and end property in milliseconds @@ -3186,8 +3242,7 @@ export type TranscriptOptionalParams = { * Speech understanding configuration/response for LLM Gateway features */ speech_understanding?: - | SpeechUnderstandingRequest - | SpeechUnderstandingResponse; + SpeechUnderstandingRequest | SpeechUnderstandingResponse; /** * The domain to use for the transcription (e.g. 'medical-v1'). */ @@ -3580,8 +3635,7 @@ export type TranscriptUtterance = { * The notifications sent to the webhook URL. */ export type TranscriptWebhookNotification = - | TranscriptReadyNotification - | RedactedAudioNotification; + TranscriptReadyNotification | RedactedAudioNotification; /** * @example diff --git a/tests/unit/exports.test.ts b/tests/unit/exports.test.ts index 973833a..5825b16 100644 --- a/tests/unit/exports.test.ts +++ b/tests/unit/exports.test.ts @@ -1,4 +1,7 @@ import type { + ActionItem, + ActionItemsRequest, + ActionItemsResponse, LiteralUnion, TextToSpeechErrorFields, TextToSpeechRequest, @@ -73,4 +76,20 @@ describe("package root exports", () => { expect(typeof infoOf).toBe("function"); expect(info.requestId).toBe("req-1"); }); + + it("exposes the Speech Understanding action items types from the root", () => { + const request: ActionItemsRequest = { + include_decisions: true, + effort: "low", + }; + const item: ActionItem = { + action_item: "Review PR", + }; + const response: ActionItemsResponse = { + status: "success", + items: [item], + }; + expect(request.include_decisions).toBe(true); + expect(response.items?.[0].action_item).toBe("Review PR"); + }); }); diff --git a/tests/unit/speech-understanding-action-items.test.ts b/tests/unit/speech-understanding-action-items.test.ts new file mode 100644 index 0000000..07a307b --- /dev/null +++ b/tests/unit/speech-understanding-action-items.test.ts @@ -0,0 +1,59 @@ +import fetchMock from "jest-fetch-mock"; +import { ActionItem, ActionItemsRequest, ActionItemsResponse } from "../../src"; +import { createClient, requestMatches } from "./utils"; + +fetchMock.enableMocks(); + +const assembly = createClient(); +const transcriptId = "transcript_123"; +const remoteAudioURL = "https://assembly.ai/espn.m4a"; + +beforeEach(() => { + jest.clearAllMocks(); + fetchMock.resetMocks(); + fetchMock.doMock(); +}); + +describe("speech understanding action items", () => { + it("should submit every action_items option", async () => { + const actionItems: ActionItemsRequest = { + include_decisions: true, + effort: "medium", + }; + fetchMock.doMockOnceIf( + requestMatches({ url: "/v2/transcript", method: "POST" }), + JSON.stringify({ id: transcriptId, status: "queued" }), + ); + + await assembly.transcripts.submit({ + audio_url: remoteAudioURL, + speech_understanding: { request: { action_items: actionItems } }, + }); + + const body = JSON.parse(fetchMock.mock.calls[0][1]?.body as string); + expect(body.speech_understanding.request.action_items).toEqual(actionItems); + }); + + it("should type the action_items response", () => { + const item: ActionItem = { + action_item: "Schedule follow-up meeting with team", + quote: "Let's schedule a follow-up next Tuesday.", + timestamp: 45000, + }; + const response: ActionItemsResponse = { + status: "success", + effort: "medium", + items: [item], + }; + + expect(response.status).toBe("success"); + expect(response.effort).toBe("medium"); + expect(response.items?.[0].action_item).toBe( + "Schedule follow-up meeting with team", + ); + expect(response.items?.[0].quote).toBe( + "Let's schedule a follow-up next Tuesday.", + ); + expect(response.items?.[0].timestamp).toBe(45000); + }); +});