@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/CHANGELOG.md +47 -0
- package/dist/chat-route.d.ts +6 -2
- package/dist/chat-route.d.ts.map +1 -1
- package/dist/helpers.d.ts +9 -2
- package/dist/helpers.d.ts.map +1 -1
- package/dist/index.cjs +273 -62
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +273 -62
- package/dist/index.js.map +1 -1
- package/dist/sse-heartbeat.d.ts +8 -0
- package/dist/sse-heartbeat.d.ts.map +1 -0
- package/dist/transformers.d.ts +13 -5
- package/dist/transformers.d.ts.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/package.json +6 -6
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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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(
|
|
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
|
-
|
|
9681
|
-
|
|
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
|
|
9884
|
-
|
|
9885
|
-
|
|
9886
|
-
|
|
9887
|
-
|
|
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
|
-
|
|
10316
|
-
|
|
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
|
|
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
|
-
|
|
11013
|
+
let response;
|
|
11014
|
+
if (version === "v6") response = createUIMessageStreamResponse({ stream: await handleChatStream({
|
|
10805
11015
|
...handlerOptions,
|
|
10806
11016
|
version: "v6"
|
|
10807
11017
|
}) });
|
|
10808
|
-
|
|
11018
|
+
else response = createUIMessageStreamResponse$1({ stream: await handleChatStream(handlerOptions) });
|
|
11019
|
+
return withSseHeartbeat(response, heartbeatMs);
|
|
10809
11020
|
}
|
|
10810
11021
|
});
|
|
10811
11022
|
}
|