@mastra/core
Version:
Mastra is a framework for building AI-powered applications and agents with a modern TypeScript stack.
1,149 lines (1,147 loc) • 34.4 kB
JavaScript
import { MastraA2AError } from '../chunk-O2CHMYVQ.js';
export { MastraA2AError } from '../chunk-O2CHMYVQ.js';
import { MessageList, convertMessages } from '../chunk-NRWJGKOK.js';
import { RequestContext } from '../chunk-BBVL3KAA.js';
import { randomUUID } from 'crypto';
var EMPTY_USAGE = {
inputTokens: void 0,
outputTokens: void 0,
totalTokens: void 0
};
function toAgentStreamChunk(runId, chunk) {
return {
...chunk,
runId,
from: "AGENT"
};
}
function isTask(result) {
return typeof result === "object" && result !== null && "status" in result && "id" in result && "kind" in result;
}
function isMessage(result) {
return typeof result === "object" && result !== null && "messageId" in result && "parts" in result;
}
function isTerminalTaskState(state) {
return state === "completed" || state === "failed" || state === "canceled" || state === "rejected";
}
function splitNextEvent(buffer) {
const normalizedBuffer = buffer.replace(/\x1E/g, "\n\n");
const match = normalizedBuffer.match(/\r?\n\r?\n/);
if (!match || match.index === void 0) {
return { rest: normalizedBuffer };
}
return {
eventBlock: normalizedBuffer.slice(0, match.index),
rest: normalizedBuffer.slice(match.index + match[0].length)
};
}
function parseEventBlock(eventBlock) {
const trimmedBlock = eventBlock.trim();
if (!trimmedBlock) {
return {};
}
const lines = trimmedBlock.split(/\r?\n/);
const dataLines = lines.filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart());
const payload = dataLines.length > 0 ? dataLines.join("\n") : trimmedBlock;
if (payload === "[DONE]") {
return { done: true };
}
let parsed;
try {
parsed = JSON.parse(payload);
} catch {
return {};
}
if ("result" in parsed && parsed.result) {
return { event: parsed.result };
}
return { event: parsed };
}
function extractTextParts(parts) {
return (parts ?? []).filter((part) => part.kind === "text" && typeof part.text === "string").map((part) => part.text).join("\n");
}
function extractTaskText(task) {
const artifactText = (task.artifacts ?? []).flatMap(
(artifact) => artifact.parts?.flatMap((part) => {
if (part.kind === "text" && "text" in part && typeof part.text === "string") {
return [part.text];
}
return [];
}) ?? []
).join("\n");
const statusText = task.status.message ? extractMessageText(task.status.message) : "";
return [artifactText, statusText].filter(Boolean).join("\n").trim();
}
function extractTaskArtifactText(task) {
return (task.artifacts ?? []).flatMap(
(artifact) => artifact.parts?.flatMap((part) => {
if (part.kind === "text" && "text" in part && typeof part.text === "string") {
return [part.text];
}
return [];
}) ?? []
).join("");
}
function extractMessageText(message) {
return extractTextParts(message.parts).trim();
}
function messagesToPrompt(messages, options) {
if (typeof messages === "string") {
return messages;
}
const converted = convertMessages(messages).to("AIV5.Model");
const lines = [];
if (options?.instructions) {
lines.push(`Instructions:
${options.instructions}`);
}
const contextMessages = options?.context;
if (contextMessages?.length) {
lines.push(
"Context:\n" + contextMessages.map((message) => {
const role = message.role ?? "unknown";
const content = typeof message.content === "string" ? message.content : JSON.stringify(message.content);
return `${role}: ${content}`;
}).join("\n")
);
}
for (const message of converted) {
if (message.role === "system") {
const content2 = Array.isArray(message.content) ? message.content.map((part) => {
if ("type" in part && part.type === "text") {
return part.text;
}
return "";
}).filter(Boolean).join("\n") : typeof message.content === "string" ? message.content : "";
if (content2) {
lines.push(`system: ${content2}`);
}
continue;
}
const content = Array.isArray(message.content) ? message.content.map((part) => {
if ("type" in part && part.type === "text") {
return part.text;
}
if ("type" in part && part.type === "file") {
return `[file:${part.filename ?? "attachment"}]`;
}
return "";
}).filter(Boolean).join("\n") : typeof message.content === "string" ? message.content : "";
lines.push(`${message.role}: ${content}`);
}
return lines.join("\n\n").trim();
}
function resumeDataToPrompt(resumeData) {
if (typeof resumeData === "string") {
return resumeData;
}
if (resumeData == null) {
return "";
}
return JSON.stringify(resumeData, null, 2);
}
function createResumeSchema() {
return JSON.stringify({
type: "object",
additionalProperties: true,
description: "Data to continue the remote A2A task."
});
}
function resolveStreamTextId(candidateIds) {
for (const candidate of candidateIds) {
if (candidate) {
return candidate;
}
}
return "text-1";
}
function resolveMemoryInfo(options) {
const threadId = typeof options?.memory?.thread === "string" ? options.memory.thread : options?.memory?.thread?.id;
return {
threadId,
resourceId: options?.memory?.resource
};
}
function createResponseMessages(text, memoryInfo = {}) {
if (!text) {
return [];
}
return new MessageList(memoryInfo).add(
{
role: "assistant",
content: text
},
"response"
).get.response.db();
}
function createGenerateResult({
runId,
text,
task,
message,
resumePayload,
resumeSchema,
threadId,
resourceId
}) {
const responseMessages = createResponseMessages(text, { threadId, resourceId });
return {
text,
usage: EMPTY_USAGE,
steps: [],
finishReason: resumePayload ? "suspended" : "stop",
warnings: [],
providerMetadata: void 0,
request: {},
reasoning: [],
reasoningText: void 0,
toolCalls: [],
toolResults: [],
sources: [],
files: [],
response: {
id: message?.messageId ?? task?.id ?? runId,
timestamp: /* @__PURE__ */ new Date(),
modelId: "a2a/remote-agent",
messages: [],
uiMessages: [],
dbMessages: responseMessages
},
totalUsage: EMPTY_USAGE,
object: void 0,
error: void 0,
tripwire: void 0,
traceId: void 0,
spanId: void 0,
runId,
suspendPayload: resumePayload,
resumeSchema,
messages: responseMessages,
rememberedMessages: [],
task,
message,
resumePayload
};
}
function unwrapA2AResult(result) {
if (!result || typeof result !== "object") {
throw MastraA2AError.invalidAgentResponse("Remote A2A agent returned an invalid response.");
}
if ("result" in result && result.result && typeof result.result === "object") {
return result.result;
}
return result;
}
function createDeferred() {
let resolve;
let reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}
async function requireResponseBody(response, operation) {
if (!response.body) {
throw MastraA2AError.invalidAgentResponse(`Remote A2A agent returned an empty stream for ${operation}.`);
}
return response.body;
}
var A2AAgent = class {
id;
name;
#url;
#description;
#headers;
#fetch;
#retries;
#backoffMs;
#maxBackoffMs;
#credentials;
#abortSignal;
#timeoutMs;
#verifyAgentCard;
#cachedBootstrap;
#runState = /* @__PURE__ */ new Map();
#memory;
#mastra;
constructor(options) {
this.#url = options.url.replace(/\/$/, "");
this.#description = options.description ?? `Remote A2A agent at ${this.#url}`;
this.#headers = options.headers ?? {};
this.#fetch = options.fetch ?? fetch;
this.#retries = options.retries ?? 0;
this.#backoffMs = options.backoffMs ?? 250;
this.#maxBackoffMs = options.maxBackoffMs ?? 1e3;
this.#credentials = options.credentials;
this.#abortSignal = options.abortSignal;
this.#timeoutMs = options.timeoutMs;
this.#verifyAgentCard = options.verifyAgentCard;
this.id = options.id ?? `a2a-${randomUUID()}`;
this.name = options.name ?? options.description ?? "A2A Agent";
}
async getAgentCard({ forceRefresh = false } = {}) {
return (await this.#getBootstrap({ forceRefresh })).card;
}
getDescription() {
return this.#description;
}
getModel = async () => ({ specificationVersion: "v2" });
hasOwnMemory() {
return Boolean(this.#memory);
}
__setMemory(memory) {
this.#memory = memory;
}
async getMemory({ requestContext = new RequestContext() } = {}) {
if (!this.#memory) {
return void 0;
}
if (typeof this.#memory !== "function") {
return this.#memory;
}
return await this.#memory({
requestContext,
mastra: this.#mastra
});
}
getInstructions = async () => "";
__registerMastra(mastra) {
this.#mastra = mastra;
}
async generate(messages, options) {
const bootstrap = await this.#getBootstrap();
const runId = options?.runId ?? randomUUID();
const prompt = messagesToPrompt(messages, options);
const memoryInfo = resolveMemoryInfo(options);
return this.#sendAndResolve({
bootstrap,
runId,
prompt,
signal: options?.abortSignal,
...memoryInfo
});
}
async resumeGenerate(resumeData, options) {
const runId = options?.runId;
if (!runId) {
throw MastraA2AError.invalidParams("A2AAgent.resumeGenerate requires a runId.");
}
const state = this.#runState.get(runId);
if (!state) {
throw MastraA2AError.invalidParams(`No resumable A2A run state found for runId "${runId}".`);
}
const bootstrap = await this.#getBootstrap();
const memoryInfo = resolveMemoryInfo(options);
if (state.waitingForInput) {
const prompt = resumeDataToPrompt(resumeData);
return this.#sendAndResolve({
bootstrap,
runId,
prompt,
signal: options?.abortSignal,
contextId: state.contextId,
referenceTaskIds: state.taskId ? [state.taskId] : void 0,
...memoryInfo
});
}
if (!state.taskId) {
throw MastraA2AError.invalidParams(`A2AAgent resume state for "${runId}" is missing a taskId.`);
}
const task = await this.#getTask({
bootstrap,
taskId: state.taskId,
signal: options?.abortSignal
});
return this.#resolveTaskToGenerateResult({
bootstrap,
runId,
task,
signal: options?.abortSignal,
...memoryInfo
});
}
async stream(messages, options) {
const bootstrap = await this.#getBootstrap();
const runId = options?.runId ?? randomUUID();
const prompt = messagesToPrompt(messages, options);
const memoryInfo = resolveMemoryInfo(options);
if (!bootstrap.streamingSupported) {
const result = await this.generate(messages, options);
return this.#createBufferedStreamResult({ runId, result, ...memoryInfo });
}
return this.#runRemoteStream({
bootstrap,
runId,
prompt,
signal: options?.abortSignal,
...memoryInfo
});
}
async resumeStream(resumeData, options) {
const runId = options?.runId;
if (!runId) {
throw MastraA2AError.invalidParams("A2AAgent.resumeStream requires a runId.");
}
const state = this.#runState.get(runId);
if (!state) {
throw MastraA2AError.invalidParams(`No resumable A2A run state found for runId "${runId}".`);
}
const bootstrap = await this.#getBootstrap();
const memoryInfo = resolveMemoryInfo(options);
if (state.waitingForInput) {
const prompt = resumeDataToPrompt(resumeData);
if (!bootstrap.streamingSupported) {
const result = await this.resumeGenerate(resumeData, options);
return this.#createBufferedStreamResult({ runId, result, ...memoryInfo });
}
return this.#runRemoteStream({
bootstrap,
runId,
prompt,
signal: options?.abortSignal,
contextId: state.contextId,
referenceTaskIds: state.taskId ? [state.taskId] : void 0,
...memoryInfo
});
}
if (!state.taskId) {
throw MastraA2AError.invalidParams(`A2AAgent resume state for "${runId}" is missing a taskId.`);
}
if (!bootstrap.streamingSupported) {
const result = await this.resumeGenerate(resumeData, options);
return this.#createBufferedStreamResult({ runId, result, ...memoryInfo });
}
return this.#resubscribeToRemoteStream({
bootstrap,
runId,
taskId: state.taskId,
initialTask: state.lastTask,
signal: options?.abortSignal,
...memoryInfo
});
}
async #getBootstrap({ forceRefresh = false } = {}) {
if (!forceRefresh && this.#cachedBootstrap) {
return this.#cachedBootstrap;
}
const cardUrl = this.#resolveCardUrl();
const response = await this.#request(cardUrl, {
method: "GET",
signal: this.#abortSignal
});
const card = await response.json();
const fetchedAt = /* @__PURE__ */ new Date();
if (this.#verifyAgentCard) {
const context = { cardUrl, fetchedAt };
await this.#verifyAgentCard.verify(card, context);
}
const bootstrap = {
card,
cardUrl,
executionUrl: card.url,
streamingSupported: card.capabilities?.streaming ?? false
};
this.#cachedBootstrap = bootstrap;
return bootstrap;
}
#resolveCardUrl() {
return this.#url.endsWith("/agent-card.json") ? this.#url : `${this.#url}/.well-known/agent-card.json`;
}
async #sendMessage({
bootstrap,
prompt,
signal,
contextId,
referenceTaskIds
}) {
const response = await this.#request(bootstrap.executionUrl, {
method: "POST",
signal,
body: {
jsonrpc: "2.0",
id: randomUUID(),
method: "message/send",
params: {
message: {
role: "user",
kind: "message",
messageId: randomUUID(),
parts: [{ kind: "text", text: prompt }],
...contextId ? { contextId } : {},
...referenceTaskIds?.length ? { referenceTaskIds } : {}
}
}
}
});
const json = await response.json();
return unwrapA2AResult(json);
}
async #sendAndResolve({
bootstrap,
runId,
prompt,
signal,
contextId,
referenceTaskIds,
threadId,
resourceId
}) {
const response = await this.#sendMessage({
bootstrap,
prompt,
signal,
contextId,
referenceTaskIds
});
if (isMessage(response)) {
this.#runState.delete(runId);
return createGenerateResult({
runId,
text: extractMessageText(response),
message: response,
threadId,
resourceId
});
}
return this.#resolveTaskToGenerateResult({
bootstrap,
runId,
task: response,
signal,
threadId,
resourceId
});
}
async #getTask({
bootstrap,
taskId,
signal
}) {
const response = await this.#request(bootstrap.executionUrl, {
method: "POST",
signal,
body: {
jsonrpc: "2.0",
id: randomUUID(),
method: "tasks/get",
params: { id: taskId }
}
});
const json = await response.json();
const result = unwrapA2AResult(json);
if (!isTask(result)) {
throw MastraA2AError.invalidAgentResponse("Remote A2A agent returned a non-task response for tasks/get.");
}
return result;
}
async #resolveTaskToGenerateResult({
bootstrap,
runId,
task,
signal,
threadId,
resourceId
}) {
let currentTask = task;
while (true) {
const evaluation = this.#evaluateTask({
bootstrap,
task: currentTask
});
if (evaluation.kind === "completed") {
this.#runState.delete(runId);
return createGenerateResult({
runId,
text: evaluation.text,
task: evaluation.task,
message: evaluation.message,
threadId,
resourceId
});
}
this.#runState.set(runId, {
runId,
contextId: evaluation.task.contextId,
taskId: evaluation.task.id,
executionUrl: bootstrap.executionUrl,
cardUrl: bootstrap.cardUrl,
streamingSupported: bootstrap.streamingSupported,
waitingForInput: evaluation.resumePayload.waitingForInput,
lastTask: evaluation.task
});
if (evaluation.task.status.state === "input-required") {
return createGenerateResult({
runId,
text: evaluation.text,
task: evaluation.task,
resumePayload: evaluation.resumePayload,
resumeSchema: evaluation.resumeSchema,
threadId,
resourceId
});
}
await this.#delay();
currentTask = await this.#getTask({
bootstrap,
taskId: evaluation.task.id,
signal
});
}
}
#evaluateTask({ bootstrap, task }) {
const text = extractTaskText(task);
if (task.status.state === "input-required") {
return {
kind: "suspended",
text,
task,
resumePayload: {
taskId: task.id,
contextId: task.contextId,
executionUrl: bootstrap.executionUrl,
cardUrl: bootstrap.cardUrl,
waitingForInput: true,
task: structuredClone(task)
},
resumeSchema: createResumeSchema()
};
}
if (isTerminalTaskState(task.status.state)) {
return {
kind: "completed",
text,
task
};
}
return {
kind: "suspended",
text,
task,
resumePayload: {
taskId: task.id,
contextId: task.contextId,
executionUrl: bootstrap.executionUrl,
cardUrl: bootstrap.cardUrl,
waitingForInput: false,
task: structuredClone(task)
}
};
}
async #runRemoteStream({
bootstrap,
runId,
prompt,
signal,
contextId,
referenceTaskIds,
threadId,
resourceId
}) {
const response = await this.#request(bootstrap.executionUrl, {
method: "POST",
signal,
stream: true,
body: {
jsonrpc: "2.0",
id: randomUUID(),
method: "message/stream",
params: {
message: {
role: "user",
kind: "message",
messageId: randomUUID(),
parts: [{ kind: "text", text: prompt }],
...contextId ? { contextId } : {},
...referenceTaskIds?.length ? { referenceTaskIds } : {}
}
}
}
});
return this.#consumeA2AStream({
bootstrap,
runId,
stream: await requireResponseBody(response, "message/stream"),
threadId,
resourceId
});
}
async #resubscribeToRemoteStream({
bootstrap,
runId,
taskId,
initialTask,
signal,
threadId,
resourceId
}) {
const response = await this.#request(bootstrap.executionUrl, {
method: "POST",
signal,
stream: true,
body: {
jsonrpc: "2.0",
id: randomUUID(),
method: "tasks/resubscribe",
params: { id: taskId }
}
});
return this.#consumeA2AStream({
bootstrap,
runId,
initialTask,
stream: await requireResponseBody(response, "tasks/resubscribe"),
threadId,
resourceId
});
}
async #consumeA2AStream({
bootstrap,
runId,
initialTask,
stream,
threadId,
resourceId
}) {
const [consumerStream, accumulatorStream] = stream.tee();
const resultDeferred = createDeferred();
const textDeferred = createDeferred();
const taskDeferred = createDeferred();
const suspendPayloadDeferred = createDeferred();
const resumeSchemaDeferred = createDeferred();
const messageList = new MessageList({ threadId, resourceId });
void this.#collectStreamEvents({
bootstrap,
initialTask,
stream: accumulatorStream
}).then((consumed) => {
if (consumed.task && consumed.suspended) {
this.#runState.set(runId, {
runId,
contextId: consumed.task.contextId,
taskId: consumed.task.id,
executionUrl: bootstrap.executionUrl,
cardUrl: bootstrap.cardUrl,
streamingSupported: bootstrap.streamingSupported,
waitingForInput: consumed.suspended.payload.waitingForInput,
lastTask: consumed.task
});
} else {
this.#runState.delete(runId);
}
textDeferred.resolve(consumed.text);
taskDeferred.resolve(consumed.task);
suspendPayloadDeferred.resolve(consumed.suspended?.payload);
resumeSchemaDeferred.resolve(consumed.suspended?.resumeSchema);
if (consumed.text) {
messageList.add(
{
role: "assistant",
content: consumed.text
},
"response"
);
}
resultDeferred.resolve(
createGenerateResult({
runId,
text: consumed.text,
task: consumed.task,
threadId,
resourceId,
...consumed.suspended ? {
resumePayload: consumed.suspended.payload,
resumeSchema: consumed.suspended.resumeSchema
} : {}
})
);
}).catch((error) => {
textDeferred.reject(error);
taskDeferred.reject(error);
suspendPayloadDeferred.reject(error);
resumeSchemaDeferred.reject(error);
resultDeferred.reject(error);
});
const streamResult = {
runId,
fullStream: this.#streamEvents({ bootstrap, runId, stream: consumerStream }),
text: textDeferred.promise,
toolResults: Promise.resolve([]),
messageList,
task: taskDeferred.promise,
suspendPayload: suspendPayloadDeferred.promise,
resumeSchema: resumeSchemaDeferred.promise,
getResult: async () => resultDeferred.promise
};
return streamResult;
}
async *#streamEvents({
bootstrap,
runId,
stream
}) {
const reader = stream.getReader();
const decoder = new TextDecoder();
let buffer = "";
let textId;
let textStarted = false;
let task;
let suspended;
let receivedDone = false;
while (true) {
const { done, value } = await reader.read();
if (done) {
buffer += decoder.decode();
} else if (value) {
buffer += decoder.decode(value, { stream: true });
}
let next = splitNextEvent(buffer);
while (next.eventBlock !== void 0) {
const parsed = parseEventBlock(next.eventBlock);
if ("done" in parsed && parsed.done) {
receivedDone = true;
buffer = next.rest;
break;
}
if ("event" in parsed && parsed.event) {
const event = parsed.event;
if (isTask(event)) {
task = event;
if (event.status.state === "input-required") {
suspended = {
taskId: event.id,
contextId: event.contextId,
executionUrl: bootstrap.executionUrl,
cardUrl: bootstrap.cardUrl,
waitingForInput: true,
task: structuredClone(event)
};
}
} else if (isMessage(event)) {
const text = extractMessageText(event);
if (text) {
textId ??= resolveStreamTextId([event.messageId, task?.id]);
if (textId) {
if (!textStarted) {
yield toAgentStreamChunk(runId, { type: "text-start", payload: { id: textId } });
textStarted = true;
}
yield toAgentStreamChunk(runId, { type: "text-delta", payload: { id: textId, text } });
}
}
} else if (event.kind === "artifact-update") {
const text = event.artifact.parts?.flatMap(
(part) => part.kind === "text" && "text" in part && typeof part.text === "string" ? [part.text] : []
).join("");
if (text) {
textId ??= resolveStreamTextId([event.artifact.artifactId, task?.id]);
if (textId) {
if (!textStarted) {
yield toAgentStreamChunk(runId, { type: "text-start", payload: { id: textId } });
textStarted = true;
}
yield toAgentStreamChunk(runId, { type: "text-delta", payload: { id: textId, text } });
}
}
} else if (event.kind === "status-update") {
task = task ? {
...task,
status: event.status
} : task;
if (event.status.state === "input-required" && task) {
suspended = {
taskId: task.id,
contextId: task.contextId,
executionUrl: bootstrap.executionUrl,
cardUrl: bootstrap.cardUrl,
waitingForInput: true,
task: structuredClone(task)
};
}
}
}
buffer = next.rest;
next = splitNextEvent(buffer);
}
if (done || receivedDone) {
if (textId && textStarted) {
yield toAgentStreamChunk(runId, { type: "text-end", payload: { id: textId } });
}
if (!suspended && task && !isTerminalTaskState(task.status.state)) {
suspended = {
taskId: task.id,
contextId: task.contextId,
executionUrl: bootstrap.executionUrl,
cardUrl: bootstrap.cardUrl,
waitingForInput: false,
task: structuredClone(task)
};
}
if (suspended) {
yield toAgentStreamChunk(runId, {
type: "tool-call-suspended",
payload: {
toolCallId: runId,
toolName: this.id,
args: {},
suspendPayload: suspended,
resumeSchema: createResumeSchema()
}
});
} else {
yield toAgentStreamChunk(runId, {
type: "finish",
payload: {
finishReason: "stop",
usage: EMPTY_USAGE
}
});
}
return;
}
}
}
async #collectStreamEvents({
bootstrap,
initialTask,
stream
}) {
const reader = stream.getReader();
const decoder = new TextDecoder();
let buffer = "";
let textBuffer = "";
let task = initialTask ? structuredClone(initialTask) : void 0;
let suspended;
while (true) {
const { done, value } = await reader.read();
if (done) {
buffer += decoder.decode();
} else if (value) {
buffer += decoder.decode(value, { stream: true });
}
let next = splitNextEvent(buffer);
while (next.eventBlock !== void 0) {
const parsed = parseEventBlock(next.eventBlock);
if ("done" in parsed && parsed.done) {
break;
}
if ("event" in parsed && parsed.event) {
const event = parsed.event;
if (isTask(event)) {
task = event;
textBuffer = extractTaskArtifactText(event) || textBuffer;
if (event.status.state === "input-required") {
suspended = {
payload: {
taskId: event.id,
contextId: event.contextId,
executionUrl: bootstrap.executionUrl,
cardUrl: bootstrap.cardUrl,
waitingForInput: true,
task: structuredClone(event)
},
resumeSchema: createResumeSchema()
};
}
} else if (isMessage(event)) {
const messageText = extractMessageText(event);
if (messageText) {
textBuffer = messageText;
}
} else if (event.kind === "artifact-update") {
task = task ? {
...task,
artifacts: [
...(task.artifacts ?? []).filter((artifact) => artifact.artifactId !== event.artifact.artifactId),
event.artifact
]
} : task;
const artifactText = event.artifact.parts?.flatMap(
(part) => part.kind === "text" && "text" in part && typeof part.text === "string" ? [part.text] : []
).join("");
if (artifactText) {
textBuffer += artifactText;
}
} else if (event.kind === "status-update") {
task = task ? {
...task,
status: event.status
} : task;
if (event.status.state === "input-required" && task) {
suspended = {
payload: {
taskId: task.id,
contextId: task.contextId,
executionUrl: bootstrap.executionUrl,
cardUrl: bootstrap.cardUrl,
waitingForInput: true,
task: structuredClone(task)
},
resumeSchema: createResumeSchema()
};
}
}
}
buffer = next.rest;
next = splitNextEvent(buffer);
}
if (done) {
break;
}
}
if (!suspended && task && !isTerminalTaskState(task.status.state)) {
suspended = {
payload: {
taskId: task.id,
contextId: task.contextId,
executionUrl: bootstrap.executionUrl,
cardUrl: bootstrap.cardUrl,
waitingForInput: false,
task: structuredClone(task)
}
};
}
return {
text: textBuffer,
task,
suspended
};
}
#createBufferedStreamResult({
runId,
result,
threadId,
resourceId
}) {
const messageList = new MessageList({ threadId, resourceId });
const toolName = this.id;
const textId = resolveStreamTextId([result.message?.messageId, result.task?.id]);
if (result.text) {
messageList.add(
{
role: "assistant",
content: result.text
},
"response"
);
}
const fullStream = (async function* () {
if (result.text) {
yield toAgentStreamChunk(runId, { type: "text-start", payload: { id: textId } });
yield toAgentStreamChunk(runId, { type: "text-delta", payload: { id: textId, text: result.text } });
yield toAgentStreamChunk(runId, { type: "text-end", payload: { id: textId } });
}
if (result.resumePayload) {
yield toAgentStreamChunk(runId, {
type: "tool-call-suspended",
payload: {
toolCallId: runId,
toolName,
args: {},
suspendPayload: result.resumePayload,
resumeSchema: result.resumeSchema ?? createResumeSchema()
}
});
return;
}
yield toAgentStreamChunk(runId, {
type: "finish",
payload: {
finishReason: "stop",
usage: EMPTY_USAGE
}
});
})();
const streamResult = {
runId,
fullStream,
text: Promise.resolve(result.text),
toolResults: Promise.resolve([]),
messageList,
task: Promise.resolve(result.task),
suspendPayload: Promise.resolve(result.resumePayload),
resumeSchema: Promise.resolve(result.resumeSchema),
getResult: async () => result
};
return streamResult;
}
async #request(url, { method = "POST", headers = {}, body, stream = false, credentials, signal } = {}) {
let attempts = 0;
let lastError;
const finalHeaders = {
accept: stream ? "text/event-stream" : "application/json",
...this.#headers,
...headers
};
while (attempts <= this.#retries) {
try {
const requestSignal = this.#resolveRequestSignal(signal);
const response = await this.#fetch(url, {
method,
headers: {
...finalHeaders,
...body ? { "content-type": "application/json" } : {}
},
body: body ? JSON.stringify(body) : void 0,
credentials: credentials ?? this.#credentials,
signal: requestSignal
});
if (!response.ok) {
throw MastraA2AError.invalidAgentResponse(`Remote A2A request failed with status ${response.status}.`, {
status: response.status,
url
});
}
return response;
} catch (error) {
lastError = error;
if (!shouldRetryRequest(error)) {
throw lastError;
}
if (attempts === this.#retries) {
break;
}
attempts += 1;
await this.#delay(attempts);
}
}
throw lastError;
}
async #delay(attempt = 0) {
const delayMs = Math.min(this.#backoffMs * Math.max(1, attempt), this.#maxBackoffMs);
if (delayMs <= 0) {
return;
}
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
#resolveRequestSignal(signal) {
if (this.#timeoutMs == null) {
return signal ?? this.#abortSignal;
}
const timeoutSignal = AbortSignal.timeout(this.#timeoutMs);
const signals = [signal, this.#abortSignal, timeoutSignal].filter(Boolean);
if (signals.length === 0) {
return void 0;
}
return signals.length === 1 ? signals[0] : AbortSignal.any(signals);
}
};
function shouldRetryRequest(error) {
if (error instanceof TypeError) {
return true;
}
if (error instanceof DOMException && error.name === "AbortError") {
return true;
}
if (error instanceof Error && error.name === "AbortError") {
return true;
}
const status = typeof error === "object" && error !== null && "status" in error && typeof error.status === "number" ? error.status : typeof error === "object" && error !== null && "data" in error && typeof error.data === "object" && error.data !== null && "status" in error.data && typeof error.data.status === "number" ? error.data.status : void 0;
if (status === void 0) {
return true;
}
return status === 408 || status === 429 || status >= 500;
}
export { A2AAgent };
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map