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