fix: align task response types with MCP spec (#658)

This commit is contained in:
Dale Seo 2026-02-13 17:56:44 -05:00 committed by GitHub
parent 453032faed
commit 08a5b0551b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 349 additions and 134 deletions

View file

@ -47,7 +47,7 @@ pub fn task_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
status: rmcp::model::TaskStatus::Working,
status_message: None,
created_at: timestamp.clone(),
last_updated_at: Some(timestamp),
last_updated_at: timestamp,
ttl: None,
poll_interval: None,
}
@ -111,7 +111,7 @@ pub fn task_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
status: rmcp::model::TaskStatus::Working,
status_message: Some("Task accepted".to_string()),
created_at: timestamp.clone(),
last_updated_at: Some(timestamp),
last_updated_at: timestamp,
ttl: None,
poll_interval: None,
};
@ -128,7 +128,7 @@ pub fn task_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
&self,
request: rmcp::model::GetTaskInfoParam,
_context: rmcp::service::RequestContext<rmcp::RoleServer>,
) -> Result<rmcp::model::GetTaskInfoResult, McpError> {
) -> Result<rmcp::model::GetTaskResult, McpError> {
use rmcp::task_manager::current_timestamp;
let task_id = request.task_id.clone();
let mut processor = (#processor).lock().await;
@ -156,11 +156,11 @@ pub fn task_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
status,
status_message: None,
created_at: timestamp.clone(),
last_updated_at: Some(timestamp),
last_updated_at: timestamp,
ttl: completed_result.descriptor.ttl,
poll_interval: None,
};
return Ok(rmcp::model::GetTaskInfoResult { task: Some(task) });
return Ok(rmcp::model::GetTaskResult { meta: None, task });
}
// If not completed, check running
@ -172,14 +172,14 @@ pub fn task_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
status: rmcp::model::TaskStatus::Working,
status_message: None,
created_at: timestamp.clone(),
last_updated_at: Some(timestamp),
last_updated_at: timestamp,
ttl: None,
poll_interval: None,
};
return Ok(rmcp::model::GetTaskInfoResult { task: Some(task) });
return Ok(rmcp::model::GetTaskResult { meta: None, task });
}
Ok(rmcp::model::GetTaskInfoResult { task: None })
Err(McpError::resource_not_found(format!("task not found: {}", task_id), None))
}
};
item_impl.items.push(syn::parse2::<ImplItem>(get_info_fn)?);
@ -191,7 +191,7 @@ pub fn task_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
&self,
request: rmcp::model::GetTaskResultParam,
_context: rmcp::service::RequestContext<rmcp::RoleServer>,
) -> Result<rmcp::model::TaskResult, McpError> {
) -> Result<rmcp::model::GetTaskPayloadResult, McpError> {
use std::time::Duration;
let task_id = request.task_id.clone();
@ -207,11 +207,7 @@ pub fn task_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
match &tool.result {
Ok(call_tool) => {
let value = ::serde_json::to_value(call_tool).unwrap_or(::serde_json::Value::Null);
return Ok(rmcp::model::TaskResult {
content_type: "application/json".to_string(),
value,
summary: None,
});
return Ok(rmcp::model::GetTaskPayloadResult(value));
}
Err(err) => return Err(McpError::internal_error(
format!("task failed: {}", err),
@ -251,12 +247,23 @@ pub fn task_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
&self,
request: rmcp::model::CancelTaskParam,
_context: rmcp::service::RequestContext<rmcp::RoleServer>,
) -> Result<(), McpError> {
) -> Result<rmcp::model::CancelTaskResult, McpError> {
use rmcp::task_manager::current_timestamp;
let task_id = request.task_id;
let mut processor = (#processor).lock().await;
if processor.cancel_task(&task_id) {
return Ok(());
let timestamp = current_timestamp();
let task = rmcp::model::Task {
task_id,
status: rmcp::model::TaskStatus::Cancelled,
status_message: None,
created_at: timestamp.clone(),
last_updated_at: timestamp,
ttl: None,
poll_interval: None,
};
return Ok(rmcp::model::CancelTaskResult { meta: None, task });
}
// If already completed, signal it's not cancellable

View file

@ -116,15 +116,15 @@ impl<H: ServerHandler> Service<RoleServer> for H {
ClientRequest::GetTaskInfoRequest(request) => self
.get_task_info(request.params, context)
.await
.map(ServerResult::GetTaskInfoResult),
.map(ServerResult::GetTaskResult),
ClientRequest::GetTaskResultRequest(request) => self
.get_task_result(request.params, context)
.await
.map(ServerResult::TaskResult),
.map(ServerResult::GetTaskPayloadResult),
ClientRequest::CancelTaskRequest(request) => self
.cancel_task(request.params, context)
.await
.map(ServerResult::empty),
.map(ServerResult::CancelTaskResult),
}
}
@ -339,7 +339,8 @@ pub trait ServerHandler: Sized + Send + Sync + 'static {
&self,
request: GetTaskInfoParams,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<GetTaskInfoResult, McpError>> + Send + '_ {
) -> impl Future<Output = Result<GetTaskResult, McpError>> + Send + '_ {
let _ = (request, context);
std::future::ready(Err(McpError::method_not_found::<GetTaskInfoMethod>()))
}
@ -347,7 +348,7 @@ pub trait ServerHandler: Sized + Send + Sync + 'static {
&self,
request: GetTaskResultParams,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<TaskResult, McpError>> + Send + '_ {
) -> impl Future<Output = Result<GetTaskPayloadResult, McpError>> + Send + '_ {
let _ = (request, context);
std::future::ready(Err(McpError::method_not_found::<GetTaskResultMethod>()))
}
@ -356,7 +357,7 @@ pub trait ServerHandler: Sized + Send + Sync + 'static {
&self,
request: CancelTaskParams,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<(), McpError>> + Send + '_ {
) -> impl Future<Output = Result<CancelTaskResult, McpError>> + Send + '_ {
let _ = (request, context);
std::future::ready(Err(McpError::method_not_found::<CancelTaskMethod>()))
}
@ -543,7 +544,7 @@ macro_rules! impl_server_handler_for_wrapper {
&self,
request: GetTaskInfoParams,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<GetTaskInfoResult, McpError>> + Send + '_ {
) -> impl Future<Output = Result<GetTaskResult, McpError>> + Send + '_ {
(**self).get_task_info(request, context)
}
@ -551,7 +552,7 @@ macro_rules! impl_server_handler_for_wrapper {
&self,
request: GetTaskResultParams,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<TaskResult, McpError>> + Send + '_ {
) -> impl Future<Output = Result<GetTaskPayloadResult, McpError>> + Send + '_ {
(**self).get_task_result(request, context)
}
@ -559,7 +560,7 @@ macro_rules! impl_server_handler_for_wrapper {
&self,
request: CancelTaskParams,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<(), McpError>> + Send + '_ {
) -> impl Future<Output = Result<CancelTaskResult, McpError>> + Send + '_ {
(**self).cancel_task(request, context)
}
}

