ai 7.0.101 → 7.0.103

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 (59) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/dist/index.d.ts +138 -22
  3. package/dist/index.js +2373 -316
  4. package/dist/index.js.map +1 -1
  5. package/dist/internal/index.js +2 -1
  6. package/dist/internal/index.js.map +1 -1
  7. package/dist/test/index.d.ts +16 -2
  8. package/dist/test/index.js +17 -0
  9. package/dist/test/index.js.map +1 -1
  10. package/docs/03-agents/07-workflow-agent.mdx +1 -1
  11. package/docs/03-ai-sdk-core/18-code-mode.mdx +39 -0
  12. package/docs/03-ai-sdk-core/32-evaluation.mdx +110 -0
  13. package/docs/03-ai-sdk-core/36-realtime.mdx +41 -18
  14. package/docs/03-ai-sdk-core/42-batch.mdx +1 -2
  15. package/docs/04-ai-sdk-ui/03-chatbot-message-persistence.mdx +35 -0
  16. package/docs/04-ai-sdk-ui/21-transport.mdx +1 -1
  17. package/docs/06-advanced/11-secure-url-fetching.mdx +8 -2
  18. package/docs/07-reference/01-ai-sdk-core/14-evaluate.mdx +54 -0
  19. package/docs/07-reference/01-ai-sdk-core/32-validate-ui-messages.mdx +12 -0
  20. package/docs/07-reference/01-ai-sdk-core/33-safe-validate-ui-messages.mdx +12 -0
  21. package/docs/07-reference/02-ai-sdk-ui/05-use-realtime.mdx +364 -48
  22. package/docs/07-reference/02-ai-sdk-ui/31-convert-to-model-messages.mdx +12 -0
  23. package/docs/07-reference/04-ai-sdk-workflow/02-workflow-chat-transport.mdx +4 -4
  24. package/docs/07-reference/05-ai-sdk-errors/ai-evaluation-unsupported-question-type-error.mdx +31 -0
  25. package/docs/07-reference/05-ai-sdk-errors/index.mdx +131 -36
  26. package/package.json +12 -12
  27. package/src/error/index.ts +1 -0
  28. package/src/evaluate/evaluate.ts +112 -0
  29. package/src/evaluate/evaluation-result.ts +39 -0
  30. package/src/evaluate/index.ts +7 -0
  31. package/src/evaluate/validate-evaluation.ts +298 -0
  32. package/src/generate-text/execute-tools-from-stream.ts +7 -0
  33. package/src/generate-text/generate-text.ts +16 -12
  34. package/src/generate-text/stream-text.ts +9 -2
  35. package/src/generate-text/tool-caller-configuration.ts +58 -4
  36. package/src/index.ts +1 -0
  37. package/src/realtime/__fixtures__/fake-live-websocket.ts +71 -0
  38. package/src/realtime/__fixtures__/fake-realtime.ts +36 -0
  39. package/src/realtime/__fixtures__/fake-webrtc.ts +133 -0
  40. package/src/realtime/browser-realtime-audio.ts +107 -10
  41. package/src/realtime/browser-realtime-live-websocket.ts +247 -0
  42. package/src/realtime/browser-realtime-transport.ts +235 -69
  43. package/src/realtime/browser-realtime-webrtc.ts +582 -0
  44. package/src/realtime/encode-realtime-frame.ts +33 -0
  45. package/src/realtime/index.ts +1 -0
  46. package/src/realtime/realtime-attempt.ts +45 -0
  47. package/src/realtime/realtime-command-tracker.ts +81 -0
  48. package/src/realtime/realtime-event-channel.ts +170 -0
  49. package/src/realtime/realtime-event-reducer.ts +3 -0
  50. package/src/realtime/realtime-session-state.ts +65 -0
  51. package/src/realtime/realtime-session.ts +768 -218
  52. package/src/realtime/realtime-types.ts +1 -1
  53. package/src/realtime/validate-realtime-setup.ts +37 -0
  54. package/src/test/evaluation-mock-model-v4.ts +27 -0
  55. package/src/ui/convert-to-model-messages.ts +3 -0
  56. package/src/ui/process-ui-message-stream.ts +4 -0
  57. package/src/ui/ui-messages.ts +5 -1
  58. package/src/ui/validate-ui-messages.ts +3 -0
  59. package/src/ui/warn-if-ui-message-has-deprecated-raw-input.ts +36 -0
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  createIdGenerator as createIdGenerator9,
15
15
  dynamicTool,
16
16
  experimental_toolCaller,
17
- generateId as generateId3,
17
+ generateId as generateId4,
18
18
  jsonSchema,
19
19
  parseJsonEventStream as parseJsonEventStream3,
20
20
  tool,
