@mastra/server 1.57.0 → 1.58.0-alpha.1
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 +63 -0
- package/dist/{a2a-uPi5sPwb.js → a2a-CHi9AJ0n.js} +598 -178
- package/dist/a2a-CHi9AJ0n.js.map +1 -0
- package/dist/{a2a-DKnwIgiw.cjs → a2a-D3ZYeXIx.cjs} +598 -178
- package/dist/a2a-D3ZYeXIx.cjs.map +1 -0
- package/dist/{api-schema-manifest-7-NC9Dyc.cjs → api-schema-manifest-J5hfk5ZZ.cjs} +2 -2
- package/dist/{api-schema-manifest-7-NC9Dyc.cjs.map → api-schema-manifest-J5hfk5ZZ.cjs.map} +1 -1
- package/dist/{api-schema-manifest-C5hpdIE_.js → api-schema-manifest-R8CsF6RX.js} +2 -2
- package/dist/{api-schema-manifest-C5hpdIE_.js.map → api-schema-manifest-R8CsF6RX.js.map} +1 -1
- package/dist/{datasets-Cvix7Rms.cjs → datasets-BMeFvFwT.cjs} +43 -1
- package/dist/datasets-BMeFvFwT.cjs.map +1 -0
- package/dist/{datasets-dhXnHLAL.js → datasets-BeBH9Seg.js} +38 -2
- package/dist/datasets-BeBH9Seg.js.map +1 -0
- package/dist/{dist-COHIL6fR.cjs → dist-DgY0llRv.cjs} +116 -28
- package/dist/dist-DgY0llRv.cjs.map +1 -0
- package/dist/{dist-DGqlUp0b.js → dist-IUXZtNBY.js} +116 -28
- package/dist/dist-IUXZtNBY.js.map +1 -0
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-agents-a2a.md +36 -2
- package/dist/route-builder-CIk5k35M.js.map +1 -1
- package/dist/route-builder-CgLVYL1c.cjs.map +1 -1
- package/dist/{routes-hSY4wTeS.cjs → routes-BzR8iVj4.cjs} +2 -2
- package/dist/{routes-hSY4wTeS.cjs.map → routes-BzR8iVj4.cjs.map} +1 -1
- package/dist/{routes-CRoTKPvQ.js → routes-C7iRCN_l.js} +2 -2
- package/dist/{routes-CRoTKPvQ.js.map → routes-C7iRCN_l.js.map} +1 -1
- package/dist/server/a2a/store.cjs +26 -2
- package/dist/server/a2a/store.cjs.map +1 -1
- package/dist/server/a2a/store.d.ts +18 -2
- package/dist/server/a2a/store.d.ts.map +1 -1
- package/dist/server/a2a/store.js +26 -3
- package/dist/server/a2a/store.js.map +1 -1
- package/dist/server/a2a/task-state.d.ts +4 -0
- package/dist/server/a2a/task-state.d.ts.map +1 -0
- package/dist/server/a2a/tasks.d.ts +1 -1
- package/dist/server/a2a/tasks.d.ts.map +1 -1
- package/dist/server/handlers/a2a.cjs +1 -1
- package/dist/server/handlers/a2a.d.ts +3 -2
- package/dist/server/handlers/a2a.d.ts.map +1 -1
- package/dist/server/handlers/a2a.js +1 -1
- package/dist/server/handlers/agent-builder.cjs +1 -1
- package/dist/server/handlers/agent-builder.js +1 -1
- package/dist/server/handlers/agent-versions.cjs +6 -0
- package/dist/server/handlers/agent-versions.cjs.map +1 -1
- package/dist/server/handlers/agent-versions.d.ts.map +1 -1
- package/dist/server/handlers/agent-versions.js +6 -0
- package/dist/server/handlers/agent-versions.js.map +1 -1
- package/dist/server/handlers/datasets.cjs +26 -11
- package/dist/server/handlers/datasets.cjs.map +1 -1
- package/dist/server/handlers/datasets.d.ts +136 -0
- package/dist/server/handlers/datasets.d.ts.map +1 -1
- package/dist/server/handlers/datasets.js +26 -11
- package/dist/server/handlers/datasets.js.map +1 -1
- package/dist/server/handlers/mcp-client-versions.cjs +1 -1
- package/dist/server/handlers/mcp-client-versions.js +1 -1
- package/dist/server/handlers/observability.cjs +20 -9
- package/dist/server/handlers/observability.cjs.map +1 -1
- package/dist/server/handlers/observability.d.ts.map +1 -1
- package/dist/server/handlers/observability.js +20 -9
- package/dist/server/handlers/observability.js.map +1 -1
- package/dist/server/handlers/prompt-block-versions.cjs +1 -1
- package/dist/server/handlers/prompt-block-versions.js +1 -1
- package/dist/server/handlers/scorer-versions.cjs +1 -1
- package/dist/server/handlers/scorer-versions.js +1 -1
- package/dist/server/handlers/stored-agents.cjs +16 -1
- package/dist/server/handlers/stored-agents.cjs.map +1 -1
- package/dist/server/handlers/stored-agents.d.ts.map +1 -1
- package/dist/server/handlers/stored-agents.js +16 -1
- package/dist/server/handlers/stored-agents.js.map +1 -1
- package/dist/server/handlers/stored-mcp-clients.cjs +1 -1
- package/dist/server/handlers/stored-mcp-clients.js +1 -1
- package/dist/server/handlers/stored-prompt-blocks.cjs +1 -1
- package/dist/server/handlers/stored-prompt-blocks.js +1 -1
- package/dist/server/handlers/stored-scorers.cjs +1 -1
- package/dist/server/handlers/stored-scorers.js +1 -1
- package/dist/server/handlers/stored-skill-favorites.cjs +1 -1
- package/dist/server/handlers/stored-skill-favorites.js +1 -1
- package/dist/server/handlers/stored-skills.cjs +1 -1
- package/dist/server/handlers/stored-skills.js +1 -1
- package/dist/server/handlers/system.cjs +1 -1
- package/dist/server/handlers/system.js +1 -1
- package/dist/server/handlers/validate-agent-instructions.cjs +52 -0
- package/dist/server/handlers/validate-agent-instructions.cjs.map +1 -0
- package/dist/server/handlers/validate-agent-instructions.d.ts +13 -0
- package/dist/server/handlers/validate-agent-instructions.d.ts.map +1 -0
- package/dist/server/handlers/validate-agent-instructions.js +51 -0
- package/dist/server/handlers/validate-agent-instructions.js.map +1 -0
- package/dist/server/handlers/workflows.cjs +12 -0
- package/dist/server/handlers/workflows.cjs.map +1 -1
- package/dist/server/handlers/workflows.d.ts.map +1 -1
- package/dist/server/handlers/workflows.js +12 -0
- package/dist/server/handlers/workflows.js.map +1 -1
- package/dist/server/handlers.cjs +1 -1
- package/dist/server/handlers.js +1 -1
- package/dist/server/schemas/datasets.d.ts +53 -0
- package/dist/server/schemas/datasets.d.ts.map +1 -1
- package/dist/server/schemas/index.cjs +3 -2
- package/dist/server/schemas/index.js +3 -3
- package/dist/server/server-adapter/index.cjs +1 -1
- package/dist/server/server-adapter/index.js +1 -1
- package/dist/server/server-adapter/routes/datasets.d.ts +136 -0
- package/dist/server/server-adapter/routes/datasets.d.ts.map +1 -1
- package/package.json +5 -5
- package/dist/a2a-DKnwIgiw.cjs.map +0 -1
- package/dist/a2a-uPi5sPwb.js.map +0 -1
- package/dist/datasets-Cvix7Rms.cjs.map +0 -1
- package/dist/datasets-dhXnHLAL.js.map +0 -1
- package/dist/dist-COHIL6fR.cjs.map +0 -1
- package/dist/dist-DGqlUp0b.js.map +0 -1
|
@@ -2,6 +2,7 @@ import { i as __toESM, n as __exportAll, t as __commonJSMin } from "./rolldown-r
|
|
|
2
2
|
import { n as createRoute } from "./route-builder-CIk5k35M.js";
|
|
3
3
|
import { r as convertInstructionsToString } from "./utils-BUK03Frl.js";
|
|
4
4
|
import { getAgentFromSystem } from "./server/handlers/agents.js";
|
|
5
|
+
import { TaskStoreVersionConflictError } from "./server/a2a/store.js";
|
|
5
6
|
import { a as agentExecutionResponseSchema, i as agentExecutionBodySchema, r as agentCardResponseSchema, t as a2aAgentIdPathParams } from "./a2a-CnU9zaGR.js";
|
|
6
7
|
import { getPublicOrigin } from "./server/handlers/auth.js";
|
|
7
8
|
import { z } from "zod/v4";
|
|
@@ -115,7 +116,7 @@ function createErrorResponse(id, error) {
|
|
|
115
116
|
};
|
|
116
117
|
}
|
|
117
118
|
function createSuccessResponse(id, result) {
|
|
118
|
-
if (
|
|
119
|
+
if (id === null) throw MastraA2AError.internalError("Cannot create success response for null ID.");
|
|
119
120
|
return {
|
|
120
121
|
jsonrpc: "2.0",
|
|
121
122
|
id,
|
|
@@ -303,6 +304,14 @@ var InMemoryPushNotificationStore = class {
|
|
|
303
304
|
}
|
|
304
305
|
};
|
|
305
306
|
//#endregion
|
|
307
|
+
//#region src/server/a2a/task-state.ts
|
|
308
|
+
function isTerminalTaskState(state) {
|
|
309
|
+
return state === "completed" || state === "failed" || state === "canceled" || state === "rejected";
|
|
310
|
+
}
|
|
311
|
+
function isInterruptedTaskState(state) {
|
|
312
|
+
return state === "input-required" || state === "auth-required";
|
|
313
|
+
}
|
|
314
|
+
//#endregion
|
|
306
315
|
//#region src/server/a2a/tasks.ts
|
|
307
316
|
function isTaskStatusUpdate(update) {
|
|
308
317
|
return "state" in update && !("parts" in update);
|
|
@@ -338,54 +347,63 @@ function applyUpdateToTask(current, update) {
|
|
|
338
347
|
return newTask;
|
|
339
348
|
}
|
|
340
349
|
async function loadOrCreateTask({ agentId, taskId, taskStore, message, contextId, metadata, logger }) {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
taskId
|
|
344
|
-
});
|
|
345
|
-
if (!data) {
|
|
346
|
-
const initialTask = {
|
|
347
|
-
id: taskId,
|
|
348
|
-
contextId: contextId || crypto.randomUUID(),
|
|
349
|
-
status: {
|
|
350
|
-
state: "submitted",
|
|
351
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
352
|
-
message: void 0
|
|
353
|
-
},
|
|
354
|
-
artifacts: [],
|
|
355
|
-
history: [message],
|
|
356
|
-
metadata,
|
|
357
|
-
kind: "task"
|
|
358
|
-
};
|
|
359
|
-
logger?.info(`[Task ${taskId}] Created new task.`);
|
|
360
|
-
await taskStore.save({
|
|
350
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
351
|
+
const snapshot = taskStore.loadWithVersion({
|
|
361
352
|
agentId,
|
|
362
|
-
|
|
353
|
+
taskId
|
|
363
354
|
});
|
|
364
|
-
|
|
355
|
+
const data = snapshot?.task;
|
|
356
|
+
if (!data) {
|
|
357
|
+
const initialTask = {
|
|
358
|
+
id: taskId,
|
|
359
|
+
contextId: contextId || crypto.randomUUID(),
|
|
360
|
+
status: {
|
|
361
|
+
state: "submitted",
|
|
362
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
363
|
+
message: void 0
|
|
364
|
+
},
|
|
365
|
+
artifacts: [],
|
|
366
|
+
history: [message],
|
|
367
|
+
metadata,
|
|
368
|
+
kind: "task"
|
|
369
|
+
};
|
|
370
|
+
logger?.info(`[Task ${taskId}] Created new task.`);
|
|
371
|
+
try {
|
|
372
|
+
await taskStore.save({
|
|
373
|
+
agentId,
|
|
374
|
+
data: initialTask,
|
|
375
|
+
expectedVersion: 0
|
|
376
|
+
});
|
|
377
|
+
return initialTask;
|
|
378
|
+
} catch (error) {
|
|
379
|
+
if (error instanceof TaskStoreVersionConflictError) continue;
|
|
380
|
+
throw error;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
logger?.info(`[Task ${taskId}] Loaded existing task.`);
|
|
384
|
+
const { status } = data;
|
|
385
|
+
if (isTerminalTaskState(status.state)) throw MastraA2AError.invalidRequest(`Task ${taskId} is in terminal state ${status.state} and cannot be restarted.`);
|
|
386
|
+
let updatedData = {
|
|
387
|
+
...data,
|
|
388
|
+
history: [...data.history || [], message]
|
|
389
|
+
};
|
|
390
|
+
if (status.state === "input-required" || status.state === "auth-required") {
|
|
391
|
+
logger?.info(`[Task ${taskId}] Changing state from '${status.state}' to 'working'.`);
|
|
392
|
+
updatedData = applyUpdateToTask(updatedData, { state: "working" });
|
|
393
|
+
} else if (status.state === "working") logger?.warn(`[Task ${taskId}] Received message while already 'working'. Proceeding.`);
|
|
394
|
+
try {
|
|
395
|
+
await taskStore.save({
|
|
396
|
+
agentId,
|
|
397
|
+
data: updatedData,
|
|
398
|
+
expectedVersion: snapshot.version
|
|
399
|
+
});
|
|
400
|
+
return updatedData;
|
|
401
|
+
} catch (error) {
|
|
402
|
+
if (error instanceof TaskStoreVersionConflictError) continue;
|
|
403
|
+
throw error;
|
|
404
|
+
}
|
|
365
405
|
}
|
|
366
|
-
|
|
367
|
-
let updatedData = data;
|
|
368
|
-
updatedData.history = [...data.history || [], message];
|
|
369
|
-
const { status } = data;
|
|
370
|
-
if ([
|
|
371
|
-
"completed",
|
|
372
|
-
"failed",
|
|
373
|
-
"canceled"
|
|
374
|
-
].includes(status.state)) {
|
|
375
|
-
logger?.warn(`[Task ${taskId}] Received message for task in final state ${status.state}. Restarting.`);
|
|
376
|
-
updatedData = applyUpdateToTask(updatedData, {
|
|
377
|
-
state: "submitted",
|
|
378
|
-
message: void 0
|
|
379
|
-
});
|
|
380
|
-
} else if (status.state === "input-required") {
|
|
381
|
-
logger?.info(`[Task ${taskId}] Changing state from 'input-required' to 'working'.`);
|
|
382
|
-
updatedData = applyUpdateToTask(updatedData, { state: "working" });
|
|
383
|
-
} else if (status.state === "working") logger?.warn(`[Task ${taskId}] Received message while already 'working'. Proceeding.`);
|
|
384
|
-
await taskStore.save({
|
|
385
|
-
agentId,
|
|
386
|
-
data: updatedData
|
|
387
|
-
});
|
|
388
|
-
return updatedData;
|
|
406
|
+
throw MastraA2AError.invalidRequest(`Task ${taskId} was updated concurrently. Retry the request.`);
|
|
389
407
|
}
|
|
390
408
|
//#endregion
|
|
391
409
|
//#region src/server/handlers/a2a.ts
|
|
@@ -572,6 +590,176 @@ function createDataArtifactUpdate({ taskId, contextId, data, lastChunk }) {
|
|
|
572
590
|
}
|
|
573
591
|
};
|
|
574
592
|
}
|
|
593
|
+
/**
|
|
594
|
+
* Task metadata keys that store the resume bookkeeping for a suspended agent
|
|
595
|
+
* run so a follow-up message for an `input-required` task can resume it.
|
|
596
|
+
*/
|
|
597
|
+
const SUSPENDED_RUN_ID_METADATA_KEY = "suspendedRunId";
|
|
598
|
+
const SUSPENDED_TOOL_CALL_ID_METADATA_KEY = "suspendedToolCallId";
|
|
599
|
+
const SUSPENDED_REQUIRES_APPROVAL_METADATA_KEY = "suspendedRequiresApproval";
|
|
600
|
+
function toJsonSafe(value) {
|
|
601
|
+
if (value === void 0) return;
|
|
602
|
+
try {
|
|
603
|
+
return JSON.parse(JSON.stringify(value));
|
|
604
|
+
} catch {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Builds the `input-required` status update for a suspended agent run.
|
|
610
|
+
* The status message carries a human-readable prompt plus a data part with
|
|
611
|
+
* the structured suspend payload and resume schema so A2A clients can
|
|
612
|
+
* render/collect the required input (HITL per the A2A spec).
|
|
613
|
+
*/
|
|
614
|
+
function createInputRequiredStatusUpdate({ taskId, contextId, suspendPayload, resumeSchema, logger }) {
|
|
615
|
+
const extractMessage = (value) => {
|
|
616
|
+
if (!value || typeof value !== "object") return;
|
|
617
|
+
const messageValue = value.message;
|
|
618
|
+
return typeof messageValue === "string" && messageValue.length > 0 ? messageValue : void 0;
|
|
619
|
+
};
|
|
620
|
+
const promptText = extractMessage(suspendPayload) ?? extractMessage(suspendPayload?.suspendPayload) ?? "Additional input is required to continue this task.";
|
|
621
|
+
const safeSuspendPayload = toJsonSafe(suspendPayload);
|
|
622
|
+
const safeResumeSchema = toJsonSafe(resumeSchema);
|
|
623
|
+
if (suspendPayload !== void 0 && safeSuspendPayload === void 0) logger?.warn(`[Task ${taskId}] Suspend payload is not JSON-serializable and was omitted from the status message.`);
|
|
624
|
+
if (resumeSchema !== void 0 && safeResumeSchema === void 0) logger?.warn(`[Task ${taskId}] Resume schema is not JSON-serializable and was omitted from the status message.`);
|
|
625
|
+
const data = {
|
|
626
|
+
...safeSuspendPayload !== void 0 ? { suspendPayload: safeSuspendPayload } : {},
|
|
627
|
+
...safeResumeSchema !== void 0 ? { resumeSchema: safeResumeSchema } : {}
|
|
628
|
+
};
|
|
629
|
+
return {
|
|
630
|
+
state: "input-required",
|
|
631
|
+
message: {
|
|
632
|
+
messageId: crypto.randomUUID(),
|
|
633
|
+
kind: "message",
|
|
634
|
+
role: "agent",
|
|
635
|
+
taskId,
|
|
636
|
+
contextId,
|
|
637
|
+
parts: [{
|
|
638
|
+
kind: "text",
|
|
639
|
+
text: promptText
|
|
640
|
+
}, ...Object.keys(data).length > 0 ? [{
|
|
641
|
+
kind: "data",
|
|
642
|
+
data
|
|
643
|
+
}] : []]
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Extracts resume data from a follow-up message for an `input-required` task.
|
|
649
|
+
* Prefers a structured data part; falls back to parsing the text as JSON
|
|
650
|
+
* (the Mastra A2A client serializes structured resume data as JSON text),
|
|
651
|
+
* and finally to the raw text.
|
|
652
|
+
*/
|
|
653
|
+
function extractResumeData(message) {
|
|
654
|
+
const dataPart = message.parts.find((part) => part.kind === "data");
|
|
655
|
+
if (dataPart && "data" in dataPart) return dataPart.data;
|
|
656
|
+
const text = message.parts.filter((part) => part.kind === "text").map((part) => part.text).join("\n").trim();
|
|
657
|
+
if (!text) return;
|
|
658
|
+
try {
|
|
659
|
+
return JSON.parse(text);
|
|
660
|
+
} catch {
|
|
661
|
+
return text;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
function getSuspendedRunId(task) {
|
|
665
|
+
const value = task?.metadata?.[SUSPENDED_RUN_ID_METADATA_KEY];
|
|
666
|
+
return typeof value === "string" ? value : void 0;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Approval suspensions (`requireApproval`) carry `{ toolCallId, toolName, args, resumeSchema }`
|
|
670
|
+
* without a nested `suspendPayload`, while `suspend()` suspensions include one
|
|
671
|
+
* (see `ToolCallApprovalPayload` / `ToolCallSuspendedPayload` in @mastra/core).
|
|
672
|
+
*/
|
|
673
|
+
function isApprovalSuspension(suspendPayload) {
|
|
674
|
+
if (!suspendPayload || typeof suspendPayload !== "object") return false;
|
|
675
|
+
const payload = suspendPayload;
|
|
676
|
+
return typeof payload.toolCallId === "string" && payload.suspendPayload === void 0;
|
|
677
|
+
}
|
|
678
|
+
const APPROVAL_AFFIRMATIVE_PATTERN = /^(y|yes|approve|approved|ok|okay|confirm|confirmed|true)[.!]?$/i;
|
|
679
|
+
const APPROVAL_NEGATIVE_PATTERN = /^(n|no|decline|declined|deny|denied|reject|rejected|false)[.!]?$/i;
|
|
680
|
+
/**
|
|
681
|
+
* Approval resumes are driven by `resumeData.approved` in the agentic loop, so
|
|
682
|
+
* plain-text replies from A2A clients ("yes", "no") are coerced to the
|
|
683
|
+
* `{ approved }` shape. Unrecognized values pass through unchanged.
|
|
684
|
+
*/
|
|
685
|
+
function normalizeResumeData(resumeData, requiresApproval) {
|
|
686
|
+
if (!requiresApproval || typeof resumeData !== "string") return resumeData;
|
|
687
|
+
const text = resumeData.trim();
|
|
688
|
+
if (APPROVAL_AFFIRMATIVE_PATTERN.test(text)) return { approved: true };
|
|
689
|
+
if (APPROVAL_NEGATIVE_PATTERN.test(text)) return { approved: false };
|
|
690
|
+
return resumeData;
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Marks a task `input-required` for a suspended agent run and records the
|
|
694
|
+
* resume bookkeeping (runId, toolCallId, approval flag) in task metadata.
|
|
695
|
+
* Shared by the send and stream paths so both report identical suspensions.
|
|
696
|
+
*/
|
|
697
|
+
function applySuspensionToTask({ task, suspendPayload, resumeSchema, runId, logger }) {
|
|
698
|
+
const nextTask = applyUpdateToTask(task, createInputRequiredStatusUpdate({
|
|
699
|
+
taskId: task.id,
|
|
700
|
+
contextId: task.contextId,
|
|
701
|
+
suspendPayload,
|
|
702
|
+
resumeSchema,
|
|
703
|
+
logger
|
|
704
|
+
}));
|
|
705
|
+
const payload = suspendPayload;
|
|
706
|
+
nextTask.metadata = {
|
|
707
|
+
...clearSuspensionMetadata(nextTask.metadata),
|
|
708
|
+
[SUSPENDED_RUN_ID_METADATA_KEY]: runId,
|
|
709
|
+
...typeof payload?.toolCallId === "string" ? { [SUSPENDED_TOOL_CALL_ID_METADATA_KEY]: payload.toolCallId } : {},
|
|
710
|
+
...isApprovalSuspension(suspendPayload) ? { [SUSPENDED_REQUIRES_APPROVAL_METADATA_KEY]: true } : {}
|
|
711
|
+
};
|
|
712
|
+
return nextTask;
|
|
713
|
+
}
|
|
714
|
+
/** Removes the suspension bookkeeping from task metadata once the run completes. */
|
|
715
|
+
function clearSuspensionMetadata(metadata) {
|
|
716
|
+
const { [SUSPENDED_RUN_ID_METADATA_KEY]: _runId, [SUSPENDED_TOOL_CALL_ID_METADATA_KEY]: _toolCallId, [SUSPENDED_REQUIRES_APPROVAL_METADATA_KEY]: _requiresApproval, ...rest } = metadata ?? {};
|
|
717
|
+
return rest;
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Claims an interrupted task for resume by transitioning it to `working`.
|
|
721
|
+
* `loadWithVersion` and the body of `InMemoryTaskStore.save` both execute
|
|
722
|
+
* synchronously, so two concurrent follow-up messages cannot both claim (and
|
|
723
|
+
* double-resume) the same suspended run.
|
|
724
|
+
*/
|
|
725
|
+
async function claimInterruptedTaskResume({ taskStore, agentId, taskId }) {
|
|
726
|
+
const snapshot = taskStore.loadWithVersion({
|
|
727
|
+
agentId,
|
|
728
|
+
taskId
|
|
729
|
+
});
|
|
730
|
+
if (snapshot?.task.status.state !== "input-required" && snapshot?.task.status.state !== "auth-required") return;
|
|
731
|
+
const task = snapshot.task;
|
|
732
|
+
const toolCallId = task.metadata?.[SUSPENDED_TOOL_CALL_ID_METADATA_KEY];
|
|
733
|
+
const claim = {
|
|
734
|
+
runId: getSuspendedRunId(task) ?? taskId,
|
|
735
|
+
...typeof toolCallId === "string" ? { toolCallId } : {},
|
|
736
|
+
requiresApproval: task.metadata?.[SUSPENDED_REQUIRES_APPROVAL_METADATA_KEY] === true
|
|
737
|
+
};
|
|
738
|
+
try {
|
|
739
|
+
await taskStore.save({
|
|
740
|
+
agentId,
|
|
741
|
+
data: applyUpdateToTask(task, { state: "working" }),
|
|
742
|
+
expectedVersion: snapshot.version
|
|
743
|
+
});
|
|
744
|
+
} catch (error) {
|
|
745
|
+
if (error instanceof TaskStoreVersionConflictError) return;
|
|
746
|
+
throw error;
|
|
747
|
+
}
|
|
748
|
+
return claim;
|
|
749
|
+
}
|
|
750
|
+
async function waitForClaimedResume({ taskStore, agentId, taskId }) {
|
|
751
|
+
let snapshot = taskStore.loadWithVersion({
|
|
752
|
+
agentId,
|
|
753
|
+
taskId
|
|
754
|
+
});
|
|
755
|
+
if (!snapshot) throw MastraA2AError.taskNotFound(taskId);
|
|
756
|
+
while (snapshot.task.status.state === "working") snapshot = await taskStore.waitForNextUpdate({
|
|
757
|
+
agentId,
|
|
758
|
+
taskId,
|
|
759
|
+
afterVersion: snapshot.version
|
|
760
|
+
});
|
|
761
|
+
return snapshot.task;
|
|
762
|
+
}
|
|
575
763
|
function resolvePushNotificationPair({ pushNotificationStore, pushNotificationSender }) {
|
|
576
764
|
if (pushNotificationSender) return {
|
|
577
765
|
pushNotificationStore: pushNotificationSender.getStore(),
|
|
@@ -600,23 +788,45 @@ function shouldSendPushNotification(previousTask, nextTask) {
|
|
|
600
788
|
"completed",
|
|
601
789
|
"failed",
|
|
602
790
|
"canceled",
|
|
603
|
-
"
|
|
791
|
+
"rejected",
|
|
792
|
+
"input-required",
|
|
793
|
+
"auth-required"
|
|
604
794
|
].includes(nextTask.status.state)) return false;
|
|
605
795
|
return previousTask?.status.state !== nextTask.status.state;
|
|
606
796
|
}
|
|
607
|
-
|
|
608
|
-
|
|
797
|
+
function createLinkedAbortController(abortSignal) {
|
|
798
|
+
const controller = new AbortController();
|
|
799
|
+
if (!abortSignal) return {
|
|
800
|
+
controller,
|
|
801
|
+
cleanup: () => {}
|
|
802
|
+
};
|
|
803
|
+
const abortFromSignal = () => {
|
|
804
|
+
if (!controller.signal.aborted) controller.abort(abortSignal.reason);
|
|
805
|
+
};
|
|
806
|
+
if (abortSignal.aborted) abortFromSignal();
|
|
807
|
+
else abortSignal.addEventListener("abort", abortFromSignal, { once: true });
|
|
808
|
+
return {
|
|
809
|
+
controller,
|
|
810
|
+
cleanup: () => abortSignal.removeEventListener("abort", abortFromSignal)
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
async function saveTaskAndMaybeSendPushNotification({ taskStore, pushNotificationSender, previousTask, nextTask, agentId, expectedVersion, logger }) {
|
|
814
|
+
const storedTask = await taskStore.save({
|
|
609
815
|
agentId,
|
|
610
|
-
data: nextTask
|
|
816
|
+
data: nextTask,
|
|
817
|
+
expectedVersion,
|
|
818
|
+
skipIfCanceled: true
|
|
611
819
|
});
|
|
612
|
-
if (
|
|
820
|
+
if (storedTask.status.state === "canceled" && nextTask.status.state !== "canceled") return storedTask;
|
|
821
|
+
if (!shouldSendPushNotification(previousTask, storedTask)) return storedTask;
|
|
613
822
|
pushNotificationSender.sendNotifications({
|
|
614
823
|
agentId,
|
|
615
|
-
task:
|
|
824
|
+
task: storedTask,
|
|
616
825
|
logger
|
|
617
826
|
}).catch((error) => {
|
|
618
827
|
logger?.error("Failed to schedule A2A push notification", error);
|
|
619
828
|
});
|
|
829
|
+
return storedTask;
|
|
620
830
|
}
|
|
621
831
|
function extractFullStreamTextDelta(value) {
|
|
622
832
|
if (typeof value !== "object" || value === null || !("type" in value)) return null;
|
|
@@ -639,12 +849,10 @@ function extractFinalStructuredObject(value) {
|
|
|
639
849
|
const objectValue = chunk.payload?.object ?? chunk.object;
|
|
640
850
|
return objectValue && typeof objectValue === "object" ? objectValue : void 0;
|
|
641
851
|
}
|
|
642
|
-
function
|
|
643
|
-
return
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
"canceled"
|
|
647
|
-
].includes(state);
|
|
852
|
+
function isSuspensionChunk(value) {
|
|
853
|
+
if (typeof value !== "object" || value === null || !("type" in value)) return false;
|
|
854
|
+
const type = value.type;
|
|
855
|
+
return type === "tool-call-suspended" || type === "tool-call-approval";
|
|
648
856
|
}
|
|
649
857
|
function artifactIdentity(artifact) {
|
|
650
858
|
return artifact.artifactId || artifact.name;
|
|
@@ -689,11 +897,15 @@ function getTaskArtifactUpdates({ previous, next }) {
|
|
|
689
897
|
artifact: structuredClone(artifact)
|
|
690
898
|
}));
|
|
691
899
|
}
|
|
692
|
-
async function executeMessageSend({ requestId, message, metadata, currentData, taskStore, pushNotificationSender, agent, agentId, logger, requestContext }) {
|
|
900
|
+
async function executeMessageSend({ requestId, message, metadata, currentData, taskStore, pushNotificationSender, agent, agentId, logger, requestContext, resume }) {
|
|
693
901
|
const { contextId } = message;
|
|
694
902
|
try {
|
|
695
903
|
const resourceId = metadata?.resourceId ?? message.metadata?.resourceId ?? agentId;
|
|
696
|
-
const result = await agent.
|
|
904
|
+
const result = resume ? await agent.resumeGenerate(normalizeResumeData(extractResumeData(message), resume.requiresApproval), {
|
|
905
|
+
runId: resume.runId,
|
|
906
|
+
...resume.toolCallId ? { toolCallId: resume.toolCallId } : {},
|
|
907
|
+
requestContext
|
|
908
|
+
}) : await agent.generate([convertToCoreMessage(message)], {
|
|
697
909
|
runId: currentData.id,
|
|
698
910
|
requestContext,
|
|
699
911
|
...contextId ? {
|
|
@@ -714,13 +926,32 @@ async function executeMessageSend({ requestId, message, metadata, currentData, t
|
|
|
714
926
|
data: result.object
|
|
715
927
|
});
|
|
716
928
|
if (artifactUpdate) currentData = applyUpdateToTask(currentData, artifactUpdate);
|
|
929
|
+
if (result.finishReason === "suspended") {
|
|
930
|
+
const previousTask = currentData;
|
|
931
|
+
currentData = applySuspensionToTask({
|
|
932
|
+
task: currentData,
|
|
933
|
+
suspendPayload: result.suspendPayload,
|
|
934
|
+
resumeSchema: result.resumeSchema,
|
|
935
|
+
runId: result.runId ?? currentData.id,
|
|
936
|
+
logger
|
|
937
|
+
});
|
|
938
|
+
await saveTaskAndMaybeSendPushNotification({
|
|
939
|
+
taskStore,
|
|
940
|
+
pushNotificationSender,
|
|
941
|
+
previousTask,
|
|
942
|
+
nextTask: currentData,
|
|
943
|
+
agentId,
|
|
944
|
+
logger
|
|
945
|
+
});
|
|
946
|
+
return createSuccessResponse(requestId, currentData);
|
|
947
|
+
}
|
|
717
948
|
const previousTask = currentData;
|
|
718
949
|
currentData = applyUpdateToTask(currentData, {
|
|
719
950
|
state: "completed",
|
|
720
951
|
message: void 0
|
|
721
952
|
});
|
|
722
953
|
currentData.metadata = {
|
|
723
|
-
...currentData.metadata,
|
|
954
|
+
...clearSuspensionMetadata(currentData.metadata),
|
|
724
955
|
execution: {
|
|
725
956
|
toolCalls: result.toolCalls,
|
|
726
957
|
toolResults: result.toolResults,
|
|
@@ -778,11 +1009,28 @@ async function handleMessageSend({ requestId, params, taskStore, pushNotificatio
|
|
|
778
1009
|
const { message, metadata } = params;
|
|
779
1010
|
const { contextId } = message;
|
|
780
1011
|
const taskId = message.taskId || crypto.randomUUID();
|
|
781
|
-
const existingTask =
|
|
1012
|
+
const existingTask = taskStore.loadWithVersion({
|
|
782
1013
|
agentId,
|
|
783
1014
|
taskId
|
|
784
|
-
});
|
|
1015
|
+
})?.task;
|
|
1016
|
+
if (message.taskId && !existingTask) throw MastraA2AError.taskNotFound(message.taskId);
|
|
785
1017
|
if (params.configuration?.blocking === false && existingTask?.status.state === "working") return createSuccessResponse(requestId, existingTask);
|
|
1018
|
+
if (existingTask?.status.state === "working" && getSuspendedRunId(existingTask)) return createSuccessResponse(requestId, await waitForClaimedResume({
|
|
1019
|
+
taskStore,
|
|
1020
|
+
agentId,
|
|
1021
|
+
taskId
|
|
1022
|
+
}));
|
|
1023
|
+
const wasInterrupted = isInterruptedTaskState(existingTask?.status.state);
|
|
1024
|
+
const resume = await claimInterruptedTaskResume({
|
|
1025
|
+
taskStore,
|
|
1026
|
+
agentId,
|
|
1027
|
+
taskId
|
|
1028
|
+
});
|
|
1029
|
+
if (wasInterrupted && !resume) return createSuccessResponse(requestId, await waitForClaimedResume({
|
|
1030
|
+
taskStore,
|
|
1031
|
+
agentId,
|
|
1032
|
+
taskId
|
|
1033
|
+
}));
|
|
786
1034
|
const { pushNotificationStore: resolvedPushNotificationStore, pushNotificationSender: resolvedPushNotificationSender } = resolvePushNotificationPair({
|
|
787
1035
|
pushNotificationStore,
|
|
788
1036
|
pushNotificationSender
|
|
@@ -828,7 +1076,8 @@ async function handleMessageSend({ requestId, params, taskStore, pushNotificatio
|
|
|
828
1076
|
agent,
|
|
829
1077
|
agentId,
|
|
830
1078
|
logger,
|
|
831
|
-
requestContext
|
|
1079
|
+
requestContext,
|
|
1080
|
+
resume
|
|
832
1081
|
});
|
|
833
1082
|
if (params.configuration?.blocking === false) {
|
|
834
1083
|
execution.catch((error) => {
|
|
@@ -905,11 +1154,20 @@ async function handleDeleteTaskPushNotificationConfig({ requestId, taskStore, pu
|
|
|
905
1154
|
})) throw MastraA2AError.invalidParams(`Push notification config not found: ${params.pushNotificationConfigId}`);
|
|
906
1155
|
return createSuccessResponse(requestId, null);
|
|
907
1156
|
}
|
|
908
|
-
async function* handleMessageStream({ requestId, params, taskStore, pushNotificationStore, pushNotificationSender, agent, agentId, logger, requestContext }) {
|
|
1157
|
+
async function* handleMessageStream({ requestId, params, taskStore, pushNotificationStore, pushNotificationSender, agent, agentId, logger, requestContext, abortSignal }) {
|
|
909
1158
|
validateMessageSendParams(params);
|
|
910
1159
|
const { message, metadata } = params;
|
|
911
1160
|
const { contextId } = message;
|
|
912
1161
|
const taskId = message.taskId || crypto.randomUUID();
|
|
1162
|
+
if (message.taskId && !taskStore.loadWithVersion({
|
|
1163
|
+
agentId,
|
|
1164
|
+
taskId
|
|
1165
|
+
})) throw MastraA2AError.taskNotFound(message.taskId);
|
|
1166
|
+
const resume = await claimInterruptedTaskResume({
|
|
1167
|
+
taskStore,
|
|
1168
|
+
agentId,
|
|
1169
|
+
taskId
|
|
1170
|
+
});
|
|
913
1171
|
const { pushNotificationStore: resolvedPushNotificationStore, pushNotificationSender: resolvedPushNotificationSender } = resolvePushNotificationPair({
|
|
914
1172
|
pushNotificationStore,
|
|
915
1173
|
pushNotificationSender
|
|
@@ -945,12 +1203,24 @@ async function* handleMessageStream({ requestId, params, taskStore, pushNotifica
|
|
|
945
1203
|
agentId,
|
|
946
1204
|
logger
|
|
947
1205
|
});
|
|
948
|
-
|
|
1206
|
+
const { controller: taskAbortController, cleanup: cleanupLinkedAbortController } = createLinkedAbortController(abortSignal);
|
|
1207
|
+
const unregisterTaskAbortController = taskStore.registerAbortController({
|
|
1208
|
+
agentId,
|
|
1209
|
+
taskId,
|
|
1210
|
+
controller: taskAbortController
|
|
1211
|
+
});
|
|
949
1212
|
try {
|
|
1213
|
+
yield createSuccessResponse(requestId, currentData);
|
|
950
1214
|
const resourceId = metadata?.resourceId ?? message.metadata?.resourceId ?? agentId;
|
|
951
|
-
const result = await agent.
|
|
1215
|
+
const result = resume ? await agent.resumeStream(normalizeResumeData(extractResumeData(message), resume.requiresApproval), {
|
|
1216
|
+
runId: resume.runId,
|
|
1217
|
+
...resume.toolCallId ? { toolCallId: resume.toolCallId } : {},
|
|
1218
|
+
requestContext,
|
|
1219
|
+
abortSignal: taskAbortController.signal
|
|
1220
|
+
}) : await agent.stream([convertToCoreMessage(message)], {
|
|
952
1221
|
runId: taskId,
|
|
953
1222
|
requestContext,
|
|
1223
|
+
abortSignal: taskAbortController.signal,
|
|
954
1224
|
...contextId ? {
|
|
955
1225
|
threadId: contextId,
|
|
956
1226
|
resourceId
|
|
@@ -959,7 +1229,22 @@ async function* handleMessageStream({ requestId, params, taskStore, pushNotifica
|
|
|
959
1229
|
let sawTextArtifact = false;
|
|
960
1230
|
let pendingTextChunk;
|
|
961
1231
|
let structuredData;
|
|
1232
|
+
let suspended = false;
|
|
1233
|
+
let streamCanceled = false;
|
|
962
1234
|
for await (const chunk of result.fullStream) {
|
|
1235
|
+
if (taskAbortController.signal.aborted) {
|
|
1236
|
+
const latestTask = await taskStore.load({
|
|
1237
|
+
agentId,
|
|
1238
|
+
taskId: currentData.id
|
|
1239
|
+
});
|
|
1240
|
+
if (latestTask) currentData = latestTask;
|
|
1241
|
+
streamCanceled = true;
|
|
1242
|
+
break;
|
|
1243
|
+
}
|
|
1244
|
+
if (isSuspensionChunk(chunk)) {
|
|
1245
|
+
suspended = true;
|
|
1246
|
+
continue;
|
|
1247
|
+
}
|
|
963
1248
|
const textDelta = extractFullStreamTextDelta(chunk);
|
|
964
1249
|
if (textDelta !== null) {
|
|
965
1250
|
if (!pendingTextChunk) {
|
|
@@ -974,13 +1259,17 @@ async function* handleMessageStream({ requestId, params, taskStore, pushNotifica
|
|
|
974
1259
|
lastChunk: false
|
|
975
1260
|
});
|
|
976
1261
|
currentData = applyUpdateToTask(currentData, textUpdate);
|
|
977
|
-
await saveTaskAndMaybeSendPushNotification({
|
|
1262
|
+
currentData = await saveTaskAndMaybeSendPushNotification({
|
|
978
1263
|
taskStore,
|
|
979
1264
|
pushNotificationSender: resolvedPushNotificationSender,
|
|
980
1265
|
nextTask: currentData,
|
|
981
1266
|
agentId,
|
|
982
1267
|
logger
|
|
983
1268
|
});
|
|
1269
|
+
if (currentData.status.state === "canceled") {
|
|
1270
|
+
streamCanceled = true;
|
|
1271
|
+
break;
|
|
1272
|
+
}
|
|
984
1273
|
yield createSuccessResponse(requestId, textUpdate);
|
|
985
1274
|
sawTextArtifact = true;
|
|
986
1275
|
pendingTextChunk = textDelta;
|
|
@@ -989,98 +1278,217 @@ async function* handleMessageStream({ requestId, params, taskStore, pushNotifica
|
|
|
989
1278
|
const finalStructuredObject = extractFinalStructuredObject(chunk);
|
|
990
1279
|
if (finalStructuredObject) structuredData = finalStructuredObject;
|
|
991
1280
|
}
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1281
|
+
if (!streamCanceled && taskAbortController.signal.aborted) {
|
|
1282
|
+
const latestTask = await taskStore.load({
|
|
1283
|
+
agentId,
|
|
1284
|
+
taskId: currentData.id
|
|
1285
|
+
});
|
|
1286
|
+
if (latestTask) currentData = latestTask;
|
|
1287
|
+
streamCanceled = true;
|
|
996
1288
|
}
|
|
997
|
-
if (
|
|
998
|
-
const
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1289
|
+
if (streamCanceled && abortSignal?.aborted && currentData.status.state !== "canceled") {
|
|
1290
|
+
const previousTask = currentData;
|
|
1291
|
+
currentData = applyUpdateToTask(currentData, {
|
|
1292
|
+
state: "canceled",
|
|
1293
|
+
message: {
|
|
1294
|
+
messageId: crypto.randomUUID(),
|
|
1295
|
+
role: "agent",
|
|
1296
|
+
parts: [{
|
|
1297
|
+
kind: "text",
|
|
1298
|
+
text: "Task canceled because the request was aborted."
|
|
1299
|
+
}],
|
|
1300
|
+
kind: "message"
|
|
1301
|
+
}
|
|
1004
1302
|
});
|
|
1005
|
-
currentData =
|
|
1006
|
-
await saveTaskAndMaybeSendPushNotification({
|
|
1303
|
+
currentData = await saveTaskAndMaybeSendPushNotification({
|
|
1007
1304
|
taskStore,
|
|
1008
1305
|
pushNotificationSender: resolvedPushNotificationSender,
|
|
1306
|
+
previousTask,
|
|
1009
1307
|
nextTask: currentData,
|
|
1010
1308
|
agentId,
|
|
1011
1309
|
logger
|
|
1012
1310
|
});
|
|
1013
|
-
yield createSuccessResponse(requestId, textUpdate);
|
|
1014
|
-
sawTextArtifact = true;
|
|
1015
|
-
pendingTextChunk = void 0;
|
|
1016
1311
|
}
|
|
1017
|
-
if (
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1312
|
+
if (!streamCanceled && suspended) {
|
|
1313
|
+
if (pendingTextChunk) {
|
|
1314
|
+
const textUpdate = createTextChunkArtifactUpdate({
|
|
1315
|
+
taskId: currentData.id,
|
|
1316
|
+
contextId: currentData.contextId,
|
|
1317
|
+
text: pendingTextChunk,
|
|
1318
|
+
append: sawTextArtifact,
|
|
1319
|
+
lastChunk: true
|
|
1320
|
+
});
|
|
1321
|
+
currentData = applyUpdateToTask(currentData, textUpdate);
|
|
1322
|
+
currentData = await saveTaskAndMaybeSendPushNotification({
|
|
1323
|
+
taskStore,
|
|
1324
|
+
pushNotificationSender: resolvedPushNotificationSender,
|
|
1325
|
+
nextTask: currentData,
|
|
1326
|
+
agentId,
|
|
1327
|
+
logger
|
|
1328
|
+
});
|
|
1329
|
+
if (currentData.status.state === "canceled") streamCanceled = true;
|
|
1330
|
+
else yield createSuccessResponse(requestId, textUpdate);
|
|
1331
|
+
}
|
|
1332
|
+
const suspensionTask = currentData;
|
|
1333
|
+
currentData = applySuspensionToTask({
|
|
1334
|
+
task: currentData,
|
|
1335
|
+
suspendPayload: await result.suspendPayload,
|
|
1336
|
+
resumeSchema: await result.resumeSchema,
|
|
1337
|
+
runId: result.runId ?? currentData.id,
|
|
1338
|
+
logger
|
|
1023
1339
|
});
|
|
1024
|
-
currentData =
|
|
1025
|
-
await saveTaskAndMaybeSendPushNotification({
|
|
1340
|
+
currentData = await saveTaskAndMaybeSendPushNotification({
|
|
1026
1341
|
taskStore,
|
|
1027
1342
|
pushNotificationSender: resolvedPushNotificationSender,
|
|
1343
|
+
previousTask: suspensionTask,
|
|
1028
1344
|
nextTask: currentData,
|
|
1029
1345
|
agentId,
|
|
1030
1346
|
logger
|
|
1031
1347
|
});
|
|
1032
|
-
|
|
1348
|
+
if (currentData.status.state === "canceled") streamCanceled = true;
|
|
1033
1349
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
completedTask.metadata = {
|
|
1040
|
-
...completedTask.metadata,
|
|
1041
|
-
execution: {
|
|
1042
|
-
toolCalls: await result.toolCalls,
|
|
1043
|
-
toolResults: await result.toolResults,
|
|
1044
|
-
usage: await result.usage,
|
|
1045
|
-
finishReason: await result.finishReason
|
|
1350
|
+
if (!streamCanceled && !suspended) {
|
|
1351
|
+
structuredData ??= await result.object;
|
|
1352
|
+
if (!pendingTextChunk && !sawTextArtifact) {
|
|
1353
|
+
const finalText = await result.text;
|
|
1354
|
+
if (finalText) pendingTextChunk = finalText;
|
|
1046
1355
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1356
|
+
if (pendingTextChunk) {
|
|
1357
|
+
const textUpdate = createTextChunkArtifactUpdate({
|
|
1358
|
+
taskId: currentData.id,
|
|
1359
|
+
contextId: currentData.contextId,
|
|
1360
|
+
text: pendingTextChunk,
|
|
1361
|
+
append: sawTextArtifact,
|
|
1362
|
+
lastChunk: !structuredData
|
|
1363
|
+
});
|
|
1364
|
+
currentData = applyUpdateToTask(currentData, textUpdate);
|
|
1365
|
+
currentData = await saveTaskAndMaybeSendPushNotification({
|
|
1366
|
+
taskStore,
|
|
1367
|
+
pushNotificationSender: resolvedPushNotificationSender,
|
|
1368
|
+
nextTask: currentData,
|
|
1369
|
+
agentId,
|
|
1370
|
+
logger
|
|
1371
|
+
});
|
|
1372
|
+
if (currentData.status.state === "canceled") streamCanceled = true;
|
|
1373
|
+
else yield createSuccessResponse(requestId, textUpdate);
|
|
1374
|
+
sawTextArtifact = true;
|
|
1375
|
+
pendingTextChunk = void 0;
|
|
1376
|
+
}
|
|
1377
|
+
if (!streamCanceled && structuredData) {
|
|
1378
|
+
const dataUpdate = createDataArtifactUpdate({
|
|
1379
|
+
taskId: currentData.id,
|
|
1380
|
+
contextId: currentData.contextId,
|
|
1381
|
+
data: structuredData,
|
|
1382
|
+
lastChunk: true
|
|
1383
|
+
});
|
|
1384
|
+
currentData = applyUpdateToTask(currentData, dataUpdate);
|
|
1385
|
+
currentData = await saveTaskAndMaybeSendPushNotification({
|
|
1386
|
+
taskStore,
|
|
1387
|
+
pushNotificationSender: resolvedPushNotificationSender,
|
|
1388
|
+
nextTask: currentData,
|
|
1389
|
+
agentId,
|
|
1390
|
+
logger
|
|
1391
|
+
});
|
|
1392
|
+
if (currentData.status.state === "canceled") streamCanceled = true;
|
|
1393
|
+
else yield createSuccessResponse(requestId, dataUpdate);
|
|
1394
|
+
}
|
|
1395
|
+
if (!streamCanceled) {
|
|
1396
|
+
const previousTask = currentData;
|
|
1397
|
+
const completedTask = applyUpdateToTask(currentData, {
|
|
1398
|
+
state: "completed",
|
|
1399
|
+
message: void 0
|
|
1400
|
+
});
|
|
1401
|
+
completedTask.metadata = {
|
|
1402
|
+
...clearSuspensionMetadata(completedTask.metadata),
|
|
1403
|
+
execution: {
|
|
1404
|
+
toolCalls: await result.toolCalls,
|
|
1405
|
+
toolResults: await result.toolResults,
|
|
1406
|
+
usage: await result.usage,
|
|
1407
|
+
finishReason: await result.finishReason
|
|
1408
|
+
}
|
|
1409
|
+
};
|
|
1410
|
+
currentData = await saveTaskAndMaybeSendPushNotification({
|
|
1411
|
+
taskStore,
|
|
1412
|
+
pushNotificationSender: resolvedPushNotificationSender,
|
|
1413
|
+
previousTask,
|
|
1414
|
+
nextTask: completedTask,
|
|
1415
|
+
agentId,
|
|
1416
|
+
logger
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
} catch (handlerError) {
|
|
1421
|
+
const latestTask = await taskStore.load({
|
|
1054
1422
|
agentId,
|
|
1055
|
-
|
|
1423
|
+
taskId: currentData.id
|
|
1056
1424
|
});
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1425
|
+
if (latestTask?.status.state === "canceled") currentData = latestTask;
|
|
1426
|
+
else if (taskAbortController.signal.aborted) {
|
|
1427
|
+
currentData = latestTask ?? currentData;
|
|
1428
|
+
if (abortSignal?.aborted) {
|
|
1429
|
+
const previousTask = currentData;
|
|
1430
|
+
currentData = applyUpdateToTask(currentData, {
|
|
1431
|
+
state: "canceled",
|
|
1432
|
+
message: {
|
|
1433
|
+
messageId: crypto.randomUUID(),
|
|
1434
|
+
role: "agent",
|
|
1435
|
+
parts: [{
|
|
1436
|
+
kind: "text",
|
|
1437
|
+
text: "Task canceled because the request was aborted."
|
|
1438
|
+
}],
|
|
1439
|
+
kind: "message"
|
|
1440
|
+
}
|
|
1441
|
+
});
|
|
1442
|
+
try {
|
|
1443
|
+
currentData = await saveTaskAndMaybeSendPushNotification({
|
|
1444
|
+
taskStore,
|
|
1445
|
+
pushNotificationSender: resolvedPushNotificationSender,
|
|
1446
|
+
previousTask,
|
|
1447
|
+
nextTask: currentData,
|
|
1448
|
+
agentId,
|
|
1449
|
+
logger
|
|
1450
|
+
});
|
|
1451
|
+
} catch (saveError) {
|
|
1452
|
+
logger?.error(`Failed to save task ${currentData.id} after request abort:`, saveError?.message);
|
|
1453
|
+
}
|
|
1069
1454
|
}
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1455
|
+
} else {
|
|
1456
|
+
currentData = latestTask ?? currentData;
|
|
1457
|
+
const previousTask = currentData;
|
|
1458
|
+
currentData = applyUpdateToTask(currentData, {
|
|
1459
|
+
state: "failed",
|
|
1460
|
+
message: {
|
|
1461
|
+
messageId: crypto.randomUUID(),
|
|
1462
|
+
role: "agent",
|
|
1463
|
+
parts: [{
|
|
1464
|
+
kind: "text",
|
|
1465
|
+
text: `Handler failed: ${handlerError instanceof Error ? handlerError.message : String(handlerError)}`
|
|
1466
|
+
}],
|
|
1467
|
+
kind: "message"
|
|
1468
|
+
}
|
|
1079
1469
|
});
|
|
1080
|
-
|
|
1081
|
-
|
|
1470
|
+
try {
|
|
1471
|
+
currentData = await saveTaskAndMaybeSendPushNotification({
|
|
1472
|
+
taskStore,
|
|
1473
|
+
pushNotificationSender: resolvedPushNotificationSender,
|
|
1474
|
+
previousTask,
|
|
1475
|
+
nextTask: currentData,
|
|
1476
|
+
agentId,
|
|
1477
|
+
logger
|
|
1478
|
+
});
|
|
1479
|
+
} catch (saveError) {
|
|
1480
|
+
logger?.error(`Failed to save task ${currentData.id} after handler error:`, saveError?.message);
|
|
1481
|
+
}
|
|
1082
1482
|
}
|
|
1483
|
+
} finally {
|
|
1484
|
+
unregisterTaskAbortController();
|
|
1485
|
+
cleanupLinkedAbortController();
|
|
1083
1486
|
}
|
|
1487
|
+
const latestTask = await taskStore.load({
|
|
1488
|
+
agentId,
|
|
1489
|
+
taskId: currentData.id
|
|
1490
|
+
});
|
|
1491
|
+
if (latestTask?.status.state === "canceled") currentData = latestTask;
|
|
1084
1492
|
yield createSuccessResponse(requestId, {
|
|
1085
1493
|
kind: "status-update",
|
|
1086
1494
|
taskId: currentData.id,
|
|
@@ -1096,7 +1504,7 @@ async function* handleTaskResubscribe({ requestId, taskStore, agentId, taskId, a
|
|
|
1096
1504
|
});
|
|
1097
1505
|
if (!snapshot) throw MastraA2AError.taskNotFound(taskId);
|
|
1098
1506
|
yield createSuccessResponse(requestId, snapshot.task);
|
|
1099
|
-
if (isTerminalTaskState(snapshot.task.status.state)) return;
|
|
1507
|
+
if (isTerminalTaskState(snapshot.task.status.state) || isInterruptedTaskState(snapshot.task.status.state)) return;
|
|
1100
1508
|
while (true) {
|
|
1101
1509
|
const { task, version } = snapshot;
|
|
1102
1510
|
const nextUpdate = await taskStore.waitForNextUpdate({
|
|
@@ -1109,14 +1517,16 @@ async function* handleTaskResubscribe({ requestId, taskStore, agentId, taskId, a
|
|
|
1109
1517
|
previous: task,
|
|
1110
1518
|
next: nextUpdate.task
|
|
1111
1519
|
})) yield createSuccessResponse(requestId, artifactUpdate);
|
|
1520
|
+
const nextState = nextUpdate.task.status.state;
|
|
1521
|
+
const streamEnded = isTerminalTaskState(nextState) || isInterruptedTaskState(nextState);
|
|
1112
1522
|
if (didTaskStatusChange(task, nextUpdate.task)) yield createSuccessResponse(requestId, {
|
|
1113
1523
|
kind: "status-update",
|
|
1114
1524
|
taskId: nextUpdate.task.id,
|
|
1115
1525
|
contextId: nextUpdate.task.contextId,
|
|
1116
1526
|
status: nextUpdate.task.status,
|
|
1117
|
-
final:
|
|
1527
|
+
final: streamEnded
|
|
1118
1528
|
});
|
|
1119
|
-
if (
|
|
1529
|
+
if (streamEnded) return;
|
|
1120
1530
|
snapshot = nextUpdate;
|
|
1121
1531
|
}
|
|
1122
1532
|
}
|
|
@@ -1154,44 +1564,53 @@ function createA2ASSEResponse(payload) {
|
|
|
1154
1564
|
} });
|
|
1155
1565
|
}
|
|
1156
1566
|
async function handleTaskCancel({ requestId, taskStore, pushNotificationSender, agentId, taskId, logger }) {
|
|
1157
|
-
let
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
logger?.info(`Task ${taskId} already in final state ${data.status.state}, cannot cancel.`);
|
|
1168
|
-
return createSuccessResponse(requestId, data);
|
|
1169
|
-
}
|
|
1170
|
-
taskStore.activeCancellations.add(taskId);
|
|
1171
|
-
const cancelUpdate = {
|
|
1172
|
-
state: "canceled",
|
|
1173
|
-
message: {
|
|
1174
|
-
role: "agent",
|
|
1175
|
-
parts: [{
|
|
1176
|
-
kind: "text",
|
|
1177
|
-
text: "Task cancelled by request."
|
|
1178
|
-
}],
|
|
1179
|
-
kind: "message",
|
|
1180
|
-
messageId: crypto.randomUUID()
|
|
1567
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
1568
|
+
const snapshot = taskStore.loadWithVersion({
|
|
1569
|
+
agentId,
|
|
1570
|
+
taskId
|
|
1571
|
+
});
|
|
1572
|
+
const data = snapshot?.task;
|
|
1573
|
+
if (!data) throw MastraA2AError.taskNotFound(taskId);
|
|
1574
|
+
if (isTerminalTaskState(data.status.state)) {
|
|
1575
|
+
logger?.info(`Task ${taskId} already in final state ${data.status.state}, cannot cancel.`);
|
|
1576
|
+
throw MastraA2AError.taskNotCancelable(taskId);
|
|
1181
1577
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1578
|
+
taskStore.activeCancellations.add(taskId);
|
|
1579
|
+
taskStore.abortTask({
|
|
1580
|
+
agentId,
|
|
1581
|
+
taskId,
|
|
1582
|
+
reason: new DOMException("Task cancelled by request.", "AbortError")
|
|
1583
|
+
});
|
|
1584
|
+
const canceledTask = applyUpdateToTask(data, {
|
|
1585
|
+
state: "canceled",
|
|
1586
|
+
message: {
|
|
1587
|
+
role: "agent",
|
|
1588
|
+
parts: [{
|
|
1589
|
+
kind: "text",
|
|
1590
|
+
text: "Task cancelled by request."
|
|
1591
|
+
}],
|
|
1592
|
+
kind: "message",
|
|
1593
|
+
messageId: crypto.randomUUID()
|
|
1594
|
+
}
|
|
1595
|
+
});
|
|
1596
|
+
try {
|
|
1597
|
+
await saveTaskAndMaybeSendPushNotification({
|
|
1598
|
+
taskStore,
|
|
1599
|
+
pushNotificationSender: resolvePushNotificationPair({ pushNotificationSender }).pushNotificationSender,
|
|
1600
|
+
previousTask: data,
|
|
1601
|
+
nextTask: canceledTask,
|
|
1602
|
+
agentId,
|
|
1603
|
+
expectedVersion: snapshot.version,
|
|
1604
|
+
logger
|
|
1605
|
+
});
|
|
1606
|
+
return createSuccessResponse(requestId, canceledTask);
|
|
1607
|
+
} catch (error) {
|
|
1608
|
+
if (!(error instanceof TaskStoreVersionConflictError)) throw error;
|
|
1609
|
+
} finally {
|
|
1610
|
+
taskStore.activeCancellations.delete(taskId);
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
throw MastraA2AError.invalidRequest(`Task ${taskId} was updated concurrently. Retry the request.`);
|
|
1195
1614
|
}
|
|
1196
1615
|
async function getAgentExecutionHandler({ requestId, mastra, agentId, requestContext, method, params, taskStore, pushNotificationStore, pushNotificationSender, logger, abortSignal }) {
|
|
1197
1616
|
const agent = await getAgentFromSystem({
|
|
@@ -1226,7 +1645,8 @@ async function getAgentExecutionHandler({ requestId, mastra, agentId, requestCon
|
|
|
1226
1645
|
agent,
|
|
1227
1646
|
agentId,
|
|
1228
1647
|
logger,
|
|
1229
|
-
requestContext
|
|
1648
|
+
requestContext,
|
|
1649
|
+
abortSignal
|
|
1230
1650
|
});
|
|
1231
1651
|
case "tasks/get": return await handleTaskGet({
|
|
1232
1652
|
requestId,
|
|
@@ -1340,4 +1760,4 @@ const AGENT_EXECUTION_ROUTE = createRoute({
|
|
|
1340
1760
|
//#endregion
|
|
1341
1761
|
export { getAgentExecutionHandler as a, handleListTaskPushNotificationConfig as c, handleSetTaskPushNotificationConfig as d, handleTaskCancel as f, getAgentCardByIdHandler as i, handleMessageSend as l, handleTaskResubscribe as m, GET_AGENT_CARD_ROUTE as n, handleDeleteTaskPushNotificationConfig as o, handleTaskGet as p, a2a_exports as r, handleGetTaskPushNotificationConfig as s, AGENT_EXECUTION_ROUTE as t, handleMessageStream as u };
|
|
1342
1762
|
|
|
1343
|
-
//# sourceMappingURL=a2a-
|
|
1763
|
+
//# sourceMappingURL=a2a-CHi9AJ0n.js.map
|