View file

@ -2537,14 +2537,9 @@ impl RequestParamsMeta for CancelTaskParams {
/// Deprecated: Use [`CancelTaskParams`] instead (SEP-1319 compliance).
#[deprecated(since = "0.13.0", note = "Use CancelTaskParams instead")]
pub type CancelTaskParam = CancelTaskParams;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct GetTaskInfoResult {
#[serde(skip_serializing_if = "Option::is_none")]
pub task: Option<crate::model::Task>,
}
/// Deprecated: Use [`GetTaskResult`] instead (spec alignment).
#[deprecated(since = "0.15.0", note = "Use GetTaskResult instead")]
pub type GetTaskInfoResult = GetTaskResult;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
@ -2720,9 +2715,10 @@ ts_union!(
| EmptyResult
| CreateTaskResult
| ListTasksResult
| GetTaskInfoResult
| TaskResult
| GetTaskResult
| CancelTaskResult
| CustomResult
| GetTaskPayloadResult
;
);

View file

@ -1,6 +1,8 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use super::Meta;
/// Canonical task lifecycle status as defined by SEP-1686.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
@ -19,21 +21,10 @@ pub enum TaskStatus {
Cancelled,
}
/// Final result for a succeeded task (returned from `tasks/result`).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct TaskResult {
/// MIME type or custom content-type identifier.
pub content_type: String,
/// The actual result payload, matching the underlying request's schema.
pub value: Value,
/// Optional short summary for UI surfaces.
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
}
/// Primary Task object that surfaces metadata during the task lifecycle.
///
/// Per spec, `lastUpdatedAt` and `ttl` are required fields.
/// `ttl` is nullable (`null` means unlimited retention).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
@ -48,10 +39,9 @@ pub struct Task {
/// ISO-8601 creation timestamp.
pub created_at: String,
/// ISO-8601 timestamp for the most recent status change.
#[serde(skip_serializing_if = "Option::is_none")]
pub last_updated_at: Option<String>,
pub last_updated_at: String,
/// Retention window in milliseconds that the receiver agreed to honor.
#[serde(skip_serializing_if = "Option::is_none")]
/// `None` (serialized as `null`) means unlimited retention.
pub ttl: Option<u64>,
/// Suggested polling interval (milliseconds).
#[serde(skip_serializing_if = "Option::is_none")]
@ -66,6 +56,43 @@ pub struct CreateTaskResult {
pub task: Task,
}
/// Response to a `tasks/get` request.
///
/// Per spec, `GetTaskResult = allOf[Result, Task]` — the Task fields are
/// flattened at the top level, not nested under a `task` key.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct GetTaskResult {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
#[serde(flatten)]
pub task: Task,
}
/// Response to a `tasks/result` request.
///
/// Per spec, the result structure matches the original request type
/// (e.g., `CallToolResult` for `tools/call`). This is represented as
/// an open object. The payload is the original request's result
/// serialized as a JSON value.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct GetTaskPayloadResult(pub Value);
/// Response to a `tasks/cancel` request.
///
/// Per spec, `CancelTaskResult = allOf[Result, Task]` — same shape as `GetTaskResult`.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct CancelTaskResult {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
#[serde(flatten)]
pub task: Task,
}
/// Paginated list of tasks
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]