@@ -40,6 +40,7 @@ import {
40
40
  AISDKError as AISDKError25,
41
41
  APICallError,
42
42
  EmptyResponseBodyError,
43
+ Experimental_EvaluationUnsupportedQuestionTypeError,
43
44
  InvalidPromptError,
44
45
  InvalidResponseDataError,
45
46
  JSONParseError,
@@ -1178,7 +1179,7 @@ import {
1178
1179
  } from "@ai-sdk/provider-utils";
1179
1180
 
1180
1181
  // src/version.ts
1181
- var VERSION = true ? "7.0.101" : "0.0.0-test";
1182
+ var VERSION = true ? "7.0.103" : "0.0.0-test";
1182
1183
 
1183
1184
  // src/util/download/download.ts
1184
1185
  var download = async ({
@@ -5248,11 +5249,16 @@ function prepareToolsForToolCallers({
5248
5249
  }) {
5249
5250
  var _a25, _b25;
5250
5251
  if (tools == null || toolCallers == null) {
5251
- return { executionTools: tools, modelTools: tools };
5252
+ return {
5253
+ executionTools: tools,
5254
+ modelTools: tools,
5255
+ toolCallerMessages: []
5256
+ };
5252
5257
  }
5253
5258
  const executionTools = { ...tools };
5254
5259
  const modelTools = { ...tools };
5255
5260
  const localToolsByCaller = /* @__PURE__ */ new Map();
5261
+ const toolCallerMessages = [];
5256
5262
  for (const [toolName, callerNames] of Object.entries(toolCallers)) {
5257
5263
  const tool2 = executionTools[toolName];
5258
5264
  if (tool2 == null) {
@@ -5296,13 +5302,44 @@ function prepareToolsForToolCallers({
5296
5302
  if ((caller == null ? void 0 : caller.type) !== "local") {
5297
5303
  continue;
5298
5304
  }
5299
- const boundCaller = caller.bind((_b25 = localToolsByCaller.get(callerName)) != null ? _b25 : {});
5305
+ const callerTools = (_b25 = localToolsByCaller.get(callerName)) != null ? _b25 : {};
5306
+ const boundCaller = caller.bind(callerTools);
5300
5307
  executionTools[callerName] = boundCaller;
5301
5308
  if (Object.prototype.hasOwnProperty.call(modelTools, callerName)) {
5302
- modelTools[callerName] = boundCaller;
5309
+ if (caller.prepareModelMessage == null) {
5310
+ modelTools[callerName] = boundCaller;
5311
+ } else {
5312
+ const content = caller.prepareModelMessage(callerTools);
5313
+ if (content != null) {
5314
+ toolCallerMessages.push({ role: "user", content });
5315
+ }
5316
+ }
5303
5317
  }
5304
5318
  }
5305
- return { executionTools, modelTools };
5319
+ return { executionTools, modelTools, toolCallerMessages };
5320
+ }
5321
+ function appendToolCallerMessages({
5322
+ messages,
5323
+ toolCallerMessages
5324
+ }) {
5325
+ var _a25;
5326
+ if (toolCallerMessages.length === 0) {
5327
+ return messages;
5328
+ }
5329
+ const latestUserText = (_a25 = messages.findLast(
5330
+ (message) => message.role === "user" && typeof message.content === "string"
5331
+ )) == null ? void 0 : _a25.content;
5332
+ const existingUserText = new Set(
5333
+ latestUserText == null ? [] : [latestUserText]
5334
+ );
5335
+ const additions = toolCallerMessages.filter((message) => {
5336
+ if (typeof message.content !== "string" || existingUserText.has(message.content)) {
5337
+ return false;
5338
+ }
5339
+ existingUserText.add(message.content);
5340
+ return true;
5341
+ });
5342
+ return additions.length === 0 ? messages : [...messages, ...additions];
5306
5343
  }
5307
5344
 
5308
5345
  // src/generate-text/tool-approval-signature.ts
@@ -5556,7 +5593,7 @@ async function generateText({
5556
5593
  experimental_include,
5557
5594
  include = experimental_include,
5558
5595
  _internal: {
5559
- generateId: generateId4 = originalGenerateId,
5596
+ generateId: generateId5 = originalGenerateId,
5560
5597
  generateCallId = originalGenerateCallId,
5561
5598
  now: now2 = now
5562
5599
  } = {},
@@ -5825,7 +5862,7 @@ async function generateText({
5825
5862
  type: "step",
5826
5863
  event: { callId, stepNumber },
5827
5864
  execute: async () => {
5828
- var _a27, _b26, _c2, _d2, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q;
5865
+ var _a27, _b26, _c2, _d2, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
5829
5866
  const accumulatedResponseMessages = [
5830
5867
  ...initialResponseMessages,
5831
5868
  ...steps.flatMap((step) => step.response.messages)
@@ -5849,29 +5886,21 @@ async function generateText({
5849
5886
  (_b26 = prepareStepResult == null ? void 0 : prepareStepResult.model) != null ? _b26 : model
5850
5887
  );
5851
5888
  const stepInstructions = (_d2 = (_c2 = prepareStepResult == null ? void 0 : prepareStepResult.instructions) != null ? _c2 : prepareStepResult == null ? void 0 : prepareStepResult.system) != null ? _d2 : instructionsForNextStep;
5852
- const promptMessages = await convertToLanguageModelPrompt({
5853
- prompt: {
5854
- instructions: stepInstructions,
5855
- messages: (_e = prepareStepResult == null ? void 0 : prepareStepResult.messages) != null ? _e : stepInputMessages
5856
- },
5857
- supportedUrls: await stepModel.supportedUrls,
5858
- download: download2,
5859
- provider: stepModel.provider.split(".")[0]
5860
- });
5861
- runtimeContext = (_f = prepareStepResult == null ? void 0 : prepareStepResult.runtimeContext) != null ? _f : runtimeContext;
5862
- toolsContext = (_g = prepareStepResult == null ? void 0 : prepareStepResult.toolsContext) != null ? _g : toolsContext;
5889
+ runtimeContext = (_e = prepareStepResult == null ? void 0 : prepareStepResult.runtimeContext) != null ? _e : runtimeContext;
5890
+ toolsContext = (_f = prepareStepResult == null ? void 0 : prepareStepResult.toolsContext) != null ? _f : toolsContext;
5863
5891
  const stepActiveTools = filterActiveTools({
5864
5892
  tools,
5865
- activeTools: (_h = prepareStepResult == null ? void 0 : prepareStepResult.activeTools) != null ? _h : activeTools
5893
+ activeTools: (_g = prepareStepResult == null ? void 0 : prepareStepResult.activeTools) != null ? _g : activeTools
5866
5894
  });
5867
5895
  const {
5868
5896
  executionTools: stepExecutionTools,
5869
- modelTools: stepModelTools
5897
+ modelTools: stepModelTools,
5898
+ toolCallerMessages
5870
5899
  } = prepareToolsForToolCallers({
5871
5900
  tools: stepActiveTools,
5872
5901
  toolCallers: resolvedToolCallers
5873
5902
  });
5874
- const stepToolOrder = (_i = prepareStepResult == null ? void 0 : prepareStepResult.toolOrder) != null ? _i : toolOrder;
5903
+ const stepToolOrder = (_h = prepareStepResult == null ? void 0 : prepareStepResult.toolOrder) != null ? _h : toolOrder;
5875
5904
  const stepTools = await prepareTools({
5876
5905
  tools: stepModelTools,
5877
5906
  toolOrder: stepToolOrder,
@@ -5880,9 +5909,21 @@ async function generateText({
5880
5909
  experimental_sandbox: stepSandbox
5881
5910
  });
5882
5911
  const stepToolChoice = prepareToolChoice({
5883
- toolChoice: (_j = prepareStepResult == null ? void 0 : prepareStepResult.toolChoice) != null ? _j : toolChoice
5912
+ toolChoice: (_i = prepareStepResult == null ? void 0 : prepareStepResult.toolChoice) != null ? _i : toolChoice
5913
+ });
5914
+ const stepMessages = appendToolCallerMessages({
5915
+ messages: (_j = prepareStepResult == null ? void 0 : prepareStepResult.messages) != null ? _j : stepInputMessages,
5916
+ toolCallerMessages
5917
+ });
5918
+ const promptMessages = await convertToLanguageModelPrompt({
5919
+ prompt: {
5920
+ instructions: stepInstructions,
5921
+ messages: stepMessages
5922
+ },
5923
+ supportedUrls: await stepModel.supportedUrls,
5924
+ download: download2,
5925
+ provider: stepModel.provider.split(".")[0]
5884
5926
  });
5885
- const stepMessages = (_k = prepareStepResult == null ? void 0 : prepareStepResult.messages) != null ? _k : stepInputMessages;
5886
5927
  const stepProviderOptions = mergeObjects(
5887
5928
  providerOptions,
5888
5929
  prepareStepResult == null ? void 0 : prepareStepResult.providerOptions
@@ -5900,8 +5941,8 @@ async function generateText({
5900
5941
  instructions: stepInstructions,
5901
5942
  messages: stepMessages,
5902
5943
  tools,
5903
- toolChoice: (_l = prepareStepResult == null ? void 0 : prepareStepResult.toolChoice) != null ? _l : toolChoice,
5904
- activeTools: (_m = prepareStepResult == null ? void 0 : prepareStepResult.activeTools) != null ? _m : activeTools,
5944
+ toolChoice: (_k = prepareStepResult == null ? void 0 : prepareStepResult.toolChoice) != null ? _k : toolChoice,
5945
+ activeTools: (_l = prepareStepResult == null ? void 0 : prepareStepResult.activeTools) != null ? _l : activeTools,
5905
5946
  toolOrder: stepToolOrder,
5906
5947
  steps: [...steps],
5907
5948
  providerOptions: stepProviderOptions,
@@ -5937,7 +5978,7 @@ async function generateText({
5937
5978
  telemetryDispatcher.onLanguageModelCallStart
5938
5979
  ]
5939
5980
  });
5940
- const executeLanguageModelCallInTelemetryContext = (_n = telemetryDispatcher.executeLanguageModelCall) != null ? _n : (async ({ execute }) => await execute());
5981
+ const executeLanguageModelCallInTelemetryContext = (_m = telemetryDispatcher.executeLanguageModelCall) != null ? _m : (async ({ execute }) => await execute());
5941
5982
  currentModelResponse = await retry(async () => {
5942
5983
  var _a28, _b27, _c3, _d3, _e2, _f2, _g2, _h2;
5943
5984
  const result = await executeLanguageModelCallInTelemetryContext(
@@ -5956,7 +5997,7 @@ async function generateText({
5956
5997
  }
5957
5998
  );
5958
5999
  const responseData = {
5959
- id: (_b27 = (_a28 = result.response) == null ? void 0 : _a28.id) != null ? _b27 : generateId4(),
6000
+ id: (_b27 = (_a28 = result.response) == null ? void 0 : _a28.id) != null ? _b27 : generateId5(),
5960
6001
  timestamp: (_d3 = (_c3 = result.response) == null ? void 0 : _c3.timestamp) != null ? _d3 : /* @__PURE__ */ new Date(),
5961
6002
  modelId: (_f2 = (_e2 = result.response) == null ? void 0 : _e2.modelId) != null ? _f2 : stepModel.modelId,
5962
6003
  headers: (_g2 = result.response) == null ? void 0 : _g2.headers,
@@ -6076,7 +6117,7 @@ async function generateText({
6076
6117
  if (toolApprovalStatus.type === "not-applicable") {
6077
6118
  continue;
6078
6119
  }
6079
- const approvalId = generateId4();
6120
+ const approvalId = generateId5();
6080
6121
  const signature = await maybeSignApproval({
6081
6122
  secret: experimental_toolApprovalSecret,
6082
6123
  approvalId,
@@ -6246,7 +6287,7 @@ async function generateText({
6246
6287
  });
6247
6288
  const stepRequest = {
6248
6289
  ...currentModelResponse.request,
6249
- body: include.requestBody ? (_o = currentModelResponse.request) == null ? void 0 : _o.body : void 0,
6290
+ body: include.requestBody ? (_n = currentModelResponse.request) == null ? void 0 : _n.body : void 0,
6250
6291
  messages: include.requestMessages ? cloneModelMessages(stepMessages) : void 0
6251
6292
  };
6252
6293
  const stepResponse = {
@@ -6254,7 +6295,7 @@ async function generateText({
6254
6295
  // deep clone msgs to avoid mutating step results in multi-step:
6255
6296
  messages: cloneModelMessages(stepResponseMessages),
6256
6297
  // Conditionally include response body:
6257
- body: include.responseBody ? (_p = currentModelResponse.response) == null ? void 0 : _p.body : void 0
6298
+ body: include.responseBody ? (_o = currentModelResponse.response) == null ? void 0 : _o.body : void 0
6258
6299
  };
6259
6300
  const currentStepResult = new DefaultStepResult({
6260
6301
  callId,
@@ -6274,7 +6315,7 @@ async function generateText({
6274
6315
  toolsContext
6275
6316
  });
6276
6317
  logWarnings({
6277
- warnings: (_q = currentModelResponse.warnings) != null ? _q : [],
6318
+ warnings: (_p = currentModelResponse.warnings) != null ? _p : [],
6278
6319
  provider: stepModel.provider,
6279
6320
  model: stepModel.modelId
6280
6321
  });
@@ -6970,6 +7011,25 @@ function getToolName(part) {
6970
7011
  }
6971
7012
  var getToolOrDynamicToolName = getToolName;
6972
7013
 
7014
+ // src/ui/warn-if-ui-message-has-deprecated-raw-input.ts
7015
+ var rawInputDeprecationWarning = {
7016
+ type: "deprecated",
7017
+ setting: "rawInput in output-error UI message parts",
7018
+ message: 'Use the "input" field instead. The "rawInput" field will be removed in the next major version.'
7019
+ };
7020
+ function warnIfUIMessageHasDeprecatedRawInput(messages) {
7021
+ const hasDeprecatedRawInput = messages.some(
7022
+ (message) => message.parts.some(
7023
+ (part) => part != null && typeof part === "object" && "type" in part && typeof part.type === "string" && (part.type === "dynamic-tool" || part.type.startsWith("tool-")) && "state" in part && part.state === "output-error" && Object.prototype.hasOwnProperty.call(part, "rawInput") && "rawInput" in part && part.rawInput !== void 0
7024
+ )
7025
+ );
7026
+ if (hasDeprecatedRawInput) {
7027
+ logWarnings({
7028
+ warnings: [rawInputDeprecationWarning]
7029
+ });
7030
+ }
7031
+ }
7032
+
6973
7033
  // src/ui/process-ui-message-stream.ts
6974
7034
  function createStreamingUIMessageState({
6975
7035
  lastMessage,
@@ -7423,6 +7483,7 @@ function processUIMessageStream({
7423
7483
  providerMetadata: chunk.providerMetadata,
7424
7484
  toolMetadata: chunk.toolMetadata
7425
7485
  });
7486
+ warnIfUIMessageHasDeprecatedRawInput([state.message]);
7426
7487
  }
7427
7488
  write();
7428
7489
  break;
@@ -8400,7 +8461,7 @@ function executeToolsFromStream({
8400
8461
  toolApproval,
8401
8462
  runtimeContext,
8402
8463
  toolApprovalSecret,
8403
- generateId: generateId4,
8464
+ generateId: generateId5,
8404
8465
  onToolExecutionStart,
8405
8466
  onToolExecutionEnd,
8406
8467
  executeToolInTelemetryContext,
@@ -8439,7 +8500,7 @@ function executeToolsFromStream({
8439
8500
  }
8440
8501
  return;
8441
8502
  }
8442
- const approvalId = generateId4();
8503
+ const approvalId = generateId5();
8443
8504
  const signature = await maybeSignApproval({
8444
8505
  secret: toolApprovalSecret,
8445
8506
  approvalId,
@@ -8474,6 +8535,11 @@ function executeToolsFromStream({
8474
8535
  reason: toolApprovalStatus.reason,
8475
8536
  providerExecuted: chunk.providerExecuted
8476
8537
  });
8538
+ controller.enqueue({
8539
+ type: "tool-output-denied",
8540
+ toolCallId: chunk.toolCallId,
8541
+ toolName: chunk.toolName
8542
+ });
8477
8543
  return;
8478
8544
  }
8479
8545
  case "approved": {
@@ -8722,7 +8788,7 @@ async function streamLanguageModelCall({
8722
8788
  toolsContext,
8723
8789
  experimental_sandbox: sandbox,
8724
8790
  _internal: {
8725
- generateId: generateId4 = originalGenerateId2,
8791
+ generateId: generateId5 = originalGenerateId2,
8726
8792
  generateCallId = originalGenerateCallId2,
8727
8793
  now: now2 = now
8728
8794
  } = {},
@@ -8808,7 +8874,7 @@ async function streamLanguageModelCall({
8808
8874
  provider: resolvedModel.provider,
8809
8875
  modelId: resolvedModel.modelId,
8810
8876
  toolChoice: stepToolChoice,
8811
- generateId: generateId4,
8877
+ generateId: generateId5,
8812
8878
  now: now2,
8813
8879
  callStartTimestampMs,
8814
8880
  onLanguageModelCallEnd
@@ -8830,7 +8896,7 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform({
8830
8896
  provider,
8831
8897
  modelId,
8832
8898
  toolChoice,
8833
- generateId: generateId4,
8899
+ generateId: generateId5,
8834
8900
  now: now2,
8835
8901
  callStartTimestampMs,
8836
8902
  onLanguageModelCallEnd
@@ -8842,7 +8908,7 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform({
8842
8908
  const reasoningPartIndexes = /* @__PURE__ */ new Map();
8843
8909
  const rawTextPartIndexes = /* @__PURE__ */ new Map();
8844
8910
  const rawReasoningPartIndexes = /* @__PURE__ */ new Map();
8845
- let responseId = generateId4();
8911
+ let responseId = generateId5();
8846
8912
  let responseModelId = modelId;
8847
8913
  let timeToFirstOutputMs;
8848
8914
  let previousOutputChunkTimestampMs;
@@ -9249,6 +9315,7 @@ var isOutputChunkType = {
9249
9315
  "tool-call": true,
9250
9316
  "tool-result": false,
9251
9317
  "tool-error": false,
9318
+ "tool-output-denied": false,
9252
9319
  "tool-execution-end": false,
9253
9320
  "model-call-start": false,
9254
9321
  "model-call-response-metadata": false,
@@ -9331,7 +9398,7 @@ function streamText({
9331
9398
  include = experimental_include,
9332
9399
  _internal: {
9333
9400
  now: now2 = now,
9334
- generateId: generateId4 = originalGenerateId3,
9401
+ generateId: generateId5 = originalGenerateId3,
9335
9402
  generateCallId = originalGenerateCallId3
9336
9403
  } = {},
9337
9404
  ...settings
@@ -9410,7 +9477,7 @@ function streamText({
9410
9477
  onToolExecutionStart: resolvedOnToolExecutionStart,
9411
9478
  onToolExecutionEnd: resolvedOnToolExecutionEnd,
9412
9479
  now: now2,
9413
- generateId: generateId4,
9480
+ generateId: generateId5,
9414
9481
  generateCallId,
9415
9482
  download: download2,
9416
9483
  // assign default values to include:
@@ -9638,7 +9705,7 @@ var DefaultStreamTextResult = class {
9638
9705
  providerOptions,
9639
9706
  prepareStep,
9640
9707
  now: now2,
9641
- generateId: generateId4,
9708
+ generateId: generateId5,
9642
9709
  generateCallId,
9643
9710
  timeout,
9644
9711
  onChunk,
@@ -9693,7 +9760,7 @@ var DefaultStreamTextResult = class {
9693
9760
  usedIds.add(id);
9694
9761
  return id;
9695
9762
  }
9696
- const generatedId = generateId4();
9763
+ const generatedId = generateId5();
9697
9764
  let uniqueId = generatedId;
9698
9765
  let suffix = 0;
9699
9766
  while (usedIds.has(uniqueId)) {
@@ -10374,7 +10441,8 @@ var DefaultStreamTextResult = class {
10374
10441
  });
10375
10442
  const {
10376
10443
  executionTools: stepExecutionTools,
10377
- modelTools: stepModelTools
10444
+ modelTools: stepModelTools,
10445
+ toolCallerMessages
10378
10446
  } = prepareToolsForToolCallers({
10379
10447
  tools: stepActiveTools,
10380
10448
  toolCallers: resolvedToolCallers
@@ -10390,7 +10458,10 @@ var DefaultStreamTextResult = class {
10390
10458
  const stepToolChoice = prepareToolChoice({
10391
10459
  toolChoice: (_h = prepareStepResult == null ? void 0 : prepareStepResult.toolChoice) != null ? _h : toolChoice
10392
10460
  });
10393
- const stepMessages = (_i = prepareStepResult == null ? void 0 : prepareStepResult.messages) != null ? _i : stepInputMessages;
10461
+ const stepMessages = appendToolCallerMessages({
10462
+ messages: (_i = prepareStepResult == null ? void 0 : prepareStepResult.messages) != null ? _i : stepInputMessages,
10463
+ toolCallerMessages
10464
+ });
10394
10465
  currentStepMessages = stepMessages;
10395
10466
  const stepInstructions = (_k = (_j = prepareStepResult == null ? void 0 : prepareStepResult.instructions) != null ? _j : prepareStepResult == null ? void 0 : prepareStepResult.system) != null ? _k : instructionsForNextStep;
10396
10467
  instructionsForNextStep = stepInstructions;
@@ -10628,7 +10699,7 @@ var DefaultStreamTextResult = class {
10628
10699
  toolApproval,
10629
10700
  runtimeContext,
10630
10701
  toolApprovalSecret: experimental_toolApprovalSecret,
10631
- generateId: generateId4,
10702
+ generateId: generateId5,
10632
10703
  // the callbacks need to be passed down and handled by executeToolCall
10633
10704
  // to guarantee that the onToolExecutionStart callback is invoked before the tool execute function
10634
10705
  onToolExecutionStart: filterNullable2(
@@ -10671,7 +10742,7 @@ var DefaultStreamTextResult = class {
10671
10742
  let modelCallPerformance = createModelCallPerformance();
10672
10743
  const toolExecutionMs = {};
10673
10744
  const createStepResponse = () => ({
10674
- id: generateId4(),
10745
+ id: generateId5(),
10675
10746
  timestamp: /* @__PURE__ */ new Date(),
10676
10747
  modelId: model.modelId
10677
10748
  });
@@ -10737,7 +10808,8 @@ var DefaultStreamTextResult = class {
10737
10808
  case "tool-input-start":
10738
10809
  case "tool-input-end":
10739
10810
  case "tool-input-delta":
10740
- case "tool-approval-request": {
10811
+ case "tool-approval-request":
10812
+ case "tool-output-denied": {
10741
10813
  enqueueStepPart(controller, chunk);
10742
10814
  break;
10743
10815
  }
@@ -11501,7 +11573,7 @@ function createUIMessageStream({
11501
11573
  onStepFinish,
11502
11574
  onEnd,
11503
11575
  onFinish,
11504
- generateId: generateId4 = generateIdFunc
11576
+ generateId: generateId5 = generateIdFunc
11505
11577
  }) {
11506
11578
  let controller;
11507
11579
  const ongoingStreamPromises = [];
@@ -11593,7 +11665,7 @@ function createUIMessageStream({
11593
11665
  });
11594
11666
  return handleUIMessageStreamFinish({
11595
11667
  stream,
11596
- messageId: generateId4(),
11668
+ messageId: generateId5(),
11597
11669
  originalMessages,
11598
11670
  onStepEnd: onStepEnd != null ? onStepEnd : onStepFinish,
11599
11671
  onEnd: onEnd != null ? onEnd : onFinish,
@@ -11677,6 +11749,7 @@ import {
11677
11749
  } from "@ai-sdk/provider-utils";
11678
11750
  async function convertToModelMessages(messages, options) {
11679
11751
  const modelMessages = [];
11752
+ warnIfUIMessageHasDeprecatedRawInput(messages);
11680
11753
  if (options == null ? void 0 : options.ignoreIncompleteToolCalls) {
11681
11754
  messages = messages.map((message) => ({
11682
11755
  ...message,
@@ -12350,6 +12423,7 @@ async function safeValidateUIMessagesInternal({
12350
12423
  value: messages,
12351
12424
  schema: uiMessagesSchema
12352
12425
  });
12426
+ warnIfUIMessageHasDeprecatedRawInput(validatedMessages);
12353
12427
  if (metadataSchema) {
12354
12428
  for (const [msgIdx, message] of validatedMessages.entries()) {
12355
12429
  await validateTypes4({
@@ -13982,10 +14056,318 @@ var DefaultEmbedManyResult = class {
13982
14056
  }
13983
14057
  };
13984
14058
 
14059
+ // src/evaluate/evaluate.ts
14060
+ import {
14061
+ Experimental_EvaluationUnsupportedQuestionTypeError as EvaluationUnsupportedQuestionTypeError
14062
+ } from "@ai-sdk/provider";
14063
+ import {
14064
+ withUserAgentSuffix as withUserAgentSuffix8
14065
+ } from "@ai-sdk/provider-utils";
14066
+
14067
+ // src/evaluate/validate-evaluation.ts
14068
+ import {
14069
+ InvalidResponseDataError as InvalidResponseDataError3
14070
+ } from "@ai-sdk/provider";
14071
+ var tolerance = 1e-6;
14072
+ function isRecord(value) {
14073
+ if (value == null || typeof value !== "object" || Array.isArray(value))
14074
+ return false;
14075
+ const prototype = Object.getPrototypeOf(value);
14076
+ return prototype === Object.prototype || prototype === null;
14077
+ }
14078
+ function isJSON(value, ancestors = /* @__PURE__ */ new Set()) {
14079
+ if (value === null || typeof value === "string" || typeof value === "boolean")
14080
+ return true;
14081
+ if (typeof value === "number") return Number.isFinite(value);
14082
+ if (typeof value !== "object" || !Array.isArray(value) && !isRecord(value))
14083
+ return false;
14084
+ if (ancestors.has(value)) return false;
14085
+ ancestors.add(value);
14086
+ const valid = Object.getOwnPropertySymbols(value).length === 0 && (Array.isArray(value) ? Array.from(value).every((item) => isJSON(item, ancestors)) : Object.values(value).every((item) => isJSON(item, ancestors)));
14087
+ ancestors.delete(value);
14088
+ return valid;
14089
+ }
14090
+ function isInput(value) {
14091
+ return (typeof value === "string" || Array.isArray(value) || isRecord(value)) && isJSON(value);
14092
+ }
14093
+ function invalidInput(parameter, value, message) {
14094
+ throw new InvalidArgumentError({ parameter, value, message });
14095
+ }
14096
+ function validateEvaluationInput({
14097
+ state,
14098
+ questions
14099
+ }) {
14100
+ if (!isInput(state)) {
14101
+ invalidInput(
14102
+ "state",
14103
+ state,
14104
+ "must be a JSON-compatible string, object, or array"
14105
+ );
14106
+ }
14107
+ if (!isRecord(questions) || Object.keys(questions).length === 0) {
14108
+ invalidInput("questions", questions, "must be a nonempty question map");
14109
+ }
14110
+ for (const [id, question] of Object.entries(questions)) {
14111
+ const parameter = `questions.${id}`;
14112
+ if (!isRecord(question) || !isInput(question.instructions)) {
14113
+ invalidInput(
14114
+ parameter,
14115
+ question,
14116
+ "instructions must be a JSON-compatible string, object, or array"
14117
+ );
14118
+ }
14119
+ const criteria = question.criteria;
14120
+ switch (question.type) {
14121
+ case "choice":
14122
+ if (!isRecord(criteria) || Object.keys(criteria).length === 0) {
14123
+ invalidInput(
14124
+ parameter,
14125
+ question,
14126
+ "choice criteria must be a nonempty option map"
14127
+ );
14128
+ }
14129
+ break;
14130
+ case "score":
14131
+ if (!Array.isArray(criteria) || criteria.length < 2) {
14132
+ invalidInput(
14133
+ parameter,
14134
+ question,
14135
+ "score criteria must contain at least two ordered levels"
14136
+ );
14137
+ }
14138
+ break;
14139
+ case "boolean":
14140
+ if (criteria === void 0) continue;
14141
+ if (!isRecord(criteria) || Object.keys(criteria).some((key) => key !== "true" && key !== "false")) {
14142
+ invalidInput(
14143
+ parameter,
14144
+ question,
14145
+ "boolean criteria may only describe true and false"
14146
+ );
14147
+ }
14148
+ break;
14149
+ default:
14150
+ invalidInput(
14151
+ parameter,
14152
+ question,
14153
+ "question type must be choice, score, or boolean"
14154
+ );
14155
+ }
14156
+ if (!isJSON(criteria) || Object.values(criteria).some((value) => value !== null && !isInput(value))) {
14157
+ invalidInput(
14158
+ parameter,
14159
+ question,
14160
+ "criteria descriptions must be JSON-compatible strings, objects, arrays, or null"
14161
+ );
14162
+ }
14163
+ }
14164
+ }
14165
+ function invalidAnswer(answers, message) {
14166
+ throw new InvalidResponseDataError3({ data: answers, message });
14167
+ }
14168
+ function isProbability(value) {
14169
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
14170
+ }
14171
+ function hasExactKeys(value, keys) {
14172
+ return Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
14173
+ }
14174
+ function validateDistribution(value, keys, answers, id, roundingError) {
14175
+ if (!isRecord(value) || !hasExactKeys(value, keys) || !Object.values(value).every(isProbability)) {
14176
+ invalidAnswer(
14177
+ answers,
14178
+ `Question "${id}" must have a complete distribution of finite probabilities in [0, 1].`
14179
+ );
14180
+ }
14181
+ const sum = Object.values(value).reduce(
14182
+ (total, probability) => total + probability,
14183
+ 0
14184
+ );
14185
+ if (Math.abs(sum - 1) > tolerance + keys.length * roundingError) {
14186
+ invalidAnswer(
14187
+ answers,
14188
+ `Question "${id}" probabilities must sum to 1 within the declared rounding precision.`
14189
+ );
14190
+ }
14191
+ }
14192
+ function validateEvaluationAnswers({
14193
+ questions,
14194
+ answers,
14195
+ rounding
14196
+ }) {
14197
+ function roundingError(decimals) {
14198
+ if (decimals === void 0) return 0;
14199
+ if (!Number.isInteger(decimals) || decimals < 0 || decimals > 15) {
14200
+ invalidAnswer(
14201
+ answers,
14202
+ "Evaluation rounding decimals must be integers between 0 and 15."
14203
+ );
14204
+ }
14205
+ return 0.5 * 10 ** -decimals;
14206
+ }
14207
+ const probabilityError = roundingError(rounding == null ? void 0 : rounding.probabilityDecimals);
14208
+ const scoreError = roundingError(rounding == null ? void 0 : rounding.scoreDecimals);
14209
+ if (!isRecord(answers) || !hasExactKeys(answers, Object.keys(questions))) {
14210
+ invalidAnswer(
14211
+ answers,
14212
+ "Evaluation must return exactly one answer for every question."
14213
+ );
14214
+ }
14215
+ for (const [id, question] of Object.entries(questions)) {
14216
+ const answer = answers[id];
14217
+ if (!isRecord(answer) || answer.type !== question.type) {
14218
+ invalidAnswer(
14219
+ answers,
14220
+ `Question "${id}" returned an answer with the wrong type.`
14221
+ );
14222
+ }
14223
+ switch (question.type) {
14224
+ case "choice": {
14225
+ if (typeof answer.choice !== "string" || !Object.hasOwn(question.criteria, answer.choice)) {
14226
+ invalidAnswer(
14227
+ answers,
14228
+ `Question "${id}" selected an unknown option.`
14229
+ );
14230
+ }
14231
+ if (answer.probabilities !== void 0) {
14232
+ validateDistribution(
14233
+ answer.probabilities,
14234
+ Object.keys(question.criteria),
14235
+ answers,
14236
+ id,
14237
+ probabilityError
14238
+ );
14239
+ const selected = answer.probabilities[answer.choice];
14240
+ if (Object.values(answer.probabilities).some(
14241
+ (probability) => probability > selected + tolerance
14242
+ )) {
14243
+ invalidAnswer(
14244
+ answers,
14245
+ `Question "${id}" did not select a highest-probability option.`
14246
+ );
14247
+ }
14248
+ }
14249
+ break;
14250
+ }
14251
+ case "score": {
14252
+ if (typeof answer.score !== "number" || !Number.isFinite(answer.score) || answer.score < 0 || answer.score > question.criteria.length - 1) {
14253
+ invalidAnswer(
14254
+ answers,
14255
+ `Question "${id}" score must be in [0, ${question.criteria.length - 1}].`
14256
+ );
14257
+ }
14258
+ if (answer.probabilities !== void 0) {
14259
+ const keys = question.criteria.map((_, index) => String(index));
14260
+ validateDistribution(
14261
+ answer.probabilities,
14262
+ keys,
14263
+ answers,
14264
+ id,
14265
+ probabilityError
14266
+ );
14267
+ const mean = Object.entries(answer.probabilities).reduce(
14268
+ (total, [index, probability]) => total + Number(index) * probability,
14269
+ 0
14270
+ );
14271
+ const meanRoundingError = keys.reduce(
14272
+ (total, index) => total + Number(index) * probabilityError,
14273
+ 0
14274
+ );
14275
+ if (Math.abs(mean - answer.score) > tolerance + meanRoundingError + scoreError) {
14276
+ invalidAnswer(
14277
+ answers,
14278
+ `Question "${id}" score must equal the probability-weighted mean within the declared rounding precision.`
14279
+ );
14280
+ }
14281
+ }
14282
+ break;
14283
+ }
14284
+ case "boolean":
14285
+ if (!isProbability(answer.probability)) {
14286
+ invalidAnswer(
14287
+ answers,
14288
+ `Question "${id}" must return P(true) as a finite probability in [0, 1].`
14289
+ );
14290
+ }
14291
+ break;
14292
+ }
14293
+ }
14294
+ }
14295
+
14296
+ // src/evaluate/evaluate.ts
14297
+ async function evaluate({
14298
+ model,
14299
+ state,
14300
+ questions,
14301
+ maxRetries,
14302
+ abortSignal,
14303
+ headers,
14304
+ providerOptions = {}
14305
+ }) {
14306
+ var _a25, _b25, _c, _d, _e, _f;
14307
+ if (model.specificationVersion !== "v4") {
14308
+ throw new UnsupportedModelVersionError({
14309
+ version: model.specificationVersion,
14310
+ provider: model.provider,
14311
+ modelId: model.modelId
14312
+ });
14313
+ }
14314
+ validateEvaluationInput({ state, questions });
14315
+ for (const [questionId, question] of Object.entries(questions)) {
14316
+ if (!model.supportedQuestionTypes.includes(question.type)) {
14317
+ throw new EvaluationUnsupportedQuestionTypeError({
14318
+ questionId,
14319
+ questionType: question.type,
14320
+ provider: model.provider,
14321
+ modelId: model.modelId
14322
+ });
14323
+ }
14324
+ }
14325
+ const { retry } = prepareRetries({ maxRetries, abortSignal });
14326
+ const result = await retry(() => {
14327
+ abortSignal == null ? void 0 : abortSignal.throwIfAborted();
14328
+ return model.doEvaluate({
14329
+ state,
14330
+ questions,
14331
+ abortSignal,
14332
+ headers: withUserAgentSuffix8(headers != null ? headers : {}, `ai/${VERSION}`),
14333
+ providerOptions
14334
+ });
14335
+ });
14336
+ abortSignal == null ? void 0 : abortSignal.throwIfAborted();
14337
+ validateEvaluationAnswers({
14338
+ questions,
14339
+ answers: result.answers,
14340
+ rounding: result.rounding
14341
+ });
14342
+ logWarnings({
14343
+ warnings: result.warnings,
14344
+ provider: model.provider,
14345
+ model: model.modelId
14346
+ });
14347
+ const inputTokens = (_a25 = result.usage) == null ? void 0 : _a25.inputTokens;
14348
+ const outputTokens = (_b25 = result.usage) == null ? void 0 : _b25.outputTokens;
14349
+ return {
14350
+ answers: result.answers,
14351
+ usage: {
14352
+ inputTokens,
14353
+ outputTokens,
14354
+ totalTokens: inputTokens != null && outputTokens != null ? inputTokens + outputTokens : void 0
14355
+ },
14356
+ warnings: result.warnings,
14357
+ rounding: result.rounding,
14358
+ providerMetadata: result.providerMetadata,
14359
+ response: {
14360
+ ...result.response,
14361
+ timestamp: (_d = (_c = result.response) == null ? void 0 : _c.timestamp) != null ? _d : /* @__PURE__ */ new Date(),
14362
+ modelId: (_f = (_e = result.response) == null ? void 0 : _e.modelId) != null ? _f : model.modelId
14363
+ }
14364
+ };
14365
+ }
14366
+
13985
14367
  // src/generate-object/generate-object.ts
13986
14368
  import {
13987
14369
  createIdGenerator as createIdGenerator6,
13988
- withUserAgentSuffix as withUserAgentSuffix8
14370
+ withUserAgentSuffix as withUserAgentSuffix9
13989
14371
  } from "@ai-sdk/provider-utils";
13990
14372
 
13991
14373
  // src/generate-text/extract-reasoning-content.ts
@@ -14488,7 +14870,7 @@ async function generateObject(options) {
14488
14870
  onStepFinish,
14489
14871
  onFinish,
14490
14872
  _internal: {
14491
- generateId: generateId4 = originalGenerateId4,
14873
+ generateId: generateId5 = originalGenerateId4,
14492
14874
  currentDate = () => /* @__PURE__ */ new Date()
14493
14875
  } = {},
14494
14876
  ...settings
@@ -14517,7 +14899,7 @@ async function generateObject(options) {
14517
14899
  enumValues
14518
14900
  });
14519
14901
  const callSettings = prepareLanguageModelCallOptions(settings);
14520
- const headersWithUserAgent = withUserAgentSuffix8(
14902
+ const headersWithUserAgent = withUserAgentSuffix9(
14521
14903
  headers != null ? headers : {},
14522
14904
  `ai/${VERSION}`
14523
14905
  );
@@ -14528,7 +14910,7 @@ async function generateObject(options) {
14528
14910
  const resolvedOnStepStart = onStepStart != null ? onStepStart : experimental_onStepStart;
14529
14911
  const resolvedOnStepEnd = onStepEnd != null ? onStepEnd : onStepFinish;
14530
14912
  const jsonSchema2 = await outputStrategy.jsonSchema();
14531
- const callId = generateId4();
14913
+ const callId = generateId5();
14532
14914
  await notify({
14533
14915
  event: {
14534
14916
  callId,
@@ -14597,7 +14979,7 @@ async function generateObject(options) {
14597
14979
  })
14598
14980
  );
14599
14981
  const responseData = {
14600
- id: (_b25 = (_a25 = generateResult.response) == null ? void 0 : _a25.id) != null ? _b25 : generateId4(),
14982
+ id: (_b25 = (_a25 = generateResult.response) == null ? void 0 : _a25.id) != null ? _b25 : generateId5(),
14601
14983
  timestamp: (_d = (_c = generateResult.response) == null ? void 0 : _c.timestamp) != null ? _d : currentDate(),
14602
14984
  modelId: (_f = (_e = generateResult.response) == null ? void 0 : _e.modelId) != null ? _f : model.modelId,
14603
14985
  headers: (_g = generateResult.response) == null ? void 0 : _g.headers,
@@ -14857,7 +15239,7 @@ function streamObject(options) {
14857
15239
  },
14858
15240
  onFinish,
14859
15241
  _internal: {
14860
- generateId: generateId4 = originalGenerateId5,
15242
+ generateId: generateId5 = originalGenerateId5,
14861
15243
  currentDate = () => /* @__PURE__ */ new Date(),
14862
15244
  now: now2 = now
14863
15245
  } = {},
@@ -14904,7 +15286,7 @@ function streamObject(options) {
14904
15286
  onError,
14905
15287
  onFinish,
14906
15288
  download: download2,
14907
- generateId: generateId4,
15289
+ generateId: generateId5,
14908
15290
  currentDate,
14909
15291
  now: now2
14910
15292
  });
@@ -14933,7 +15315,7 @@ var DefaultStreamObjectResult = class {
14933
15315
  onError,
14934
15316
  onFinish,
14935
15317
  download: download2,
14936
- generateId: generateId4,
15318
+ generateId: generateId5,
14937
15319
  currentDate,
14938
15320
  now: now2
14939
15321
  }) {
@@ -14967,7 +15349,7 @@ var DefaultStreamObjectResult = class {
14967
15349
  }
14968
15350
  });
14969
15351
  this.baseStream = stitchableStream.stream.pipeThrough(eventProcessor);
14970
- const callId = generateId4();
15352
+ const callId = generateId5();
14971
15353
  (async () => {
14972
15354
  const jsonSchema2 = await outputStrategy.jsonSchema();
14973
15355
  await notify({
@@ -15065,7 +15447,7 @@ var DefaultStreamObjectResult = class {
15065
15447
  let accumulatedText = "";
15066
15448
  let textDelta = "";
15067
15449
  let fullResponse = {
15068
- id: generateId4(),
15450
+ id: generateId5(),
15069
15451
  timestamp: currentDate(),
15070
15452
  modelId: model.modelId
15071
15453
  };
@@ -15381,7 +15763,7 @@ var DefaultStreamObjectResult = class {
15381
15763
  // src/generate-speech/generate-speech.ts
15382
15764
  import {
15383
15765
  detectMediaType as detectMediaType4,
15384
- withUserAgentSuffix as withUserAgentSuffix9
15766
+ withUserAgentSuffix as withUserAgentSuffix10
15385
15767
  } from "@ai-sdk/provider-utils";
15386
15768
 
15387
15769
  // src/generate-speech/generated-audio-file.ts
@@ -15428,7 +15810,7 @@ async function generateSpeech({
15428
15810
  if (!resolvedModel) {
15429
15811
  throw new Error("Model could not be resolved");
15430
15812
  }
15431
- const headersWithUserAgent = withUserAgentSuffix9(
15813
+ const headersWithUserAgent = withUserAgentSuffix10(
15432
15814
  headers != null ? headers : {},
15433
15815
  `ai/${VERSION}`
15434
15816
  );
@@ -15738,7 +16120,7 @@ import {
15738
16120
  convertBase64ToUint8Array as convertBase64ToUint8Array5,
15739
16121
  delay as defaultDelay,
15740
16122
  generateId,
15741
- withUserAgentSuffix as withUserAgentSuffix10,
16123
+ withUserAgentSuffix as withUserAgentSuffix11,
15742
16124
  detectMediaType as detectMediaType5
15743
16125
  } from "@ai-sdk/provider-utils";
15744
16126
  var defaultDownload = createDownload();
@@ -15765,7 +16147,7 @@ async function experimental_generateVideo({
15765
16147
  }) {
15766
16148
  var _a25;
15767
16149
  const model = resolveVideoModel(modelArg);
15768
- const headersWithUserAgent = withUserAgentSuffix10(
16150
+ const headersWithUserAgent = withUserAgentSuffix11(
15769
16151
  headers != null ? headers : {},
15770
16152
  `ai/${VERSION}`
15771
16153
  );
@@ -16172,7 +16554,7 @@ async function invokeModelMaxVideosPerCall(model) {
16172
16554
  // src/generate-video/start-video.ts
16173
16555
  import {
16174
16556
  generateId as generateId2,
16175
- withUserAgentSuffix as withUserAgentSuffix11
16557
+ withUserAgentSuffix as withUserAgentSuffix12
16176
16558
  } from "@ai-sdk/provider-utils";
16177
16559
  async function experimental_startVideo({
16178
16560
  model: modelArg,
@@ -16238,7 +16620,7 @@ async function experimental_startVideo({
16238
16620
  generateAudio,
16239
16621
  providerOptions: providerOptions != null ? providerOptions : {},
16240
16622
  headers: {
16241
- ...withUserAgentSuffix11(headers != null ? headers : {}, `ai/${VERSION}`),
16623
+ ...withUserAgentSuffix12(headers != null ? headers : {}, `ai/${VERSION}`),
16242
16624
  ...callerIdempotencyKey ? {} : { "idempotency-key": `aisdk_vid_${generateId2()}` }
16243
16625
  },
16244
16626
  abortSignal,
@@ -16254,7 +16636,7 @@ async function experimental_startVideo({
16254
16636
  }
16255
16637
 
16256
16638
  // src/generate-video/get-video-status.ts
16257
- import { withUserAgentSuffix as withUserAgentSuffix12 } from "@ai-sdk/provider-utils";
16639
+ import { withUserAgentSuffix as withUserAgentSuffix13 } from "@ai-sdk/provider-utils";
16258
16640
  async function experimental_getVideoStatus(modelArg, {
16259
16641
  operation,
16260
16642
  headers,
@@ -16274,7 +16656,7 @@ async function experimental_getVideoStatus(modelArg, {
16274
16656
  return retry(
16275
16657
  () => model.doStatus({
16276
16658
  operation,
16277
- headers: withUserAgentSuffix12(headers != null ? headers : {}, `ai/${VERSION}`),
16659
+ headers: withUserAgentSuffix13(headers != null ? headers : {}, `ai/${VERSION}`),
16278
16660
  abortSignal
16279
16661
  })
16280
16662
  );
@@ -17029,6 +17411,9 @@ function resolveRealtimeToolDescription({
17029
17411
  });
17030
17412
  }
17031
17413
 
17414
+ // src/realtime/realtime-session.ts
17415
+ import { UnsupportedFunctionalityError as UnsupportedFunctionalityError6 } from "@ai-sdk/provider";
17416
+
17032
17417
  // src/realtime/browser-realtime-audio.ts
17033
17418
  var BrowserRealtimeAudio = class {
17034
17419
  constructor(options) {
@@ -17036,16 +17421,23 @@ var BrowserRealtimeAudio = class {
17036
17421
  this.captureProcessor = null;
17037
17422
  this.captureSource = null;
17038
17423
  this.captureStream = null;
17424
+ this.ownsCaptureStream = true;
17425
+ this.captureCleanup = [];
17426
+ this.captureGeneration = 0;
17039
17427
  this.playbackContext = null;
17040
17428
  this.playbackQueue = [];
17041
17429
  this.playbackTime = 0;
17042
17430
  this.playbackStartTime = 0;
17043
17431
  this.activeSources = /* @__PURE__ */ new Set();
17044
17432
  this.isPlaying = false;
17433
+ this.playbackPaused = false;
17434
+ var _a25;
17045
17435
  this.captureSampleRate = options.captureSampleRate;
17046
17436
  this.playbackSampleRate = options.playbackSampleRate;
17047
17437
  this.onAudio = options.onAudio;
17048
17438
  this.onPlayingChange = options.onPlayingChange;
17439
+ this.maxPlaybackSeconds = (_a25 = options.maxPlaybackSeconds) != null ? _a25 : Infinity;
17440
+ this.onError = options.onError;
17049
17441
  this.onCapturingChange = options.onCapturingChange;
17050
17442
  }
17051
17443
  ensurePlaybackContext() {
@@ -17053,35 +17445,97 @@ var BrowserRealtimeAudio = class {
17053
17445
  this.playbackContext = new AudioContext({
17054
17446
  sampleRate: this.playbackSampleRate
17055
17447
  });
17448
+ const context = this.playbackContext;
17449
+ context.onstatechange = () => {
17450
+ if (this.playbackContext === context)
17451
+ this.setPlaying(
17452
+ context.state === "running" && this.activeSources.size > 0
17453
+ );
17454
+ };
17056
17455
  }
17057
17456
  }
17058
- startCapture(stream) {
17457
+ async resumePlayback() {
17458
+ var _a25;
17459
+ this.ensurePlaybackContext();
17460
+ const context = this.playbackContext;
17461
+ if (this.playbackPaused) this.stopPlayback();
17462
+ if (this.playbackContext !== context) return;
17463
+ await (context == null ? void 0 : context.resume());
17464
+ if (this.playbackContext !== context) return;
17465
+ this.playbackPaused = false;
17466
+ this.setPlaying(
17467
+ ((_a25 = this.playbackContext) == null ? void 0 : _a25.state) === "running" && this.activeSources.size > 0
17468
+ );
17469
+ }
17470
+ startCapture(stream, options) {
17471
+ var _a25, _b25;
17472
+ const generation = this.captureGeneration + 1;
17473
+ this.stopCapture();
17474
+ if (generation !== this.captureGeneration) {
17475
+ if ((_a25 = options == null ? void 0 : options.ownsStream) != null ? _a25 : true)
17476
+ stream.getTracks().forEach((track) => track.stop());
17477
+ return;
17478
+ }
17479
+ this.captureStream = stream;
17480
+ this.ownsCaptureStream = (_b25 = options == null ? void 0 : options.ownsStream) != null ? _b25 : true;
17059
17481
  const ctx = new AudioContext({ sampleRate: this.captureSampleRate });
17060
17482
  this.captureContext = ctx;
17061
- this.captureStream = stream;
17483
+ void ctx.resume().catch((error) => {
17484
+ var _a26;
17485
+ if (this.captureContext === ctx) (_a26 = this.onError) == null ? void 0 : _a26.call(this, error);
17486
+ });
17062
17487
  const source = ctx.createMediaStreamSource(stream);
17063
17488
  this.captureSource = source;
17064
- const processor = ctx.createScriptProcessor(4096, 1, 1);
17489
+ const processor = ctx.createScriptProcessor(1024, 1, 1);
17065
17490
  this.captureProcessor = processor;
17066
17491
  processor.onaudioprocess = (event) => {
17492
+ var _a26;
17493
+ if (this.captureContext !== ctx) return;
17067
17494
  const inputData = event.inputBuffer.getChannelData(0);
17068
17495
  const samples = resampleAudio(
17069
17496
  new Float32Array(inputData),
17070
17497
  ctx.sampleRate,
17071
17498
  this.captureSampleRate
17072
17499
  );
17073
- this.onAudio(encodeRealtimeAudio(samples));
17500
+ try {
17501
+ this.onAudio(encodeRealtimeAudio(samples));
17502
+ } catch (error) {
17503
+ (_a26 = this.onError) == null ? void 0 : _a26.call(
17504
+ this,
17505
+ error instanceof Error ? error : new Error(String(error))
17506
+ );
17507
+ }
17074
17508
  };
17075
17509
  source.connect(processor);
17076
17510
  processor.connect(ctx.destination);
17077
- this.onCapturingChange(true);
17511
+ const updateCapturing = () => this.onCapturingChange(
17512
+ stream.getAudioTracks().some(
17513
+ (track) => track.enabled && !track.muted && track.readyState === "live"
17514
+ )
17515
+ );
17516
+ for (const track of stream.getAudioTracks()) {
17517
+ for (const event of ["ended", "mute", "unmute"]) {
17518
+ track.addEventListener(event, updateCapturing);
17519
+ this.captureCleanup.push(
17520
+ () => track.removeEventListener(event, updateCapturing)
17521
+ );
17522
+ }
17523
+ }
17524
+ updateCapturing();
17078
17525
  }
17079
17526
  stopCapture() {
17080
17527
  var _a25, _b25, _c, _d;
17528
+ this.captureGeneration++;
17529
+ if (this.captureProcessor != null)
17530
+ this.captureProcessor.onaudioprocess = null;
17531
+ for (const cleanup of this.captureCleanup) cleanup();
17532
+ this.captureCleanup = [];
17081
17533
  (_a25 = this.captureProcessor) == null ? void 0 : _a25.disconnect();
17082
17534
  (_b25 = this.captureSource) == null ? void 0 : _b25.disconnect();
17083
- void ((_c = this.captureContext) == null ? void 0 : _c.close());
17084
- (_d = this.captureStream) == null ? void 0 : _d.getTracks().forEach((track) => track.stop());
17535
+ void ((_c = this.captureContext) == null ? void 0 : _c.close().catch(() => {
17536
+ }));
17537
+ if (this.ownsCaptureStream)
17538
+ (_d = this.captureStream) == null ? void 0 : _d.getTracks().forEach((track) => track.stop());
17085
17539
  this.captureProcessor = null;
17086
17540
  this.captureSource = null;
17087
17541
  this.captureContext = null;
@@ -17089,15 +17543,34 @@ var BrowserRealtimeAudio = class {
17089
17543
  this.onCapturingChange(false);
17090
17544
  }
17091
17545
  playAudio(base64Audio) {
17546
+ var _a25, _b25, _c;
17547
+ if (this.playbackPaused) return;
17092
17548
  this.ensurePlaybackContext();
17093
- this.playbackQueue.push(decodeRealtimeAudio(base64Audio));
17549
+ const samples = decodeRealtimeAudio(base64Audio);
17550
+ if (Math.max(
17551
+ 0,
17552
+ this.playbackTime - ((_b25 = (_a25 = this.playbackContext) == null ? void 0 : _a25.currentTime) != null ? _b25 : 0)
17553
+ ) + samples.length / this.playbackSampleRate > this.maxPlaybackSeconds) {
17554
+ this.playbackPaused = true;
17555
+ this.stopPlayback();
17556
+ (_c = this.onError) == null ? void 0 : _c.call(
17557
+ this,
17558
+ new Error(
17559
+ "Realtime audio playback buffer is full; playback paused, call resumePlayback() to resume at the live edge"
17560
+ )
17561
+ );
17562
+ return;
17563
+ }
17564
+ this.playbackQueue.push(samples);
17094
17565
  this.schedulePlayback();
17095
17566
  }
17096
17567
  stopPlayback() {
17097
17568
  this.playbackQueue = [];
17098
17569
  for (const source of this.activeSources) {
17099
17570
  try {
17571
+ source.onended = null;
17100
17572
  source.stop();
17573
+ source.disconnect();
17101
17574
  } catch (e) {
17102
17575
  }
17103
17576
  }
@@ -17116,8 +17589,12 @@ var BrowserRealtimeAudio = class {
17116
17589
  var _a25;
17117
17590
  this.stopCapture();
17118
17591
  this.stopPlayback();
17119
- void ((_a25 = this.playbackContext) == null ? void 0 : _a25.close());
17592
+ if (this.playbackContext != null) this.playbackContext.onstatechange = null;
17593
+ void ((_a25 = this.playbackContext) == null ? void 0 : _a25.close().catch(() => {
17594
+ }));
17120
17595
  this.playbackContext = null;
17596
+ this.playbackTime = 0;
17597
+ this.playbackPaused = false;
17121
17598
  }
17122
17599
  setPlaying(isPlaying) {
17123
17600
  var _a25, _b25;
@@ -17147,8 +17624,10 @@ var BrowserRealtimeAudio = class {
17147
17624
  source.start(startTime);
17148
17625
  this.playbackTime = startTime + buffer.duration;
17149
17626
  this.activeSources.add(source);
17150
- this.setPlaying(true);
17627
+ this.setPlaying(ctx.state === "running");
17628
+ if (this.playbackContext !== ctx) return;
17151
17629
  source.onended = () => {
17630
+ source.disconnect();
17152
17631
  this.activeSources.delete(source);
17153
17632
  if (this.playbackQueue.length === 0 && this.activeSources.size === 0) {
17154
17633
  this.setPlaying(false);
@@ -17158,103 +17637,1258 @@ var BrowserRealtimeAudio = class {
17158
17637
  }
17159
17638
  };
17160
17639
 
17161
- // src/realtime/browser-realtime-transport.ts
17640
+ // src/realtime/realtime-event-channel.ts
17162
17641
  import { safeParseJSON as safeParseJSON5 } from "@ai-sdk/provider-utils";
17642
+ var RealtimeEventChannel = class {
17643
+ constructor(options) {
17644
+ this.options = options;
17645
+ this.active = true;
17646
+ this.finishing = false;
17647
+ this.incoming = Promise.resolve();
17648
+ this.outgoing = Promise.resolve();
17649
+ this.incomingCount = 0;
17650
+ this.incomingBytes = 0;
17651
+ this.outgoingCount = 0;
17652
+ var _a25, _b25, _c;
17653
+ this.parse = (_c = (_b25 = (_a25 = options.model).createServerEventParser) == null ? void 0 : _b25.call(_a25)) != null ? _c : options.model.parseServerEvent.bind(options.model);
17654
+ }
17655
+ dispose() {
17656
+ this.active = false;
17657
+ }
17658
+ /** Stop accepting messages, but deliver already received terminal events. */
17659
+ async finish() {
17660
+ this.stopWriting();
17661
+ await this.incoming;
17662
+ this.dispose();
17663
+ }
17664
+ stopWriting() {
17665
+ this.finishing = true;
17666
+ }
17667
+ send(event, shouldSend) {
17668
+ var _a25;
17669
+ if (!this.active || this.finishing)
17670
+ throw new Error("Realtime connection is closed");
17671
+ if (this.outgoingCount >= ((_a25 = this.options.maxPending) != null ? _a25 : 512)) {
17672
+ throw new Error("Realtime outgoing queue is full");
17673
+ }
17674
+ this.outgoingCount++;
17675
+ const operation = this.outgoing.then(async () => {
17676
+ if (!this.active || this.finishing)
17677
+ throw new Error("Realtime connection is closed");
17678
+ if ((shouldSend == null ? void 0 : shouldSend()) === false) return;
17679
+ if (!this.active || this.finishing)
17680
+ throw new Error("Realtime connection is closed");
17681
+ const data = await this.options.model.serializeClientEvent(event);
17682
+ if (!this.active || this.finishing)
17683
+ throw new Error("Realtime connection is closed");
17684
+ if ((shouldSend == null ? void 0 : shouldSend()) === false) return;
17685
+ if (!this.active || this.finishing)
17686
+ throw new Error("Realtime connection is closed");
17687
+ if (data != null) this.options.send(data);
17688
+ });
17689
+ this.outgoing = operation.catch((error) => {
17690
+ if (!this.finishing) {
17691
+ if (event.type !== "input-audio-append" && event.type !== "session-close")
17692
+ this.report(error);
17693
+ }
17694
+ }).finally(() => {
17695
+ this.outgoingCount--;
17696
+ });
17697
+ return operation;
17698
+ }
17699
+ receive(data) {
17700
+ var _a25, _b25, _c;
17701
+ if (!this.active || this.finishing) return;
17702
+ const bytes = typeof data === "string" ? data.length * 2 : data instanceof Blob ? data.size : (_a25 = data.byteLength) != null ? _a25 : 0;
17703
+ if (this.incomingCount >= ((_b25 = this.options.maxPending) != null ? _b25 : 512) || this.incomingBytes + bytes > ((_c = this.options.maxPendingBytes) != null ? _c : 8 * 1024 * 1024)) {
17704
+ this.finishing = true;
17705
+ this.fatal(
17706
+ new Error(
17707
+ "Realtime incoming queue is full; input protocol continuity was lost"
17708
+ )
17709
+ );
17710
+ return;
17711
+ }
17712
+ this.incomingCount++;
17713
+ this.incomingBytes += bytes;
17714
+ this.incoming = this.incoming.then(async () => {
17715
+ var _a26, _b26;
17716
+ if (!this.active) return;
17717
+ const text2 = typeof data === "string" ? data : data instanceof Blob ? await data.text() : new TextDecoder().decode(data);
17718
+ const parsed = await safeParseJSON5({ text: text2 });
17719
+ if (!this.active) return;
17720
+ if (!parsed.success)
17721
+ throw new Error("Invalid JSON in realtime server message", {
17722
+ cause: parsed.error
17723
+ });
17724
+ const health = (_b26 = (_a26 = this.options.model).getHealthCheckResponse) == null ? void 0 : _b26.call(
17725
+ _a26,
17726
+ parsed.value
17727
+ );
17728
+ if (!this.active) return;
17729
+ if (health != null && !this.finishing) this.options.send(health);
17730
+ if (!this.active) return;
17731
+ const result = this.parse(parsed.value);
17732
+ for (const event of Array.isArray(result) ? result : [result]) {
17733
+ if (!this.active) return;
17734
+ try {
17735
+ await this.options.onEvent(event);
17736
+ } catch (error) {
17737
+ this.report(error);
17738
+ }
17739
+ }
17740
+ }).catch((error) => {
17741
+ this.finishing = true;
17742
+ this.fatal(error instanceof Error ? error : new Error(String(error)));
17743
+ }).finally(() => {
17744
+ this.incomingCount--;
17745
+ this.incomingBytes -= bytes;
17746
+ });
17747
+ }
17748
+ fatal(error) {
17749
+ if (!this.active) return;
17750
+ if (this.options.onFatalError != null) this.options.onFatalError(error);
17751
+ else this.report(error);
17752
+ }
17753
+ report(error) {
17754
+ if (!this.active) return;
17755
+ try {
17756
+ this.options.onError(
17757
+ error instanceof Error ? error : new Error(String(error))
17758
+ );
17759
+ } catch (e) {
17760
+ }
17761
+ }
17762
+ };
17763
+
17764
+ // src/realtime/encode-realtime-frame.ts
17765
+ var REALTIME_MAX_FRAME_BYTES = 128 * 1024;
17766
+ var REALTIME_MAX_BUFFERED_BYTES = 128 * 1024;
17767
+ function encodeRealtimeFrame(value) {
17768
+ if (value instanceof ArrayBuffer || ArrayBuffer.isView(value))
17769
+ return { data: value, byteLength: value.byteLength };
17770
+ if (value instanceof Blob) return { data: value, byteLength: value.size };
17771
+ const data = typeof value === "string" ? value : JSON.stringify(value);
17772
+ if (data === void 0)
17773
+ throw new Error("Realtime event could not be encoded as JSON");
17774
+ return { data, byteLength: new TextEncoder().encode(data).byteLength };
17775
+ }
17776
+ function assertRealtimeFrameBudget(frame, bufferedAmount) {
17777
+ if (frame.byteLength > REALTIME_MAX_FRAME_BYTES)
17778
+ throw new Error(
17779
+ `Realtime frame exceeds the ${REALTIME_MAX_FRAME_BYTES}-byte limit; reduce the event payload`
17780
+ );
17781
+ if (bufferedAmount + frame.byteLength > REALTIME_MAX_BUFFERED_BYTES)
17782
+ throw new Error(
17783
+ `Realtime send buffer is full (${REALTIME_MAX_BUFFERED_BYTES}-byte limit); wait before retrying`
17784
+ );
17785
+ }
17786
+
17787
+ // src/realtime/browser-realtime-transport.ts
17788
+ function getCloseError(event) {
17789
+ if (event.code === 1e3 && event.wasClean) return void 0;
17790
+ return new Error(
17791
+ `Realtime WebSocket closed unexpectedly (code ${event.code}${event.reason === "" ? "" : `: ${event.reason}`})`
17792
+ );
17793
+ }
17163
17794
  var BrowserRealtimeTransport = class {
17164
17795
  constructor(options) {
17796
+ this.options = options;
17165
17797
  this.ws = null;
17166
- this.sendQueue = Promise.resolve();
17798
+ this.epoch = 0;
17799
+ this.failing = false;
17800
+ this.closing = false;
17801
+ var _a25;
17167
17802
  this.model = options.model;
17803
+ this.continuous = ((_a25 = options.model.capabilities) == null ? void 0 : _a25.conversation) === "continuous";
17168
17804
  this.onServerEvent = options.onServerEvent;
17169
17805
  this.onError = options.onError;
17170
17806
  this.onClose = options.onClose;
17171
17807
  }
17808
+ get isOpen() {
17809
+ var _a25;
17810
+ return !this.closing && !this.failing && ((_a25 = this.ws) == null ? void 0 : _a25.readyState) === WebSocket.OPEN;
17811
+ }
17172
17812
  connect({
17813
+ mode,
17173
17814
  token,
17174
17815
  url,
17175
- onOpen
17816
+ onOpen,
17817
+ protocols
17176
17818
  }) {
17177
- var _a25;
17178
- (_a25 = this.ws) == null ? void 0 : _a25.close();
17179
- this.ws = null;
17180
- const wsConfig = this.model.getWebSocketConfig({ token, url });
17181
- const ws = new WebSocket(wsConfig.url, wsConfig.protocols);
17819
+ var _a25, _b25;
17820
+ this.disconnect();
17821
+ const epoch = this.epoch;
17822
+ if (mode !== "relay" && (mode !== "client-secret" || typeof token !== "string" || token.trim() === ""))
17823
+ throw new Error(
17824
+ "Realtime client-secret connection requires a nonempty token"
17825
+ );
17826
+ const wsConfig = mode === "relay" ? { url, protocols } : (_b25 = (_a25 = this.model).getWebSocketConfig) == null ? void 0 : _b25.call(_a25, { token, url });
17827
+ if (this.epoch !== epoch) return;
17828
+ if (wsConfig == null)
17829
+ throw new Error("Model does not support client-secret WebSockets");
17830
+ const finalUrl = wsConfig.url;
17831
+ const finalProtocols = wsConfig.protocols;
17832
+ if (this.epoch !== epoch) return;
17833
+ try {
17834
+ if (typeof finalUrl !== "string" || !/^wss?:\/\//i.test(finalUrl))
17835
+ throw new Error("Invalid realtime WebSocket URL");
17836
+ const parsed = new URL(finalUrl);
17837
+ if (!["ws:", "wss:"].includes(parsed.protocol) || parsed.hostname === "" || finalUrl.includes("#"))
17838
+ throw new Error("Invalid realtime WebSocket URL");
17839
+ } catch (e) {
17840
+ throw new Error(
17841
+ "Invalid realtime WebSocket URL; expected an absolute ws:// or wss:// URL with a host and no fragment"
17842
+ );
17843
+ }
17844
+ if (this.epoch !== epoch) return;
17845
+ const ws = new WebSocket(finalUrl, finalProtocols);
17182
17846
  this.ws = ws;
17847
+ let starting = false;
17848
+ let connectionError;
17849
+ const codec = new RealtimeEventChannel({
17850
+ model: this.model,
17851
+ send: (data) => {
17852
+ if (this.epoch !== epoch || this.ws !== ws || !this.isOpen)
17853
+ throw new Error("Realtime WebSocket is not open");
17854
+ this.sendRaw(data);
17855
+ },
17856
+ onEvent: this.onServerEvent,
17857
+ onError: (error) => {
17858
+ if (!starting) this.onError(error);
17859
+ },
17860
+ onFatalError: (error) => this.fail(error)
17861
+ });
17862
+ if (this.epoch !== epoch) {
17863
+ codec.dispose();
17864
+ return;
17865
+ }
17866
+ this.codec = codec;
17183
17867
  ws.onopen = () => {
17184
17868
  if (this.ws !== ws) return;
17185
- onOpen();
17869
+ starting = true;
17870
+ try {
17871
+ void Promise.resolve(onOpen()).catch((error) => {
17872
+ if (this.ws === ws)
17873
+ this.fail(
17874
+ error instanceof Error ? error : new Error(String(error))
17875
+ );
17876
+ }).finally(() => {
17877
+ starting = false;
17878
+ });
17879
+ } catch (error) {
17880
+ if (this.epoch === epoch)
17881
+ this.fail(error instanceof Error ? error : new Error(String(error)));
17882
+ }
17186
17883
  };
17187
17884
  ws.onmessage = (messageEvent) => {
17188
- void this.handleMessage(messageEvent);
17885
+ var _a26;
17886
+ if (this.ws === ws) (_a26 = this.codec) == null ? void 0 : _a26.receive(messageEvent.data);
17189
17887
  };
17190
17888
  ws.onerror = () => {
17191
- this.onError(new Error("WebSocket connection error"));
17889
+ if (this.ws === ws) {
17890
+ connectionError = new Error("WebSocket connection error");
17891
+ this.failing = true;
17892
+ }
17192
17893
  };
17193
- ws.onclose = () => {
17894
+ ws.onclose = (event) => {
17895
+ var _a26;
17194
17896
  if (this.ws === ws) {
17195
17897
  this.ws = null;
17196
- this.onClose();
17898
+ const closeError = (_a26 = getCloseError(event)) != null ? _a26 : connectionError;
17899
+ codec.stopWriting();
17900
+ this.notifyClosing();
17901
+ if (this.epoch !== epoch) return;
17902
+ const complete = () => {
17903
+ if (this.epoch !== epoch) return;
17904
+ clearTimeout(this.drainTimer);
17905
+ this.epoch++;
17906
+ codec.dispose();
17907
+ try {
17908
+ this.onClose(closeError);
17909
+ } catch (error) {
17910
+ this.reportCallbackError(error);
17911
+ }
17912
+ };
17913
+ this.drainTimer = setTimeout(complete, 1e3);
17914
+ void this.awaitDrain(codec.finish(), complete);
17197
17915
  }
17198
17916
  };
17199
17917
  }
17200
17918
  disconnect() {
17201
17919
  var _a25;
17202
- (_a25 = this.ws) == null ? void 0 : _a25.close();
17920
+ this.epoch++;
17921
+ clearTimeout(this.drainTimer);
17922
+ const ws = this.ws;
17203
17923
  this.ws = null;
17924
+ (_a25 = this.codec) == null ? void 0 : _a25.dispose();
17925
+ this.codec = void 0;
17926
+ this.failing = false;
17927
+ this.closing = false;
17928
+ ws == null ? void 0 : ws.close();
17929
+ }
17930
+ sendEvent(event, shouldSend, automaticAudio = false) {
17931
+ if (this.failing || this.closing)
17932
+ throw new Error("Realtime connection is closed");
17933
+ if (this.codec == null) return Promise.resolve();
17934
+ const epoch = this.epoch;
17935
+ const failed = (error) => {
17936
+ if (automaticAudio && this.epoch === epoch && this.isOpen)
17937
+ this.fail(error instanceof Error ? error : new Error(String(error)));
17938
+ };
17939
+ try {
17940
+ const sent = this.codec.send(event, shouldSend);
17941
+ void sent.catch(failed);
17942
+ return sent;
17943
+ } catch (error) {
17944
+ failed(error);
17945
+ throw error;
17946
+ }
17204
17947
  }
17205
- sendEvent(event) {
17206
- this.sendQueue = this.sendQueue.then(async () => {
17207
- const serialized = await this.model.serializeClientEvent(event);
17208
- if (serialized != null) {
17209
- this.sendRaw(serialized);
17948
+ finish() {
17949
+ var _a25, _b25;
17950
+ return (_b25 = (_a25 = this.codec) == null ? void 0 : _a25.finish()) != null ? _b25 : Promise.resolve();
17951
+ }
17952
+ /** Drain accepted events when media or protocol continuity is lost. */
17953
+ fail(error) {
17954
+ var _a25;
17955
+ if (this.failing) return;
17956
+ this.failing = true;
17957
+ const epoch = this.epoch;
17958
+ const ws = this.ws;
17959
+ this.ws = null;
17960
+ (_a25 = this.codec) == null ? void 0 : _a25.stopWriting();
17961
+ ws == null ? void 0 : ws.close();
17962
+ this.notifyClosing();
17963
+ if (this.epoch !== epoch) return;
17964
+ const drain = this.finish();
17965
+ if (this.options.onFatalError != null)
17966
+ this.options.onFatalError(error, drain);
17967
+ else {
17968
+ const complete = () => {
17969
+ if (epoch !== this.epoch) return;
17970
+ this.disconnect();
17971
+ try {
17972
+ this.onClose();
17973
+ } catch (cause) {
17974
+ this.reportCallbackError(cause);
17975
+ }
17976
+ };
17977
+ this.drainTimer = setTimeout(complete, 1e3);
17978
+ void this.awaitDrain(drain, complete);
17979
+ this.reportCallbackError(error);
17980
+ }
17981
+ }
17982
+ notifyClosing() {
17983
+ var _a25, _b25;
17984
+ if (this.closing) return;
17985
+ this.closing = true;
17986
+ try {
17987
+ (_b25 = (_a25 = this.options).onClosing) == null ? void 0 : _b25.call(_a25);
17988
+ } catch (error) {
17989
+ this.reportCallbackError(error);
17990
+ }
17991
+ }
17992
+ async awaitDrain(drain, complete) {
17993
+ try {
17994
+ await drain;
17995
+ } catch (e) {
17996
+ }
17997
+ try {
17998
+ complete();
17999
+ } catch (error) {
18000
+ this.reportCallbackError(error);
18001
+ }
18002
+ }
18003
+ reportCallbackError(error) {
18004
+ try {
18005
+ this.onError(error instanceof Error ? error : new Error(String(error)));
18006
+ } catch (e) {
18007
+ }
18008
+ }
18009
+ sendRaw(data) {
18010
+ const ws = this.ws;
18011
+ const epoch = this.epoch;
18012
+ if (ws == null || !this.isOpen) return;
18013
+ const frame = encodeRealtimeFrame(data);
18014
+ if (this.epoch !== epoch || this.ws !== ws || !this.isOpen)
18015
+ throw new Error("Realtime connection is closed");
18016
+ if (this.continuous) assertRealtimeFrameBudget(frame, ws.bufferedAmount);
18017
+ ws.send(frame.data);
18018
+ }
18019
+ dispose() {
18020
+ this.disconnect();
18021
+ }
18022
+ };
18023
+
18024
+ // src/realtime/browser-realtime-live-websocket.ts
18025
+ import { UnsupportedFunctionalityError as UnsupportedFunctionalityError5 } from "@ai-sdk/provider";
18026
+ var BrowserRealtimeLiveWebSocket = class {
18027
+ constructor(options) {
18028
+ this.options = options;
18029
+ this.generation = 0;
18030
+ this.ready = false;
18031
+ this.capturingStarted = false;
18032
+ this.pendingAudio = 0;
18033
+ this.captureGeneration = 0;
18034
+ this.captureEnabled = true;
18035
+ var _a25, _b25, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
18036
+ const input = (_a25 = options.sessionConfig) == null ? void 0 : _a25.inputAudioFormat;
18037
+ const output = (_b25 = options.sessionConfig) == null ? void 0 : _b25.outputAudioFormat;
18038
+ const sampleRate = (_e = (_d = (_c = input == null ? void 0 : input.rate) != null ? _c : output == null ? void 0 : output.rate) != null ? _d : options.sampleRate) != null ? _e : 24e3;
18039
+ const format = {
18040
+ type: (_g = (_f = input == null ? void 0 : input.type) != null ? _f : output == null ? void 0 : output.type) != null ? _g : "audio/pcm",
18041
+ rate: sampleRate
18042
+ };
18043
+ this.config = {
18044
+ ...options.sessionConfig,
18045
+ inputAudioFormat: {
18046
+ ...format,
18047
+ ...input
18048
+ },
18049
+ outputAudioFormat: {
18050
+ ...format,
18051
+ ...output
17210
18052
  }
17211
- }).catch((error) => {
17212
- this.onError(
17213
- error instanceof Error ? error : new Error(`Failed to send realtime event: ${String(error)}`)
18053
+ };
18054
+ this.transport = new BrowserRealtimeTransport({
18055
+ model: options.model,
18056
+ onServerEvent: options.onEvent,
18057
+ onError: options.onError,
18058
+ onFatalError: options.onFatalError,
18059
+ onClosing: () => {
18060
+ var _a26;
18061
+ const generation = this.generation;
18062
+ (_a26 = options.onClosing) == null ? void 0 : _a26.call(options);
18063
+ if (generation === this.generation) this.stopCapture();
18064
+ },
18065
+ onClose: options.onClose
18066
+ });
18067
+ this.audio = new BrowserRealtimeAudio({
18068
+ captureSampleRate: (_i = (_h = this.config.inputAudioFormat) == null ? void 0 : _h.rate) != null ? _i : sampleRate,
18069
+ playbackSampleRate: (_k = (_j = this.config.outputAudioFormat) == null ? void 0 : _j.rate) != null ? _k : sampleRate,
18070
+ maxPlaybackSeconds: (_l = options.maxPlaybackBufferSeconds) != null ? _l : 2,
18071
+ onAudio: (audio) => this.sendAudio(audio),
18072
+ onError: options.onError,
18073
+ onCapturingChange: options.onCapturing,
18074
+ onPlayingChange: options.onPlaying
18075
+ });
18076
+ }
18077
+ connect(options) {
18078
+ const generation = this.generation + 1;
18079
+ this.dispose();
18080
+ if (generation !== this.generation) return;
18081
+ for (const format of [
18082
+ this.config.inputAudioFormat,
18083
+ this.config.outputAudioFormat
18084
+ ]) {
18085
+ if ((format == null ? void 0 : format.type) !== "audio/pcm")
18086
+ throw new UnsupportedFunctionalityError5({
18087
+ functionality: "Continuous browser WebSocket audio supports PCM16 only"
18088
+ });
18089
+ if (format.rate == null || !Number.isFinite(format.rate) || format.rate < 8e3 || format.rate > 96e3)
18090
+ throw new Error("Invalid realtime PCM sample rate");
18091
+ }
18092
+ this.stream = options.stream;
18093
+ this.captureEnabled = options.capture !== false;
18094
+ this.audio.ensurePlaybackContext();
18095
+ void this.audio.resumePlayback().catch((error) => {
18096
+ if (generation === this.generation) this.options.onError(error);
18097
+ });
18098
+ this.transport.connect({
18099
+ mode: "relay",
18100
+ url: options.url,
18101
+ protocols: options.protocols,
18102
+ onOpen: () => {
18103
+ var _a25, _b25;
18104
+ return this.transport.sendEvent({
18105
+ type: (_b25 = (_a25 = this.options.model.capabilities) == null ? void 0 : _a25.startup) != null ? _b25 : "session-update",
18106
+ config: this.config
18107
+ });
18108
+ }
18109
+ });
18110
+ }
18111
+ startCapture() {
18112
+ this.ready = true;
18113
+ if (!this.captureEnabled || this.capturingStarted) return;
18114
+ void this.resumeCapture().catch((error) => this.options.onError(error));
18115
+ }
18116
+ async resumeCapture(suppliedStream) {
18117
+ const generation = this.captureGeneration + 1;
18118
+ this.stopCapture();
18119
+ if (generation !== this.captureGeneration) return;
18120
+ this.ready = true;
18121
+ this.capturingStarted = true;
18122
+ this.captureEnabled = true;
18123
+ if (suppliedStream != null) this.stream = suppliedStream;
18124
+ const supplied = this.stream;
18125
+ const stream = supplied != null ? supplied : await navigator.mediaDevices.getUserMedia({ audio: true });
18126
+ if (generation !== this.captureGeneration || !this.ready) {
18127
+ if (supplied == null) stream.getTracks().forEach((track) => track.stop());
18128
+ return;
18129
+ }
18130
+ if (!stream.getAudioTracks().some((track) => track.readyState === "live")) {
18131
+ if (supplied == null) stream.getTracks().forEach((track) => track.stop());
18132
+ throw new Error("Realtime requires a live audio track");
18133
+ }
18134
+ this.audio.startCapture(stream, { ownsStream: supplied == null });
18135
+ }
18136
+ sendAudio(audio) {
18137
+ if (!this.ready || !this.transport.isOpen) return;
18138
+ if (this.pendingAudio >= 8) {
18139
+ this.transport.fail(new Error("Realtime audio send queue is full"));
18140
+ return;
18141
+ }
18142
+ const generation = this.generation;
18143
+ let sent;
18144
+ try {
18145
+ sent = this.transport.sendEvent(
18146
+ { type: "input-audio-append", audio },
18147
+ void 0,
18148
+ true
17214
18149
  );
18150
+ } catch (error) {
18151
+ if (!this.ready || !this.transport.isOpen) return;
18152
+ throw error;
18153
+ }
18154
+ this.pendingAudio++;
18155
+ void sent.then(
18156
+ () => {
18157
+ if (generation === this.generation) this.pendingAudio--;
18158
+ },
18159
+ (error) => {
18160
+ if (generation === this.generation && this.ready && this.transport.isOpen)
18161
+ this.options.onFatalError(error);
18162
+ }
18163
+ );
18164
+ }
18165
+ sendEvent(event, shouldSend) {
18166
+ var _a25, _b25;
18167
+ if (event.type === "session-update") {
18168
+ for (const key of ["inputAudioFormat", "outputAudioFormat"]) {
18169
+ const update = event.config[key];
18170
+ if (update != null && (update.type !== ((_a25 = this.config[key]) == null ? void 0 : _a25.type) || update.rate != null && update.rate !== ((_b25 = this.config[key]) == null ? void 0 : _b25.rate))) {
18171
+ throw new Error(
18172
+ "Changing Live WebSocket audio format requires reconnecting"
18173
+ );
18174
+ }
18175
+ }
18176
+ }
18177
+ return this.transport.sendEvent(event, shouldSend);
18178
+ }
18179
+ playAudio(audio) {
18180
+ try {
18181
+ this.audio.playAudio(audio);
18182
+ } catch (error) {
18183
+ this.options.onError(
18184
+ error instanceof Error ? error : new Error(String(error))
18185
+ );
18186
+ }
18187
+ }
18188
+ stopPlayback() {
18189
+ this.audio.stopPlayback();
18190
+ }
18191
+ resumePlayback() {
18192
+ return this.audio.resumePlayback();
18193
+ }
18194
+ stopCapture() {
18195
+ this.captureGeneration++;
18196
+ this.ready = false;
18197
+ this.capturingStarted = false;
18198
+ this.captureEnabled = false;
18199
+ this.audio.stopCapture();
18200
+ }
18201
+ finish() {
18202
+ return this.transport.finish();
18203
+ }
18204
+ dispose() {
18205
+ this.generation++;
18206
+ this.stopCapture();
18207
+ this.ready = false;
18208
+ this.capturingStarted = false;
18209
+ this.pendingAudio = 0;
18210
+ this.stream = void 0;
18211
+ this.transport.disconnect();
18212
+ this.audio.dispose();
18213
+ }
18214
+ };
18215
+
18216
+ // src/realtime/browser-realtime-webrtc.ts
18217
+ import { safeParseJSON as safeParseJSON6 } from "@ai-sdk/provider-utils";
18218
+ var MAX_SESSION_ANSWER_BYTES = 1024 * 1024;
18219
+ async function readSessionAnswer(response) {
18220
+ var _a25;
18221
+ const reader = (_a25 = response.body) == null ? void 0 : _a25.getReader();
18222
+ if (reader == null) throw new Error("Invalid realtime session answer");
18223
+ const decoder = new TextDecoder();
18224
+ let bytes = 0;
18225
+ let text2 = "";
18226
+ try {
18227
+ if (Number(response.headers.get("content-length")) > MAX_SESSION_ANSWER_BYTES)
18228
+ throw new Error("Realtime session answer exceeds the 1 MiB limit");
18229
+ while (true) {
18230
+ const { done, value } = await reader.read();
18231
+ if (done) return text2 + decoder.decode();
18232
+ bytes += value.byteLength;
18233
+ if (bytes > MAX_SESSION_ANSWER_BYTES)
18234
+ throw new Error("Realtime session answer exceeds the 1 MiB limit");
18235
+ text2 += decoder.decode(value, { stream: true });
18236
+ }
18237
+ } catch (error) {
18238
+ void reader.cancel(error).catch(() => {
18239
+ });
18240
+ throw error;
18241
+ } finally {
18242
+ reader.releaseLock();
18243
+ }
18244
+ }
18245
+ function selectAudioTrack(stream) {
18246
+ var _a25;
18247
+ const live = stream.getAudioTracks().filter((track) => track.readyState === "live");
18248
+ return (_a25 = live.find((track) => track.enabled && !track.muted)) != null ? _a25 : live[0];
18249
+ }
18250
+ var BrowserRealtimeWebRTC = class {
18251
+ constructor(options) {
18252
+ this.options = options;
18253
+ this.ownsStream = false;
18254
+ this.generation = 0;
18255
+ this.closingNotified = false;
18256
+ this.captureGeneration = 0;
18257
+ this.senderOperations = Promise.resolve();
18258
+ this.trackCleanups = [];
18259
+ }
18260
+ async connect({
18261
+ api,
18262
+ sessionConfig,
18263
+ stream,
18264
+ timeoutMs,
18265
+ capture = true
18266
+ }) {
18267
+ var _a25, _b25;
18268
+ const generation = this.generation + 1;
18269
+ this.dispose();
18270
+ const current = () => generation === this.generation;
18271
+ if (!current()) return;
18272
+ const config = (_b25 = (_a25 = this.options.model).getWebRTCConfig) == null ? void 0 : _b25.call(_a25);
18273
+ if (!current()) return;
18274
+ if (config == null)
18275
+ throw new Error("This model does not support WebRTC configuration");
18276
+ const abort = new AbortController();
18277
+ this.abort = abort;
18278
+ const timeout = setTimeout(
18279
+ () => abort.abort(new Error("Realtime startup timed out")),
18280
+ timeoutMs
18281
+ );
18282
+ const cancelled = new Promise((_, reject) => {
18283
+ abort.signal.addEventListener(
18284
+ "abort",
18285
+ () => reject(abort.signal.reason),
18286
+ { once: true }
18287
+ );
18288
+ });
18289
+ const start = async () => {
18290
+ var _a26;
18291
+ const captureGeneration = this.captureGeneration;
18292
+ let media = capture ? stream != null ? stream : await navigator.mediaDevices.getUserMedia({ audio: true }) : void 0;
18293
+ if (!current() || abort.signal.aborted) {
18294
+ if (stream == null) media == null ? void 0 : media.getTracks().forEach((track2) => track2.stop());
18295
+ throw new Error("Realtime connection cancelled");
18296
+ }
18297
+ if (captureGeneration !== this.captureGeneration) {
18298
+ if (stream == null) media == null ? void 0 : media.getTracks().forEach((track2) => track2.stop());
18299
+ media = void 0;
18300
+ }
18301
+ this.stream = media;
18302
+ this.ownsStream = stream == null;
18303
+ const track = media == null ? void 0 : selectAudioTrack(media);
18304
+ if (media != null && track == null) {
18305
+ throw new Error("Realtime requires a live audio track");
18306
+ }
18307
+ const pc = new RTCPeerConnection();
18308
+ this.pc = pc;
18309
+ const audio = document.createElement("audio");
18310
+ audio.autoplay = true;
18311
+ this.audio = audio;
18312
+ audio.onplaying = () => {
18313
+ if (current()) this.options.onPlaying(true);
18314
+ };
18315
+ const stopped = () => {
18316
+ if (current()) this.options.onPlaying(false);
18317
+ };
18318
+ audio.onpause = stopped;
18319
+ audio.onended = stopped;
18320
+ audio.onwaiting = stopped;
18321
+ pc.ontrack = (event) => {
18322
+ var _a27;
18323
+ if (!current()) return;
18324
+ audio.srcObject = (_a27 = event.streams[0]) != null ? _a27 : new MediaStream([event.track]);
18325
+ void audio.play().catch((error) => {
18326
+ if (current()) this.options.onError(error);
18327
+ });
18328
+ };
18329
+ pc.onconnectionstatechange = () => {
18330
+ var _a27;
18331
+ if (!current()) return;
18332
+ if (pc.connectionState === "connected")
18333
+ clearTimeout(this.disconnectTimer);
18334
+ else if (pc.connectionState === "disconnected") {
18335
+ clearTimeout(this.disconnectTimer);
18336
+ this.disconnectTimer = setTimeout(() => {
18337
+ if (current() && pc.connectionState === "disconnected")
18338
+ this.fail(
18339
+ new Error(
18340
+ "Realtime peer disconnected beyond recovery grace period"
18341
+ )
18342
+ );
18343
+ }, (_a27 = this.options.disconnectTimeoutMs) != null ? _a27 : 5e3);
18344
+ } else if (pc.connectionState === "closed") {
18345
+ this.drainClose(new Error("Realtime peer connection closed"));
18346
+ } else if (pc.connectionState === "failed") {
18347
+ this.fail(
18348
+ new Error(`Realtime peer connection ${pc.connectionState}`)
18349
+ );
18350
+ }
18351
+ };
18352
+ pc.oniceconnectionstatechange = () => {
18353
+ if (current() && pc.iceConnectionState === "failed")
18354
+ this.fail(new Error("Realtime ICE connection failed"));
18355
+ };
18356
+ const dc = pc.createDataChannel(config.dataChannelLabel);
18357
+ this.dc = dc;
18358
+ const codec = new RealtimeEventChannel({
18359
+ model: this.options.model,
18360
+ onEvent: this.options.onEvent,
18361
+ onError: this.options.onError,
18362
+ onFatalError: (error) => {
18363
+ if (current()) this.fail(error);
18364
+ },
18365
+ send: (data) => {
18366
+ if (!current() || this.finishing || dc.readyState !== "open")
18367
+ throw new Error("Realtime data channel is not open");
18368
+ if (dc.bufferedAmount > 1024 * 1024)
18369
+ throw new Error("Realtime data channel buffer is full");
18370
+ const encoded = typeof data === "string" ? data : JSON.stringify(data);
18371
+ if (!current() || this.finishing || dc.readyState !== "open")
18372
+ throw new Error("Realtime data channel is not open");
18373
+ dc.send(encoded);
18374
+ }
18375
+ });
18376
+ if (!current()) {
18377
+ codec.dispose();
18378
+ return;
18379
+ }
18380
+ this.codec = codec;
18381
+ dc.onmessage = (event) => {
18382
+ var _a27;
18383
+ if (current()) (_a27 = this.codec) == null ? void 0 : _a27.receive(event.data);
18384
+ };
18385
+ dc.onclose = () => {
18386
+ if (current())
18387
+ this.drainClose(new Error("Realtime data channel closed"));
18388
+ };
18389
+ dc.onerror = () => {
18390
+ if (current()) this.fail(new Error("Realtime data channel error"));
18391
+ };
18392
+ const opened = new Promise((resolve3, reject) => {
18393
+ this.rejectStartup = reject;
18394
+ dc.onopen = () => {
18395
+ this.rejectStartup = void 0;
18396
+ resolve3();
18397
+ };
18398
+ });
18399
+ void opened.catch(() => {
18400
+ });
18401
+ const gathered = new Promise((resolve3) => {
18402
+ pc.onicegatheringstatechange = () => {
18403
+ if (pc.iceGatheringState === "complete") resolve3();
18404
+ };
18405
+ });
18406
+ this.sender = media != null && track != null ? pc.addTrack(track, media) : pc.addTransceiver("audio", { direction: "sendrecv" }).sender;
18407
+ this.observeCapture();
18408
+ if (!current() || abort.signal.aborted) return;
18409
+ const offer = await pc.createOffer();
18410
+ if (!current() || abort.signal.aborted) return;
18411
+ await pc.setLocalDescription(offer);
18412
+ if (pc.iceGatheringState !== "complete")
18413
+ await Promise.race([gathered, cancelled]);
18414
+ if (!current() || abort.signal.aborted) return;
18415
+ const sdp = (_a26 = pc.localDescription) == null ? void 0 : _a26.sdp;
18416
+ if (sdp == null) throw new Error("Realtime offer has no SDP");
18417
+ const response = await fetch(api, {
18418
+ method: "POST",
18419
+ headers: { "Content-Type": "application/json" },
18420
+ body: JSON.stringify({ sdp, sessionConfig }),
18421
+ signal: abort.signal
18422
+ });
18423
+ if (!response.ok)
18424
+ throw new Error(
18425
+ `Failed to create realtime session: ${response.status}`
18426
+ );
18427
+ const parsed = await safeParseJSON6({
18428
+ text: await readSessionAnswer(response)
18429
+ });
18430
+ if (!current() || abort.signal.aborted) return;
18431
+ if (!parsed.success || typeof parsed.value !== "object" || parsed.value == null || !("sdp" in parsed.value) || typeof parsed.value.sdp !== "string" || parsed.value.sdp.trim().length === 0 || !("sessionId" in parsed.value) || typeof parsed.value.sessionId !== "string" || parsed.value.sessionId.trim().length === 0) {
18432
+ throw new Error("Invalid realtime session answer");
18433
+ }
18434
+ await pc.setRemoteDescription({ type: "answer", sdp: parsed.value.sdp });
18435
+ await Promise.race([opened, cancelled]);
18436
+ };
18437
+ try {
18438
+ await Promise.race([start(), cancelled]);
18439
+ } catch (error) {
18440
+ if (current()) this.dispose();
18441
+ throw error;
18442
+ } finally {
18443
+ clearTimeout(timeout);
18444
+ this.rejectStartup = void 0;
18445
+ }
18446
+ }
18447
+ sendEvent(event, shouldSend) {
18448
+ var _a25, _b25, _c;
18449
+ if (this.finishing || ((_a25 = this.dc) == null ? void 0 : _a25.readyState) !== "open")
18450
+ throw new Error("Realtime data channel is not open");
18451
+ return (_c = (_b25 = this.codec) == null ? void 0 : _b25.send(event, shouldSend)) != null ? _c : Promise.resolve();
18452
+ }
18453
+ async resumePlayback() {
18454
+ if (this.audio == null)
18455
+ throw new Error("Realtime playback is not connected");
18456
+ await this.audio.play();
18457
+ }
18458
+ stopPlayback() {
18459
+ var _a25;
18460
+ (_a25 = this.audio) == null ? void 0 : _a25.pause();
18461
+ }
18462
+ finish() {
18463
+ var _a25, _b25;
18464
+ return (_b25 = (_a25 = this.codec) == null ? void 0 : _a25.finish()) != null ? _b25 : Promise.resolve();
18465
+ }
18466
+ fail(error) {
18467
+ var _a25;
18468
+ if (this.finishing === "failure") return;
18469
+ this.finishing = "failure";
18470
+ clearTimeout(this.finishTimer);
18471
+ if (this.rejectStartup != null) {
18472
+ this.rejectStartup(error);
18473
+ (_a25 = this.abort) == null ? void 0 : _a25.abort(error);
18474
+ return;
18475
+ }
18476
+ const generation = this.generation;
18477
+ const drain = this.finish();
18478
+ this.notifyClosing();
18479
+ if (generation !== this.generation) return;
18480
+ void this.stopCaptureForShutdown(generation);
18481
+ if (generation !== this.generation) return;
18482
+ if (this.options.onFatalError != null) {
18483
+ this.options.onFatalError(error, drain);
18484
+ return;
18485
+ }
18486
+ const complete = () => {
18487
+ if (generation !== this.generation) return;
18488
+ this.dispose();
18489
+ try {
18490
+ this.options.onClose();
18491
+ } catch (cause) {
18492
+ this.reportCallbackError(cause);
18493
+ }
18494
+ };
18495
+ this.finishTimer = setTimeout(complete, 1e3);
18496
+ void this.awaitDrain(drain, complete);
18497
+ try {
18498
+ this.options.onError(error);
18499
+ } catch (e) {
18500
+ }
18501
+ }
18502
+ drainClose(error) {
18503
+ if (this.finishing) return;
18504
+ if (this.rejectStartup != null) {
18505
+ this.fail(error);
18506
+ return;
18507
+ }
18508
+ this.finishing = "close";
18509
+ const generation = this.generation;
18510
+ const complete = () => {
18511
+ if (generation !== this.generation || this.finishing !== "close") return;
18512
+ clearTimeout(this.finishTimer);
18513
+ try {
18514
+ this.options.onClose(error);
18515
+ } catch (cause) {
18516
+ this.reportCallbackError(cause);
18517
+ } finally {
18518
+ if (generation === this.generation) this.dispose();
18519
+ }
18520
+ };
18521
+ this.finishTimer = setTimeout(complete, 1e3);
18522
+ void this.awaitDrain(this.finish(), complete);
18523
+ this.notifyClosing();
18524
+ if (generation !== this.generation) return;
18525
+ void this.stopCaptureForShutdown(generation);
18526
+ }
18527
+ notifyClosing() {
18528
+ var _a25, _b25;
18529
+ if (this.closingNotified) return;
18530
+ this.closingNotified = true;
18531
+ try {
18532
+ (_b25 = (_a25 = this.options).onClosing) == null ? void 0 : _b25.call(_a25);
18533
+ } catch (error) {
18534
+ this.reportCallbackError(error);
18535
+ }
18536
+ }
18537
+ async stopCaptureForShutdown(generation) {
18538
+ try {
18539
+ await this.stopCapture();
18540
+ } catch (cause) {
18541
+ if (generation !== this.generation || this.finishing !== "close") return;
18542
+ try {
18543
+ this.fail(cause instanceof Error ? cause : new Error(String(cause)));
18544
+ } catch (error) {
18545
+ this.reportCallbackError(error);
18546
+ }
18547
+ }
18548
+ }
18549
+ async awaitDrain(drain, complete) {
18550
+ try {
18551
+ await drain;
18552
+ } catch (e) {
18553
+ }
18554
+ try {
18555
+ complete();
18556
+ } catch (error) {
18557
+ this.reportCallbackError(error);
18558
+ }
18559
+ }
18560
+ reportCallbackError(error) {
18561
+ try {
18562
+ this.options.onError(
18563
+ error instanceof Error ? error : new Error(String(error))
18564
+ );
18565
+ } catch (e) {
18566
+ }
18567
+ }
18568
+ async stopCapture() {
18569
+ var _a25;
18570
+ const generation = ++this.captureGeneration;
18571
+ for (const cleanup of this.trackCleanups) cleanup();
18572
+ this.trackCleanups = [];
18573
+ if (this.ownsStream)
18574
+ (_a25 = this.stream) == null ? void 0 : _a25.getTracks().forEach((track) => track.stop());
18575
+ this.stream = void 0;
18576
+ const pc = this.pc;
18577
+ const sender = this.sender;
18578
+ await this.queueSenderOperation(async () => {
18579
+ if (pc !== this.pc || sender !== this.sender) return;
18580
+ if (pc != null && sender != null) await this.detachSender(pc, sender);
18581
+ if (generation === this.captureGeneration && pc === this.pc)
18582
+ this.options.onCapturing(false);
18583
+ });
18584
+ }
18585
+ async startCapture(supplied) {
18586
+ const generation = this.captureGeneration + 1;
18587
+ await this.stopCapture();
18588
+ if (generation !== this.captureGeneration || this.pc == null || this.finishing)
18589
+ return;
18590
+ const media = supplied != null ? supplied : await navigator.mediaDevices.getUserMedia({ audio: true });
18591
+ if (generation !== this.captureGeneration || this.pc == null || this.finishing) {
18592
+ if (supplied == null) media.getTracks().forEach((track2) => track2.stop());
18593
+ return;
18594
+ }
18595
+ const track = selectAudioTrack(media);
18596
+ if (track == null) {
18597
+ if (supplied == null) media.getTracks().forEach((track2) => track2.stop());
18598
+ throw new Error("Realtime requires a live audio track");
18599
+ }
18600
+ const pc = this.pc;
18601
+ const sender = this.sender;
18602
+ await this.queueSenderOperation(async () => {
18603
+ const current = () => generation === this.captureGeneration && pc === this.pc && sender === this.sender && pc.connectionState !== "closed" && !this.finishing;
18604
+ let attached = false;
18605
+ try {
18606
+ if (!current() || sender == null) return;
18607
+ await sender.replaceTrack(track);
18608
+ if (!current()) {
18609
+ if (pc === this.pc) await this.detachSender(pc, sender);
18610
+ return;
18611
+ }
18612
+ this.stream = media;
18613
+ this.ownsStream = supplied == null;
18614
+ attached = true;
18615
+ this.observeCapture();
18616
+ } finally {
18617
+ if (!attached && supplied == null)
18618
+ media.getTracks().forEach((track2) => track2.stop());
18619
+ }
18620
+ });
18621
+ }
18622
+ queueSenderOperation(operation) {
18623
+ const pending = this.senderOperations.then(operation);
18624
+ this.senderOperations = pending.catch(() => {
18625
+ });
18626
+ return pending;
18627
+ }
18628
+ async detachSender(pc, sender) {
18629
+ if (pc.connectionState === "closed") return;
18630
+ try {
18631
+ await sender.replaceTrack(null);
18632
+ } catch (error) {
18633
+ if (pc !== this.pc) return;
18634
+ this.closePeer(pc);
18635
+ this.fail(error instanceof Error ? error : new Error(String(error)));
18636
+ }
18637
+ }
18638
+ closePeer(pc) {
18639
+ pc.ontrack = null;
18640
+ pc.onconnectionstatechange = null;
18641
+ pc.oniceconnectionstatechange = null;
18642
+ pc.onicegatheringstatechange = null;
18643
+ if (pc.connectionState !== "closed") pc.close();
18644
+ }
18645
+ observeCapture() {
18646
+ const generation = this.captureGeneration;
18647
+ const sender = this.sender;
18648
+ const track = sender == null ? void 0 : sender.track;
18649
+ const update = () => {
18650
+ if (generation === this.captureGeneration && this.sender === sender)
18651
+ this.options.onCapturing(
18652
+ (sender == null ? void 0 : sender.track) != null && sender.track.readyState === "live" && sender.track.enabled && !sender.track.muted
18653
+ );
18654
+ };
18655
+ if (track != null) {
18656
+ for (const event of ["ended", "mute", "unmute"]) {
18657
+ track.addEventListener(event, update);
18658
+ this.trackCleanups.push(() => track.removeEventListener(event, update));
18659
+ }
18660
+ }
18661
+ update();
18662
+ }
18663
+ dispose() {
18664
+ var _a25, _b25, _c;
18665
+ this.generation++;
18666
+ this.captureGeneration++;
18667
+ clearTimeout(this.finishTimer);
18668
+ clearTimeout(this.disconnectTimer);
18669
+ this.finishing = void 0;
18670
+ this.closingNotified = false;
18671
+ this.senderOperations = Promise.resolve();
18672
+ (_a25 = this.abort) == null ? void 0 : _a25.abort(new Error("Realtime connection cancelled"));
18673
+ this.abort = void 0;
18674
+ (_b25 = this.codec) == null ? void 0 : _b25.dispose();
18675
+ this.codec = void 0;
18676
+ if (this.dc != null) {
18677
+ this.dc.onopen = null;
18678
+ this.dc.onclose = null;
18679
+ this.dc.onerror = null;
18680
+ this.dc.onmessage = null;
18681
+ this.dc.close();
18682
+ this.dc = void 0;
18683
+ }
18684
+ if (this.pc != null) {
18685
+ this.closePeer(this.pc);
18686
+ this.pc = void 0;
18687
+ }
18688
+ for (const cleanup of this.trackCleanups) cleanup();
18689
+ this.trackCleanups = [];
18690
+ if (this.ownsStream)
18691
+ (_c = this.stream) == null ? void 0 : _c.getTracks().forEach((track) => track.stop());
18692
+ this.stream = void 0;
18693
+ this.sender = void 0;
18694
+ if (this.audio != null) {
18695
+ this.audio.onplaying = null;
18696
+ this.audio.onpause = null;
18697
+ this.audio.onended = null;
18698
+ this.audio.onwaiting = null;
18699
+ this.audio.pause();
18700
+ this.audio.srcObject = null;
18701
+ this.audio = void 0;
18702
+ }
18703
+ this.options.onCapturing(false);
18704
+ this.options.onPlaying(false);
18705
+ }
18706
+ };
18707
+
18708
+ // src/realtime/realtime-attempt.ts
18709
+ var RealtimeAttempt = class {
18710
+ constructor() {
18711
+ this.abort = new AbortController();
18712
+ this.timers = /* @__PURE__ */ new Map();
18713
+ this.active = true;
18714
+ this.ready = false;
18715
+ this.closing = false;
18716
+ this.transportClosing = false;
18717
+ }
18718
+ timer(name25, duration, callback) {
18719
+ this.clearTimer(name25);
18720
+ this.timers.set(
18721
+ name25,
18722
+ setTimeout(() => {
18723
+ this.timers.delete(name25);
18724
+ if (this.active) callback();
18725
+ }, duration)
18726
+ );
18727
+ }
18728
+ clearTimer(name25) {
18729
+ clearTimeout(this.timers.get(name25));
18730
+ this.timers.delete(name25);
18731
+ }
18732
+ beginClose() {
18733
+ var _a25;
18734
+ this.closing = true;
18735
+ return (_a25 = this.closePromise) != null ? _a25 : this.closePromise = new Promise((resolve3) => {
18736
+ this.settleClose = resolve3;
17215
18737
  });
17216
18738
  }
17217
- sendRaw(data) {
18739
+ retire() {
17218
18740
  var _a25;
17219
- if (((_a25 = this.ws) == null ? void 0 : _a25.readyState) === WebSocket.OPEN) {
17220
- if (typeof data === "string" || data instanceof ArrayBuffer || ArrayBuffer.isView(data) || data instanceof Blob) {
17221
- this.ws.send(data);
17222
- } else {
17223
- this.ws.send(JSON.stringify(data));
17224
- }
17225
- }
18741
+ if (!this.active) return;
18742
+ this.active = false;
18743
+ this.abort.abort();
18744
+ for (const timer of this.timers.values()) clearTimeout(timer);
18745
+ this.timers.clear();
18746
+ (_a25 = this.settleClose) == null ? void 0 : _a25.call(this);
18747
+ this.settleClose = void 0;
17226
18748
  }
17227
- dispose() {
17228
- this.disconnect();
18749
+ };
18750
+
18751
+ // src/realtime/realtime-command-tracker.ts
18752
+ import { generateId as generateId3 } from "@ai-sdk/provider-utils";
18753
+ var RealtimeCommandTracker = class {
18754
+ constructor(sendCommand) {
18755
+ this.sendCommand = sendCommand;
18756
+ this.pending = /* @__PURE__ */ new Map();
18757
+ this.recent = /* @__PURE__ */ new Set();
18758
+ }
18759
+ validateId(id) {
18760
+ if (this.pending.has(id) || this.recent.has(id))
18761
+ throw new Error(
18762
+ "Realtime command ID was already used; retry with a fresh eventId"
18763
+ );
18764
+ if (this.pending.size >= 512)
18765
+ throw new Error("Too many pending realtime commands");
17229
18766
  }
17230
- async handleMessage(messageEvent) {
17231
- let text2;
17232
- if (typeof messageEvent.data === "string") {
17233
- text2 = messageEvent.data;
17234
- } else if (messageEvent.data instanceof Blob) {
17235
- text2 = await messageEvent.data.text();
17236
- } else {
17237
- text2 = new TextDecoder().decode(messageEvent.data);
17238
- }
17239
- const parseResult = await safeParseJSON5({ text: text2 });
17240
- if (!parseResult.success) return;
17241
- const rawEvent = parseResult.value;
17242
- if (this.model.getHealthCheckResponse != null) {
17243
- const autoResponse = this.model.getHealthCheckResponse(rawEvent);
17244
- if (autoResponse != null) {
17245
- this.sendRaw(autoResponse);
17246
- }
18767
+ complete(id) {
18768
+ const command = this.pending.get(id);
18769
+ if (command == null) return;
18770
+ this.pending.delete(id);
18771
+ this.recent.add(id);
18772
+ if (this.recent.size > 4096) {
18773
+ const oldest = this.recent.values().next().value;
18774
+ if (oldest != null) this.recent.delete(oldest);
17247
18775
  }
17248
- const result = this.model.parseServerEvent(rawEvent);
17249
- const events = Array.isArray(result) ? result : [result];
17250
- for (const event of events) {
17251
- await this.onServerEvent(event);
18776
+ return command;
18777
+ }
18778
+ send(event) {
18779
+ var _a25;
18780
+ const muted = event.type === "input-audio-mute" ? true : event.type === "input-audio-unmute" ? false : void 0;
18781
+ if (event.type === "input-audio-mute" || event.type === "input-audio-unmute")
18782
+ event = { ...event, eventId: (_a25 = event.eventId) != null ? _a25 : generateId3() };
18783
+ const id = "eventId" in event ? event.eventId : void 0;
18784
+ if (id != null) {
18785
+ this.validateId(id);
18786
+ this.pending.set(id, { muted });
18787
+ }
18788
+ const awaitsAcknowledgement = event.type === "context-append" || event.type === "session-update" || muted != null;
18789
+ try {
18790
+ const sent = this.sendCommand(event);
18791
+ void sent.then(
18792
+ () => {
18793
+ if (id != null && !awaitsAcknowledgement) this.complete(id);
18794
+ },
18795
+ () => {
18796
+ if (id != null) this.complete(id);
18797
+ }
18798
+ );
18799
+ return sent;
18800
+ } catch (error) {
18801
+ if (id != null) this.complete(id);
18802
+ throw error;
17252
18803
  }
17253
18804
  }
18805
+ receive(event) {
18806
+ if (event.type === "command-acknowledged" && event.clientEventId != null)
18807
+ return this.complete(event.clientEventId);
18808
+ if (event.type === "error" && event.clientEventId != null)
18809
+ this.complete(event.clientEventId);
18810
+ }
17254
18811
  };
17255
18812
 
18813
+ // src/realtime/validate-realtime-setup.ts
18814
+ var setupSchema = z.object({
18815
+ token: z.string().refine((value) => value.trim().length > 0),
18816
+ url: z.string().refine((value) => {
18817
+ try {
18818
+ const url = new URL(value);
18819
+ return (url.protocol === "ws:" || url.protocol === "wss:") && url.hostname !== "";
18820
+ } catch (e) {
18821
+ return false;
18822
+ }
18823
+ }),
18824
+ expiresAt: z.number().positive().max(Number.MAX_SAFE_INTEGER).optional(),
18825
+ tools: z.array(
18826
+ z.object({
18827
+ type: z.literal("function"),
18828
+ name: z.string().min(1),
18829
+ description: z.string().optional(),
18830
+ parameters: z.record(z.string(), z.unknown())
18831
+ })
18832
+ ).optional()
18833
+ });
18834
+ function validateRealtimeSetup(payload) {
18835
+ const result = setupSchema.safeParse(payload);
18836
+ if (!result.success)
18837
+ throw new Error(
18838
+ "Invalid realtime setup: expected a nonempty token, a ws(s) URL, and valid optional expiresAt/tools"
18839
+ );
18840
+ return result.data;
18841
+ }
18842
+
18843
+ // src/realtime/realtime-session-state.ts
18844
+ function createSessionState() {
18845
+ return {
18846
+ transcripts: [],
18847
+ delegations: [],
18848
+ isInputMuted: false,
18849
+ finalization: "pending"
18850
+ };
18851
+ }
18852
+ function reduceSessionState(state, event, limit, muted) {
18853
+ var _a25;
18854
+ switch (event.type) {
18855
+ case "session-started":
18856
+ return {
18857
+ ...state,
18858
+ sessionId: event.sessionId,
18859
+ delegationMode: event.delegationMode
18860
+ };
18861
+ case "session-usage":
18862
+ return { ...state, usage: event.usage };
18863
+ case "session-closed":
18864
+ return {
18865
+ ...state,
18866
+ sessionId: (_a25 = event.sessionId) != null ? _a25 : state.sessionId,
18867
+ usage: event.usage,
18868
+ terminationReason: event.reason,
18869
+ finalization: "confirmed"
18870
+ };
18871
+ case "transcript-fragment":
18872
+ return {
18873
+ ...state,
18874
+ transcripts: [...state.transcripts, event].slice(-limit)
18875
+ };
18876
+ case "delegation-created":
18877
+ return state.delegations.some(
18878
+ (item) => item.delegationId === event.delegationId
18879
+ ) ? state : {
18880
+ ...state,
18881
+ delegations: [...state.delegations, event].slice(-limit)
18882
+ };
18883
+ case "command-acknowledged":
18884
+ return muted == null ? state : { ...state, isInputMuted: muted };
18885
+ default:
18886
+ return state;
18887
+ }
18888
+ }
18889
+
17256
18890
  // src/realtime/realtime-event-reducer.ts
17257
- import { safeParseJSON as safeParseJSON6 } from "@ai-sdk/provider-utils";
18891
+ import { safeParseJSON as safeParseJSON7 } from "@ai-sdk/provider-utils";
17258
18892
  function createInitialRealtimeState() {
17259
18893
  return {
17260
18894
  status: "disconnected",
@@ -17383,7 +19017,7 @@ var RealtimeEventReducer = class {
17383
19017
  case "function-call-arguments-done": {
17384
19018
  this.toolArgAccumulators.delete(event.callId);
17385
19019
  this.toolCallIdToName.set(event.callId, event.name);
17386
- const parseResult = await safeParseJSON6({ text: event.arguments });
19020
+ const parseResult = await safeParseJSON7({ text: event.arguments });
17387
19021
  const parsedInput = parseResult.success ? parseResult.value : {};
17388
19022
  const messageId = this.toolCallIdToMessageId.get(event.callId);
17389
19023
  if (messageId != null) {
@@ -17623,98 +19257,375 @@ var RealtimeEventReducer = class {
17623
19257
  // src/realtime/realtime-session.ts
17624
19258
  var AbstractRealtimeSession = class {
17625
19259
  constructor(options) {
19260
+ this.options = options;
17626
19261
  this.state = createInitialRealtimeState();
19262
+ this.publication = 0;
19263
+ this.captureGeneration = 0;
19264
+ this.captureRequested = false;
17627
19265
  this.currentResponseItemId = null;
17628
- // Tool calls requested by the current (tool-bearing) response, the outputs
17629
- // that have been submitted for them, and whether that response has finished
17630
- // delivering its tool calls. Used to request a single response only once
17631
- // every tool output for the turn has been submitted.
17632
19266
  this.toolCallsInResponse = /* @__PURE__ */ new Set();
17633
19267
  this.submittedToolOutputs = /* @__PURE__ */ new Set();
17634
19268
  this.responseToolCallsClosed = false;
17635
- var _a25, _b25, _c, _d, _e, _f, _g, _h;
17636
- this.model = options.model;
17637
- this.api = options.api;
17638
- this.sessionConfig = options.sessionConfig;
19269
+ var _a25, _b25, _c, _d, _e;
19270
+ const capabilities = options.model.capabilities;
19271
+ this.sessionLifecycle = (capabilities == null ? void 0 : capabilities.startup) === "session-start" || (capabilities == null ? void 0 : capabilities.finalization) === "session-close";
19272
+ this.continuous = (capabilities == null ? void 0 : capabilities.conversation) === "continuous";
17639
19273
  this.maxEvents = (_a25 = options.maxEvents) != null ? _a25 : 500;
19274
+ if (!Number.isSafeInteger(this.maxEvents) || this.maxEvents < 1)
19275
+ throw new Error("maxEvents must be a positive integer");
19276
+ for (const timeout of [
19277
+ (_b25 = options.startupTimeoutMs) != null ? _b25 : 3e4,
19278
+ (_c = options.closeTimeoutMs) != null ? _c : 15e3,
19279
+ (_d = options.rtcDisconnectTimeoutMs) != null ? _d : 5e3
19280
+ ]) {
19281
+ if (!Number.isFinite(timeout) || timeout <= 0 || timeout > 2147483647)
19282
+ throw new Error(
19283
+ "Realtime timeouts must be positive finite timer durations"
19284
+ );
19285
+ }
19286
+ const budget = (_e = options.maxPlaybackBufferSeconds) != null ? _e : 2;
19287
+ if (!Number.isFinite(budget) || budget <= 0)
19288
+ throw new Error("maxPlaybackBufferSeconds must be positive and finite");
17640
19289
  this.reducer = new RealtimeEventReducer(this.maxEvents);
17641
19290
  this.onToolCall = options.onToolCall;
17642
19291
  this.onEvent = options.onEvent;
17643
19292
  this.onError = options.onError;
17644
- const sampleRate = (_b25 = options.sampleRate) != null ? _b25 : 24e3;
17645
- const captureSampleRate = (_e = (_d = (_c = options.sessionConfig) == null ? void 0 : _c.inputAudioFormat) == null ? void 0 : _d.rate) != null ? _e : sampleRate;
17646
- const playbackSampleRate = (_h = (_g = (_f = options.sessionConfig) == null ? void 0 : _f.outputAudioFormat) == null ? void 0 : _g.rate) != null ? _h : sampleRate;
17647
- this.transport = new BrowserRealtimeTransport({
17648
- model: this.model,
17649
- onServerEvent: (event) => this.handleServerEvent(event),
17650
- onError: (error) => {
17651
- var _a26;
17652
- this.applyState(this.reducer.setStatus(this.state, "error"));
17653
- (_a26 = this.onError) == null ? void 0 : _a26.call(this, error);
17654
- },
17655
- onClose: () => {
17656
- this.applyState(this.reducer.setStatus(this.state, "disconnected"));
17657
- }
17658
- });
17659
- this.audio = new BrowserRealtimeAudio({
17660
- captureSampleRate,
17661
- playbackSampleRate,
17662
- onAudio: (audio) => this.sendAudio(audio),
17663
- onCapturingChange: (isCapturing) => {
17664
- this.applyState(this.reducer.setCapturing(this.state, isCapturing));
17665
- },
17666
- onPlayingChange: (isPlaying) => {
17667
- this.applyState(this.reducer.setPlaying(this.state, isPlaying));
17668
- }
17669
- });
17670
19293
  }
17671
- // ── Connection ─────────────────────────────────────────────────────
17672
- async connect() {
17673
- var _a25;
17674
- this.applyState(this.reducer.setStatus(this.state, "connecting"));
19294
+ validateConnection() {
19295
+ var _a25, _b25, _c;
19296
+ const { model, api } = this.options;
19297
+ if ((!this.continuous || api.session != null) && this.options.maxPlaybackBufferSeconds != null)
19298
+ throw new Error(
19299
+ "maxPlaybackBufferSeconds is supported only for continuous PCM sessions"
19300
+ );
19301
+ const connection = api.session != null ? "webrtc" : api.token != null ? "client-secret-websocket" : "server-websocket";
19302
+ if (!((_b25 = (_a25 = model.capabilities) == null ? void 0 : _a25.connections) != null ? _b25 : ["client-secret-websocket"]).includes(connection) || ((_c = model.capabilities) == null ? void 0 : _c.transports) != null && !model.capabilities.transports.includes(
19303
+ api.session != null ? "webrtc" : "websocket"
19304
+ ))
19305
+ throw new Error(`Realtime model does not support ${connection}`);
19306
+ if (api.session != null) {
19307
+ if (model.getWebRTCConfig == null)
19308
+ throw new Error("Realtime model does not support WebRTC configuration");
19309
+ return;
19310
+ }
19311
+ if (this.continuous && api.websocket == null)
19312
+ throw new Error(
19313
+ "Continuous PCM sessions require an application WebSocket relay"
19314
+ );
19315
+ if (api.token != null && model.getWebSocketConfig == null)
19316
+ throw new Error(
19317
+ "Realtime model does not support client-secret WebSocket configuration"
19318
+ );
19319
+ }
19320
+ async connect(connectOptions) {
19321
+ var _a25, _b25, _c, _d;
19322
+ if ((_a25 = this.attempt) == null ? void 0 : _a25.active)
19323
+ throw new Error("Realtime session is already active");
19324
+ const attempt = new RealtimeAttempt();
19325
+ this.attempt = attempt;
19326
+ const current = () => this.attempt === attempt && attempt.active;
17675
19327
  try {
17676
- const response = await fetch(this.api.token, {
17677
- method: "POST",
17678
- headers: { "Content-Type": "application/json" },
17679
- body: JSON.stringify({ sessionConfig: this.sessionConfig })
19328
+ this.applyState({ ...this.state, status: "connecting" });
19329
+ if (!current()) return;
19330
+ this.validateConnection();
19331
+ const { model, api, sessionConfig } = this.options;
19332
+ if ((connectOptions == null ? void 0 : connectOptions.capture) === false) this.stopAudioCapture();
19333
+ if (!current()) return;
19334
+ if ((connectOptions == null ? void 0 : connectOptions.stream) != null && connectOptions.stream !== this.suppliedStream) {
19335
+ if (this.captureRequested && !this.sessionLifecycle && !this.continuous && api.session == null)
19336
+ this.startAudioCapture(connectOptions.stream);
19337
+ else this.suppliedStream = connectOptions.stream;
19338
+ }
19339
+ if (!current()) return;
19340
+ this.reducer = new RealtimeEventReducer(this.maxEvents);
19341
+ this.currentResponseItemId = null;
19342
+ this.toolCallsInResponse.clear();
19343
+ this.submittedToolOutputs.clear();
19344
+ this.responseToolCallsClosed = false;
19345
+ if (this.sessionLifecycle)
19346
+ this.applyState({ ...this.state, session: createSessionState() });
19347
+ if (!current()) return;
19348
+ this.commands = new RealtimeCommandTracker((event) => {
19349
+ const writable = () => current() && !attempt.transportClosing && attempt.cause == null && (!attempt.closing || event.type === "session-close");
19350
+ if (!writable()) throw new Error("Realtime connection is closed");
19351
+ return this.sendTransport(event, writable);
17680
19352
  });
17681
- if (!response.ok) {
17682
- throw new Error(`Failed to fetch realtime setup: ${response.status}`);
17683
- }
17684
- const setupData = await response.json();
17685
- const { token, url, tools: toolDefinitions } = setupData;
17686
- const config = {
17687
- ...this.sessionConfig,
17688
- tools: toolDefinitions
19353
+ attempt.timer("startup", (_b25 = this.options.startupTimeoutMs) != null ? _b25 : 3e4, () => {
19354
+ if (current())
19355
+ this.fail(new Error("Realtime session startup timed out"));
19356
+ });
19357
+ const callbacks = {
19358
+ model,
19359
+ onEvent: async (event) => {
19360
+ var _a26, _b26, _c2;
19361
+ if (!current()) return;
19362
+ try {
19363
+ await this.handleServerEvent(event, attempt);
19364
+ } catch (error) {
19365
+ if (current())
19366
+ this.fail(
19367
+ error,
19368
+ (_c2 = (_b26 = (_a26 = this.rtc) != null ? _a26 : this.pcm) != null ? _b26 : this.transport) == null ? void 0 : _c2.finish()
19369
+ );
19370
+ }
19371
+ },
19372
+ onError: (error) => {
19373
+ if (current()) void this.reportError(error, attempt);
19374
+ },
19375
+ onFatalError: (error, drain) => {
19376
+ if (current()) this.fail(error, drain);
19377
+ },
19378
+ onClosing: () => {
19379
+ if (!current()) return;
19380
+ attempt.beginClose();
19381
+ attempt.transportClosing = true;
19382
+ attempt.clearTimer("startup");
19383
+ attempt.clearTimer("close");
19384
+ this.stopAudioCapture();
19385
+ if (!current()) return;
19386
+ this.applyState({
19387
+ ...this.state,
19388
+ status: attempt.cause == null ? "closing" : "error"
19389
+ });
19390
+ },
19391
+ onClose: (error) => {
19392
+ var _a26, _b26;
19393
+ if (!current()) return;
19394
+ const finalizationConfirmed = ((_a26 = model.capabilities) == null ? void 0 : _a26.finalization) === "session-close" && ((_b26 = this.state.session) == null ? void 0 : _b26.finalization) === "confirmed";
19395
+ if (error != null && !finalizationConfirmed) this.fail(error);
19396
+ else if (!attempt.ready && !finalizationConfirmed)
19397
+ this.fail(
19398
+ new Error("Realtime connection closed before becoming ready")
19399
+ );
19400
+ else this.disconnect();
19401
+ },
19402
+ onCapturing: (isCapturing) => {
19403
+ if (current()) this.applyState({ ...this.state, isCapturing });
19404
+ },
19405
+ onPlaying: (isPlaying) => {
19406
+ if (current()) this.applyState({ ...this.state, isPlaying });
19407
+ }
17689
19408
  };
17690
- this.audio.ensurePlaybackContext();
17691
- this.transport.connect({
17692
- token,
17693
- url,
17694
- onOpen: () => {
17695
- this.sendEvent({
17696
- type: "session-update",
17697
- config
19409
+ if (api.session != null) {
19410
+ this.rtc = new BrowserRealtimeWebRTC({
19411
+ ...callbacks,
19412
+ disconnectTimeoutMs: this.options.rtcDisconnectTimeoutMs
19413
+ });
19414
+ await this.rtc.connect({
19415
+ api: api.session,
19416
+ sessionConfig,
19417
+ stream: connectOptions == null ? void 0 : connectOptions.stream,
19418
+ capture: connectOptions == null ? void 0 : connectOptions.capture,
19419
+ timeoutMs: (_c = this.options.startupTimeoutMs) != null ? _c : 3e4
19420
+ });
19421
+ } else if (api.websocket != null && ((_d = model.capabilities) == null ? void 0 : _d.conversation) === "continuous") {
19422
+ this.pcm = new BrowserRealtimeLiveWebSocket({
19423
+ ...callbacks,
19424
+ sessionConfig,
19425
+ sampleRate: this.options.sampleRate,
19426
+ maxPlaybackBufferSeconds: this.options.maxPlaybackBufferSeconds
19427
+ });
19428
+ this.pcm.connect({
19429
+ url: api.websocket,
19430
+ protocols: api.protocols,
19431
+ stream: connectOptions == null ? void 0 : connectOptions.stream,
19432
+ capture: connectOptions == null ? void 0 : connectOptions.capture
19433
+ });
19434
+ } else {
19435
+ this.ensureAudio();
19436
+ let config = sessionConfig != null ? sessionConfig : {};
19437
+ let token;
19438
+ let url = api.websocket;
19439
+ if (api.token != null) {
19440
+ const response = await fetch(api.token, {
19441
+ method: "POST",
19442
+ headers: { "Content-Type": "application/json" },
19443
+ body: JSON.stringify({ sessionConfig }),
19444
+ signal: attempt.abort.signal
19445
+ });
19446
+ if (!current()) return;
19447
+ if (!response.ok)
19448
+ throw new Error(
19449
+ `Failed to fetch realtime setup: ${response.status}`
19450
+ );
19451
+ const payload = await response.json().catch(() => {
19452
+ throw new Error("Invalid realtime setup response");
17698
19453
  });
19454
+ if (!current() || attempt.closing) return;
19455
+ const setup = validateRealtimeSetup(payload);
19456
+ token = setup.token;
19457
+ url = setup.url;
19458
+ config = {
19459
+ ...sessionConfig,
19460
+ ...setup.tools == null ? {} : { tools: setup.tools }
19461
+ };
17699
19462
  }
17700
- });
19463
+ if (url == null) throw new Error("Realtime WebSocket URL is missing");
19464
+ if (api.token != null && token == null)
19465
+ throw new Error("Realtime client-secret connection requires a token");
19466
+ this.ensureAudio().ensurePlaybackContext();
19467
+ if (!current()) return;
19468
+ this.transport = new BrowserRealtimeTransport({
19469
+ ...callbacks,
19470
+ onServerEvent: callbacks.onEvent
19471
+ });
19472
+ this.transport.connect({
19473
+ ...token != null ? { mode: "client-secret", token } : { mode: "relay", protocols: api.protocols },
19474
+ url,
19475
+ onOpen: () => {
19476
+ var _a26, _b26;
19477
+ const writable = () => current() && !attempt.closing && attempt.cause == null;
19478
+ if (!writable()) return;
19479
+ return this.sendTransport(
19480
+ {
19481
+ type: (_b26 = (_a26 = model.capabilities) == null ? void 0 : _a26.startup) != null ? _b26 : "session-update",
19482
+ config
19483
+ },
19484
+ writable
19485
+ );
19486
+ }
19487
+ });
19488
+ }
17701
19489
  } catch (error) {
17702
- this.applyState(this.reducer.setStatus(this.state, "error"));
17703
- (_a25 = this.onError) == null ? void 0 : _a25.call(
17704
- this,
17705
- error instanceof Error ? error : new Error(`Connection failed: ${String(error)}`)
17706
- );
19490
+ if (current()) this.fail(error);
19491
+ }
19492
+ }
19493
+ fail(error, drain) {
19494
+ const attempt = this.attempt;
19495
+ if (attempt == null || !attempt.active || attempt.cause != null) return;
19496
+ attempt.cause = error instanceof Error ? error : new Error(String(error));
19497
+ if (drain != null) attempt.beginClose();
19498
+ this.applyState({ ...this.state, status: "error" });
19499
+ if (this.attempt !== attempt || !attempt.active) return;
19500
+ this.stopAudioCapture();
19501
+ if (this.attempt !== attempt || !attempt.active) return;
19502
+ if (drain == null) this.disconnect();
19503
+ else void this.drainAttempt(attempt, drain);
19504
+ void this.reportError(attempt.cause, attempt);
19505
+ }
19506
+ async drainAttempt(attempt, drain) {
19507
+ if (this.attempt === attempt && attempt.active)
19508
+ attempt.timer("drain", 1e3, () => this.finishAttempt(attempt));
19509
+ try {
19510
+ await drain;
19511
+ } catch (e) {
17707
19512
  }
19513
+ this.finishAttempt(attempt);
19514
+ }
19515
+ finishAttempt(attempt) {
19516
+ if (this.attempt !== attempt || !attempt.active) return;
19517
+ try {
19518
+ this.disconnect();
19519
+ } catch (error) {
19520
+ void this.reportError(error, attempt);
19521
+ }
19522
+ }
19523
+ closeFailed(attempt, error) {
19524
+ var _a25, _b25;
19525
+ if (this.attempt !== attempt || !attempt.active || attempt.transportClosing)
19526
+ return;
19527
+ attempt.clearTimer("close");
19528
+ const transport = (_b25 = (_a25 = this.rtc) != null ? _a25 : this.pcm) != null ? _b25 : this.transport;
19529
+ void this.drainAttempt(attempt, transport == null ? void 0 : transport.finish());
19530
+ void this.reportError(error, attempt);
17708
19531
  }
17709
19532
  disconnect() {
17710
- this.transport.disconnect();
17711
- this.applyState(this.reducer.setStatus(this.state, "disconnected"));
19533
+ const attempt = this.attempt;
19534
+ this.captureGeneration++;
19535
+ this.captureRequested = false;
19536
+ this.suppliedStream = void 0;
19537
+ const transport = this.transport;
19538
+ const audio = this.audio;
19539
+ const pcm = this.pcm;
19540
+ const rtc = this.rtc;
19541
+ this.transport = void 0;
19542
+ this.audio = void 0;
19543
+ this.pcm = void 0;
19544
+ this.rtc = void 0;
19545
+ this.commands = void 0;
19546
+ attempt == null ? void 0 : attempt.retire();
19547
+ transport == null ? void 0 : transport.dispose();
19548
+ audio == null ? void 0 : audio.dispose();
19549
+ pcm == null ? void 0 : pcm.dispose();
19550
+ rtc == null ? void 0 : rtc.dispose();
19551
+ if (this.attempt !== attempt) return;
19552
+ const session = this.state.session;
19553
+ this.applyState({
19554
+ ...this.state,
19555
+ status: (attempt == null ? void 0 : attempt.cause) != null ? "error" : "disconnected",
19556
+ isCapturing: false,
19557
+ isPlaying: false,
19558
+ ...session != null ? {
19559
+ session: session.finalization === "confirmed" ? session : { ...session, finalization: "unconfirmed" }
19560
+ } : {}
19561
+ });
19562
+ }
19563
+ /** Wait for final usage when the model supports a session-close acknowledgement. */
19564
+ close(options) {
19565
+ var _a25, _b25, _c, _d;
19566
+ const attempt = this.attempt;
19567
+ if ((attempt == null ? void 0 : attempt.closePromise) != null) return attempt.closePromise;
19568
+ if (this.state.status !== "connected" || ((_a25 = this.options.model.capabilities) == null ? void 0 : _a25.finalization) !== "session-close" || attempt == null) {
19569
+ this.disconnect();
19570
+ return Promise.resolve();
19571
+ }
19572
+ if ((options == null ? void 0 : options.eventId) != null) (_b25 = this.commands) == null ? void 0 : _b25.validateId(options.eventId);
19573
+ const promise = attempt.beginClose();
19574
+ this.applyState({ ...this.state, status: "closing" });
19575
+ if (this.attempt !== attempt || !attempt.active || attempt.transportClosing)
19576
+ return promise;
19577
+ this.stopAudioCapture();
19578
+ if (this.attempt !== attempt || !attempt.active || attempt.transportClosing)
19579
+ return promise;
19580
+ attempt.timer(
19581
+ "close",
19582
+ (_c = this.options.closeTimeoutMs) != null ? _c : 15e3,
19583
+ () => this.finishAttempt(attempt)
19584
+ );
19585
+ const failed = (error) => this.closeFailed(attempt, error);
19586
+ try {
19587
+ void ((_d = this.commands) == null ? void 0 : _d.send({ type: "session-close", eventId: options == null ? void 0 : options.eventId }).catch(failed));
19588
+ } catch (error) {
19589
+ failed(error);
19590
+ }
19591
+ return promise;
19592
+ }
19593
+ sendTransport(event, guard, automaticAudio = false) {
19594
+ var _a25, _b25;
19595
+ const transport = (_b25 = (_a25 = this.rtc) != null ? _a25 : this.pcm) != null ? _b25 : this.transport;
19596
+ if (transport == null) throw new Error("Realtime connection is not open");
19597
+ if (transport === this.transport)
19598
+ return this.transport.sendEvent(event, guard, automaticAudio);
19599
+ return transport.sendEvent(event, guard);
17712
19600
  }
17713
- // ── Sending events ─────────────────────────────────────────────────
17714
19601
  sendEvent(event) {
17715
- this.transport.sendEvent(event);
19602
+ var _a25, _b25, _c, _d;
19603
+ if (this.state.status === "error" || this.state.status === "closing" || ((_a25 = this.attempt) == null ? void 0 : _a25.closing) || !((_b25 = this.attempt) == null ? void 0 : _b25.active))
19604
+ throw new Error("Realtime session is not accepting submissions");
19605
+ if (this.sessionLifecycle && this.state.status !== "connected")
19606
+ throw new Error("Realtime session is not accepting submissions");
19607
+ if (this.rtc != null && event.type === "input-audio-append" || (this.continuous || this.rtc != null) && (event.type === "input-audio-commit" || event.type === "input-audio-clear"))
19608
+ throw new UnsupportedFunctionalityError6({
19609
+ functionality: "JSON audio commands unsupported for this session transport"
19610
+ });
19611
+ if (event.type === "session-close")
19612
+ return this.close({ eventId: event.eventId });
19613
+ if (event.type === "session-start" && this.sessionLifecycle)
19614
+ throw new Error("Realtime session has already started");
19615
+ if (!this.sessionLifecycle && this.state.session == null) {
19616
+ const attempt = this.attempt;
19617
+ return this.sendTransport(
19618
+ event,
19619
+ () => this.attempt === attempt && attempt.active && !attempt.closing && attempt.cause == null
19620
+ );
19621
+ }
19622
+ return (_d = (_c = this.commands) == null ? void 0 : _c.send(event)) != null ? _d : this.sendTransport(event);
17716
19623
  }
17717
19624
  sendTextMessage(text2) {
19625
+ if (this.continuous)
19626
+ throw new UnsupportedFunctionalityError6({
19627
+ functionality: "sendTextMessage for continuous sessions; client-delegation text is application-owned, use context-append for context"
19628
+ });
17718
19629
  this.sendEvent({
17719
19630
  type: "conversation-item-create",
17720
19631
  item: { type: "text-message", role: "user", text: text2 }
@@ -17722,8 +19633,12 @@ var AbstractRealtimeSession = class {
17722
19633
  this.sendEvent({ type: "response-create" });
17723
19634
  this.applyState(this.reducer.addUserTextMessage(this.state, text2));
17724
19635
  }
17725
- sendAudio(base64Audio) {
17726
- this.sendEvent({ type: "input-audio-append", audio: base64Audio });
19636
+ sendAudio(audio) {
19637
+ const attempt = this.attempt;
19638
+ void this.sendEvent({ type: "input-audio-append", audio }).catch((error) => {
19639
+ if ((attempt == null ? void 0 : attempt.active) && this.attempt === attempt)
19640
+ void this.reportError(error, attempt);
19641
+ });
17727
19642
  }
17728
19643
  commitAudio() {
17729
19644
  this.sendEvent({ type: "input-audio-commit" });
@@ -17740,161 +19655,301 @@ var AbstractRealtimeSession = class {
17740
19655
  cancelResponse() {
17741
19656
  this.sendEvent({ type: "response-cancel" });
17742
19657
  }
17743
- // ── Tool output ───────────────────────────────────────────────────
19658
+ sendAutomaticAudio(audio) {
19659
+ var _a25;
19660
+ if (this.state.status !== "connected" || !((_a25 = this.attempt) == null ? void 0 : _a25.active) || this.attempt.closing)
19661
+ return;
19662
+ const attempt = this.attempt;
19663
+ const failed = (error) => {
19664
+ if ((attempt == null ? void 0 : attempt.active) && this.attempt === attempt && !attempt.closing)
19665
+ this.fail(error);
19666
+ };
19667
+ try {
19668
+ void this.sendTransport(
19669
+ { type: "input-audio-append", audio },
19670
+ () => this.attempt === attempt && attempt.active && !attempt.closing,
19671
+ true
19672
+ ).catch(failed);
19673
+ } catch (error) {
19674
+ failed(error);
19675
+ }
19676
+ }
17744
19677
  addToolOutput(callId, result) {
19678
+ if (this.continuous)
19679
+ throw new UnsupportedFunctionalityError6({
19680
+ functionality: "addToolOutput for continuous sessions; client delegation is application-owned"
19681
+ });
19682
+ const attempt = this.attempt;
19683
+ if (!(attempt == null ? void 0 : attempt.active) || attempt.closing || attempt.cause != null)
19684
+ throw new Error("Realtime session is not accepting submissions");
17745
19685
  const { state, output } = this.reducer.addToolOutput(
17746
19686
  this.state,
17747
19687
  callId,
17748
19688
  result
17749
19689
  );
17750
19690
  this.applyState(state);
19691
+ if (this.attempt !== attempt || !(attempt == null ? void 0 : attempt.active)) return;
17751
19692
  this.sendEvent({
17752
19693
  type: "conversation-item-create",
17753
- item: {
17754
- type: "function-call-output",
17755
- callId: output.callId,
17756
- name: output.name,
17757
- output: output.output
17758
- }
19694
+ item: { type: "function-call-output", ...output }
17759
19695
  });
17760
19696
  this.submittedToolOutputs.add(callId);
17761
19697
  this.maybeRequestToolResponse();
17762
19698
  }
17763
- /**
17764
- * Requests a single response once the tool-bearing response has finished
17765
- * delivering its tool calls and every one of them has an output. Requesting a
17766
- * response after each individual output can cause the model to continue
17767
- * without the full tool context on multi-tool turns.
17768
- */
17769
19699
  maybeRequestToolResponse() {
17770
- if (!this.responseToolCallsClosed) return;
17771
- if (this.toolCallsInResponse.size === 0) return;
17772
- for (const callId of this.toolCallsInResponse) {
17773
- if (!this.submittedToolOutputs.has(callId)) return;
17774
- }
19700
+ var _a25;
19701
+ if (this.state.status !== "connected" || ((_a25 = this.attempt) == null ? void 0 : _a25.closing) || !this.responseToolCallsClosed || this.toolCallsInResponse.size === 0 || [...this.toolCallsInResponse].some(
19702
+ (id) => !this.submittedToolOutputs.has(id)
19703
+ ))
19704
+ return;
17775
19705
  this.sendEvent({ type: "response-create" });
17776
19706
  this.toolCallsInResponse.clear();
17777
19707
  this.submittedToolOutputs.clear();
17778
19708
  this.responseToolCallsClosed = false;
17779
19709
  }
17780
- // ── Audio capture ──────────────────────────────────────────────────
17781
19710
  startAudioCapture(stream) {
17782
- this.audio.startCapture(stream);
19711
+ var _a25;
19712
+ const legacy = !this.sessionLifecycle && !this.continuous && this.options.api.session == null;
19713
+ if (!legacy && this.state.status !== "connected")
19714
+ throw new Error("Realtime session is not accepting capture");
19715
+ if (((_a25 = this.attempt) == null ? void 0 : _a25.active) && (this.attempt.closing || this.attempt.cause != null))
19716
+ throw new Error("Realtime session is not accepting capture");
19717
+ this.suppliedStream = stream;
19718
+ if (legacy) {
19719
+ this.captureGeneration++;
19720
+ this.captureRequested = true;
19721
+ this.ensureAudio().startCapture(stream);
19722
+ return;
19723
+ }
19724
+ const attempt = this.attempt;
19725
+ void this.resumeAudioCapture().catch((error) => {
19726
+ if (attempt == null ? void 0 : attempt.active) void this.reportError(error, attempt);
19727
+ });
19728
+ }
19729
+ async resumeAudioCapture() {
19730
+ const accepting = () => {
19731
+ var _a25, _b25;
19732
+ return !((_a25 = this.attempt) == null ? void 0 : _a25.closing) && ((_b25 = this.attempt) == null ? void 0 : _b25.cause) == null && (this.state.status === "connected" || !this.sessionLifecycle && this.state.status === "connecting");
19733
+ };
19734
+ if (!accepting())
19735
+ throw new Error("Realtime session is not accepting capture");
19736
+ this.captureRequested = true;
19737
+ if (this.rtc != null) return this.rtc.startCapture(this.suppliedStream);
19738
+ if (this.pcm != null) return this.pcm.resumeCapture(this.suppliedStream);
19739
+ const audio = this.audio;
19740
+ if (audio == null)
19741
+ throw new Error("Realtime capture transport is not ready");
19742
+ const captureGeneration = ++this.captureGeneration;
19743
+ const attempt = this.attempt;
19744
+ const supplied = this.suppliedStream;
19745
+ const stream = supplied != null ? supplied : await navigator.mediaDevices.getUserMedia({ audio: true });
19746
+ if (!(attempt == null ? void 0 : attempt.active) || this.attempt !== attempt || this.audio !== audio || !accepting() || captureGeneration !== this.captureGeneration) {
19747
+ if (supplied == null) stream.getTracks().forEach((track) => track.stop());
19748
+ return;
19749
+ }
19750
+ audio.startCapture(stream, {
19751
+ ownsStream: supplied == null || !this.sessionLifecycle || this.options.api.token != null
19752
+ });
17783
19753
  }
17784
19754
  stopAudioCapture() {
17785
- this.audio.stopCapture();
19755
+ this.captureGeneration++;
19756
+ this.captureRequested = false;
19757
+ if (this.audio != null && (!this.sessionLifecycle || this.options.api.token != null))
19758
+ this.suppliedStream = void 0;
19759
+ const pcm = this.pcm;
19760
+ const audio = this.audio;
19761
+ const rtc = this.rtc;
19762
+ const attempt = this.attempt;
19763
+ if (rtc != null)
19764
+ void rtc.stopCapture().catch((error) => {
19765
+ if ((attempt == null ? void 0 : attempt.active) && this.attempt === attempt)
19766
+ void this.reportError(error, attempt);
19767
+ });
19768
+ pcm == null ? void 0 : pcm.stopCapture();
19769
+ audio == null ? void 0 : audio.stopCapture();
17786
19770
  }
17787
- // ── Playback ───────────────────────────────────────────────────────
17788
19771
  stopPlayback() {
17789
- this.audio.stopPlayback();
19772
+ var _a25, _b25, _c;
19773
+ (_c = (_b25 = (_a25 = this.rtc) != null ? _a25 : this.pcm) != null ? _b25 : this.audio) == null ? void 0 : _c.stopPlayback();
19774
+ }
19775
+ async resumePlayback() {
19776
+ var _a25, _b25, _c;
19777
+ await ((_c = (_b25 = (_a25 = this.rtc) != null ? _a25 : this.pcm) != null ? _b25 : this.audio) == null ? void 0 : _c.resumePlayback());
17790
19778
  }
17791
- // ── Cleanup ────────────────────────────────────────────────────────
17792
19779
  dispose() {
17793
- this.transport.dispose();
17794
- this.audio.dispose();
17795
- this.applyState(
17796
- this.reducer.setStatus(
17797
- this.reducer.setPlaying(
17798
- this.reducer.setCapturing(this.state, false),
17799
- false
17800
- ),
17801
- "disconnected"
17802
- )
17803
- );
19780
+ this.disconnect();
19781
+ }
19782
+ ensureAudio() {
19783
+ var _a25, _b25, _c, _d, _e, _f;
19784
+ if (this.audio != null) return this.audio;
19785
+ const { sessionConfig, sampleRate } = this.options;
19786
+ const audio = new BrowserRealtimeAudio({
19787
+ captureSampleRate: (_c = (_b25 = (_a25 = sessionConfig == null ? void 0 : sessionConfig.inputAudioFormat) == null ? void 0 : _a25.rate) != null ? _b25 : sampleRate) != null ? _c : 24e3,
19788
+ playbackSampleRate: (_f = (_e = (_d = sessionConfig == null ? void 0 : sessionConfig.outputAudioFormat) == null ? void 0 : _d.rate) != null ? _e : sampleRate) != null ? _f : 24e3,
19789
+ onAudio: (value) => {
19790
+ if (this.audio === audio) this.sendAutomaticAudio(value);
19791
+ },
19792
+ onError: (error) => {
19793
+ if (this.audio !== audio) return;
19794
+ void this.reportError(error, this.attempt);
19795
+ },
19796
+ onCapturingChange: (isCapturing) => {
19797
+ if (this.audio === audio)
19798
+ this.applyState({ ...this.state, isCapturing });
19799
+ },
19800
+ onPlayingChange: (isPlaying) => {
19801
+ if (this.audio === audio) this.applyState({ ...this.state, isPlaying });
19802
+ }
19803
+ });
19804
+ this.audio = audio;
19805
+ return audio;
17804
19806
  }
17805
- // ── Private helpers ────────────────────────────────────────────────
17806
19807
  applyState(nextState) {
17807
- const previousState = this.state;
19808
+ const publication = ++this.publication;
19809
+ const previous = this.state;
17808
19810
  this.state = nextState;
17809
- if (previousState.status !== nextState.status) {
17810
- this.setState("status", nextState.status);
17811
- }
17812
- if (previousState.messages !== nextState.messages) {
17813
- this.setState("messages", nextState.messages);
17814
- }
17815
- if (previousState.events !== nextState.events) {
17816
- this.setState("events", nextState.events);
17817
- }
17818
- if (previousState.isCapturing !== nextState.isCapturing) {
17819
- this.setState("isCapturing", nextState.isCapturing);
17820
- }
17821
- if (previousState.isPlaying !== nextState.isPlaying) {
17822
- this.setState("isPlaying", nextState.isPlaying);
17823
- }
17824
- }
17825
- async executeToolCall({
17826
- name: name25,
17827
- args,
17828
- callId
17829
- }) {
17830
- var _a25, _b25;
17831
- if (this.onToolCall == null) {
17832
- (_a25 = this.onError) == null ? void 0 : _a25.call(this, new Error(`No handler provided for tool "${name25}"`));
19811
+ const update = (key) => {
19812
+ if (publication === this.publication && previous[key] !== nextState[key])
19813
+ this.setState(key, nextState[key]);
19814
+ };
19815
+ update("status");
19816
+ update("messages");
19817
+ update("events");
19818
+ update("isCapturing");
19819
+ update("isPlaying");
19820
+ update("session");
19821
+ }
19822
+ async executeTool(callId, name25, args, attempt) {
19823
+ if (this.continuous || this.attempt !== attempt || !attempt.active || attempt.closing || attempt.cause != null)
17833
19824
  return;
17834
- }
17835
19825
  try {
19826
+ if (this.onToolCall == null) {
19827
+ void this.reportError(
19828
+ new Error(`No handler provided for tool "${name25}"`),
19829
+ attempt
19830
+ );
19831
+ return;
19832
+ }
17836
19833
  const result = await this.onToolCall({
17837
19834
  toolCall: { toolCallId: callId, toolName: name25, args }
17838
19835
  });
17839
- if (result !== void 0) {
19836
+ if (result !== void 0 && this.attempt === attempt && (attempt == null ? void 0 : attempt.active) && this.state.status === "connected")
17840
19837
  this.addToolOutput(callId, result);
17841
- }
17842
19838
  } catch (error) {
17843
- (_b25 = this.onError) == null ? void 0 : _b25.call(
17844
- this,
17845
- error instanceof Error ? error : new Error(`Client tool execution failed: ${String(error)}`)
17846
- );
19839
+ if (attempt.active) void this.reportError(error, attempt);
17847
19840
  }
17848
19841
  }
17849
- async handleServerEvent(event) {
17850
- var _a25;
19842
+ async handleServerEvent(event, attempt) {
19843
+ var _a25, _b25, _c, _d, _e, _f;
19844
+ const current = () => this.attempt === attempt && attempt.active;
19845
+ if (!current()) return;
19846
+ if (event.type === "session-started" && event.delegationMode === "provider")
19847
+ throw new UnsupportedFunctionalityError6({
19848
+ functionality: "Provider delegation mode; this realtime runtime supports client delegation only"
19849
+ });
19850
+ if (this.continuous && event.type === "audio-delta")
19851
+ throw new UnsupportedFunctionalityError6({
19852
+ functionality: "Turn-based audio-delta in a continuous PCM session; use audio-chunk"
19853
+ });
19854
+ const command = (_a25 = this.commands) == null ? void 0 : _a25.receive(event);
17851
19855
  const result = await this.reducer.reduceServerEvent(this.state, event);
17852
- this.applyState(result.state);
17853
- (_a25 = this.onEvent) == null ? void 0 : _a25.call(this, event);
19856
+ if (!current()) return;
19857
+ const session = (_b25 = this.state.session) != null ? _b25 : event.type === "session-started" ? createSessionState() : void 0;
19858
+ this.applyState({
19859
+ ...result.state,
19860
+ status: this.state.status,
19861
+ ...session == null ? {} : {
19862
+ session: reduceSessionState(
19863
+ session,
19864
+ event,
19865
+ this.maxEvents,
19866
+ command == null ? void 0 : command.muted
19867
+ )
19868
+ }
19869
+ });
19870
+ if (!current()) return;
17854
19871
  for (const effect of result.effects) {
17855
- this.handleReducerEffect(effect);
19872
+ this.handleReducerEffect(effect, attempt);
19873
+ if (!current()) return;
17856
19874
  }
19875
+ if (event.type === "audio-chunk")
19876
+ (_d = (_c = this.pcm) != null ? _c : this.audio) == null ? void 0 : _d.playAudio(event.delta);
19877
+ if (!current()) return;
17857
19878
  if (event.type === "response-done" && this.toolCallsInResponse.size > 0) {
17858
19879
  this.responseToolCallsClosed = true;
17859
19880
  this.maybeRequestToolResponse();
17860
19881
  }
19882
+ if (!current()) return;
19883
+ if (event.type === "session-closed" && session != null) {
19884
+ this.disconnect();
19885
+ this.notifyEvent(event, attempt, true);
19886
+ return;
19887
+ }
19888
+ if ((event.type === "session-started" || event.type === "session-created" || event.type === "session-updated") && attempt.active && !attempt.closing && attempt.cause == null) {
19889
+ const ready = ((_e = this.options.model.capabilities) == null ? void 0 : _e.startup) === "session-start" ? event.type === "session-started" : event.type !== "session-started";
19890
+ if (ready) {
19891
+ attempt.ready = true;
19892
+ attempt.clearTimer("startup");
19893
+ this.applyState({ ...this.state, status: "connected" });
19894
+ if (!current() || attempt.closing) return;
19895
+ (_f = this.pcm) == null ? void 0 : _f.startCapture();
19896
+ }
19897
+ }
19898
+ if (current()) this.notifyEvent(event, attempt);
17861
19899
  }
17862
- handleReducerEffect(effect) {
17863
- var _a25;
19900
+ handleReducerEffect(effect, attempt) {
19901
+ var _a25, _b25, _c, _d;
17864
19902
  switch (effect.type) {
17865
- case "play-audio": {
19903
+ case "play-audio":
17866
19904
  this.currentResponseItemId = effect.itemId;
17867
- this.audio.playAudio(effect.delta);
19905
+ (_a25 = this.audio) == null ? void 0 : _a25.playAudio(effect.delta);
17868
19906
  break;
17869
- }
17870
- case "speech-started": {
17871
- if (this.state.isPlaying) {
17872
- const playedMs = this.audio.getPlaybackOffsetMs();
17873
- this.audio.stopPlayback();
17874
- if (this.currentResponseItemId != null) {
19907
+ case "speech-started":
19908
+ if (!this.continuous && this.state.isPlaying) {
19909
+ const playedMs = (_c = (_b25 = this.audio) == null ? void 0 : _b25.getPlaybackOffsetMs()) != null ? _c : 0;
19910
+ const itemId = this.currentResponseItemId;
19911
+ (_d = this.audio) == null ? void 0 : _d.stopPlayback();
19912
+ if (this.attempt !== attempt || !attempt.active) return;
19913
+ if (itemId != null && !attempt.closing && attempt.cause == null)
17875
19914
  this.sendEvent({
17876
19915
  type: "conversation-item-truncate",
17877
- itemId: this.currentResponseItemId,
19916
+ itemId,
17878
19917
  contentIndex: 0,
17879
19918
  audioEndMs: Math.round(playedMs)
17880
19919
  });
17881
- }
17882
19920
  }
17883
19921
  break;
17884
- }
17885
- case "tool-call": {
19922
+ case "tool-call":
19923
+ if (this.continuous) break;
17886
19924
  this.toolCallsInResponse.add(effect.callId);
17887
- void this.executeToolCall({
17888
- name: effect.name,
17889
- args: effect.args,
17890
- callId: effect.callId
17891
- });
19925
+ void this.executeTool(effect.callId, effect.name, effect.args, attempt);
17892
19926
  break;
17893
- }
17894
- case "error": {
17895
- (_a25 = this.onError) == null ? void 0 : _a25.call(this, effect.error);
19927
+ case "error":
19928
+ void this.reportError(effect.error, attempt);
17896
19929
  break;
17897
- }
19930
+ }
19931
+ }
19932
+ async reportError(error, attempt) {
19933
+ var _a25;
19934
+ if (this.attempt !== attempt) return;
19935
+ try {
19936
+ await ((_a25 = this.onError) == null ? void 0 : _a25.call(
19937
+ this,
19938
+ error instanceof Error ? error : new Error(String(error))
19939
+ ));
19940
+ } catch (e) {
19941
+ }
19942
+ }
19943
+ notifyEvent(event, attempt, terminal = false) {
19944
+ var _a25;
19945
+ if (this.attempt !== attempt || !attempt.active && !terminal) return;
19946
+ const report = (error) => {
19947
+ if (attempt.active || terminal) void this.reportError(error, attempt);
19948
+ };
19949
+ try {
19950
+ void Promise.resolve((_a25 = this.onEvent) == null ? void 0 : _a25.call(this, event)).catch(report);
19951
+ } catch (error) {
19952
+ report(error);
17898
19953
  }
17899
19954
  }
17900
19955
  };
@@ -18210,7 +20265,7 @@ var DefaultProviderRegistry = class {
18210
20265
 
18211
20266
  // src/rerank/rerank.ts
18212
20267
  import {
18213
- InvalidResponseDataError as InvalidResponseDataError3
20268
+ InvalidResponseDataError as InvalidResponseDataError4
18214
20269
  } from "@ai-sdk/provider";
18215
20270
  import {
18216
20271
  createIdGenerator as createIdGenerator8
@@ -18456,7 +20511,7 @@ function validateRankingIndices({
18456
20511
  }) {
18457
20512
  for (const { index } of ranking) {
18458
20513
  if (!Number.isInteger(index) || index < 0 || index >= documents.length) {
18459
- throw new InvalidResponseDataError3({
20514
+ throw new InvalidResponseDataError4({
18460
20515
  data: ranking,
18461
20516
  message: `Invalid ranking index ${index}. Expected an integer between 0 and ${documents.length - 1}.`
18462
20517
  });
@@ -18478,7 +20533,7 @@ var DefaultRerankResult = class {
18478
20533
  // src/transcribe/transcribe.ts
18479
20534
  import {
18480
20535
  detectMediaType as detectMediaType6,
18481
- withUserAgentSuffix as withUserAgentSuffix13
20536
+ withUserAgentSuffix as withUserAgentSuffix14
18482
20537
  } from "@ai-sdk/provider-utils";
18483
20538
  var defaultDownload2 = createDownload();
18484
20539
  async function transcribe({
@@ -18498,7 +20553,7 @@ async function transcribe({
18498
20553
  maxRetries: maxRetriesArg,
18499
20554
  abortSignal
18500
20555
  });
18501
- const headersWithUserAgent = withUserAgentSuffix13(
20556
+ const headersWithUserAgent = withUserAgentSuffix14(
18502
20557
  headers != null ? headers : {},
18503
20558
  `ai/${VERSION}`
18504
20559
  );
@@ -18551,11 +20606,11 @@ var DefaultTranscriptionResult = class {
18551
20606
 
18552
20607
  // src/transcribe/stream-transcribe.ts
18553
20608
  import {
18554
- UnsupportedFunctionalityError as UnsupportedFunctionalityError5
20609
+ UnsupportedFunctionalityError as UnsupportedFunctionalityError7
18555
20610
  } from "@ai-sdk/provider";
18556
20611
  import {
18557
20612
  DelayedPromise as DelayedPromise3,
18558
- withUserAgentSuffix as withUserAgentSuffix14
20613
+ withUserAgentSuffix as withUserAgentSuffix15
18559
20614
  } from "@ai-sdk/provider-utils";
18560
20615
  function streamTranscribe({
18561
20616
  model,
@@ -18574,12 +20629,12 @@ function streamTranscribe({
18574
20629
  }
18575
20630
  const doStream = (_a25 = resolvedModel.doStream) == null ? void 0 : _a25.bind(resolvedModel);
18576
20631
  if (doStream == null) {
18577
- throw new UnsupportedFunctionalityError5({
20632
+ throw new UnsupportedFunctionalityError7({
18578
20633
  functionality: "streaming transcription",
18579
20634
  message: `The ${resolvedModel.provider} model "${resolvedModel.modelId}" does not support streaming transcription.` + (typeof model === "string" ? " String model IDs resolve through the global provider (AI Gateway by default). If that provider does not support streaming transcription, pass a provider model instance instead (e.g. openai.transcription('gpt-realtime-whisper')) or upgrade @ai-sdk/gateway to a version with streaming transcription support." : "")
18580
20635
  });
18581
20636
  }
18582
- const headersWithUserAgent = withUserAgentSuffix14(
20637
+ const headersWithUserAgent = withUserAgentSuffix15(
18583
20638
  headers != null ? headers : {},
18584
20639
  `ai/${VERSION}`
18585
20640
  );
@@ -18764,7 +20819,7 @@ var experimental_transcribe = transcribe;
18764
20819
  // src/translate/stream-translate.ts
18765
20820
  import {
18766
20821
  DelayedPromise as DelayedPromise4,
18767
- withUserAgentSuffix as withUserAgentSuffix15
20822
+ withUserAgentSuffix as withUserAgentSuffix16
18768
20823
  } from "@ai-sdk/provider-utils";
18769
20824
  function streamTranslate({
18770
20825
  model,
@@ -18781,7 +20836,7 @@ function streamTranslate({
18781
20836
  }) {
18782
20837
  const resolvedModel = resolveSpeechTranslationModel(model);
18783
20838
  const doStream = resolvedModel.doStream.bind(resolvedModel);
18784
- const headersWithUserAgent = withUserAgentSuffix15(
20839
+ const headersWithUserAgent = withUserAgentSuffix16(
18785
20840
  headers != null ? headers : {},
18786
20841
  `ai/${VERSION}`
18787
20842
  );
@@ -18978,7 +21033,7 @@ function streamTranslate({
18978
21033
  // src/ui/call-completion-api.ts
18979
21034
  import {
18980
21035
  parseJsonEventStream,
18981
- withUserAgentSuffix as withUserAgentSuffix16,
21036
+ withUserAgentSuffix as withUserAgentSuffix17,
18982
21037
  getRuntimeEnvironmentUserAgent as getRuntimeEnvironmentUserAgent2
18983
21038
  } from "@ai-sdk/provider-utils";
18984
21039
  import { EmptyResponseBodyError as EmptyResponseBodyError2 } from "@ai-sdk/provider";
@@ -19047,7 +21102,7 @@ async function callCompletionApi({
19047
21102
  ...body
19048
21103
  }),
19049
21104
  credentials,
19050
- headers: withUserAgentSuffix16(
21105
+ headers: withUserAgentSuffix17(
19051
21106
  {
19052
21107
  "Content-Type": "application/json",
19053
21108
  ...headers
@@ -19150,13 +21205,13 @@ import {
19150
21205
  } from "@ai-sdk/provider-utils";
19151
21206
 
19152
21207
  // src/ui/convert-file-list-to-file-ui-parts.ts
19153
- import { UnsupportedFunctionalityError as UnsupportedFunctionalityError6 } from "@ai-sdk/provider";
21208
+ import { UnsupportedFunctionalityError as UnsupportedFunctionalityError8 } from "@ai-sdk/provider";
19154
21209
  async function convertFileListToFileUIParts(files) {
19155
21210
  if (files == null) {
19156
21211
  return [];
19157
21212
  }
19158
21213
  if (!globalThis.FileList || !(files instanceof globalThis.FileList)) {
19159
- throw new UnsupportedFunctionalityError6({
21214
+ throw new UnsupportedFunctionalityError8({
19160
21215
  functionality: "FileList",
19161
21216
  message: "FileList is not supported in the current environment"
19162
21217
  });
@@ -19340,8 +21395,8 @@ var DefaultChatTransport = class extends HttpChatTransport {
19340
21395
  // src/ui/chat.ts
19341
21396
  var AbstractChat = class {
19342
21397
  constructor({
19343
- generateId: generateId4 = generateIdFunc2,
19344
- id = generateId4(),
21398
+ generateId: generateId5 = generateIdFunc2,
21399
+ id = generateId5(),
19345
21400
  transport = new DefaultChatTransport(),
19346
21401
  messageMetadataSchema,
19347
21402
  dataPartSchemas,
@@ -19553,7 +21608,7 @@ var AbstractChat = class {
19553
21608
  };
19554
21609
  this.id = id;
19555
21610
  this.transport = transport;
19556
- this.generateId = generateId4;
21611
+ this.generateId = generateId5;
19557
21612
  this.messageMetadataSchema = messageMetadataSchema;
19558
21613
  this.dataPartSchemas = dataPartSchemas;
19559
21614
  this.state = state;
@@ -20046,6 +22101,7 @@ export {
20046
22101
  EmptyResponseBodyError,
20047
22102
  AbstractRealtimeSession as Experimental_AbstractRealtimeSession,
20048
22103
  ToolLoopAgent as Experimental_Agent,
22104
+ Experimental_EvaluationUnsupportedQuestionTypeError,
20049
22105
  HttpChatTransport,
20050
22106
  InvalidArgumentError,
20051
22107
  InvalidDataContentError,
@@ -20119,6 +22175,7 @@ export {
20119
22175
  experimental_createProviderRegistry,
20120
22176
  decodeRealtimeAudio as experimental_decodeRealtimeAudio,
20121
22177
  encodeRealtimeAudio as experimental_encodeRealtimeAudio,
22178
+ evaluate as experimental_evaluate,
20122
22179
  filterActiveTools as experimental_filterActiveTools,
20123
22180
  experimental_generateSpeech,
20124
22181
  experimental_generateVideo,
@@ -20139,7 +22196,7 @@ export {
20139
22196
  extractReasoningMiddleware,
20140
22197
  fingerprintTools,
20141
22198
  gateway3 as gateway,
20142
- generateId3 as generateId,
22199
+ generateId4 as generateId,
20143
22200
  generateImage,
20144
22201
  generateObject,
20145
22202
  generateSpeech,