@livekit/agents-plugin-openai 1.0.50 → 1.1.0

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.
Files changed (118) hide show
  1. package/dist/index.cjs +5 -2
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +1 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +4 -2
  7. package/dist/index.js.map +1 -1
  8. package/dist/llm.cjs +8 -0
  9. package/dist/llm.cjs.map +1 -1
  10. package/dist/llm.d.cts +1 -0
  11. package/dist/llm.d.ts +1 -0
  12. package/dist/llm.d.ts.map +1 -1
  13. package/dist/llm.js +8 -0
  14. package/dist/llm.js.map +1 -1
  15. package/dist/llm.test.cjs +31 -16
  16. package/dist/llm.test.cjs.map +1 -1
  17. package/dist/llm.test.js +32 -17
  18. package/dist/llm.test.js.map +1 -1
  19. package/dist/realtime/api_proto.cjs.map +1 -1
  20. package/dist/realtime/api_proto.d.cts +7 -3
  21. package/dist/realtime/api_proto.d.ts +7 -3
  22. package/dist/realtime/api_proto.d.ts.map +1 -1
  23. package/dist/realtime/api_proto.js.map +1 -1
  24. package/dist/realtime/realtime_model.cjs +46 -22
  25. package/dist/realtime/realtime_model.cjs.map +1 -1
  26. package/dist/realtime/realtime_model.d.cts +2 -1
  27. package/dist/realtime/realtime_model.d.ts +2 -1
  28. package/dist/realtime/realtime_model.d.ts.map +1 -1
  29. package/dist/realtime/realtime_model.js +46 -22
  30. package/dist/realtime/realtime_model.js.map +1 -1
  31. package/dist/realtime/realtime_model.test.cjs +104 -14
  32. package/dist/realtime/realtime_model.test.cjs.map +1 -1
  33. package/dist/realtime/realtime_model.test.js +104 -14
  34. package/dist/realtime/realtime_model.test.js.map +1 -1
  35. package/dist/realtime/realtime_model_beta.cjs +40 -22
  36. package/dist/realtime/realtime_model_beta.cjs.map +1 -1
  37. package/dist/realtime/realtime_model_beta.d.ts.map +1 -1
  38. package/dist/realtime/realtime_model_beta.js +40 -22
  39. package/dist/realtime/realtime_model_beta.js.map +1 -1
  40. package/dist/responses/llm.cjs +71 -16
  41. package/dist/responses/llm.cjs.map +1 -1
  42. package/dist/responses/llm.d.cts +10 -25
  43. package/dist/responses/llm.d.ts +10 -25
  44. package/dist/responses/llm.d.ts.map +1 -1
  45. package/dist/responses/llm.js +71 -14
  46. package/dist/responses/llm.js.map +1 -1
  47. package/dist/responses/llm.test.cjs +32 -17
  48. package/dist/responses/llm.test.cjs.map +1 -1
  49. package/dist/responses/llm.test.js +33 -18
  50. package/dist/responses/llm.test.js.map +1 -1
  51. package/dist/stt.cjs +18 -3
  52. package/dist/stt.cjs.map +1 -1
  53. package/dist/stt.d.cts +2 -0
  54. package/dist/stt.d.ts +2 -0
  55. package/dist/stt.d.ts.map +1 -1
  56. package/dist/stt.js +19 -4
  57. package/dist/stt.js.map +1 -1
  58. package/dist/stt.test.cjs +11 -3
  59. package/dist/stt.test.cjs.map +1 -1
  60. package/dist/stt.test.js +12 -4
  61. package/dist/stt.test.js.map +1 -1
  62. package/dist/tts.cjs +11 -0
  63. package/dist/tts.cjs.map +1 -1
  64. package/dist/tts.d.cts +2 -0
  65. package/dist/tts.d.ts +2 -0
  66. package/dist/tts.d.ts.map +1 -1
  67. package/dist/tts.js +11 -0
  68. package/dist/tts.js.map +1 -1
  69. package/dist/tts.test.cjs +11 -3
  70. package/dist/tts.test.cjs.map +1 -1
  71. package/dist/tts.test.js +12 -4
  72. package/dist/tts.test.js.map +1 -1
  73. package/dist/ws/index.cjs +29 -0
  74. package/dist/ws/index.cjs.map +1 -0
  75. package/dist/ws/index.d.cts +3 -0
  76. package/dist/ws/index.d.ts +3 -0
  77. package/dist/ws/index.d.ts.map +1 -0
  78. package/dist/ws/index.js +5 -0
  79. package/dist/ws/index.js.map +1 -0
  80. package/dist/ws/llm.cjs +502 -0
  81. package/dist/ws/llm.cjs.map +1 -0
  82. package/dist/ws/llm.d.cts +74 -0
  83. package/dist/ws/llm.d.ts +74 -0
  84. package/dist/ws/llm.d.ts.map +1 -0
  85. package/dist/ws/llm.js +485 -0
  86. package/dist/ws/llm.js.map +1 -0
  87. package/dist/ws/llm.test.cjs +26 -0
  88. package/dist/ws/llm.test.cjs.map +1 -0
  89. package/dist/ws/llm.test.d.cts +2 -0
  90. package/dist/ws/llm.test.d.ts +2 -0
  91. package/dist/ws/llm.test.d.ts.map +1 -0
  92. package/dist/ws/llm.test.js +25 -0
  93. package/dist/ws/llm.test.js.map +1 -0
  94. package/dist/ws/types.cjs +128 -0
  95. package/dist/ws/types.cjs.map +1 -0
  96. package/dist/ws/types.d.cts +167 -0
  97. package/dist/ws/types.d.ts +167 -0
  98. package/dist/ws/types.d.ts.map +1 -0
  99. package/dist/ws/types.js +95 -0
  100. package/dist/ws/types.js.map +1 -0
  101. package/package.json +6 -5
  102. package/src/index.ts +1 -0
  103. package/src/llm.test.ts +31 -17
  104. package/src/llm.ts +9 -0
  105. package/src/realtime/api_proto.ts +8 -2
  106. package/src/realtime/realtime_model.test.ts +129 -14
  107. package/src/realtime/realtime_model.ts +51 -26
  108. package/src/realtime/realtime_model_beta.ts +42 -25
  109. package/src/responses/llm.test.ts +32 -18
  110. package/src/responses/llm.ts +105 -19
  111. package/src/stt.test.ts +12 -4
  112. package/src/stt.ts +21 -4
  113. package/src/tts.test.ts +12 -4
  114. package/src/tts.ts +13 -0
  115. package/src/ws/index.ts +17 -0
  116. package/src/ws/llm.test.ts +30 -0
  117. package/src/ws/llm.ts +665 -0
  118. package/src/ws/types.ts +131 -0