View file

@ -407,6 +407,70 @@
"content"
]
},
"CancelTaskResult": {
"description": "Response to a `tasks/cancel` request.\n\nPer spec, `CancelTaskResult = allOf[Result, Task]` — same shape as `GetTaskResult`.",
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
},
"createdAt": {
"description": "ISO-8601 creation timestamp.",
"type": "string"
},
"lastUpdatedAt": {
"description": "ISO-8601 timestamp for the most recent status change.",
"type": "string"
},
"pollInterval": {
"description": "Suggested polling interval (milliseconds).",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"status": {
"description": "Current lifecycle status (see [`TaskStatus`]).",
"allOf": [
{
"$ref": "#/definitions/TaskStatus"
}
]
},
"statusMessage": {
"description": "Optional human-readable status message for UI surfaces.",
"type": [
"string",
"null"
]
},
"taskId": {
"description": "Unique task identifier generated by the receiver.",
"type": "string"
},
"ttl": {
"description": "Retention window in milliseconds that the receiver agreed to honor.\n`None` (serialized as `null`) means unlimited retention.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
}
},
"required": [
"taskId",
"status",
"createdAt",
"lastUpdatedAt"
]
},
"CancelledNotificationMethod": {
"type": "string",
"format": "const",
@ -937,21 +1001,72 @@
"messages"
]
},
"GetTaskInfoResult": {
"GetTaskPayloadResult": {
"description": "Response to a `tasks/result` request.\n\nPer spec, the result structure matches the original request type\n(e.g., `CallToolResult` for `tools/call`). This is represented as\nan open object. The payload is the original request's result\nserialized as a JSON value."
},
"GetTaskResult": {
"description": "Response to a `tasks/get` request.\n\nPer spec, `GetTaskResult = allOf[Result, Task]` — the Task fields are\nflattened at the top level, not nested under a `task` key.",
"type": "object",
"properties": {
"task": {
"anyOf": [
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
},
"createdAt": {
"description": "ISO-8601 creation timestamp.",
"type": "string"
},
"lastUpdatedAt": {
"description": "ISO-8601 timestamp for the most recent status change.",
"type": "string"
},
"pollInterval": {
"description": "Suggested polling interval (milliseconds).",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"status": {
"description": "Current lifecycle status (see [`TaskStatus`]).",
"allOf": [
{
"$ref": "#/definitions/Task"
},
{
"type": "null"
"$ref": "#/definitions/TaskStatus"
}
]
},
"statusMessage": {
"description": "Optional human-readable status message for UI surfaces.",
"type": [
"string",
"null"
]
},
"taskId": {
"description": "Unique task identifier generated by the receiver.",
"type": "string"
},
"ttl": {
"description": "Retention window in milliseconds that the receiver agreed to honor.\n`None` (serialized as `null`) means unlimited retention.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
}
},
"additionalProperties": false
"required": [
"taskId",
"status",
"createdAt",
"lastUpdatedAt"
]
},
"Icon": {
"description": "A URL pointing to an icon resource or a base64-encoded data URI.\n\nClients that support rendering icons MUST support at least the following MIME types:\n- image/png - PNG images (safe, universal compatibility)\n- image/jpeg (and image/jpg) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- image/svg+xml - SVG images (scalable but requires security precautions)\n- image/webp - WebP images (modern, efficient format)",
@ -2701,13 +2816,16 @@
"$ref": "#/definitions/ListTasksResult"
},
{
"$ref": "#/definitions/GetTaskInfoResult"
"$ref": "#/definitions/GetTaskResult"
},
{
"$ref": "#/definitions/TaskResult"
"$ref": "#/definitions/CancelTaskResult"
},
{
"$ref": "#/definitions/CustomResult"
},
{
"$ref": "#/definitions/GetTaskPayloadResult"
}
]
},
@ -2813,7 +2931,7 @@
"const": "string"
},
"Task": {
"description": "Primary Task object that surfaces metadata during the task lifecycle.",
"description": "Primary Task object that surfaces metadata during the task lifecycle.\n\nPer spec, `lastUpdatedAt` and `ttl` are required fields.\n`ttl` is nullable (`null` means unlimited retention).",
"type": "object",
"properties": {
"createdAt": {
@ -2822,10 +2940,7 @@
},
"lastUpdatedAt": {
"description": "ISO-8601 timestamp for the most recent status change.",
"type": [
"string",
"null"
]
"type": "string"
},
"pollInterval": {
"description": "Suggested polling interval (milliseconds).",
@ -2856,7 +2971,7 @@
"type": "string"
},
"ttl": {
"description": "Retention window in milliseconds that the receiver agreed to honor.",
"description": "Retention window in milliseconds that the receiver agreed to honor.\n`None` (serialized as `null`) means unlimited retention.",
"type": [
"integer",
"null"
@ -2868,7 +2983,8 @@
"required": [
"taskId",
"status",
"createdAt"
"createdAt",
"lastUpdatedAt"
]
},
"TaskRequestsCapability": {
@ -2907,30 +3023,6 @@
}
}
},
"TaskResult": {
"description": "Final result for a succeeded task (returned from `tasks/result`).",
"type": "object",
"properties": {
"contentType": {
"description": "MIME type or custom content-type identifier.",
"type": "string"
},
"summary": {
"description": "Optional short summary for UI surfaces.",
"type": [
"string",
"null"
]
},
"value": {
"description": "The actual result payload, matching the underlying request's schema."
}
},
"required": [
"contentType",
"value"
]
},
"TaskStatus": {
"description": "Canonical task lifecycle status as defined by SEP-1686.",
"oneOf": [

View file

@ -407,6 +407,70 @@
"content"
]
},
"CancelTaskResult": {
"description": "Response to a `tasks/cancel` request.\n\nPer spec, `CancelTaskResult = allOf[Result, Task]` — same shape as `GetTaskResult`.",
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
},
"createdAt": {
"description": "ISO-8601 creation timestamp.",
"type": "string"
},
"lastUpdatedAt": {
"description": "ISO-8601 timestamp for the most recent status change.",
"type": "string"
},
"pollInterval": {
"description": "Suggested polling interval (milliseconds).",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"status": {
"description": "Current lifecycle status (see [`TaskStatus`]).",
"allOf": [
{
"$ref": "#/definitions/TaskStatus"
}
]
},
"statusMessage": {
"description": "Optional human-readable status message for UI surfaces.",
"type": [
"string",
"null"
]
},
"taskId": {
"description": "Unique task identifier generated by the receiver.",
"type": "string"
},
"ttl": {
"description": "Retention window in milliseconds that the receiver agreed to honor.\n`None` (serialized as `null`) means unlimited retention.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
}
},
"required": [
"taskId",
"status",
"createdAt",
"lastUpdatedAt"
]
},
"CancelledNotificationMethod": {
"type": "string",
"format": "const",
@ -937,21 +1001,72 @@
"messages"
]
},
"GetTaskInfoResult": {
"GetTaskPayloadResult": {
"description": "Response to a `tasks/result` request.\n\nPer spec, the result structure matches the original request type\n(e.g., `CallToolResult` for `tools/call`). This is represented as\nan open object. The payload is the original request's result\nserialized as a JSON value."
},
"GetTaskResult": {
"description": "Response to a `tasks/get` request.\n\nPer spec, `GetTaskResult = allOf[Result, Task]` — the Task fields are\nflattened at the top level, not nested under a `task` key.",
"type": "object",
"properties": {
"task": {
"anyOf": [
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
},
"createdAt": {
"description": "ISO-8601 creation timestamp.",
"type": "string"
},
"lastUpdatedAt": {
"description": "ISO-8601 timestamp for the most recent status change.",
"type": "string"
},
"pollInterval": {
"description": "Suggested polling interval (milliseconds).",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"status": {
"description": "Current lifecycle status (see [`TaskStatus`]).",
"allOf": [
{
"$ref": "#/definitions/Task"
},
{
"type": "null"
"$ref": "#/definitions/TaskStatus"
}
]
},
"statusMessage": {
"description": "Optional human-readable status message for UI surfaces.",
"type": [
"string",
"null"
]
},
"taskId": {
"description": "Unique task identifier generated by the receiver.",
"type": "string"
},
"ttl": {
"description": "Retention window in milliseconds that the receiver agreed to honor.\n`None` (serialized as `null`) means unlimited retention.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
}
},
"additionalProperties": false
"required": [
"taskId",
"status",
"createdAt",
"lastUpdatedAt"
]
},
"Icon": {
"description": "A URL pointing to an icon resource or a base64-encoded data URI.\n\nClients that support rendering icons MUST support at least the following MIME types:\n- image/png - PNG images (safe, universal compatibility)\n- image/jpeg (and image/jpg) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- image/svg+xml - SVG images (scalable but requires security precautions)\n- image/webp - WebP images (modern, efficient format)",
@ -2701,13 +2816,16 @@
"$ref": "#/definitions/ListTasksResult"
},
{
"$ref": "#/definitions/GetTaskInfoResult"
"$ref": "#/definitions/GetTaskResult"
},
{
"$ref": "#/definitions/TaskResult"
"$ref": "#/definitions/CancelTaskResult"
},
{
"$ref": "#/definitions/CustomResult"
},
{
"$ref": "#/definitions/GetTaskPayloadResult"
}
]
},
@ -2813,7 +2931,7 @@
"const": "string"
},
"Task": {
"description": "Primary Task object that surfaces metadata during the task lifecycle.",
"description": "Primary Task object that surfaces metadata during the task lifecycle.\n\nPer spec, `lastUpdatedAt` and `ttl` are required fields.\n`ttl` is nullable (`null` means unlimited retention).",
"type": "object",
"properties": {
"createdAt": {
@ -2822,10 +2940,7 @@
},
"lastUpdatedAt": {
"description": "ISO-8601 timestamp for the most recent status change.",
"type": [
"string",
"null"
]
"type": "string"
},
"pollInterval": {
"description": "Suggested polling interval (milliseconds).",
@ -2856,7 +2971,7 @@
"type": "string"
},
"ttl": {
"description": "Retention window in milliseconds that the receiver agreed to honor.",
"description": "Retention window in milliseconds that the receiver agreed to honor.\n`None` (serialized as `null`) means unlimited retention.",
"type": [
"integer",
"null"
@ -2868,7 +2983,8 @@
"required": [
"taskId",
"status",
"createdAt"
"createdAt",
"lastUpdatedAt"
]
},
"TaskRequestsCapability": {
@ -2907,30 +3023,6 @@
}
}
},
"TaskResult": {
"description": "Final result for a succeeded task (returned from `tasks/result`).",
"type": "object",
"properties": {
"contentType": {
"description": "MIME type or custom content-type identifier.",
"type": "string"
},
"summary": {
"description": "Optional short summary for UI surfaces.",
"type": [
"string",
"null"
]
},
"value": {
"description": "The actual result payload, matching the underlying request's schema."
}
},
"required": [
"contentType",
"value"
]
},
"TaskStatus": {
"description": "Canonical task lifecycle status as defined by SEP-1686.",
"oneOf": [