@ai-sdk/assemblyai 2.0.7 → 2.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,368 @@
1
+ import { createTestServer } from '@ai-sdk/test-server/with-vitest';
2
+ import { AssemblyAITranscriptionModel } from './assemblyai-transcription-model';
3
+ import { createAssemblyAI } from './assemblyai-provider';
4
+ import { readFile } from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import { describe, it, expect, vi } from 'vitest';
7
+
8
+ vi.mock('./version', () => ({
9
+ VERSION: '0.0.0-test',
10
+ }));
11
+
12
+ const audioData = await readFile(path.join(__dirname, 'transcript-test.mp3'));
13
+ const provider = createAssemblyAI({ apiKey: 'test-api-key' });
14
+ const model = provider.transcription('best');
15
+
16
+ const server = createTestServer({
17
+ 'https://api.assemblyai.com/v2/transcript': {},
18
+ 'https://api.assemblyai.com/v2/transcript/9ea68fd3-f953-42c1-9742-976c447fb463':
19
+ {},
20
+ 'https://api.assemblyai.com/v2/upload': {
21
+ response: {
22
+ type: 'json-value',
23
+ body: {
24
+ id: '9ea68fd3-f953-42c1-9742-976c447fb463',
25
+ upload_url: 'https://storage.assemblyai.com/mock-upload-url',
26
+ },
27
+ },
28
+ },
29
+ });
30
+
31
+ describe('doGenerate', () => {
32
+ function prepareJsonResponse({
33
+ headers,
34
+ }: {
35
+ headers?: Record<string, string>;
36
+ } = {}) {
37
+ server.urls['https://api.assemblyai.com/v2/transcript'].response = {
38
+ type: 'json-value',
39
+ body: {
40
+ id: '9ea68fd3-f953-42c1-9742-976c447fb463',
41
+ status: 'queued',
42
+ },
43
+ };
44
+
45
+ server.urls[
46
+ 'https://api.assemblyai.com/v2/transcript/9ea68fd3-f953-42c1-9742-976c447fb463'
47
+ ].response = {
48
+ type: 'json-value',
49
+ headers,
50
+ body: {
51
+ id: '9ea68fd3-f953-42c1-9742-976c447fb463',
52
+ audio_url: 'https://assembly.ai/test.mp3',
53
+ status: 'completed',
54
+ webhook_auth: true,
55
+ auto_highlights: true,
56
+ redact_pii: true,
57
+ summarization: true,
58
+ language_model: 'assemblyai_default',
59
+ acoustic_model: 'assemblyai_default',
60
+ language_code: 'en_us',
61
+ language_detection: true,
62
+ language_confidence_threshold: 0.7,
63
+ language_confidence: 0.9959,
64
+ speech_model: 'best',
65
+ text: 'Hello, world!',
66
+ words: [
67
+ {
68
+ confidence: 0.97465,
69
+ start: 250,
70
+ end: 650,
71
+ text: 'Hello,',
72
+ channel: 'channel',
73
+ speaker: 'speaker',
74
+ },
75
+ {
76
+ confidence: 0.99999,
77
+ start: 730,
78
+ end: 1022,
79
+ text: 'world',
80
+ channel: 'channel',
81
+ speaker: 'speaker',
82
+ },
83
+ ],
84
+ utterances: [
85
+ {
86
+ confidence: 0.9359033333333334,
87
+ start: 250,
88
+ end: 26950,
89
+ text: 'Hello, world!',
90
+ words: [
91
+ {
92
+ confidence: 0.97503,
93
+ start: 250,
94
+ end: 650,
95
+ text: 'Hello,',
96
+ speaker: 'A',
97
+ },
98
+ {
99
+ confidence: 0.99999,
100
+ start: 730,
101
+ end: 1022,
102
+ text: 'world',
103
+ speaker: 'A',
104
+ },
105
+ ],
106
+ speaker: 'A',
107
+ channel: 'channel',
108
+ },
109
+ ],
110
+ confidence: 0.9404651451800253,
111
+ audio_duration: 281,
112
+ punctuate: true,
113
+ format_text: true,
114
+ disfluencies: false,
115
+ multichannel: false,
116
+ audio_channels: 1,
117
+ webhook_url: 'https://your-webhook-url.tld/path',
118
+ webhook_status_code: 200,
119
+ webhook_auth_header_name: 'webhook-secret',
120
+ auto_highlights_result: {
121
+ status: 'success',
122
+ results: [
123
+ {
124
+ count: 1,
125
+ rank: 0.08,
126
+ text: 'Hello, world!',
127
+ timestamps: [
128
+ {
129
+ start: 250,
130
+ end: 26950,
131
+ },
132
+ ],
133
+ },
134
+ ],
135
+ },
136
+ audio_start_from: 10,
137
+ audio_end_at: 280,
138
+ word_boost: ['hello', 'world'],
139
+ boost_param: 'high',
140
+ filter_profanity: true,
141
+ redact_pii_audio: true,
142
+ redact_pii_audio_quality: 'mp3',
143
+ redact_pii_policies: [
144
+ 'us_social_security_number',
145
+ 'credit_card_number',
146
+ ],
147
+ redact_pii_sub: 'hash',
148
+ speaker_labels: true,
149
+ speakers_expected: 2,
150
+ content_safety: true,
151
+ content_safety_labels: {
152
+ status: 'success',
153
+ results: [
154
+ {
155
+ text: 'Hello, world!',
156
+ labels: [
157
+ {
158
+ label: 'disasters',
159
+ confidence: 0.8142836093902588,
160
+ severity: 0.4093044400215149,
161
+ },
162
+ ],
163
+ sentences_idx_start: 0,
164
+ sentences_idx_end: 5,
165
+ timestamp: {
166
+ start: 250,
167
+ end: 28840,
168
+ },
169
+ },
170
+ ],
171
+ summary: {
172
+ disasters: 0.9940800441842205,
173
+ health_issues: 0.9216489289040967,
174
+ },
175
+ severity_score_summary: {
176
+ disasters: {
177
+ low: 0.5733263024656846,
178
+ medium: 0.42667369753431533,
179
+ high: 0,
180
+ },
181
+ health_issues: {
182
+ low: 0.22863814977924785,
183
+ medium: 0.45014154926938227,
184
+ high: 0.32122030095136983,
185
+ },
186
+ },
187
+ },
188
+ iab_categories: true,
189
+ iab_categories_result: {
190
+ status: 'success',
191
+ results: [
192
+ {
193
+ text: 'Hello, world!',
194
+ labels: [
195
+ {
196
+ relevance: 0.988274097442627,
197
+ label: 'Home&Garden>IndoorEnvironmentalQuality',
198
+ },
199
+ {
200
+ relevance: 0.5821335911750793,
201
+ label: 'NewsAndPolitics>Weather',
202
+ },
203
+ ],
204
+ timestamp: {
205
+ start: 250,
206
+ end: 28840,
207
+ },
208
+ },
209
+ ],
210
+ summary: {
211
+ 'NewsAndPolitics>Weather': 1,
212
+ 'Home&Garden>IndoorEnvironmentalQuality': 0.9043831825256348,
213
+ },
214
+ },
215
+ auto_chapters: true,
216
+ chapters: [
217
+ {
218
+ gist: 'Hello, world!',
219
+ headline: 'Hello, world!',
220
+ summary: 'Hello, world!',
221
+ start: 250,
222
+ end: 28840,
223
+ },
224
+ {
225
+ gist: 'Hello, world!',
226
+ headline: 'Hello, world!',
227
+ summary: 'Hello, world!',
228
+ start: 29610,
229
+ end: 280340,
230
+ },
231
+ ],
232
+ summary_type: 'bullets',
233
+ summary_model: 'informative',
234
+ summary: '- Hello, world!',
235
+ sentiment_analysis: true,
236
+ entity_detection: true,
237
+ entities: [
238
+ {
239
+ entity_type: 'location',
240
+ text: 'Canada',
241
+ start: 2548,
242
+ end: 3130,
243
+ },
244
+ {
245
+ entity_type: 'location',
246
+ text: 'the US',
247
+ start: 5498,
248
+ end: 6382,
249
+ },
250
+ ],
251
+ speech_threshold: 0.5,
252
+ error: 'error',
253
+ dual_channel: false,
254
+ speed_boost: true,
255
+ },
256
+ };
257
+ }
258
+
259
+ it('should pass the model', async () => {
260
+ prepareJsonResponse();
261
+
262
+ await model.doGenerate({
263
+ audio: audioData,
264
+ mediaType: 'audio/wav',
265
+ });
266
+
267
+ expect(await server.calls[1].requestBodyJson).toMatchObject({
268
+ audio_url: 'https://storage.assemblyai.com/mock-upload-url',
269
+ speech_model: 'best',
270
+ });
271
+ });
272
+
273
+ it('should pass headers', async () => {
274
+ prepareJsonResponse();
275
+
276
+ const provider = createAssemblyAI({
277
+ apiKey: 'test-api-key',
278
+ headers: {
279
+ 'Custom-Provider-Header': 'provider-header-value',
280
+ },
281
+ });
282
+
283
+ await provider.transcription('best').doGenerate({
284
+ audio: audioData,
285
+ mediaType: 'audio/wav',
286
+ headers: {
287
+ 'Custom-Request-Header': 'request-header-value',
288
+ },
289
+ });
290
+
291
+ expect(server.calls[0].requestHeaders).toMatchObject({
292
+ authorization: 'test-api-key',
293
+ 'content-type': 'application/octet-stream',
294
+ 'custom-provider-header': 'provider-header-value',
295
+ 'custom-request-header': 'request-header-value',
296
+ });
297
+ expect(server.calls[0].requestUserAgent).toContain(
298
+ `ai-sdk/assemblyai/0.0.0-test`,
299
+ );
300
+ });
301
+
302
+ it('should extract the transcription text', async () => {
303
+ prepareJsonResponse();
304
+
305
+ const result = await model.doGenerate({
306
+ audio: audioData,
307
+ mediaType: 'audio/wav',
308
+ });
309
+
310
+ expect(result.text).toBe('Hello, world!');
311
+ });
312
+
313
+ it('should include response data with timestamp, modelId and headers', async () => {
314
+ prepareJsonResponse({
315
+ headers: {
316
+ 'x-request-id': 'test-request-id',
317
+ 'x-ratelimit-remaining': '123',
318
+ },
319
+ });
320
+
321
+ const testDate = new Date(0);
322
+ const customModel = new AssemblyAITranscriptionModel('best', {
323
+ provider: 'test-provider',
324
+ url: ({ path }) => `https://api.assemblyai.com${path}`,
325
+ headers: () => ({}),
326
+ _internal: {
327
+ currentDate: () => testDate,
328
+ },
329
+ });
330
+
331
+ const result = await customModel.doGenerate({
332
+ audio: audioData,
333
+ mediaType: 'audio/wav',
334
+ });
335
+
336
+ expect(result.response).toMatchObject({
337
+ timestamp: testDate,
338
+ modelId: 'best',
339
+ headers: {
340
+ 'content-type': 'application/json',
341
+ 'x-request-id': 'test-request-id',
342
+ 'x-ratelimit-remaining': '123',
343
+ },
344
+ });
345
+ });
346
+
347
+ it('should use real date when no custom date provider is specified', async () => {
348
+ prepareJsonResponse();
349
+
350
+ const testDate = new Date(0);
351
+ const customModel = new AssemblyAITranscriptionModel('best', {
352
+ provider: 'test-provider',
353
+ url: ({ path }) => `https://api.assemblyai.com${path}`,
354
+ headers: () => ({}),
355
+ _internal: {
356
+ currentDate: () => testDate,
357
+ },
358
+ });
359
+
360
+ const result = await customModel.doGenerate({
361
+ audio: audioData,
362
+ mediaType: 'audio/wav',
363
+ });
364
+
365
+ expect(result.response.timestamp.getTime()).toEqual(testDate.getTime());
366
+ expect(result.response.modelId).toBe('best');
367
+ });
368
+ });