@@ -8,12 +8,14 @@ import {
8
8
  APITimeoutError,
9
9
  DEFAULT_API_CONNECT_OPTIONS,
10
10
  llm,
11
+ log,
11
12
  toError,
12
13
  } from '@livekit/agents';
13
14
  import OpenAI from 'openai';
14
15
  import type { ChatModels } from '../models.js';
16
+ import { WSLLM } from '../ws/llm.js';
15
17
 
16
- interface LLMOptions {
18
+ export interface LLMOptions {
17
19
  model: string | ChatModels;
18
20
  apiKey?: string;
19
21
  baseURL?: string;
@@ -24,30 +26,32 @@ interface LLMOptions {
24
26
  store?: boolean;
25
27
  metadata?: Record<string, string>;
26
28
  strictToolSchema?: boolean;
29
+
30
+ /**
31
+ * Whether to use the WebSocket API.
32
+ * @default true
33
+ */
34
+ useWebSocket?: boolean;
27
35
  }
28
36
 
37
+ type HttpLLMOptions = Omit<LLMOptions, 'useWebSocket'>;
38
+
29
39
  const defaultLLMOptions: LLMOptions = {
30
40
  model: 'gpt-4.1',
31
41
  apiKey: process.env.OPENAI_API_KEY,
32
42
  strictToolSchema: true,
43
+ useWebSocket: true,
33
44
  };
34
45
 
35
- export class LLM extends llm.LLM {
46
+ class ResponsesHttpLLM extends llm.LLM {
36
47
  #client: OpenAI;
37
- #opts: LLMOptions;
48
+ #opts: HttpLLMOptions;
38
49
 
39
- /**
40
- * Create a new instance of OpenAI Responses LLM.
41
- *
42
- * @remarks
43
- * `apiKey` must be set to your OpenAI API key, either using the argument or by setting the
44
- * `OPENAI_API_KEY` environment variable.
45
- */
46
- constructor(opts: Partial<LLMOptions> = defaultLLMOptions) {
50
+ constructor(opts: Partial<HttpLLMOptions> = defaultLLMOptions) {
47
51
  super();
48
52
 
49
53
  this.#opts = { ...defaultLLMOptions, ...opts };
50
- if (this.#opts.apiKey === undefined) {
54
+ if (this.#opts.apiKey === undefined && this.#opts.client === undefined) {
51
55
  throw new Error('OpenAI API key is required, whether as an argument or as $OPENAI_API_KEY');
52
56
  }
53
57
 
@@ -59,15 +63,15 @@ export class LLM extends llm.LLM {
59
63
  });
60
64
  }
61
65
 
62
- label(): string {
66
+ override label(): string {
63
67
  return 'openai.responses.LLM';
64
68
  }
65
69
 
66
- get model(): string {
70
+ override get model(): string {
67
71
  return this.#opts.model;
68
72
  }
69
73
 
70
- chat({
74
+ override chat({
71
75
  chatCtx,
72
76
  toolCtx,
73
77
  connOptions = DEFAULT_API_CONNECT_OPTIONS,
@@ -81,7 +85,7 @@ export class LLM extends llm.LLM {
81
85
  parallelToolCalls?: boolean;
82
86
  toolChoice?: llm.ToolChoice;
83
87
  extraKwargs?: Record<string, unknown>;
84
- }): LLMStream {
88
+ }): ResponsesHttpLLMStream {
85
89
  const modelOptions: Record<string, unknown> = { ...(extraKwargs || {}) };
86
90
 
87
91
  parallelToolCalls =
@@ -110,7 +114,7 @@ export class LLM extends llm.LLM {
110
114
  modelOptions.metadata = this.#opts.metadata;
111
115
  }
112
116
 
113
- return new LLMStream(this, {
117
+ return new ResponsesHttpLLMStream(this, {
114
118
  model: this.#opts.model,
115
119
  client: this.#client,
116
120
  chatCtx,
@@ -122,7 +126,7 @@ export class LLM extends llm.LLM {
122
126
  }
123
127
  }
124
128
 
125
- export class LLMStream extends llm.LLMStream {
129
+ class ResponsesHttpLLMStream extends llm.LLMStream {
126
130
  private model: string | ChatModels;
127
131
  private client: OpenAI;
128
132
  private modelOptions: Record<string, unknown>;
@@ -130,7 +134,7 @@ export class LLMStream extends llm.LLMStream {
130
134
  private responseId: string;
131
135
 
132
136
  constructor(
133
- llm: LLM,
137
+ llm: ResponsesHttpLLM,
134
138
  {
135
139
  model,
136
140
  client,
@@ -325,3 +329,85 @@ export class LLMStream extends llm.LLMStream {
325
329
  return undefined;
326
330
  }
327
331
  }
332
+
333
+ export class LLM extends llm.LLM {
334
+ #opts: LLMOptions;
335
+ #llm: llm.LLM;
336
+ #logger = log();
337
+
338
+ /**
339
+ * Create a new instance of OpenAI Responses LLM.
340
+ *
341
+ * @remarks
342
+ * `apiKey` must be set to your OpenAI API key, either using the argument or by setting the
343
+ * `OPENAI_API_KEY` environment variable.
344
+ */
345
+ constructor(opts: Partial<LLMOptions> = defaultLLMOptions) {
346
+ super();
347
+
348
+ this.#opts = { ...defaultLLMOptions, ...opts };
349
+ const { useWebSocket, client, ...baseOpts } = this.#opts;
350
+
351
+ if (useWebSocket) {
352
+ if (client !== undefined) {
353
+ this.#logger.warn(
354
+ 'WebSocket mode does not support custom client; provided client will be ignored',
355
+ );
356
+ }
357
+ this.#llm = new WSLLM(baseOpts);
358
+ } else {
359
+ this.#llm = new ResponsesHttpLLM({ ...baseOpts, client });
360
+ }
361
+
362
+ // Forward events from the inner delegate so consumers listening on this
363
+ // wrapper instance (e.g. AgentActivity) receive them.
364
+ this.#llm.on('metrics_collected', (metrics) => this.emit('metrics_collected', metrics));
365
+ this.#llm.on('error', (error) => this.emit('error', error));
366
+ }
367
+
368
+ override label(): string {
369
+ return this.#llm.label();
370
+ }
371
+
372
+ override get model(): string {
373
+ return this.#llm.model;
374
+ }
375
+
376
+ override prewarm(): void {
377
+ this.#llm.prewarm();
378
+ }
379
+
380
+ // Ref: python livekit-plugins/livekit-plugins-openai/livekit/plugins/openai/responses/llm.py - 229-233 lines
381
+ override async aclose(): Promise<void> {
382
+ await this.#llm.aclose();
383
+ }
384
+
385
+ async close(): Promise<void> {
386
+ await this.aclose();
387
+ }
388
+
389
+ override chat({
390
+ chatCtx,
391
+ toolCtx,
392
+ connOptions = DEFAULT_API_CONNECT_OPTIONS,
393
+ parallelToolCalls,
394
+ toolChoice,
395
+ extraKwargs,
396
+ }: {
397
+ chatCtx: llm.ChatContext;
398
+ toolCtx?: llm.ToolContext;
399
+ connOptions?: APIConnectOptions;
400
+ parallelToolCalls?: boolean;
401
+ toolChoice?: llm.ToolChoice;
402
+ extraKwargs?: Record<string, unknown>;
403
+ }): llm.LLMStream {
404
+ return this.#llm.chat({
405
+ chatCtx,
406
+ toolCtx,
407
+ connOptions,
408
+ parallelToolCalls,
409
+ toolChoice,
410
+ extraKwargs,
411
+ });
412
+ }
413
+ }
package/src/stt.test.ts CHANGED
@@ -3,9 +3,17 @@
3
3
  // SPDX-License-Identifier: Apache-2.0
4
4
  import { VAD } from '@livekit/agents-plugin-silero';
5
5
  import { stt } from '@livekit/agents-plugins-test';
6
- import { describe } from 'vitest';
6
+ import { describe, it } from 'vitest';
7
7
  import { STT } from './stt.js';
8
8
 
9
- describe('OpenAI', async () => {
10
- await stt(new STT(), await VAD.load(), { streaming: false });
11
- });
9
+ const hasOpenAIApiKey = Boolean(process.env.OPENAI_API_KEY);
10
+
11
+ if (hasOpenAIApiKey) {
12
+ describe('OpenAI', async () => {
13
+ await stt(new STT(), await VAD.load(), { streaming: false });
14
+ });
15
+ } else {
16
+ describe('OpenAI', () => {
17
+ it.skip('requires OPENAI_API_KEY', () => {});
18
+ });
19
+ }
package/src/stt.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  // SPDX-FileCopyrightText: 2024 LiveKit, Inc.
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
- import { type AudioBuffer, mergeFrames, stt } from '@livekit/agents';
4
+ import { type AudioBuffer, mergeFrames, normalizeLanguage, stt } from '@livekit/agents';
5
5
  import type { AudioFrame } from '@livekit/rtc-node';
6
6
  import { OpenAI } from 'openai';
7
7
  import type { GroqAudioModels, WhisperModels } from './models.js';
@@ -28,6 +28,19 @@ export class STT extends stt.STT {
28
28
  #client: OpenAI;
29
29
  label = 'openai.STT';
30
30
 
31
+ get model(): string {
32
+ return this.#opts.model;
33
+ }
34
+
35
+ get provider(): string {
36
+ try {
37
+ const url = new URL(this.#client.baseURL);
38
+ return url.host;
39
+ } catch {
40
+ return 'api.openai.com';
41
+ }
42
+ }
43
+
31
44
  /**
32
45
  * Create a new instance of OpenAI STT.
33
46
  *
@@ -38,7 +51,11 @@ export class STT extends stt.STT {
38
51
  constructor(opts: Partial<STTOptions> = defaultSTTOptions) {
39
52
  super({ streaming: false, interimResults: false, alignedTranscript: false });
40
53
 
41
- this.#opts = { ...defaultSTTOptions, ...opts };
54
+ this.#opts = {
55
+ ...defaultSTTOptions,
56
+ ...opts,
57
+ language: normalizeLanguage(opts.language ?? defaultSTTOptions.language),
58
+ };
42
59
  if (this.#opts.apiKey === undefined) {
43
60
  throw new Error('OpenAI API key is required, whether as an argument or as $OPENAI_API_KEY');
44
61
  }
@@ -113,7 +130,7 @@ export class STT extends stt.STT {
113
130
 
114
131
  #sanitizeOptions(language?: string): STTOptions {
115
132
  if (language) {
116
- return { ...this.#opts, language };
133
+ return { ...this.#opts, language: normalizeLanguage(language) };
117
134
  } else {
118
135
  return this.#opts;
119
136
  }
@@ -165,7 +182,7 @@ export class STT extends stt.STT {
165
182
  alternatives: [
166
183
  {
167
184
  text: resp.text || '',
168
- language: config.language || '',
185
+ language: normalizeLanguage(config.language || ''),
169
186
  startTime: 0,
170
187
  endTime: 0,
171
188
  confidence: 0,
package/src/tts.test.ts CHANGED
@@ -2,10 +2,18 @@
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
4
  import { tts } from '@livekit/agents-plugins-test';
5
- import { describe } from 'vitest';
5
+ import { describe, it } from 'vitest';
6
6
  import { STT } from './stt.js';
7
7
  import { TTS } from './tts.js';
8
8
 
9
- describe('OpenAI', async () => {
10
- await tts(new TTS(), new STT(), { streaming: false });
11
- });
9
+ const hasOpenAIApiKey = Boolean(process.env.OPENAI_API_KEY);
10
+
11
+ if (hasOpenAIApiKey) {
12
+ describe('OpenAI', async () => {
13
+ await tts(new TTS(), new STT(), { streaming: false });
14
+ });
15
+ } else {
16
+ describe('OpenAI', () => {
17
+ it.skip('requires OPENAI_API_KEY', () => {});
18
+ });
19
+ }
package/src/tts.ts CHANGED
@@ -32,6 +32,19 @@ export class TTS extends tts.TTS {
32
32
  label = 'openai.TTS';
33
33
  private abortController = new AbortController();
34
34
 
35
+ get model(): string {
36
+ return this.#opts.model;
37
+ }
38
+
39
+ get provider(): string {
40
+ try {
41
+ const url = new URL(this.#client.baseURL);
42
+ return url.host;
43
+ } catch {
44
+ return 'api.openai.com';
45
+ }
46
+ }
47
+
35
48
  /**
36
49
  * Create a new instance of OpenAI TTS.
37
50
  *
@@ -0,0 +1,17 @@
1
+ // SPDX-FileCopyrightText: 2025 LiveKit, Inc.
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+
5
+ export { ResponsesWebSocket } from './llm.js';
6
+ export type {
7
+ WsErrorEvent,
8
+ WsFunctionCallItem,
9
+ WsOutputItem,
10
+ WsOutputItemDoneEvent,
11
+ WsOutputTextDeltaEvent,
12
+ WsResponseCompletedEvent,
13
+ WsResponseCreateEvent,
14
+ WsResponseCreatedEvent,
15
+ WsResponseFailedEvent,
16
+ WsServerEvent,
17
+ } from './types.js';
@@ -0,0 +1,30 @@
1
+ // SPDX-FileCopyrightText: 2025 LiveKit, Inc.
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ import { initializeLogger } from '@livekit/agents';
5
+ import { llm, llmStrict } from '@livekit/agents-plugins-test';
6
+ import { describe } from 'vitest';
7
+ import { LLM } from '../responses/llm.js';
8
+
9
+ initializeLogger({ level: 'silent', pretty: false });
10
+
11
+ describe('OpenAI Responses WS wrapper', async () => {
12
+ await llm(
13
+ new LLM({
14
+ temperature: 0,
15
+ strictToolSchema: false,
16
+ useWebSocket: true,
17
+ }),
18
+ true,
19
+ );
20
+ });
21
+
22
+ describe('OpenAI Responses WS wrapper strict tool schema', async () => {
23
+ await llmStrict(
24
+ new LLM({
25
+ temperature: 0,
26
+ strictToolSchema: true,
27
+ useWebSocket: true,
28
+ }),
29
+ );
30
+ });