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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -9404,6 +9404,7 @@ function convertFullStreamChunkToUIMessageStream({ part, messageMetadataValue, s
9404
9404
  //#endregion
9405
9405
  //#region src/transformers.ts
9406
9406
  const PRIMITIVE_CACHE_SYMBOL = Symbol("primitive-cache");
9407
+ const COMPLETED_STEPS_SYMBOL = Symbol("completed-steps-cache");
9407
9408
  function cloneWorkflowStep(step, includeOutput) {
9408
9409
  return {
9409
9410
  name: step.name,
@@ -9531,7 +9532,8 @@ function createAgentStreamToAISDKTransformer(convertMastraChunkToAISDK, { lastMe
9531
9532
  if (transformedChunk) if (transformedChunk.type === "tool-agent") {
9532
9533
  const payload = transformedChunk.payload;
9533
9534
  const agentTransformed = transformAgent(payload, bufferedSteps);
9534
- if (agentTransformed) controller.enqueue(agentTransformed);
9535
+ if (agentTransformed) if (Array.isArray(agentTransformed)) for (const part of agentTransformed) controller.enqueue(part);
9536
+ else controller.enqueue(agentTransformed);
9535
9537
  } else if (transformedChunk.type === "tool-workflow") {
9536
9538
  const payload = transformedChunk.payload;
9537
9539
  const workflowChunk = transformWorkflow(payload, bufferedSteps, true, void 0, void 0, convertMastraChunkToAISDK);
@@ -9614,30 +9616,7 @@ function AgentStreamToAISDKV6Transformer({ lastMessageId, sendStart = true, send
9614
9616
  });
9615
9617
  }
9616
9618
  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
- });
9619
+ if (!bufferedSteps.has(runId)) bufferedSteps.set(runId, createAgentRunState());
9641
9620
  return bufferedSteps.get(runId);
9642
9621
  }
9643
9622
  function upsertPendingToolCall(pendingToolCalls = [], toolCallId, updates) {
@@ -9673,36 +9652,109 @@ function appendPendingToolCallArgs(pendingToolCalls = [], payload) {
9673
9652
  function removePendingToolCall(pendingToolCalls = [], toolCallId) {
9674
9653
  return pendingToolCalls.filter((call) => call.toolCallId !== toolCallId);
9675
9654
  }
9655
+ function createAgentResponseState() {
9656
+ return {
9657
+ id: "",
9658
+ timestamp: /* @__PURE__ */ new Date(),
9659
+ modelId: "",
9660
+ messages: []
9661
+ };
9662
+ }
9663
+ function createAgentRunState(id = "") {
9664
+ return {
9665
+ id,
9666
+ object: null,
9667
+ finishReason: null,
9668
+ usage: null,
9669
+ warnings: [],
9670
+ text: "",
9671
+ reasoning: [],
9672
+ sources: [],
9673
+ files: [],
9674
+ toolCalls: [],
9675
+ pendingToolCalls: [],
9676
+ toolResults: [],
9677
+ request: {},
9678
+ response: createAgentResponseState(),
9679
+ providerMetadata: void 0,
9680
+ steps: [],
9681
+ status: "running"
9682
+ };
9683
+ }
9684
+ function cloneAgentResponse(response, { includeMessages }) {
9685
+ if (!response) return response;
9686
+ return {
9687
+ ...response,
9688
+ ...Object.prototype.hasOwnProperty.call(response, "messages") ? { messages: includeMessages ? response.messages : [] } : {},
9689
+ ...Object.prototype.hasOwnProperty.call(response, "dbMessages") ? { dbMessages: includeMessages ? response.dbMessages : [] } : {},
9690
+ ...Object.prototype.hasOwnProperty.call(response, "uiMessages") ? { uiMessages: includeMessages ? response.uiMessages : [] } : {}
9691
+ };
9692
+ }
9693
+ function cloneAgentStep(step, { includeDetails }) {
9694
+ if (includeDetails) return {
9695
+ ...step,
9696
+ response: cloneAgentResponse(step.response, { includeMessages: true })
9697
+ };
9698
+ return {
9699
+ ...step,
9700
+ object: null,
9701
+ files: [],
9702
+ sources: [],
9703
+ toolCalls: [],
9704
+ pendingToolCalls: [],
9705
+ toolResults: [],
9706
+ dynamicToolCalls: [],
9707
+ dynamicToolResults: [],
9708
+ staticToolCalls: [],
9709
+ staticToolResults: [],
9710
+ text: "",
9711
+ reasoning: [],
9712
+ content: Array.isArray(step.content) ? [] : step.content,
9713
+ reasoningText: typeof step.reasoningText === "string" ? "" : step.reasoningText,
9714
+ response: cloneAgentResponse(step.response, { includeMessages: false })
9715
+ };
9716
+ }
9717
+ function serializeAgentRun(current, { includeCompletedStepDetails, includeResponseMessages }) {
9718
+ const { _textOffset: _to, _reasoningOffset: _ro, ...data } = current;
9719
+ return {
9720
+ ...data,
9721
+ response: cloneAgentResponse(data.response, { includeMessages: includeResponseMessages }),
9722
+ steps: data.steps.map((step) => cloneAgentStep(step, { includeDetails: includeCompletedStepDetails }))
9723
+ };
9724
+ }
9725
+ function createAgentDataPart(args) {
9726
+ const { current, runId, includeCompletedStepDetails, includeResponseMessages } = args;
9727
+ return {
9728
+ type: "data-tool-agent",
9729
+ id: runId,
9730
+ data: serializeAgentRun(current, {
9731
+ includeCompletedStepDetails,
9732
+ includeResponseMessages
9733
+ })
9734
+ };
9735
+ }
9736
+ function createAgentStepDataPart(args) {
9737
+ const { runId, stepIndex, step } = args;
9738
+ return {
9739
+ type: "data-tool-agent-step",
9740
+ id: `${runId}:${stepIndex}`,
9741
+ data: {
9742
+ runId,
9743
+ stepIndex,
9744
+ step: cloneAgentStep(step, { includeDetails: true })
9745
+ }
9746
+ };
9747
+ }
9676
9748
  function transformAgent(payload, bufferedSteps) {
9677
9749
  let hasChanged = false;
9750
+ let completedStep = null;
9678
9751
  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
- });
9752
+ case "start": {
9753
+ const startState = createAgentRunState(payload.payload.id);
9754
+ bufferedSteps.set(payload.runId, startState);
9704
9755
  hasChanged = true;
9705
9756
  break;
9757
+ }
9706
9758
  case "tool-call-input-streaming-start": {
9707
9759
  const toolInputStartRun = ensureAgentRunState(bufferedSteps, payload.runId);
9708
9760
  const existing = toolInputStartRun.pendingToolCalls?.find((call) => call.toolCallId === payload.payload.toolCallId);
@@ -9833,6 +9885,7 @@ function transformAgent(payload, bufferedSteps) {
9833
9885
  break;
9834
9886
  case "step-finish": {
9835
9887
  const stepRun = ensureAgentRunState(bufferedSteps, payload.runId);
9888
+ const stepIndex = stepRun.steps.length;
9836
9889
  const { steps: _steps, _textOffset, _reasoningOffset, ...stepRunWithoutSteps } = stepRun;
9837
9890
  const textOffset = _textOffset || 0;
9838
9891
  const reasoningOffset = _reasoningOffset || 0;
@@ -9874,18 +9927,28 @@ function transformAgent(payload, bufferedSteps) {
9874
9927
  _textOffset: stepRun.text.length,
9875
9928
  _reasoningOffset: stepRun.reasoning.length
9876
9929
  });
9930
+ completedStep = {
9931
+ stepIndex,
9932
+ step: stepResult
9933
+ };
9877
9934
  hasChanged = true;
9878
9935
  break;
9879
9936
  }
9880
9937
  default: break;
9881
9938
  }
9882
9939
  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
- };
9940
+ const snapshot = createAgentDataPart({
9941
+ current: bufferedSteps.get(payload.runId),
9942
+ runId: payload.runId,
9943
+ includeCompletedStepDetails: payload.type === "finish",
9944
+ includeResponseMessages: payload.type === "finish"
9945
+ });
9946
+ if (completedStep) return [snapshot, createAgentStepDataPart({
9947
+ runId: payload.runId,
9948
+ stepIndex: completedStep.stepIndex,
9949
+ step: completedStep.step
9950
+ })];
9951
+ return snapshot;
9889
9952
  }
9890
9953
  return null;
9891
9954
  }
@@ -10311,10 +10374,24 @@ function transformNetwork(payload, bufferedNetworks, isNested) {
10311
10374
  const step = current.steps.find((step) => step.id === stepId);
10312
10375
  if (!step) return null;
10313
10376
  step[PRIMITIVE_CACHE_SYMBOL] = step[PRIMITIVE_CACHE_SYMBOL] || /* @__PURE__ */ new Map();
10377
+ if (payload.payload.type === "start") delete step[COMPLETED_STEPS_SYMBOL];
10314
10378
  const result = transformAgent(payload.payload, step[PRIMITIVE_CACHE_SYMBOL]);
10315
- if (result) {
10316
- const { request, response, ...data } = result.data;
10379
+ const snapshot = Array.isArray(result) ? result[0] : result;
10380
+ if (snapshot) {
10381
+ const { request, response, ...data } = snapshot.data;
10382
+ if (Array.isArray(result)) {
10383
+ const { stepIndex, step: completedStepDetail } = result[1].data;
10384
+ step[COMPLETED_STEPS_SYMBOL] = step[COMPLETED_STEPS_SYMBOL] || /* @__PURE__ */ new Map();
10385
+ step[COMPLETED_STEPS_SYMBOL].set(stepIndex, completedStepDetail);
10386
+ }
10317
10387
  step.task = data;
10388
+ const completedSteps = step[COMPLETED_STEPS_SYMBOL];
10389
+ if (completedSteps && completedSteps.size > 0 && Array.isArray(data.steps)) {
10390
+ for (const [stepIndex, completedStepDetail] of completedSteps) if (stepIndex < data.steps.length) data.steps[stepIndex] = {
10391
+ ...data.steps[stepIndex],
10392
+ ...completedStepDetail
10393
+ };
10394
+ }
10318
10395
  }
10319
10396
  bufferedNetworks.set(payload.runId, current);
10320
10397
  return {
@@ -10438,6 +10515,124 @@ function toAISdkStream(stream, options = {
10438
10515
  }));
10439
10516
  }
10440
10517
  //#endregion
10518
+ //#region src/sse-heartbeat.ts
10519
+ const SSE_HEARTBEAT_BYTES = new TextEncoder().encode(": heartbeat\n\n");
10520
+ const LF_BYTE = 10;
10521
+ const MAX_TIMEOUT_MS = 2147483647;
10522
+ /** Throws when an enabled heartbeat interval cannot be scheduled with a timer. */
10523
+ function assertValidHeartbeatMs(heartbeatMs) {
10524
+ 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}`);
10525
+ }
10526
+ /**
10527
+ * Adds periodic SSE comment heartbeats to an AI SDK response body.
10528
+ * AI SDK serialization uses LF-delimited frames, which this private wrapper preserves and relies on.
10529
+ */
10530
+ function withSseHeartbeat(response, heartbeatMs) {
10531
+ assertValidHeartbeatMs(heartbeatMs);
10532
+ if (heartbeatMs === void 0 || heartbeatMs <= 0 || !response.body) return response;
10533
+ const reader = response.body.getReader();
10534
+ let heartbeatTimeout;
10535
+ let wakePull;
10536
+ let readResult;
10537
+ let readError;
10538
+ let hasReadError = false;
10539
+ let reading = false;
10540
+ let finished = false;
10541
+ let readerReleased = false;
10542
+ let atFrameBoundary = true;
10543
+ let lastByte;
10544
+ let nextHeartbeatAt = performance.now() + heartbeatMs;
10545
+ const clearHeartbeat = () => {
10546
+ if (heartbeatTimeout !== void 0) {
10547
+ clearTimeout(heartbeatTimeout);
10548
+ heartbeatTimeout = void 0;
10549
+ }
10550
+ };
10551
+ const releaseReader = () => {
10552
+ if (readerReleased) return;
10553
+ readerReleased = true;
10554
+ reader.releaseLock();
10555
+ };
10556
+ const updateFrameBoundary = (chunk) => {
10557
+ if (chunk.byteLength === 0) return;
10558
+ atFrameBoundary = chunk.byteLength === 1 ? lastByte === LF_BYTE && chunk[0] === LF_BYTE : chunk[chunk.byteLength - 2] === LF_BYTE && chunk[chunk.byteLength - 1] === LF_BYTE;
10559
+ lastByte = chunk[chunk.byteLength - 1];
10560
+ };
10561
+ const startRead = () => {
10562
+ if (reading || readResult || hasReadError || finished) return;
10563
+ reading = true;
10564
+ reader.read().then((result) => {
10565
+ reading = false;
10566
+ readResult = result;
10567
+ wakePull?.("read");
10568
+ }, (error) => {
10569
+ reading = false;
10570
+ readError = error;
10571
+ hasReadError = true;
10572
+ wakePull?.("read");
10573
+ });
10574
+ };
10575
+ const forwardRead = (controller) => {
10576
+ if (hasReadError) {
10577
+ finished = true;
10578
+ releaseReader();
10579
+ controller.error(readError);
10580
+ return;
10581
+ }
10582
+ if (!readResult) return;
10583
+ const result = readResult;
10584
+ readResult = void 0;
10585
+ if (result.done) {
10586
+ finished = true;
10587
+ releaseReader();
10588
+ controller.close();
10589
+ return;
10590
+ }
10591
+ updateFrameBoundary(result.value);
10592
+ controller.enqueue(result.value);
10593
+ };
10594
+ const stream = new ReadableStream({
10595
+ async pull(controller) {
10596
+ if (finished) return;
10597
+ startRead();
10598
+ if (readResult || hasReadError) {
10599
+ forwardRead(controller);
10600
+ return;
10601
+ }
10602
+ const next = await new Promise((resolve) => {
10603
+ wakePull = resolve;
10604
+ if (atFrameBoundary) heartbeatTimeout = setTimeout(() => resolve("heartbeat"), Math.max(0, nextHeartbeatAt - performance.now()));
10605
+ });
10606
+ wakePull = void 0;
10607
+ clearHeartbeat();
10608
+ if (finished) return;
10609
+ if (readResult || hasReadError) {
10610
+ forwardRead(controller);
10611
+ return;
10612
+ }
10613
+ if (next === "heartbeat") {
10614
+ controller.enqueue(SSE_HEARTBEAT_BYTES.slice());
10615
+ nextHeartbeatAt = performance.now() + heartbeatMs;
10616
+ }
10617
+ },
10618
+ async cancel(reason) {
10619
+ if (finished) return;
10620
+ finished = true;
10621
+ clearHeartbeat();
10622
+ try {
10623
+ await reader.cancel(reason);
10624
+ } finally {
10625
+ releaseReader();
10626
+ }
10627
+ }
10628
+ });
10629
+ return new Response(stream, {
10630
+ status: response.status,
10631
+ statusText: response.statusText,
10632
+ headers: response.headers
10633
+ });
10634
+ }
10635
+ //#endregion
10441
10636
  //#region src/chat-route.ts
10442
10637
  /**
10443
10638
  * Collects every approval response across all assistant messages in a v6
@@ -10640,11 +10835,13 @@ async function handleChatStream({ mastra, agentId, agentVersion, params, default
10640
10835
  * @param {boolean} [options.sendFinish=true] - Whether to send finish events in the stream
10641
10836
  * @param {boolean} [options.sendReasoning=false] - Whether to include reasoning steps in the stream
10642
10837
  * @param {boolean} [options.sendSources=false] - Whether to include source citations in the stream
10838
+ * @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
10839
  * @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
10840
  *
10645
10841
  * @returns {ReturnType<typeof registerApiRoute>} A registered API route handler
10646
10842
  *
10647
10843
  * @throws {Error} When path doesn't include `:agentId` and no fixed agent is specified
10844
+ * @throws {RangeError} When `heartbeatMs` is `NaN`, positive infinity, or greater than 2,147,483,647
10648
10845
  * @throws {Error} When agent ID is missing at runtime
10649
10846
  * @throws {Error} When specified agent is not found in Mastra instance
10650
10847
  *
@@ -10667,12 +10864,13 @@ async function handleChatStream({ mastra, agentId, agentVersion, params, default
10667
10864
  * @remarks
10668
10865
  * - The route handler expects a JSON body with a `messages` array
10669
10866
  * - 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
10867
+ * - The response is a Server-Sent Events (SSE) stream compatible with the selected AI SDK version
10671
10868
  * - If both `agent` and `:agentId` are present, a warning is logged and the fixed `agent` takes precedence
10672
10869
  * - Request context from the incoming request overrides `defaultOptions.requestContext` if both are present
10673
10870
  */
10674
- function chatRoute({ path = "/chat/:agentId", agent, defaultOptions, experimentalTransform, version = "v5", agentVersion, sendStart = true, sendFinish = true, sendReasoning = false, sendSources = false, onError }) {
10871
+ function chatRoute({ path = "/chat/:agentId", agent, defaultOptions, experimentalTransform, version = "v5", agentVersion, sendStart = true, sendFinish = true, sendReasoning = false, sendSources = false, heartbeatMs, onError }) {
10675
10872
  if (!agent && !path.includes("/:agentId")) throw new Error("Path must include :agentId to route to the correct agent or pass the agent explicitly");
10873
+ assertValidHeartbeatMs(heartbeatMs);
10676
10874
  return registerApiRoute(path, {
10677
10875
  method: "POST",
10678
10876
  openapi: {
@@ -10801,11 +10999,13 @@ function chatRoute({ path = "/chat/:agentId", agent, defaultOptions, experimenta
10801
10999
  sendSources,
10802
11000
  onError
10803
11001
  };
10804
- if (version === "v6") return createUIMessageStreamResponse({ stream: await handleChatStream({
11002
+ let response;
11003
+ if (version === "v6") response = createUIMessageStreamResponse({ stream: await handleChatStream({
10805
11004
  ...handlerOptions,
10806
11005
  version: "v6"
10807
11006
  }) });
10808
- return createUIMessageStreamResponse$1({ stream: await handleChatStream(handlerOptions) });
11007
+ else response = createUIMessageStreamResponse$1({ stream: await handleChatStream(handlerOptions) });
11008
+ return withSseHeartbeat(response, heartbeatMs);
10809
11009
  }
10810
11010
  });
10811
11011
  }