@livekit/agents-plugin-openai 1.2.4 → 1.2.5

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.
@@ -185,6 +185,10 @@ export class RealtimeModel extends llm.RealtimeModel {
185
185
  autoToolReplyGeneration: false,
186
186
  audioOutput: modalities.includes('audio'),
187
187
  manualFunctionCalls: true,
188
+ midSessionChatCtxUpdate: true,
189
+ midSessionInstructionsUpdate: true,
190
+ midSessionToolsUpdate: true,
191
+ perResponseToolChoice: true,
188
192
  });
189
193
 
190
194
  const isAzure = !!(options.apiVersion || options.entraToken || options.azureDeployment);
@@ -477,17 +481,75 @@ export class RealtimeSession extends llm.RealtimeSession {
477
481
  async updateChatCtx(_chatCtx: llm.ChatContext): Promise<void> {
478
482
  const unlock = await this.updateChatCtxLock.lock();
479
483
  try {
484
+ const validation = llm.validateChatContextStructure(_chatCtx);
485
+ const blockingErrors = validation.issues.filter(
486
+ (issue: llm.ChatContextValidationIssue) =>
487
+ issue.severity === 'error' && issue.code !== 'timestamp_order',
488
+ );
489
+ const timestampOrderIssue = validation.issues.find(
490
+ (issue: llm.ChatContextValidationIssue) => issue.code === 'timestamp_order',
491
+ );
492
+ if (blockingErrors.length > 0) {
493
+ this.#logger.error(
494
+ { issues: validation.issues, blockingErrors },
495
+ 'Invalid chat context supplied to updateChatCtx',
496
+ );
497
+ throw new Error(
498
+ `Invalid chat context: ${validation.errors} errors, ${validation.warnings} warnings`,
499
+ );
500
+ }
501
+ if (timestampOrderIssue) {
502
+ this.#logger.warn(
503
+ { timestampOrderIssue },
504
+ 'Proceeding with non-monotonic createdAt ordering in realtime chat context',
505
+ );
506
+ }
507
+ if (lkOaiDebug > 0 && validation.warnings > 0) {
508
+ this.#logger.debug(
509
+ {
510
+ warnings: validation.warnings,
511
+ issues: validation.issues,
512
+ },
513
+ 'Chat context warnings detected before realtime update',
514
+ );
515
+ }
516
+
480
517
  const events = await this.createChatCtxUpdateEvents(_chatCtx);
481
518
  const futures: Future<void>[] = [];
519
+ const ownedCreateFutures: { [id: string]: Future<void> } = {};
520
+ const ownedDeleteFutures: { [id: string]: Future<void> } = {};
521
+
522
+ const cleanupTimedOutFutures = () => {
523
+ // remove timed-out entries so late server acks
524
+ // don't resolve stale futures from a previous updateChatCtx call.
525
+ for (const [itemId, future] of Object.entries(ownedDeleteFutures)) {
526
+ if (this.itemDeleteFutures[itemId] === future) {
527
+ delete this.itemDeleteFutures[itemId];
528
+ }
529
+ }
530
+ for (const [itemId, future] of Object.entries(ownedCreateFutures)) {
531
+ if (this.itemCreateFutures[itemId] === future) {
532
+ delete this.itemCreateFutures[itemId];
533
+ }
534
+ }
535
+ };
482
536
 
483
537
  for (const event of events) {
484
- const future = new Future<void>();
485
- futures.push(future);
486
-
487
538
  if (event.type === 'conversation.item.create') {
539
+ const future = new Future<void>();
540
+ futures.push(future);
488
541
  this.itemCreateFutures[event.item.id] = future;
542
+ ownedCreateFutures[event.item.id] = future;
489
543
  } else if (event.type == 'conversation.item.delete') {
544
+ const existingDeleteFuture = this.itemDeleteFutures[event.item_id];
545
+ if (existingDeleteFuture) {
546
+ futures.push(existingDeleteFuture);
547
+ continue;
548
+ }
549
+ const future = new Future<void>();
550
+ futures.push(future);
490
551
  this.itemDeleteFutures[event.item_id] = future;
552
+ ownedDeleteFutures[event.item_id] = future;
491
553
  }
492
554
 
493
555
  this.sendEvent(event);
@@ -497,13 +559,21 @@ export class RealtimeSession extends llm.RealtimeSession {
497
559
  return;
498
560
  }
499
561
 
500
- // wait for futures to resolve or timeout
501
- await Promise.race([
502
- Promise.all(futures),
503
- delay(5000).then(() => {
504
- throw new Error('Chat ctx update events timed out');
505
- }),
506
- ]);
562
+ // wait for futures to resolve or timeout.
563
+ // Cancel the timeout branch once futures resolve to avoid stale cleanup.
564
+ const timeoutController = new AbortController();
565
+ const timeoutPromise = delay(5000, { signal: timeoutController.signal }).then(() => {
566
+ cleanupTimedOutFutures();
567
+ throw new Error('Chat ctx update events timed out');
568
+ });
569
+
570
+ try {
571
+ await Promise.race([Promise.all(futures), timeoutPromise]);
572
+ } finally {
573
+ if (!timeoutController.signal.aborted) {
574
+ timeoutController.abort();
575
+ }
576
+ }
507
577
  } catch (e) {
508
578
  this.#logger.error((e as Error).message);
509
579
  throw e;
@@ -177,6 +177,10 @@ export class RealtimeModel extends llm.RealtimeModel {
177
177
  autoToolReplyGeneration: false,
178
178
  audioOutput: modalities.includes('audio'),
179
179
  manualFunctionCalls: true,
180
+ midSessionChatCtxUpdate: true,
181
+ midSessionInstructionsUpdate: true,
182
+ midSessionToolsUpdate: true,
183
+ perResponseToolChoice: true,
180
184
  });
181
185
 
182
186
  const isAzure = !!(options.apiVersion || options.entraToken || options.azureDeployment);
@@ -339,6 +339,7 @@ class ResponsesHttpLLMStream extends llm.LLMStream {
339
339
  promptTokens: event.response.usage.input_tokens,
340
340
  promptCachedTokens: event.response.usage.input_tokens_details.cached_tokens,
341
341
  totalTokens: event.response.usage.total_tokens,
342
+ serviceTier: event.response.service_tier ?? undefined,
342
343
  },
343
344
  };
344
345
  }
package/src/ws/llm.ts CHANGED
@@ -590,6 +590,7 @@ export class WSLLMStream extends llm.LLMStream {
590
590
  promptTokens: event.response.usage.input_tokens,
591
591
  promptCachedTokens: event.response.usage.input_tokens_details.cached_tokens,
592
592
  totalTokens: event.response.usage.total_tokens,
593
+ serviceTier: event.response.service_tier ?? undefined,
593
594
  },
594
595
  };
595
596
  }
package/src/ws/types.ts CHANGED
@@ -66,6 +66,7 @@ export const wsResponseCompletedEventSchema = z.object({
66
66
  response: z
67
67
  .object({
68
68
  id: z.string(),
69
+ service_tier: z.string().nullable().optional(),
69
70
  usage: z
70
71
  .object({
71
72
  output_tokens: z.number(),