@mastra/ai-sdk 1.7.2 → 1.8.0-alpha.2

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.
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ export type { WorkflowDataPart, WorkflowStepDataPart } from './transformers.js';
6
6
  export { networkRoute, handleNetworkStream } from './network-route.js';
7
7
  export type { NetworkRouteOptions, NetworkStreamHandlerParams, NetworkStreamHandlerOptions } from './network-route.js';
8
8
  export type { NetworkDataPart } from './transformers.js';
9
- export type { AgentDataPart } from './transformers.js';
9
+ export type { AgentDataPart, AgentStepDataPart } from './transformers.js';
10
10
  export { toAISdkStream, toAISdkV5Stream } from './convert-streams.js';
11
11
  export { workflowSnapshotToStream } from './convert-snapshot.js';
12
12
  export { smoothStream } from './smooth-stream.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC3D,YAAY,EACV,gBAAgB,EAChB,uBAAuB,EACvB,wBAAwB,EACxB,wBAAwB,EACxB,mBAAmB,GACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACvE,YAAY,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,4BAA4B,EAAE,MAAM,kBAAkB,CAAC;AACxH,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACpE,YAAY,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,2BAA2B,EAAE,MAAM,iBAAiB,CAAC;AACpH,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,YAAY,EAAE,qBAAqB,EAAE,4BAA4B,EAAE,MAAM,iBAAiB,CAAC;AAG3F,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,+BAA+B,EAAE,MAAM,cAAc,CAAC;AAGhH,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC3D,YAAY,EACV,gBAAgB,EAChB,uBAAuB,EACvB,wBAAwB,EACxB,wBAAwB,EACxB,mBAAmB,GACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACvE,YAAY,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,4BAA4B,EAAE,MAAM,kBAAkB,CAAC;AACxH,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACpE,YAAY,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,2BAA2B,EAAE,MAAM,iBAAiB,CAAC;AACpH,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEvE,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,YAAY,EAAE,qBAAqB,EAAE,4BAA4B,EAAE,MAAM,iBAAiB,CAAC;AAG3F,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,+BAA+B,EAAE,MAAM,cAAc,CAAC;AAGhH,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -8840,6 +8840,7 @@ const isMastraTextStreamChunk = (chunk) => {
8840
8840
  "tool-call",
8841
8841
  "tool-result",
8842
8842
  "tool-error",
8843
+ "tool-output-denied",
8843
8844
  "error",
8844
8845
  "start-step",
8845
8846
  "finish-step",
@@ -9192,6 +9193,11 @@ function normalizeV6Usage(usage) {
9192
9193
  };
9193
9194
  }
9194
9195
  function convertMastraChunkToAISDKv6({ chunk, mode = "stream" }) {
9196
+ if (chunk.type === "tool-output-denied") return {
9197
+ type: "tool-output-denied",
9198
+ toolCallId: chunk.payload.toolCallId,
9199
+ toolName: chunk.payload.toolName
9200
+ };
9195
9201
  if (chunk.type === "tool-call-approval") {
9196
9202
  const displayTransform = getTransformedToolPayload(chunk.metadata, "display", "approval");
9197
9203
  return [{
@@ -9320,6 +9326,10 @@ function convertFullStreamChunkToUIMessageStream({ part, messageMetadataValue, s
9320
9326
  ...part.providerExecuted != null ? { providerExecuted: part.providerExecuted } : {},
9321
9327
  ...part.dynamic != null ? { dynamic: part.dynamic } : {}
9322
9328
  };
9329
+ case "tool-output-denied": return {
9330
+ type: "tool-output-denied",
9331
+ toolCallId: part.toolCallId
9332
+ };
9323
9333
  case "tool-output":
9324
9334
  if (part.output.from === "AGENT") return {
9325
9335
  type: "tool-agent",
@@ -9372,6 +9382,7 @@ function convertFullStreamChunkToUIMessageStream({ part, messageMetadataValue, s
9372
9382
  case "finish":
9373
9383
  if (sendFinish) return {
9374
9384
  type: "finish",
9385
+ ...part.finishReason != null ? { finishReason: part.finishReason } : {},
9375
9386
  ...messageMetadataValue != null ? { messageMetadata: messageMetadataValue } : {}
9376
9387
  };
9377
9388
  return;
@@ -9404,6 +9415,7 @@ function convertFullStreamChunkToUIMessageStream({ part, messageMetadataValue, s
9404
9415
  //#endregion
9405
9416
  //#region src/transformers.ts
9406
9417
  const PRIMITIVE_CACHE_SYMBOL = Symbol("primitive-cache");
9418
+ const COMPLETED_STEPS_SYMBOL = Symbol("completed-steps-cache");
9407
9419
  function cloneWorkflowStep(step, includeOutput) {
9408
9420
  return {
9409
9421
  name: step.name,
@@ -9531,7 +9543,8 @@ function createAgentStreamToAISDKTransformer(convertMastraChunkToAISDK, { lastMe
9531
9543
  if (transformedChunk) if (transformedChunk.type === "tool-agent") {
9532
9544
  const payload = transformedChunk.payload;
9533
9545
  const agentTransformed = transformAgent(payload, bufferedSteps);
9534
- if (agentTransformed) controller.enqueue(agentTransformed);
9546
+ if (agentTransformed) if (Array.isArray(agentTransformed)) for (const part of agentTransformed) controller.enqueue(part);
9547
+ else controller.enqueue(agentTransformed);
9535
9548
  } else if (transformedChunk.type === "tool-workflow") {
9536
9549
  const payload = transformedChunk.payload;
9537
9550
  const workflowChunk = transformWorkflow(payload, bufferedSteps, true, void 0, void 0, convertMastraChunkToAISDK);
@@ -9614,30 +9627,7 @@ function AgentStreamToAISDKV6Transformer({ lastMessageId, sendStart = true, send
9614
9627
  });
9615
9628
  }
9616
9629
  function ensureAgentRunState(bufferedSteps, runId) {
9617
- if (!bufferedSteps.has(runId)) bufferedSteps.set(runId, {
9618
- id: "",
9619
- object: null,
9620
- finishReason: null,
9621
- usage: null,
9622
- warnings: [],
9623
- text: "",
9624
- reasoning: [],
9625
- sources: [],
9626
- files: [],
9627
- toolCalls: [],
9628
- pendingToolCalls: [],
9629
- toolResults: [],
9630
- request: {},
9631
- response: {
9632
- id: "",
9633
- timestamp: /* @__PURE__ */ new Date(),
9634
- modelId: "",
9635
- messages: []
9636
- },
9637
- providerMetadata: void 0,
9638
- steps: [],
9639
- status: "running"
9640
- });
9630
+ if (!bufferedSteps.has(runId)) bufferedSteps.set(runId, createAgentRunState());
9641
9631
  return bufferedSteps.get(runId);
9642
9632
  }
9643
9633
  function upsertPendingToolCall(pendingToolCalls = [], toolCallId, updates) {
@@ -9673,36 +9663,109 @@ function appendPendingToolCallArgs(pendingToolCalls = [], payload) {
9673
9663
  function removePendingToolCall(pendingToolCalls = [], toolCallId) {
9674
9664
  return pendingToolCalls.filter((call) => call.toolCallId !== toolCallId);
9675
9665
  }
9666
+ function createAgentResponseState() {
9667
+ return {
9668
+ id: "",
9669
+ timestamp: /* @__PURE__ */ new Date(),
9670
+ modelId: "",
9671
+ messages: []
9672
+ };
9673
+ }
9674
+ function createAgentRunState(id = "") {
9675
+ return {
9676
+ id,
9677
+ object: null,
9678
+ finishReason: null,
9679
+ usage: null,
9680
+ warnings: [],
9681
+ text: "",
9682
+ reasoning: [],
9683
+ sources: [],
9684
+ files: [],
9685
+ toolCalls: [],
9686
+ pendingToolCalls: [],
9687
+ toolResults: [],
9688
+ request: {},
9689
+ response: createAgentResponseState(),
9690
+ providerMetadata: void 0,
9691
+ steps: [],
9692
+ status: "running"
9693
+ };
9694
+ }
9695
+ function cloneAgentResponse(response, { includeMessages }) {
9696
+ if (!response) return response;
9697
+ return {
9698
+ ...response,
9699
+ ...Object.prototype.hasOwnProperty.call(response, "messages") ? { messages: includeMessages ? response.messages : [] } : {},
9700
+ ...Object.prototype.hasOwnProperty.call(response, "dbMessages") ? { dbMessages: includeMessages ? response.dbMessages : [] } : {},
9701
+ ...Object.prototype.hasOwnProperty.call(response, "uiMessages") ? { uiMessages: includeMessages ? response.uiMessages : [] } : {}
9702
+ };
9703
+ }
9704
+ function cloneAgentStep(step, { includeDetails }) {
9705
+ if (includeDetails) return {
9706
+ ...step,
9707
+ response: cloneAgentResponse(step.response, { includeMessages: true })
9708
+ };
9709
+ return {
9710
+ ...step,
9711
+ object: null,
9712
+ files: [],
9713
+ sources: [],
9714
+ toolCalls: [],
9715
+ pendingToolCalls: [],
9716
+ toolResults: [],
9717
+ dynamicToolCalls: [],
9718
+ dynamicToolResults: [],
9719
+ staticToolCalls: [],
9720
+ staticToolResults: [],
9721
+ text: "",
9722
+ reasoning: [],
9723
+ content: Array.isArray(step.content) ? [] : step.content,
9724
+ reasoningText: typeof step.reasoningText === "string" ? "" : step.reasoningText,
9725
+ response: cloneAgentResponse(step.response, { includeMessages: false })
9726
+ };
9727
+ }
9728
+ function serializeAgentRun(current, { includeCompletedStepDetails, includeResponseMessages }) {
9729
+ const { _textOffset: _to, _reasoningOffset: _ro, ...data } = current;
9730
+ return {
9731
+ ...data,
9732
+ response: cloneAgentResponse(data.response, { includeMessages: includeResponseMessages }),
9733
+ steps: data.steps.map((step) => cloneAgentStep(step, { includeDetails: includeCompletedStepDetails }))
9734
+ };
9735
+ }
9736
+ function createAgentDataPart(args) {
9737
+ const { current, runId, includeCompletedStepDetails, includeResponseMessages } = args;
9738
+ return {
9739
+ type: "data-tool-agent",
9740
+ id: runId,
9741
+ data: serializeAgentRun(current, {
9742
+ includeCompletedStepDetails,
9743
+ includeResponseMessages
9744
+ })
9745
+ };
9746
+ }
9747
+ function createAgentStepDataPart(args) {
9748
+ const { runId, stepIndex, step } = args;
9749
+ return {
9750
+ type: "data-tool-agent-step",
9751
+ id: `${runId}:${stepIndex}`,
9752
+ data: {
9753
+ runId,
9754
+ stepIndex,
9755
+ step: cloneAgentStep(step, { includeDetails: true })
9756
+ }
9757
+ };
9758
+ }
9676
9759
  function transformAgent(payload, bufferedSteps) {
9677
9760
  let hasChanged = false;
9761
+ let completedStep = null;
9678
9762
  switch (payload.type) {
9679
- case "start":
9680
- bufferedSteps.set(payload.runId, {
9681
- id: payload.payload.id,
9682
- object: null,
9683
- finishReason: null,
9684
- usage: null,
9685
- warnings: [],
9686
- text: "",
9687
- reasoning: [],
9688
- sources: [],
9689
- files: [],
9690
- toolCalls: [],
9691
- pendingToolCalls: [],
9692
- toolResults: [],
9693
- request: {},
9694
- response: {
9695
- id: "",
9696
- timestamp: /* @__PURE__ */ new Date(),
9697
- modelId: "",
9698
- messages: []
9699
- },
9700
- providerMetadata: void 0,
9701
- steps: [],
9702
- status: "running"
9703
- });
9763
+ case "start": {
9764
+ const startState = createAgentRunState(payload.payload.id);
9765
+ bufferedSteps.set(payload.runId, startState);
9704
9766
  hasChanged = true;
9705
9767
  break;
9768
+ }
9706
9769
  case "tool-call-input-streaming-start": {
9707
9770
  const toolInputStartRun = ensureAgentRunState(bufferedSteps, payload.runId);
9708
9771
  const existing = toolInputStartRun.pendingToolCalls?.find((call) => call.toolCallId === payload.payload.toolCallId);
@@ -9833,6 +9896,7 @@ function transformAgent(payload, bufferedSteps) {
9833
9896
  break;
9834
9897
  case "step-finish": {
9835
9898
  const stepRun = ensureAgentRunState(bufferedSteps, payload.runId);
9899
+ const stepIndex = stepRun.steps.length;
9836
9900
  const { steps: _steps, _textOffset, _reasoningOffset, ...stepRunWithoutSteps } = stepRun;
9837
9901
  const textOffset = _textOffset || 0;
9838
9902
  const reasoningOffset = _reasoningOffset || 0;
@@ -9874,18 +9938,28 @@ function transformAgent(payload, bufferedSteps) {
9874
9938
  _textOffset: stepRun.text.length,
9875
9939
  _reasoningOffset: stepRun.reasoning.length
9876
9940
  });
9941
+ completedStep = {
9942
+ stepIndex,
9943
+ step: stepResult
9944
+ };
9877
9945
  hasChanged = true;
9878
9946
  break;
9879
9947
  }
9880
9948
  default: break;
9881
9949
  }
9882
9950
  if (hasChanged) {
9883
- const { _textOffset: _to, _reasoningOffset: _ro, ...data } = bufferedSteps.get(payload.runId);
9884
- return {
9885
- type: "data-tool-agent",
9886
- id: payload.runId,
9887
- data
9888
- };
9951
+ const snapshot = createAgentDataPart({
9952
+ current: bufferedSteps.get(payload.runId),
9953
+ runId: payload.runId,
9954
+ includeCompletedStepDetails: payload.type === "finish",
9955
+ includeResponseMessages: payload.type === "finish"
9956
+ });
9957
+ if (completedStep) return [snapshot, createAgentStepDataPart({
9958
+ runId: payload.runId,
9959
+ stepIndex: completedStep.stepIndex,
9960
+ step: completedStep.step
9961
+ })];
9962
+ return snapshot;
9889
9963
  }
9890
9964
  return null;
9891
9965
  }
@@ -10311,10 +10385,24 @@ function transformNetwork(payload, bufferedNetworks, isNested) {
10311
10385
  const step = current.steps.find((step) => step.id === stepId);
10312
10386
  if (!step) return null;
10313
10387
  step[PRIMITIVE_CACHE_SYMBOL] = step[PRIMITIVE_CACHE_SYMBOL] || /* @__PURE__ */ new Map();
10388
+ if (payload.payload.type === "start") delete step[COMPLETED_STEPS_SYMBOL];
10314
10389
  const result = transformAgent(payload.payload, step[PRIMITIVE_CACHE_SYMBOL]);
10315
- if (result) {
10316
- const { request, response, ...data } = result.data;
10390
+ const snapshot = Array.isArray(result) ? result[0] : result;
10391
+ if (snapshot) {
10392
+ const { request, response, ...data } = snapshot.data;
10393
+ if (Array.isArray(result)) {
10394
+ const { stepIndex, step: completedStepDetail } = result[1].data;
10395
+ step[COMPLETED_STEPS_SYMBOL] = step[COMPLETED_STEPS_SYMBOL] || /* @__PURE__ */ new Map();
10396
+ step[COMPLETED_STEPS_SYMBOL].set(stepIndex, completedStepDetail);
10397
+ }
10317
10398
  step.task = data;
10399
+ const completedSteps = step[COMPLETED_STEPS_SYMBOL];
10400
+ if (completedSteps && completedSteps.size > 0 && Array.isArray(data.steps)) {
10401
+ for (const [stepIndex, completedStepDetail] of completedSteps) if (stepIndex < data.steps.length) data.steps[stepIndex] = {
10402
+ ...data.steps[stepIndex],
10403
+ ...completedStepDetail
10404
+ };
10405
+ }
10318
10406
  }
10319
10407
  bufferedNetworks.set(payload.runId, current);
10320
10408
  return {
@@ -10438,6 +10526,124 @@ function toAISdkStream(stream, options = {
10438
10526
  }));
10439
10527
  }
10440
10528
  //#endregion
10529
+ //#region src/sse-heartbeat.ts
10530
+ const SSE_HEARTBEAT_BYTES = new TextEncoder().encode(": heartbeat\n\n");
10531
+ const LF_BYTE = 10;
10532
+ const MAX_TIMEOUT_MS = 2147483647;
10533
+ /** Throws when an enabled heartbeat interval cannot be scheduled with a timer. */
10534
+ function assertValidHeartbeatMs(heartbeatMs) {
10535
+ if (heartbeatMs !== void 0 && !(heartbeatMs <= 0) && (!Number.isFinite(heartbeatMs) || heartbeatMs > MAX_TIMEOUT_MS)) throw new RangeError(`heartbeatMs must be a finite number no greater than ${MAX_TIMEOUT_MS}`);
10536
+ }
10537
+ /**
10538
+ * Adds periodic SSE comment heartbeats to an AI SDK response body.
10539
+ * AI SDK serialization uses LF-delimited frames, which this private wrapper preserves and relies on.
10540
+ */
10541
+ function withSseHeartbeat(response, heartbeatMs) {
10542
+ assertValidHeartbeatMs(heartbeatMs);
10543
+ if (heartbeatMs === void 0 || heartbeatMs <= 0 || !response.body) return response;
10544
+ const reader = response.body.getReader();
10545
+ let heartbeatTimeout;
10546
+ let wakePull;
10547
+ let readResult;
10548
+ let readError;
10549
+ let hasReadError = false;
10550
+ let reading = false;
10551
+ let finished = false;
10552
+ let readerReleased = false;
10553
+ let atFrameBoundary = true;
10554
+ let lastByte;
10555
+ let nextHeartbeatAt = performance.now() + heartbeatMs;
10556
+ const clearHeartbeat = () => {
10557
+ if (heartbeatTimeout !== void 0) {
10558
+ clearTimeout(heartbeatTimeout);
10559
+ heartbeatTimeout = void 0;
10560
+ }
10561
+ };
10562
+ const releaseReader = () => {
10563
+ if (readerReleased) return;
10564
+ readerReleased = true;
10565
+ reader.releaseLock();
10566
+ };
10567
+ const updateFrameBoundary = (chunk) => {
10568
+ if (chunk.byteLength === 0) return;
10569
+ atFrameBoundary = chunk.byteLength === 1 ? lastByte === LF_BYTE && chunk[0] === LF_BYTE : chunk[chunk.byteLength - 2] === LF_BYTE && chunk[chunk.byteLength - 1] === LF_BYTE;
10570
+ lastByte = chunk[chunk.byteLength - 1];
10571
+ };
10572
+ const startRead = () => {
10573
+ if (reading || readResult || hasReadError || finished) return;
10574
+ reading = true;
10575
+ reader.read().then((result) => {
10576
+ reading = false;
10577
+ readResult = result;
10578
+ wakePull?.("read");
10579
+ }, (error) => {
10580
+ reading = false;
10581
+ readError = error;
10582
+ hasReadError = true;
10583
+ wakePull?.("read");
10584
+ });
10585
+ };
10586
+ const forwardRead = (controller) => {
10587
+ if (hasReadError) {
10588
+ finished = true;
10589
+ releaseReader();
10590
+ controller.error(readError);
10591
+ return;
10592
+ }
10593
+ if (!readResult) return;
10594
+ const result = readResult;
10595
+ readResult = void 0;
10596
+ if (result.done) {
10597
+ finished = true;
10598
+ releaseReader();
10599
+ controller.close();
10600
+ return;
10601
+ }
10602
+ updateFrameBoundary(result.value);
10603
+ controller.enqueue(result.value);
10604
+ };
10605
+ const stream = new ReadableStream({
10606
+ async pull(controller) {
10607
+ if (finished) return;
10608
+ startRead();
10609
+ if (readResult || hasReadError) {
10610
+ forwardRead(controller);
10611
+ return;
10612
+ }
10613
+ const next = await new Promise((resolve) => {
10614
+ wakePull = resolve;
10615
+ if (atFrameBoundary) heartbeatTimeout = setTimeout(() => resolve("heartbeat"), Math.max(0, nextHeartbeatAt - performance.now()));
10616
+ });
10617
+ wakePull = void 0;
10618
+ clearHeartbeat();
10619
+ if (finished) return;
10620
+ if (readResult || hasReadError) {
10621
+ forwardRead(controller);
10622
+ return;
10623
+ }
10624
+ if (next === "heartbeat") {
10625
+ controller.enqueue(SSE_HEARTBEAT_BYTES.slice());
10626
+ nextHeartbeatAt = performance.now() + heartbeatMs;
10627
+ }
10628
+ },
10629
+ async cancel(reason) {
10630
+ if (finished) return;
10631
+ finished = true;
10632
+ clearHeartbeat();
10633
+ try {
10634
+ await reader.cancel(reason);
10635
+ } finally {
10636
+ releaseReader();
10637
+ }
10638
+ }
10639
+ });
10640
+ return new Response(stream, {
10641
+ status: response.status,
10642
+ statusText: response.statusText,
10643
+ headers: response.headers
10644
+ });
10645
+ }
10646
+ //#endregion
10441
10647
  //#region src/chat-route.ts
10442
10648
  /**
10443
10649
  * Collects every approval response across all assistant messages in a v6
@@ -10640,11 +10846,13 @@ async function handleChatStream({ mastra, agentId, agentVersion, params, default
10640
10846
  * @param {boolean} [options.sendFinish=true] - Whether to send finish events in the stream
10641
10847
  * @param {boolean} [options.sendReasoning=false] - Whether to include reasoning steps in the stream
10642
10848
  * @param {boolean} [options.sendSources=false] - Whether to include source citations in the stream
10849
+ * @param {number} [options.heartbeatMs] - Target interval for periodic SSE comment heartbeats. Already-buffered source events and stream lifecycle signals take priority. Values up to 0 disable heartbeats. `NaN`, positive infinity, and values above 2,147,483,647 throw a `RangeError`.
10643
10850
  * @param {(error: unknown) => string} [options.onError] - Custom error serializer streamed to the client. When omitted, errors are passed through a default serializer that strips sensitive fields (e.g. `APICallError.requestBodyValues`, which holds the system prompt) before they reach the client.
10644
10851
  *
10645
10852
  * @returns {ReturnType<typeof registerApiRoute>} A registered API route handler
10646
10853
  *
10647
10854
  * @throws {Error} When path doesn't include `:agentId` and no fixed agent is specified
10855
+ * @throws {RangeError} When `heartbeatMs` is `NaN`, positive infinity, or greater than 2,147,483,647
10648
10856
  * @throws {Error} When agent ID is missing at runtime
10649
10857
  * @throws {Error} When specified agent is not found in Mastra instance
10650
10858
  *
@@ -10667,12 +10875,13 @@ async function handleChatStream({ mastra, agentId, agentVersion, params, default
10667
10875
  * @remarks
10668
10876
  * - The route handler expects a JSON body with a `messages` array
10669
10877
  * - Messages should follow the format: `{ role: 'user' | 'assistant' | 'system', content: string }`
10670
- * - The response is a Server-Sent Events (SSE) stream compatible with AI SDK v5
10878
+ * - The response is a Server-Sent Events (SSE) stream compatible with the selected AI SDK version
10671
10879
  * - If both `agent` and `:agentId` are present, a warning is logged and the fixed `agent` takes precedence
10672
10880
  * - Request context from the incoming request overrides `defaultOptions.requestContext` if both are present
10673
10881
  */
10674
- function chatRoute({ path = "/chat/:agentId", agent, defaultOptions, experimentalTransform, version = "v5", agentVersion, sendStart = true, sendFinish = true, sendReasoning = false, sendSources = false, onError }) {
10882
+ function chatRoute({ path = "/chat/:agentId", agent, defaultOptions, experimentalTransform, version = "v5", agentVersion, sendStart = true, sendFinish = true, sendReasoning = false, sendSources = false, heartbeatMs, onError }) {
10675
10883
  if (!agent && !path.includes("/:agentId")) throw new Error("Path must include :agentId to route to the correct agent or pass the agent explicitly");
10884
+ assertValidHeartbeatMs(heartbeatMs);
10676
10885
  return registerApiRoute(path, {
10677
10886
  method: "POST",
10678
10887
  openapi: {
@@ -10801,11 +11010,13 @@ function chatRoute({ path = "/chat/:agentId", agent, defaultOptions, experimenta
10801
11010
  sendSources,
10802
11011
  onError
10803
11012
  };
10804
- if (version === "v6") return createUIMessageStreamResponse({ stream: await handleChatStream({
11013
+ let response;
11014
+ if (version === "v6") response = createUIMessageStreamResponse({ stream: await handleChatStream({
10805
11015
  ...handlerOptions,
10806
11016
  version: "v6"
10807
11017
  }) });
10808
- return createUIMessageStreamResponse$1({ stream: await handleChatStream(handlerOptions) });
11018
+ else response = createUIMessageStreamResponse$1({ stream: await handleChatStream(handlerOptions) });
11019
+ return withSseHeartbeat(response, heartbeatMs);
10809
11020
  }
10810
11021
  });
10811
11022
  }