From 4f319b558e288fa841d829b68ca705fd8911ef5f Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Fri, 9 Oct 2026 18:33:35 +0200 Subject: [PATCH] feat(schema): update to v1.25.0 and v2.0.0-alpha.8 --- schema/schema.json | 276 ++++++++++++++++++++++-------- schema/v2/schema.unstable.json | 295 +++++++++++++++++++++++---------- scripts/generate.js | 124 ++++++++++++-- src/acp.ts | 1 + src/schema-deserialize.ts | 23 +++ src/schema/guards.gen.ts | 165 +++++++++++++++++- src/schema/index.ts | 1 + src/schema/types.gen.ts | 145 ++++++++-------- src/schema/zod.gen.ts | 238 +++++++++++++------------- src/typedoc.json | 1 + src/typedoc.v2.json | 1 + src/v2/acp.ts | 3 +- src/v2/schema/guards.gen.ts | 290 ++++++++++++++++++++++++++------ src/v2/schema/index.ts | 2 +- src/v2/schema/types.gen.ts | 145 +++++++++------- src/v2/schema/zod.gen.ts | 181 ++++++++++---------- 16 files changed, 1325 insertions(+), 566 deletions(-) diff --git a/schema/schema.json b/schema/schema.json index d335fcec..9ba8797c 100644 --- a/schema/schema.json +++ b/schema/schema.json @@ -327,15 +327,13 @@ "description": "Line number to start reading from (1-based).", "type": ["integer", "null"], "format": "uint32", - "minimum": 0, - "x-deserialize-default-on-error": true + "minimum": 0 }, "limit": { "description": "Maximum number of lines to read.", "type": ["integer", "null"], "format": "uint32", - "minimum": 0, - "x-deserialize-default-on-error": true + "minimum": 0 }, "_meta": { "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)", @@ -1183,30 +1181,24 @@ "type": "array", "items": { "type": "string" - }, - "x-deserialize-default-on-error": true, - "x-deserialize-skip-invalid-items": true + } }, "env": { "description": "Environment variables for the command.", "type": "array", "items": { "$ref": "#/$defs/EnvVariable" - }, - "x-deserialize-default-on-error": true, - "x-deserialize-skip-invalid-items": true + } }, "cwd": { "description": "Working directory for the command. Must be an absolute path.", - "type": ["string", "null"], - "x-deserialize-default-on-error": true + "type": ["string", "null"] }, "outputByteLimit": { "description": "Maximum number of output bytes to retain.\n\nWhen the limit is exceeded, the Client truncates from the beginning of the output\nto stay within the limit.\n\nThe Client MUST ensure truncation happens at a character boundary to maintain valid\nstring output, even if this means the retained output is slightly less than the\nspecified limit.", "type": ["integer", "null"], "format": "uint64", - "minimum": 0, - "x-deserialize-default-on-error": true + "minimum": 0 }, "_meta": { "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)", @@ -3372,14 +3364,11 @@ "type": "array", "items": { "type": "string" - }, - "x-deserialize-default-on-error": true, - "x-deserialize-skip-invalid-items": true + } }, "env": { "description": "Additional environment variables to set on the configured agent invocation for terminal auth.\nThese values override same-named variables in the base launch configuration.", "type": "object", - "x-deserialize-default-on-error": true, "additionalProperties": { "type": "string" } @@ -5107,7 +5096,7 @@ ] }, { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nAdvisory information for the user that is not part of session history.\n\nAgents MUST only send this update when the Client advertised\n[`ClientSessionCapabilities::notices`].", + "description": "Information for the user that is not part of session history.\n\nAgents MUST only send this update when the Client advertised\n[`ClientSessionCapabilities::notices`].", "type": "object", "properties": { "sessionUpdate": { @@ -5123,7 +5112,7 @@ ] }, { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA context compaction has been created or updated.\n\nAgents MUST only send this update when the Client advertised\n[`ClientSessionCapabilities::compaction`].", + "description": "A context compaction has been created or updated.\n\nAgents MUST only send this update when the Client advertised\n[`ClientSessionCapabilities::compaction`].", "type": "object", "properties": { "sessionUpdate": { @@ -5139,7 +5128,7 @@ ] }, { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA content block appended to a context compaction's retained summary.\n\nAgents MUST only send this update when the Client advertised\n[`ClientSessionCapabilities::compaction`].", + "description": "A content block appended to a context compaction's retained summary.\n\nAgents MUST only send this update when the Client advertised\n[`ClientSessionCapabilities::compaction`].", "type": "object", "properties": { "sessionUpdate": { @@ -5806,7 +5795,7 @@ "required": ["used", "size"] }, "NoticeSeverity": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nSeverity hint for a session notice.", + "description": "Severity hint for a session notice.", "anyOf": [ { "description": "Informational notice.", @@ -5831,7 +5820,7 @@ ] }, "Notice": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nFire-and-forget advisory information for the user.\n\nNotices are live events rather than session history. Agents must not rely on\na notice being received, displayed, or seen by the user.\nAgents MUST only send notices when the Client advertised\n[`ClientSessionCapabilities::notices`]. Otherwise, Agents may use an agent\nmessage when the information should still be surfaced to the user.\n\nSee RFD: [Session Notices](https://agentclientprotocol.com/rfds/session-notices)", + "description": "Fire-and-forget information for the user.\n\nNotices are live events rather than session history. Agents must not rely on\na notice being received, displayed, or seen by the user.\nAgents MUST only send notices when the Client advertised\n[`ClientSessionCapabilities::notices`]. Otherwise, Agents may use an agent\nmessage when the information should still be surfaced to the user.\n\nSee RFD: [Session Notices](https://agentclientprotocol.com/rfds/session-notices)", "type": "object", "properties": { "severity": { @@ -5862,11 +5851,11 @@ "required": ["severity", "title"] }, "CompactionId": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nUnique identifier for a context compaction within a session.", + "description": "Unique identifier for a context compaction within a session.", "type": "string" }, "CompactionStatus": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nLifecycle state of a context compaction.", + "description": "Lifecycle state of a context compaction.", "anyOf": [ { "description": "Compaction has started and has not finished.", @@ -5896,7 +5885,7 @@ ] }, "CompactionUpdate": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA context compaction upsert. The first update fixes the compaction's\ntimeline position. Later updates with the same ID patch that entity in place.\nAgents MUST only send this update when the Client advertised\n[`ClientSessionCapabilities::compaction`].\n\n`summary`, `error`, and `_meta` have patch semantics: omission leaves the\nstored value unchanged, `null` clears it, and a concrete value replaces it.\n`summary: []` also clears the retained summary. A non-empty summary is only\nvalid with `completed`; `error` is only valid with `failed`.", + "description": "A context compaction upsert. The first notification fixes the compaction's\ntimeline position. Later updates with the same ID patch that entity in place.\nAgents MUST only send this update when the Client advertised\n[`ClientSessionCapabilities::compaction`].\n\n`summary`, `error`, and `_meta` have patch semantics: omission leaves the\nstored value unchanged, `null` clears it, and a concrete value replaces it.\n`summary: []` also clears the summary.", "type": "object", "properties": { "compactionId": { @@ -5916,7 +5905,7 @@ ] }, "summary": { - "description": "Complete replacement user-displayable summary retained by the compaction.", + "description": "Complete replacement user-displayable summary content for the compaction.", "type": ["array", "null"], "items": { "$ref": "#/$defs/ContentBlock" @@ -5925,7 +5914,7 @@ "x-deserialize-skip-invalid-items": true }, "error": { - "description": "Human-readable description of why the compaction failed.", + "description": "Human-readable error details for the compaction.", "type": ["string", "null"], "x-deserialize-default-on-error": true }, @@ -5939,7 +5928,7 @@ "required": ["compactionId", "status"] }, "CompactionSummaryChunk": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA content block appended to the retained summary of an in-progress\ncompaction. Agents send chunks only after an `in_progress` update and before\nthe terminal update for the same ID. Agents MUST only send this update when\nthe Client advertised [`ClientSessionCapabilities::compaction`].", + "description": "A content block appended to a compaction's summary. A first-seen ID creates\nan in-progress compaction. Chunks append in receive order.\nAgents MUST only send this update when the Client advertised\n[`ClientSessionCapabilities::compaction`].", "type": "object", "properties": { "compactionId": { @@ -6141,22 +6130,28 @@ } } }, - "IdleStateUpdate": { - "description": "The child is ready to process another prompt.", + "ErrorStopReason": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nDetails of a failure that ended a child's foreground work.", "type": "object", "properties": { - "stopReason": { - "description": "Reason foreground work stopped. Optional; omitted or `null` means not reported.", + "error": { + "description": "The failure, as a JSON-RPC error object.\n\nOptional. Omitted or `null` both mean the agent is not reporting failure details.\nAgents SHOULD include it.", "anyOf": [ { - "$ref": "#/$defs/StopReason" + "$ref": "#/$defs/Error" }, { "type": "null" } ], "x-deserialize-default-on-error": true - }, + } + } + }, + "IdleStateUpdate": { + "description": "The child is ready to process another prompt.\n\nAn omitted, `null`, or malformed `stopReason` means not reported.", + "type": "object", + "properties": { "usage": { "description": "**UNSTABLE** Token usage for completed foreground work.\n\nOptional; omitted or `null` means not reported.", "anyOf": [ @@ -6174,8 +6169,173 @@ "type": ["object", "null"], "x-deserialize-default-on-error": true, "additionalProperties": true + }, + "stopReason": { + "description": "Why foreground work stopped. The value selects one of this type's variants, which may add fields of their own.\n\nOptional. Omitted or `null` both mean the agent is not reporting a stop reason; a malformed value is treated the same way.\n\nSee protocol docs: [Current work state](https://agentclientprotocol.com/rfds/subagents#current-work-state)", + "type": ["string", "null"], + "x-deserialize-default-on-error": true } - } + }, + "anyOf": [ + { + "description": "The work ended successfully.", + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "end_turn" + } + }, + "required": ["stopReason"] + }, + { + "description": "The work ended because the agent reached the maximum number of tokens.", + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "max_tokens" + } + }, + "required": ["stopReason"] + }, + { + "description": "The work ended because the agent reached the maximum number of allowed\nagent requests.", + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "max_turn_requests" + } + }, + "required": ["stopReason"] + }, + { + "description": "The work ended because the agent refused to continue.", + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "refusal" + } + }, + "required": ["stopReason"] + }, + { + "description": "The work was cancelled.", + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "cancelled" + } + }, + "required": ["stopReason"] + }, + { + "description": "The work ended because something failed.", + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "error" + } + }, + "required": ["stopReason"], + "allOf": [ + { + "$ref": "#/$defs/ErrorStopReason" + } + ] + }, + { + "title": "other", + "description": "Custom or future stop reason.\n\nValues beginning with `_` are reserved for implementation-specific\nextensions. Other unknown values are reserved for future ACP variants.", + "type": "object", + "properties": { + "stopReason": { + "description": "Unrecognized stop reason.", + "type": "string" + } + }, + "required": ["stopReason"], + "not": { + "anyOf": [ + { + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "end_turn" + } + }, + "required": ["stopReason"] + }, + { + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "max_tokens" + } + }, + "required": ["stopReason"] + }, + { + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "max_turn_requests" + } + }, + "required": ["stopReason"] + }, + { + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "refusal" + } + }, + "required": ["stopReason"] + }, + { + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "cancelled" + } + }, + "required": ["stopReason"] + }, + { + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "error" + } + }, + "required": ["stopReason"] + } + ] + }, + "additionalProperties": true + }, + { + "title": "none", + "description": "No stop reason: `stopReason` is omitted or `null`.", + "type": "object", + "properties": { + "stopReason": { + "type": "null" + } + } + } + ] }, "RequiresActionStateUpdate": { "description": "Foreground work is blocked on user action.", @@ -6797,7 +6957,7 @@ "type": "object", "properties": { "compaction": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nSupport for ID-addressed context compaction updates. Omitted or `null`\nmeans unsupported; `{}` advertises the complete compaction contract.", + "description": "Support for ID-addressed context compaction updates. Omitted or `null`\nmeans unsupported; `{}` advertises the complete compaction contract.", "anyOf": [ { "$ref": "#/$defs/CompactionCapabilities" @@ -6821,7 +6981,7 @@ "x-deserialize-default-on-error": true }, "notices": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nSupport for live advisory `notice` session updates.\n\nOptional. Omitted or `null` both mean the client does not advertise support.\nSupplying `{}` means the client can present notices to the user.", + "description": "Support for live user-facing `notice` session updates.\n\nOptional. Omitted or `null` both mean the client does not advertise support.\nSupplying `{}` means the client can present notices to the user.", "anyOf": [ { "$ref": "#/$defs/NoticeCapabilities" @@ -6841,7 +7001,7 @@ } }, "CompactionCapabilities": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nClient support for ID-addressed context compaction updates.", + "description": "Client support for ID-addressed context compaction updates.", "type": "object" }, "SessionConfigOptionsCapabilities": { @@ -6881,7 +7041,7 @@ } }, "NoticeCapabilities": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nClient support for presenting live advisory notices to the user.", + "description": "Client support for presenting live notices to the user.", "type": "object" }, "SubagentCapabilities": { @@ -7199,18 +7359,14 @@ "type": "array", "items": { "type": "string" - }, - "x-deserialize-default-on-error": true, - "x-deserialize-skip-invalid-items": true + } }, "mcpServers": { "description": "List of MCP (Model Context Protocol) servers the agent should connect to.", "type": "array", "items": { "$ref": "#/$defs/McpServer" - }, - "x-deserialize-default-on-error": true, - "x-deserialize-skip-invalid-items": true + } }, "_meta": { "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)", @@ -7431,9 +7587,7 @@ "type": "array", "items": { "$ref": "#/$defs/McpServer" - }, - "x-deserialize-default-on-error": true, - "x-deserialize-skip-invalid-items": true + } }, "cwd": { "description": "The working directory for this session. Must be an absolute path.", @@ -7444,9 +7598,7 @@ "type": "array", "items": { "type": "string" - }, - "x-deserialize-default-on-error": true, - "x-deserialize-skip-invalid-items": true + } }, "sessionId": { "description": "The ID of the session to load.", @@ -7533,18 +7685,14 @@ "type": "array", "items": { "type": "string" - }, - "x-deserialize-default-on-error": true, - "x-deserialize-skip-invalid-items": true + } }, "mcpServers": { "description": "List of MCP servers to connect to for this session.", "type": "array", "items": { "$ref": "#/$defs/McpServer" - }, - "x-deserialize-default-on-error": true, - "x-deserialize-skip-invalid-items": true + } }, "_meta": { "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)", @@ -7578,18 +7726,14 @@ "type": "array", "items": { "type": "string" - }, - "x-deserialize-default-on-error": true, - "x-deserialize-skip-invalid-items": true + } }, "mcpServers": { "description": "List of MCP servers to connect to for this session.", "type": "array", "items": { "$ref": "#/$defs/McpServer" - }, - "x-deserialize-default-on-error": true, - "x-deserialize-skip-invalid-items": true + } }, "_meta": { "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)", @@ -9054,9 +9198,7 @@ "type": "array", "items": { "$ref": "#/$defs/TextDocumentContentChangeEvent" - }, - "x-deserialize-default-on-error": true, - "x-deserialize-skip-invalid-items": true + } }, "_meta": { "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)", diff --git a/schema/v2/schema.unstable.json b/schema/v2/schema.unstable.json index 26f48e40..69c0c3d0 100644 --- a/schema/v2/schema.unstable.json +++ b/schema/v2/schema.unstable.json @@ -4245,18 +4245,14 @@ "type": "array", "items": { "type": "string" - }, - "x-deserialize-default-on-error": true, - "x-deserialize-skip-invalid-items": true + } }, "env": { "description": "Additional environment variables to set on the configured agent invocation for terminal auth.\nNames MUST be unique. These values override same-named variables in the\nbase launch configuration.", "type": "array", "items": { "$ref": "#/$defs/EnvVariable" - }, - "x-deserialize-default-on-error": true, - "x-deserialize-skip-invalid-items": true + } }, "_meta": { "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", @@ -5136,7 +5132,7 @@ "x-method": "session/prompt" }, "MessageId": { - "description": "Unique identifier for a message within a session.", + "description": "Identifier for a message, unique among messages of the same type within a session.\n\nEach message type, such as user messages, agent messages, and agent thoughts,\nhas its own ID space: messages of different types may share an ID and remain\ndistinct messages.", "type": "string" }, "StartNesResponse": { @@ -6023,7 +6019,7 @@ ] }, { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nAdvisory information for the user that is not part of session history.\n\nNo Client capability is required. Clients that do not understand or\npresent notices may ignore them.", + "description": "Information for the user that is not part of session history.\n\nNo Client capability is required. Clients that do not understand or\npresent notices may ignore them.", "type": "object", "properties": { "sessionUpdate": { @@ -6039,7 +6035,7 @@ ] }, { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA context compaction has been created or updated.", + "description": "A context compaction has been created or updated.", "type": "object", "properties": { "sessionUpdate": { @@ -6055,7 +6051,7 @@ ] }, { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA content block appended to a context compaction's retained summary.", + "description": "A content block appended to a context compaction's retained summary.", "type": "object", "properties": { "sessionUpdate": { @@ -6498,40 +6494,23 @@ } } }, - "StopReason": { - "description": "Reasons why an agent stops active session work.\n\nSee protocol docs: [Stop Reasons](https://agentclientprotocol.com/protocol/v2/draft/prompt-lifecycle#stop-reasons)", - "anyOf": [ - { - "description": "The active work ended successfully.", - "type": "string", - "const": "end_turn" - }, - { - "description": "The active work ended because the agent reached the maximum number of tokens.", - "type": "string", - "const": "max_tokens" - }, - { - "description": "The active work ended because the agent reached the maximum number of\nallowed agent requests before returning idle.", - "type": "string", - "const": "max_turn_requests" - }, - { - "description": "The active work ended because the agent refused to continue. The user\nprompt and everything that comes after it won't be included in the next\nprompt, so this should be reflected in the UI.", - "type": "string", - "const": "refusal" - }, - { - "description": "Active session work was cancelled by the client via `session/cancel`.\n\nAgents should report this stop reason on an idle `state_update` session update\nwhen cancellation succeeds, even if cancellation causes exceptions in\nunderlying operations.", - "type": "string", - "const": "cancelled" - }, - { - "title": "other", - "description": "Custom or future stop reason.\n\nValues beginning with `_` are reserved for implementation-specific\nextensions. Unknown values that do not begin with `_` are reserved for\nfuture ACP variants.", - "type": "string" + "ErrorStopReason": { + "description": "Details of a failure that ended active work.", + "type": "object", + "properties": { + "error": { + "description": "The failure, as a JSON-RPC error object.\n\nOptional. Omitted or `null` both mean the agent is not reporting failure details.\nAgents SHOULD include it.", + "anyOf": [ + { + "$ref": "#/$defs/Error" + }, + { + "type": "null" + } + ], + "x-deserialize-default-on-error": true } - ] + } }, "Usage": { "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nToken usage information for completed session work.", @@ -6586,21 +6565,9 @@ "required": ["totalTokens", "inputTokens", "outputTokens"] }, "IdleStateUpdate": { - "description": "The agent is ready to process a new prompt.", + "description": "The agent is ready to process a new prompt.\n\nAgents SHOULD include a `stopReason` when the idle transition ends foreground\nwork. An omitted, `null`, or malformed `stopReason` means the agent is not\nreporting one.", "type": "object", "properties": { - "stopReason": { - "description": "Indicates why foreground work stopped.\n\nOptional. Omitted or `null` both mean the agent is not reporting a stop reason.\nAgents SHOULD include this when the idle transition ends foreground work.", - "anyOf": [ - { - "$ref": "#/$defs/StopReason" - }, - { - "type": "null" - } - ], - "x-deserialize-default-on-error": true - }, "usage": { "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nToken usage for completed foreground work.\n\nOptional. Omitted or `null` both mean the agent is not reporting token\nusage for this state update.", "anyOf": [ @@ -6618,8 +6585,173 @@ "type": ["object", "null"], "x-deserialize-default-on-error": true, "additionalProperties": true + }, + "stopReason": { + "description": "Why foreground work stopped. The value selects one of this type's variants, which may add fields of their own.\n\nOptional. Omitted or `null` both mean the agent is not reporting a stop reason; a malformed value is treated the same way.\n\nSee protocol docs: [Stop Reasons](https://agentclientprotocol.com/protocol/v2/draft/prompt-lifecycle#stop-reasons)", + "type": ["string", "null"], + "x-deserialize-default-on-error": true } - } + }, + "anyOf": [ + { + "description": "The active work ended successfully.", + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "end_turn" + } + }, + "required": ["stopReason"] + }, + { + "description": "The active work ended because the agent reached the maximum number of tokens.", + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "max_tokens" + } + }, + "required": ["stopReason"] + }, + { + "description": "The active work ended because the agent reached the maximum number of\nallowed agent requests before returning idle.", + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "max_turn_requests" + } + }, + "required": ["stopReason"] + }, + { + "description": "The active work ended because the agent refused to continue. The user\nprompt and everything that comes after it won't be included in the next\nprompt, so this should be reflected in the UI.", + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "refusal" + } + }, + "required": ["stopReason"] + }, + { + "description": "Active session work was cancelled by the client via `session/cancel`.\n\nAgents should report this stop reason on an idle `state_update` session update\nwhen cancellation succeeds, even if cancellation causes exceptions in\nunderlying operations.", + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "cancelled" + } + }, + "required": ["stopReason"] + }, + { + "description": "The active work ended because something failed.\n\nFor work started by a prompt, this covers failures after the user message\nwas inserted; earlier failures are an error response to `session/prompt`.", + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "error" + } + }, + "required": ["stopReason"], + "allOf": [ + { + "$ref": "#/$defs/ErrorStopReason" + } + ] + }, + { + "title": "other", + "description": "Custom or future stop reason.\n\nValues beginning with `_` are reserved for implementation-specific\nextensions. Unknown values that do not begin with `_` are reserved for\nfuture ACP variants.", + "type": "object", + "properties": { + "stopReason": { + "description": "Custom or future stop reason.\n\nValues beginning with `_` are reserved for implementation-specific\nextensions. Unknown values that do not begin with `_` are reserved for\nfuture ACP variants.", + "type": "string" + } + }, + "required": ["stopReason"], + "not": { + "anyOf": [ + { + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "end_turn" + } + }, + "required": ["stopReason"] + }, + { + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "max_tokens" + } + }, + "required": ["stopReason"] + }, + { + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "max_turn_requests" + } + }, + "required": ["stopReason"] + }, + { + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "refusal" + } + }, + "required": ["stopReason"] + }, + { + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "cancelled" + } + }, + "required": ["stopReason"] + }, + { + "type": "object", + "properties": { + "stopReason": { + "type": "string", + "const": "error" + } + }, + "required": ["stopReason"] + } + ] + }, + "additionalProperties": true + }, + { + "title": "none", + "description": "No stop reason: `stopReason` is omitted or `null`.", + "type": "object", + "properties": { + "stopReason": { + "type": "null" + } + } + } + ] }, "RequiresActionStateUpdate": { "description": "Foreground work is blocked on user action.", @@ -7381,7 +7513,7 @@ "required": ["used", "size"] }, "NoticeSeverity": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nSeverity hint for a session notice.", + "description": "Severity hint for a session notice.", "anyOf": [ { "description": "Informational notice.", @@ -7406,7 +7538,7 @@ ] }, "Notice": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nFire-and-forget advisory information for the user.\n\nNotices are live events rather than session history. Agents must not rely on\na notice being received, displayed, or seen by the user.\nNo Client capability is required, and unsupported Clients may ignore notices.\n\nSee RFD: [Session Notices](https://agentclientprotocol.com/rfds/session-notices)", + "description": "Fire-and-forget information for the user.\n\nNotices are live events rather than session history. Agents must not rely on\na notice being received, displayed, or seen by the user.\nNo Client capability is required, and unsupported Clients may ignore notices.\n\nSee RFD: [Session Notices](https://agentclientprotocol.com/rfds/session-notices)", "type": "object", "properties": { "severity": { @@ -7437,11 +7569,11 @@ "required": ["severity", "title"] }, "CompactionId": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nUnique identifier for a context compaction within a session.", + "description": "Unique identifier for a context compaction within a session.", "type": "string" }, "CompactionStatus": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nLifecycle state of a context compaction.", + "description": "Lifecycle state of a context compaction.", "anyOf": [ { "description": "Compaction has started and has not finished.", @@ -7471,7 +7603,7 @@ ] }, "CompactionUpdate": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA context compaction upsert. The first update fixes the compaction's\ntimeline position. Later updates with the same ID patch that entity in place.\n\n`summary`, `error`, and `_meta` have patch semantics: omission leaves the\nstored value unchanged, `null` clears it, and a concrete value replaces it.\n`summary: []` also clears the retained summary. A non-empty summary is only\nvalid with `completed`; `error` is only valid with `failed`.", + "description": "A context compaction upsert. The first notification fixes the compaction's\ntimeline position. Later updates with the same ID patch that entity in place.\n\n`summary`, `error`, and `_meta` have patch semantics: omission leaves the\nstored value unchanged, `null` clears it, and a concrete value replaces it.\n`summary: []` also clears the summary.", "type": "object", "properties": { "compactionId": { @@ -7491,7 +7623,7 @@ ] }, "summary": { - "description": "Complete replacement user-displayable summary retained by the compaction.", + "description": "Complete replacement user-displayable summary content for the compaction.", "type": ["array", "null"], "items": { "$ref": "#/$defs/ContentBlock" @@ -7500,7 +7632,7 @@ "x-deserialize-skip-invalid-items": true }, "error": { - "description": "Human-readable description of why the compaction failed.", + "description": "Human-readable error details for the compaction.", "type": ["string", "null"], "x-deserialize-default-on-error": true }, @@ -7514,7 +7646,7 @@ "required": ["compactionId", "status"] }, "CompactionSummaryChunk": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA content block appended to the retained summary of an in-progress\ncompaction. Agents send chunks only after an `in_progress` update and before\nthe terminal update for the same ID.", + "description": "A content block appended to a compaction's summary. A first-seen ID creates\nan in-progress compaction. Chunks append in receive order.", "type": "object", "properties": { "compactionId": { @@ -7907,7 +8039,7 @@ }, { "title": "PromptRequest", - "description": "Processes a user prompt within a session.\n\nAcceptance means insertion into the ACP conversation:\n- Receives user messages with optional context (files, images, etc.)\n- Returns the inserted user message's ID without waiting for processing to finish\n\nThe Agent reports the user message with the same ID through `session/update`;\nthis notification may arrive before or after the response. Processing state,\noutput, tool calls, and completion are also reported through session updates.\n\nSee protocol docs: [Prompt Lifecycle](https://agentclientprotocol.com/protocol/v2/draft/prompt-lifecycle)", + "description": "Processes a user prompt within a session.\n\nAcceptance means insertion into the ACP conversation:\n- Receives user messages with optional context (files, images, etc.)\n- Returns the inserted user message's ID without waiting for processing to finish\n\nThe Agent reports the user message with the same ID through `session/update`;\nthis notification may arrive before or after the response. Processing state,\noutput, tool calls, and completion are also reported through session updates.\n\nAn error response means the user message was not inserted. Once it is\ninserted, the Agent MUST NOT answer with an error, even if the work fails\nbefore the response is sent; it ends that work with the `error` stop reason.\n\nSee protocol docs: [Prompt Lifecycle](https://agentclientprotocol.com/protocol/v2/draft/prompt-lifecycle)", "allOf": [ { "$ref": "#/$defs/PromptRequest" @@ -8373,18 +8505,14 @@ "type": "array", "items": { "$ref": "#/$defs/AbsolutePath" - }, - "x-deserialize-default-on-error": true, - "x-deserialize-skip-invalid-items": true + } }, "mcpServers": { "description": "List of MCP (Model Context Protocol) servers the agent should connect to.", "type": "array", "items": { "$ref": "#/$defs/McpServer" - }, - "x-deserialize-default-on-error": true, - "x-deserialize-skip-invalid-items": true + } }, "_meta": { "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", @@ -8695,18 +8823,14 @@ "type": "array", "items": { "$ref": "#/$defs/AbsolutePath" - }, - "x-deserialize-default-on-error": true, - "x-deserialize-skip-invalid-items": true + } }, "mcpServers": { "description": "List of MCP servers to connect to for this session.", "type": "array", "items": { "$ref": "#/$defs/McpServer" - }, - "x-deserialize-default-on-error": true, - "x-deserialize-skip-invalid-items": true + } }, "_meta": { "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", @@ -8744,18 +8868,14 @@ "type": "array", "items": { "$ref": "#/$defs/AbsolutePath" - }, - "x-deserialize-default-on-error": true, - "x-deserialize-skip-invalid-items": true + } }, "mcpServers": { "description": "List of MCP servers to connect to for this session.", "type": "array", "items": { "$ref": "#/$defs/McpServer" - }, - "x-deserialize-default-on-error": true, - "x-deserialize-skip-invalid-items": true + } }, "replayFrom": { "description": "Inclusive cursor describing where conversation replay should begin.\n\nOptional. Omitted or `null` both mean the Agent should resume without\nreplaying previous conversation history. Replay cursors are inclusive:\nreplay includes the position identified by the cursor. Supplying\n`{ \"type\": \"start\" }` means the Agent should replay all retained\nconversation history before responding.", @@ -8766,8 +8886,7 @@ { "type": "null" } - ], - "x-deserialize-default-on-error": true + ] }, "_meta": { "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", @@ -10133,9 +10252,7 @@ "type": "array", "items": { "$ref": "#/$defs/TextDocumentContentChangeEvent" - }, - "x-deserialize-default-on-error": true, - "x-deserialize-skip-invalid-items": true + } }, "_meta": { "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", diff --git a/scripts/generate.js b/scripts/generate.js index 173844a0..4849e2d4 100644 --- a/scripts/generate.js +++ b/scripts/generate.js @@ -10,8 +10,8 @@ import * as fs from "fs/promises"; import { dirname } from "path"; import * as prettier from "prettier"; -const CURRENT_V1_SCHEMA_RELEASE = "schema-v1.24.1"; -const CURRENT_V2_SCHEMA_RELEASE = "schema-v2.0.0-alpha.7"; +const CURRENT_V1_SCHEMA_RELEASE = "schema-v1.25.0"; +const CURRENT_V2_SCHEMA_RELEASE = "schema-v2.0.0-alpha.8"; const CHECK_GENERATED = process.argv.includes("--check"); // ── Extensible-union pipeline ──────────────────────────────────────────────── @@ -47,6 +47,7 @@ const V1_EXTENSIBLE_UNIONS = [ "CreateElicitationRequest", "CreateElicitationResponse", "ElicitationPropertySchema", + "IdleStateUpdate", "MultiSelectItems", "StateUpdate", ]; @@ -59,6 +60,7 @@ const V2_EXTENSIBLE_UNIONS = [ "CreateElicitationResponse", "DiffChange", "ElicitationPropertySchema", + "IdleStateUpdate", "McpServer", "MultiSelectItems", "NesSuggestion", @@ -599,7 +601,24 @@ function annotateExtensibleUnions(schemaDefs) { const defExclusions = new Map(); for (const [name, def] of Object.entries(schemaDefs)) { const exclusion = annotateUnionNode(def); - if (exclusion) defExclusions.set(name, exclusion); + if (exclusion) { + const tag = def.properties?.[exclusion.key]; + if (tag?.["x-deserialize-default-on-error"]) { + // The nullable shared tag selects a branch with no reported reason. + // Its field-level catch alone cannot salvage the sibling variant union. + if ( + JSON.stringify(tag.type) !== JSON.stringify(["string", "null"]) || + (def.required ?? []).includes(exclusion.key) || + Object.hasOwn(tag, "default") + ) { + throw new Error( + `${name}: unsupported default-on-error discriminator shape`, + ); + } + exclusion.defaultOnErrorTag = true; + } + defExclusions.set(name, exclusion); + } for (const child of Object.values(def)) { walkSchema(child, annotateUnionNode); } @@ -675,7 +694,7 @@ function notClauseExclusion(not) { function detectExtensibleUnions(schemaDefs, expectedUnions, lane, branded) { const unions = []; for (const [name, def] of Object.entries(schemaDefs)) { - const union = analyzeExtensibleUnion(name, def, branded); + const union = analyzeExtensibleUnion(name, def, branded, schemaDefs); if (union) unions.push(union); } @@ -909,7 +928,7 @@ function emitExtensibleUnionGuards(unions) { ); } -function analyzeExtensibleUnion(name, def, branded) { +function analyzeExtensibleUnion(name, def, branded, schemaDefs) { const variants = def.anyOf ?? def.oneOf; if (!Array.isArray(variants)) return undefined; @@ -938,12 +957,19 @@ function analyzeExtensibleUnion(name, def, branded) { .map((variant) => { const refs = allOfRefs(variant); const constValue = variant.properties?.[discriminant]?.const; + const nullTag = variant.properties?.[discriminant]?.type === "null"; + const optionalNullTag = + nullTag && !(variant.required ?? []).includes(discriminant); const label = constValue !== undefined ? String(constValue) : (variant.title ?? refs[0] ?? discriminant); - if (constValue === undefined && variant.properties?.[discriminant]) { + if ( + constValue === undefined && + variant.properties?.[discriminant] && + !nullTag + ) { throw new Error( `${name}: known variant "${label}" declares "${discriminant}" ` + `without a const tag; analyzeExtensibleUnion cannot emit a sound guard for it`, @@ -960,12 +986,21 @@ function analyzeExtensibleUnion(name, def, branded) { } const typeParts = refs.map((ref) => `types.${ref}`); - const zodParts = refs.map((ref) => `validate.z${ref}`); + const zodParts = refs.map((ref) => + referencedPayloadExpr(ref, schemaDefs), + ); if (constValue !== undefined) { typeParts.push(`{ ${discriminant}: ${JSON.stringify(constValue)} }`); zodParts.push( `z.object({ ${discriminant}: z.literal(${JSON.stringify(constValue)}) })`, ); + } else if (nullTag) { + typeParts.push( + `{ ${discriminant}${optionalNullTag ? "?" : ""}: null }`, + ); + zodParts.push( + `z.object({ ${discriminant}: z.null()${optionalNullTag ? ".optional()" : ""} })`, + ); } const inlineRequired = requiredInlineProps(`${name}.${label}`, variant, [ discriminant, @@ -989,7 +1024,15 @@ function analyzeExtensibleUnion(name, def, branded) { // alone would also accept custom-tagged values, since z.object ignores // unknown keys. const tagLiteral = - constValue !== undefined ? JSON.stringify(constValue) : "undefined"; + constValue !== undefined + ? JSON.stringify(constValue) + : nullTag + ? "null" + : "undefined"; + const tagExpr = `tagOf(value, ${JSON.stringify(discriminant)})`; + const tagCheck = optionalNullTag + ? `(${tagExpr} === null || ${tagExpr} === undefined)` + : `${tagExpr} === ${tagLiteral}`; return { label, @@ -997,9 +1040,7 @@ function analyzeExtensibleUnion(name, def, branded) { schemaConst, tsType: `(${typeParts.join(" & ")})${commonPick}`, zodExpr: chainAnd(zodParts), - checkExpr: - `tagOf(value, ${JSON.stringify(discriminant)}) === ${tagLiteral} &&\n` + - ` ${schemaConst}.safeParse(value).success`, + checkExpr: `${tagCheck} &&\n ${schemaConst}.safeParse(value).success`, }; }); @@ -1025,6 +1066,36 @@ function analyzeExtensibleUnion(name, def, branded) { }; } +// Guards narrow the original value, not a salvaged parse result. Validate an +// optional referenced payload before its field-level default-on-error catch +// can hide invalid data (e.g. ErrorStopReason.error must really be an Error). +function referencedPayloadExpr(name, schemaDefs) { + const def = schemaDefs[name]; + const props = []; + for (const [key, property] of Object.entries(def?.properties ?? {})) { + if ( + !property["x-deserialize-default-on-error"] || + (def.required ?? []).includes(key) + ) { + continue; + } + const variants = property.anyOf; + if ( + variants?.length !== 2 || + !variants.some((variant) => variant.type === "null") + ) { + continue; + } + const ref = variants.find((variant) => variant.$ref)?.$ref; + if (ref) { + props.push(`${JSON.stringify(key)}: validate.z${refName(ref)}.nullish()`); + } + } + return props.length + ? `validate.z${name}.and(z.object({ ${props.join(", ")} }))` + : `validate.z${name}`; +} + // Zod for the def-level common properties that are required and not salvaged // by deserialization defaults (e.g. CreateElicitationRequest's `message`). // Salvaged (x-deserialize-default-on-error) props are deliberately excluded: @@ -1278,9 +1349,8 @@ function createDeserializationResolvers( ctx.chain.current = ctx.nodes.base(ctx); if (defLevel) { - ctx.chain.current = deserializeWrap( + ctx.chain.current = extensibleUnionExpression( ctx, - "preserveCustomPayload", ctx.chain.current, defLevel, schemaDeserializeImport, @@ -1299,9 +1369,8 @@ function createDeserializationResolvers( const defLevel = annotatedDefExclusion(ctx, defExclusions); if (!defLevel) return undefined; - ctx.chain.current = deserializeWrap( + ctx.chain.current = extensibleUnionExpression( ctx, - "preserveCustomPayload", ctx.nodes.base(ctx), defLevel, schemaDeserializeImport, @@ -1337,6 +1406,31 @@ function annotatedDefExclusion(ctx, defExclusions) { return defExclusions.get(segments[2]); } +function extensibleUnionExpression( + ctx, + expression, + exclusion, + schemaDeserializeImport, +) { + const preserved = deserializeWrap( + ctx, + "preserveCustomPayload", + expression, + exclusion, + schemaDeserializeImport, + ); + if (!exclusion.defaultOnErrorTag) return preserved; + return ctx + .$( + schemaDeserializeSymbol( + ctx.plugin, + "defaultOnErrorOptionalStringTag", + schemaDeserializeImport, + ), + ) + .call(preserved, ctx.$.fromValue(exclusion.key)); +} + // Both schema-deserialize helpers share the (schema, key, knownTags) contract. function deserializeWrap( ctx, diff --git a/src/acp.ts b/src/acp.ts index 392b5979..c8b8571a 100644 --- a/src/acp.ts +++ b/src/acp.ts @@ -14,6 +14,7 @@ export { CreateElicitationRequest, CreateElicitationResponse, ElicitationPropertySchema, + IdleStateUpdate, MultiSelectItems, StateUpdate, } from "./schema/guards.gen.js"; diff --git a/src/schema-deserialize.ts b/src/schema-deserialize.ts index 7ab1b028..430982fb 100644 --- a/src/schema-deserialize.ts +++ b/src/schema-deserialize.ts @@ -112,6 +112,29 @@ export function preserveCustomPayload( }) as z.ZodType, z.input>; } +// A shared optional discriminator must be salvaged before selecting a union +// branch, not only inside the common-properties half of an intersection. +// Normalize only malformed tags; valid strings still select their own branch, +// so this cannot turn a malformed known payload into a different variant. +export function defaultOnErrorOptionalStringTag( + schema: Schema, + key: string, +) { + return z.preprocess((value) => { + if (value === null || typeof value !== "object" || Array.isArray(value)) { + return value; + } + const record = value as Record; + const tag = record[key]; + if (tag === undefined || tag === null || typeof tag === "string") { + return value; + } + const normalized = { ...record }; + delete normalized[key]; + return normalized; + }, schema); +} + export function vecSkipError( itemSchema: ItemSchema, ) { diff --git a/src/schema/guards.gen.ts b/src/schema/guards.gen.ts index 3f253602..eaa0df1d 100644 --- a/src/schema/guards.gen.ts +++ b/src/schema/guards.gen.ts @@ -39,15 +39,34 @@ const zGuardMultiSelectItemsTitled = validate.zTitledMultiSelectItems; const zGuardStateUpdateRunning = validate.zRunningStateUpdate.and( z.object({ state: z.literal("running") }), ); -const zGuardStateUpdateIdle = validate.zIdleStateUpdate.and( - z.object({ state: z.literal("idle") }), -); +const zGuardStateUpdateIdle = validate.zIdleStateUpdate + .and(z.object({ usage: validate.zUsage.nullish() })) + .and(z.object({ state: z.literal("idle") })); const zGuardStateUpdateRequiresAction = validate.zRequiresActionStateUpdate.and( z.object({ state: z.literal("requires_action") }), ); const zGuardStateUpdateUnknown = validate.zUnknownStateUpdate.and( z.object({ state: z.literal("unknown") }), ); +const zGuardIdleStateUpdateEndTurn = z.object({ + stopReason: z.literal("end_turn"), +}); +const zGuardIdleStateUpdateMaxTokens = z.object({ + stopReason: z.literal("max_tokens"), +}); +const zGuardIdleStateUpdateMaxTurnRequests = z.object({ + stopReason: z.literal("max_turn_requests"), +}); +const zGuardIdleStateUpdateRefusal = z.object({ + stopReason: z.literal("refusal"), +}); +const zGuardIdleStateUpdateCancelled = z.object({ + stopReason: z.literal("cancelled"), +}); +const zGuardIdleStateUpdateError = validate.zErrorStopReason + .and(z.object({ error: validate.zError.nullish() })) + .and(z.object({ stopReason: z.literal("error") })); +const zGuardIdleStateUpdateNone = z.object({ stopReason: z.null().optional() }); const zGuardCreateElicitationResponseAccept = validate.zElicitationAcceptAction.and( z.object({ action: z.literal("accept") }), @@ -364,6 +383,146 @@ export const StateUpdate = { }, } as const; +/** + * The child is ready to process another prompt. + * + * An omitted, `null`, or malformed `stopReason` means not reported. + */ +export type IdleStateUpdate = types.IdleStateUpdate; +/** + * Validated type guards for `IdleStateUpdate`'s known variants. + * + * Each guard validates the variant's payload, not just its discriminant + * tag: a malformed known variant (right tag, wrong payload) matches no + * guard — mirroring wire validation, which rejects such values instead + * of classifying them as custom. + * + * Guards check the value as given: fields that wire deserialization + * salvages to a default (e.g. a malformed `_meta`) are only normalized + * by parsing, and for ambiguous raw shapes (a known tag combined with + * another variant's payload) guards are conservative where wire parsing + * may still accept the value — narrow wire-parsed values when exact + * parity matters. + */ +export const IdleStateUpdate = { + /** Narrow to the `end_turn` variant, validating its payload. */ + isEndTurn( + value: types.IdleStateUpdate, + ): value is { stopReason: "end_turn" } & Pick< + types.IdleStateUpdate, + "usage" | "_meta" | "stopReason" + > { + return ( + tagOf(value, "stopReason") === "end_turn" && + zGuardIdleStateUpdateEndTurn.safeParse(value).success + ); + }, + + /** Narrow to the `max_tokens` variant, validating its payload. */ + isMaxTokens( + value: types.IdleStateUpdate, + ): value is { stopReason: "max_tokens" } & Pick< + types.IdleStateUpdate, + "usage" | "_meta" | "stopReason" + > { + return ( + tagOf(value, "stopReason") === "max_tokens" && + zGuardIdleStateUpdateMaxTokens.safeParse(value).success + ); + }, + + /** Narrow to the `max_turn_requests` variant, validating its payload. */ + isMaxTurnRequests( + value: types.IdleStateUpdate, + ): value is { stopReason: "max_turn_requests" } & Pick< + types.IdleStateUpdate, + "usage" | "_meta" | "stopReason" + > { + return ( + tagOf(value, "stopReason") === "max_turn_requests" && + zGuardIdleStateUpdateMaxTurnRequests.safeParse(value).success + ); + }, + + /** Narrow to the `refusal` variant, validating its payload. */ + isRefusal( + value: types.IdleStateUpdate, + ): value is { stopReason: "refusal" } & Pick< + types.IdleStateUpdate, + "usage" | "_meta" | "stopReason" + > { + return ( + tagOf(value, "stopReason") === "refusal" && + zGuardIdleStateUpdateRefusal.safeParse(value).success + ); + }, + + /** Narrow to the `cancelled` variant, validating its payload. */ + isCancelled( + value: types.IdleStateUpdate, + ): value is { stopReason: "cancelled" } & Pick< + types.IdleStateUpdate, + "usage" | "_meta" | "stopReason" + > { + return ( + tagOf(value, "stopReason") === "cancelled" && + zGuardIdleStateUpdateCancelled.safeParse(value).success + ); + }, + + /** Narrow to the `error` variant, validating its payload. */ + isError( + value: types.IdleStateUpdate, + ): value is (types.ErrorStopReason & { stopReason: "error" }) & + Pick { + return ( + tagOf(value, "stopReason") === "error" && + zGuardIdleStateUpdateError.safeParse(value).success + ); + }, + + /** Narrow to the `none` variant, validating its payload. */ + isNone( + value: types.IdleStateUpdate, + ): value is { stopReason?: null } & Pick< + types.IdleStateUpdate, + "usage" | "_meta" | "stopReason" + > { + return ( + (tagOf(value, "stopReason") === null || + tagOf(value, "stopReason") === undefined) && + zGuardIdleStateUpdateNone.safeParse(value).success + ); + }, + + /** + * Narrow to a custom or future variant: the `stopReason` tag matches no known variant. + * + * TypeScript keeps the known variants in the narrowed union (they are + * structural subtypes of the catch-all), so read vendor payload keys + * via a widening cast: `(value as Record).someKey`. + */ + isCustom( + value: types.IdleStateUpdate, + ): value is { stopReason: string; [key: string]: unknown } & Pick< + types.IdleStateUpdate, + "usage" | "_meta" | "stopReason" + > { + const tag = tagOf(value, "stopReason"); + return ( + typeof tag === "string" && + ![ + "cancelled", + "end_turn", + "error", + "max_tokens", + "max_turn_requests", + "refusal", + ].includes(tag) + ); + }, +} as const; + /** * Response from the client to an elicitation request. */ diff --git a/src/schema/index.ts b/src/schema/index.ts index 21ec1dab..706c0427 100644 --- a/src/schema/index.ts +++ b/src/schema/index.ts @@ -79,6 +79,7 @@ export type { EnvVariable, Error, ErrorCode, + ErrorStopReason, ExtNotification, ExtRequest, ExtResponse, diff --git a/src/schema/types.gen.ts b/src/schema/types.gen.ts index 6a3d172e..42794356 100644 --- a/src/schema/types.gen.ts +++ b/src/schema/types.gen.ts @@ -4203,22 +4203,12 @@ export type UsageUpdate = { }; /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * Severity hint for a session notice. - * - * @experimental */ export type NoticeSeverity = "info" | "warning" | "error" | string; /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * - * Fire-and-forget advisory information for the user. + * Fire-and-forget information for the user. * * Notices are live events rather than session history. Agents must not rely on * a notice being received, displayed, or seen by the user. @@ -4227,8 +4217,6 @@ export type NoticeSeverity = "info" | "warning" | "error" | string; * message when the information should still be surfaced to the user. * * See RFD: [Session Notices](https://agentclientprotocol.com/rfds/session-notices) - * - * @experimental */ export type Notice = { /** @@ -4256,44 +4244,25 @@ export type Notice = { }; /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * Unique identifier for a context compaction within a session. - * - * @experimental */ export type CompactionId = string; /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * Lifecycle state of a context compaction. - * - * @experimental */ export type CompactionStatus = "in_progress" | "completed" | "failed" | "cancelled" | string; /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * - * A context compaction upsert. The first update fixes the compaction's + * A context compaction upsert. The first notification fixes the compaction's * timeline position. Later updates with the same ID patch that entity in place. * Agents MUST only send this update when the Client advertised * [`ClientSessionCapabilities::compaction`]. * * `summary`, `error`, and `_meta` have patch semantics: omission leaves the * stored value unchanged, `null` clears it, and a concrete value replaces it. - * `summary: []` also clears the retained summary. A non-empty summary is only - * valid with `completed`; `error` is only valid with `failed`. - * - * @experimental + * `summary: []` also clears the summary. */ export type CompactionUpdate = { /** @@ -4305,11 +4274,11 @@ export type CompactionUpdate = { */ status: CompactionStatus; /** - * Complete replacement user-displayable summary retained by the compaction. + * Complete replacement user-displayable summary content for the compaction. */ summary?: Array | null; /** - * Human-readable description of why the compaction failed. + * Human-readable error details for the compaction. */ error?: string | null; /** @@ -4321,16 +4290,10 @@ export type CompactionUpdate = { }; /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * - * A content block appended to the retained summary of an in-progress - * compaction. Agents send chunks only after an `in_progress` update and before - * the terminal update for the same ID. Agents MUST only send this update when - * the Client advertised [`ClientSessionCapabilities::compaction`]. - * - * @experimental + * A content block appended to a compaction's summary. A first-seen ID creates + * an in-progress compaction. Chunks append in receive order. + * Agents MUST only send this update when the Client advertised + * [`ClientSessionCapabilities::compaction`]. */ export type CompactionSummaryChunk = { /** @@ -4451,13 +4414,59 @@ export type RunningStateUpdate = { }; /** - * The child is ready to process another prompt. + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Details of a failure that ended a child's foreground work. + * + * @experimental */ -export type IdleStateUpdate = { +export type ErrorStopReason = { /** - * Reason foreground work stopped. Optional; omitted or `null` means not reported. + * The failure, as a JSON-RPC error object. + * + * Optional. Omitted or `null` both mean the agent is not reporting failure details. + * Agents SHOULD include it. */ - stopReason?: StopReason | null; + error?: Error | null; +}; + +/** + * The child is ready to process another prompt. + * + * An omitted, `null`, or malformed `stopReason` means not reported. + */ +export type IdleStateUpdate = ( + | { + stopReason: "end_turn"; + } + | { + stopReason: "max_tokens"; + } + | { + stopReason: "max_turn_requests"; + } + | { + stopReason: "refusal"; + } + | { + stopReason: "cancelled"; + } + | (ErrorStopReason & { + stopReason: "error"; + }) + | { + /** + * Unrecognized stop reason. + */ + stopReason: string; + [key: string]: unknown; + } + | { + stopReason?: null; + } +) & { /** * **UNSTABLE** Token usage for completed foreground work. * @@ -4474,6 +4483,14 @@ export type IdleStateUpdate = { _meta?: { [key: string]: unknown; } | null; + /** + * Why foreground work stopped. The value selects one of this type's variants, which may add fields of their own. + * + * Optional. Omitted or `null` both mean the agent is not reporting a stop reason; a malformed value is treated the same way. + * + * See protocol docs: [Current work state](https://agentclientprotocol.com/rfds/subagents#current-work-state) + */ + stopReason?: string | null; }; /** @@ -4906,14 +4923,8 @@ export type FileSystemCapabilities = { */ export type ClientSessionCapabilities = { /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * Support for ID-addressed context compaction updates. Omitted or `null` * means unsupported; `{}` advertises the complete compaction contract. - * - * @experimental */ compaction?: CompactionCapabilities | null; /** @@ -4924,16 +4935,10 @@ export type ClientSessionCapabilities = { */ configOptions?: SessionConfigOptionsCapabilities | null; /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * - * Support for live advisory `notice` session updates. + * Support for live user-facing `notice` session updates. * * Optional. Omitted or `null` both mean the client does not advertise support. * Supplying `{}` means the client can present notices to the user. - * - * @experimental */ notices?: NoticeCapabilities | null; /** @@ -4949,13 +4954,7 @@ export type ClientSessionCapabilities = { }; /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * Client support for ID-addressed context compaction updates. - * - * @experimental */ export type CompactionCapabilities = { [key: string]: unknown; @@ -5005,13 +5004,7 @@ export type BooleanConfigOptionCapabilities = { }; /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * - * Client support for presenting live advisory notices to the user. - * - * @experimental + * Client support for presenting live notices to the user. */ export type NoticeCapabilities = { [key: string]: unknown; diff --git a/src/schema/zod.gen.ts b/src/schema/zod.gen.ts index ab40cef1..273109b0 100644 --- a/src/schema/zod.gen.ts +++ b/src/schema/zod.gen.ts @@ -2,6 +2,7 @@ import { defaultOnError, + defaultOnErrorOptionalStringTag, excludeKnownTags, preserveCustomPayload, requiredDefaultOnError, @@ -55,26 +56,20 @@ export const zWriteTextFileRequest = z.object({ export const zReadTextFileRequest = z.object({ sessionId: zSessionId, path: z.string(), - line: defaultOnError( - z - .int() - .gte(0) - .max(4294967295, { - error: "Invalid value: Expected uint32 to be <= 4294967295", - }) - .nullish(), - () => undefined, - ), - limit: defaultOnError( - z - .int() - .gte(0) - .max(4294967295, { - error: "Invalid value: Expected uint32 to be <= 4294967295", - }) - .nullish(), - () => undefined, - ), + line: z + .int() + .gte(0) + .max(4294967295, { + error: "Invalid value: Expected uint32 to be <= 4294967295", + }) + .nullish(), + limit: z + .int() + .gte(0) + .max(4294967295, { + error: "Invalid value: Expected uint32 to be <= 4294967295", + }) + .nullish(), _meta: defaultOnError( z.record(z.string(), z.unknown()).nullish(), () => undefined, @@ -480,10 +475,10 @@ export const zEnvVariable = z.object({ export const zCreateTerminalRequest = z.object({ sessionId: zSessionId, command: z.string(), - args: defaultOnError(vecSkipError(z.string()).optional(), () => []), - env: defaultOnError(vecSkipError(zEnvVariable).optional(), () => []), - cwd: defaultOnError(z.string().nullish(), () => undefined), - outputByteLimit: defaultOnError(z.number().nullish(), () => undefined), + args: z.array(z.string()).optional(), + env: z.array(zEnvVariable).optional(), + cwd: z.string().nullish(), + outputByteLimit: z.number().nullish(), _meta: defaultOnError( z.record(z.string(), z.unknown()).nullish(), () => undefined, @@ -1492,11 +1487,8 @@ export const zAuthMethodTerminal = z.object({ id: zAuthMethodId, name: z.string(), description: defaultOnError(z.string().nullish(), () => undefined), - args: defaultOnError(vecSkipError(z.string()).optional(), () => []), - env: defaultOnError( - z.record(z.string(), z.string()).optional(), - () => undefined, - ), + args: z.array(z.string()).optional(), + env: z.record(z.string(), z.string()).optional(), _meta: defaultOnError( z.record(z.string(), z.unknown()).nullish(), () => undefined, @@ -2669,13 +2661,7 @@ export const zUsageUpdate = z.object({ }); /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * Severity hint for a session notice. - * - * @experimental */ export const zNoticeSeverity = z.union([ z.literal("info"), @@ -2685,11 +2671,7 @@ export const zNoticeSeverity = z.union([ ]); /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * - * Fire-and-forget advisory information for the user. + * Fire-and-forget information for the user. * * Notices are live events rather than session history. Agents must not rely on * a notice being received, displayed, or seen by the user. @@ -2698,8 +2680,6 @@ export const zNoticeSeverity = z.union([ * message when the information should still be surfaced to the user. * * See RFD: [Session Notices](https://agentclientprotocol.com/rfds/session-notices) - * - * @experimental */ export const zNotice = z.object({ severity: zNoticeSeverity, @@ -2712,24 +2692,12 @@ export const zNotice = z.object({ }); /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * Unique identifier for a context compaction within a session. - * - * @experimental */ export const zCompactionId = z.string(); /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * Lifecycle state of a context compaction. - * - * @experimental */ export const zCompactionStatus = z.union([ z.literal("in_progress"), @@ -2740,21 +2708,14 @@ export const zCompactionStatus = z.union([ ]); /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * - * A context compaction upsert. The first update fixes the compaction's + * A context compaction upsert. The first notification fixes the compaction's * timeline position. Later updates with the same ID patch that entity in place. * Agents MUST only send this update when the Client advertised * [`ClientSessionCapabilities::compaction`]. * * `summary`, `error`, and `_meta` have patch semantics: omission leaves the * stored value unchanged, `null` clears it, and a concrete value replaces it. - * `summary: []` also clears the retained summary. A non-empty summary is only - * valid with `completed`; `error` is only valid with `failed`. - * - * @experimental + * `summary: []` also clears the summary. */ export const zCompactionUpdate = z.object({ compactionId: zCompactionId, @@ -2771,16 +2732,10 @@ export const zCompactionUpdate = z.object({ }); /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * - * A content block appended to the retained summary of an in-progress - * compaction. Agents send chunks only after an `in_progress` update and before - * the terminal update for the same ID. Agents MUST only send this update when - * the Client advertised [`ClientSessionCapabilities::compaction`]. - * - * @experimental + * A content block appended to a compaction's summary. A first-seen ID creates + * an in-progress compaction. Chunks append in receive order. + * Agents MUST only send this update when the Client advertised + * [`ClientSessionCapabilities::compaction`]. */ export const zCompactionSummaryChunk = z.object({ compactionId: zCompactionId, @@ -2840,17 +2795,91 @@ export const zRunningStateUpdate = z.object({ }); /** - * The child is ready to process another prompt. + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Details of a failure that ended a child's foreground work. + * + * @experimental */ -export const zIdleStateUpdate = z.object({ - stopReason: defaultOnError(zStopReason.nullish(), () => undefined), - usage: defaultOnError(zUsage.nullish(), () => undefined), - _meta: defaultOnError( - z.record(z.string(), z.unknown()).nullish(), - () => undefined, - ), +export const zErrorStopReason = z.object({ + error: defaultOnError(zError.nullish(), () => undefined), }); +/** + * The child is ready to process another prompt. + * + * An omitted, `null`, or malformed `stopReason` means not reported. + * + * Custom variants (unknown `stopReason` values) keep their extra + * properties exactly as received; unlike known variants, those keys + * bypass lenient-field salvage and arrive unvalidated. + */ +export const zIdleStateUpdate = defaultOnErrorOptionalStringTag( + preserveCustomPayload( + z.intersection( + z.union([ + z.object({ + stopReason: z.literal("end_turn"), + }), + z.object({ + stopReason: z.literal("max_tokens"), + }), + z.object({ + stopReason: z.literal("max_turn_requests"), + }), + z.object({ + stopReason: z.literal("refusal"), + }), + z.object({ + stopReason: z.literal("cancelled"), + }), + zErrorStopReason.and( + z.object({ + stopReason: z.literal("error"), + }), + ), + excludeKnownTags( + z.object({ + stopReason: z.string(), + }), + "stopReason", + [ + "cancelled", + "end_turn", + "error", + "max_tokens", + "max_turn_requests", + "refusal", + ], + ), + z.object({ + stopReason: z.null().optional(), + }), + ]), + z.object({ + usage: defaultOnError(zUsage.nullish(), () => undefined), + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), + stopReason: defaultOnError(z.string().nullish(), () => undefined), + }), + ), + "stopReason", + [ + "cancelled", + "end_turn", + "error", + "max_tokens", + "max_turn_requests", + "refusal", + ], + ), + "stopReason", +); + /** * Foreground work is blocked on user action. */ @@ -3197,13 +3226,7 @@ export const zFileSystemCapabilities = z.object({ }); /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * Client support for ID-addressed context compaction updates. - * - * @experimental */ export const zCompactionCapabilities = z.record(z.string(), z.unknown()); @@ -3234,13 +3257,7 @@ export const zSessionConfigOptionsCapabilities = z.object({ }); /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * - * Client support for presenting live advisory notices to the user. - * - * @experimental + * Client support for presenting live notices to the user. */ export const zNoticeCapabilities = z.record(z.string(), z.unknown()); @@ -3659,11 +3676,8 @@ export const zMcpServer = z.union([ */ export const zNewSessionRequest = z.object({ cwd: z.string(), - additionalDirectories: defaultOnError( - vecSkipError(z.string()).optional(), - () => [], - ), - mcpServers: requiredDefaultOnError(vecSkipError(zMcpServer), () => []), + additionalDirectories: z.array(z.string()).optional(), + mcpServers: z.array(zMcpServer), _meta: defaultOnError( z.record(z.string(), z.unknown()).nullish(), () => undefined, @@ -3678,12 +3692,9 @@ export const zNewSessionRequest = z.object({ * See protocol docs: [Loading Sessions](https://agentclientprotocol.com/protocol/session-setup#loading-sessions) */ export const zLoadSessionRequest = z.object({ - mcpServers: requiredDefaultOnError(vecSkipError(zMcpServer), () => []), + mcpServers: z.array(zMcpServer), cwd: z.string(), - additionalDirectories: defaultOnError( - vecSkipError(z.string()).optional(), - () => [], - ), + additionalDirectories: z.array(z.string()).optional(), sessionId: zSessionId, _meta: defaultOnError( z.record(z.string(), z.unknown()).nullish(), @@ -3735,11 +3746,8 @@ export const zDeleteSessionRequest = z.object({ export const zForkSessionRequest = z.object({ sessionId: zSessionId, cwd: z.string(), - additionalDirectories: defaultOnError( - vecSkipError(z.string()).optional(), - () => [], - ), - mcpServers: defaultOnError(vecSkipError(zMcpServer).optional(), () => []), + additionalDirectories: z.array(z.string()).optional(), + mcpServers: z.array(zMcpServer).optional(), _meta: defaultOnError( z.record(z.string(), z.unknown()).nullish(), () => undefined, @@ -3757,11 +3765,8 @@ export const zForkSessionRequest = z.object({ export const zResumeSessionRequest = z.object({ sessionId: zSessionId, cwd: z.string(), - additionalDirectories: defaultOnError( - vecSkipError(z.string()).optional(), - () => [], - ), - mcpServers: defaultOnError(vecSkipError(zMcpServer).optional(), () => []), + additionalDirectories: z.array(z.string()).optional(), + mcpServers: z.array(zMcpServer).optional(), _meta: defaultOnError( z.record(z.string(), z.unknown()).nullish(), () => undefined, @@ -4399,10 +4404,7 @@ export const zDidChangeDocumentNotification = z.object({ sessionId: zSessionId, uri: z.string(), version: z.number(), - contentChanges: requiredDefaultOnError( - vecSkipError(zTextDocumentContentChangeEvent), - () => [], - ), + contentChanges: z.array(zTextDocumentContentChangeEvent), _meta: defaultOnError( z.record(z.string(), z.unknown()).nullish(), () => undefined, diff --git a/src/typedoc.json b/src/typedoc.json index d39a9aac..5df7242e 100644 --- a/src/typedoc.json +++ b/src/typedoc.json @@ -45,6 +45,7 @@ "schema/types.gen.ts:CreateElicitationRequest", "schema/types.gen.ts:CreateElicitationResponse", "schema/types.gen.ts:ElicitationPropertySchema", + "schema/types.gen.ts:IdleStateUpdate", "schema/types.gen.ts:MultiSelectItems", "schema/types.gen.ts:StateUpdate" ], diff --git a/src/typedoc.v2.json b/src/typedoc.v2.json index 2325139e..46a0f491 100644 --- a/src/typedoc.v2.json +++ b/src/typedoc.v2.json @@ -20,6 +20,7 @@ "v2/schema/types.gen.ts:CreateElicitationResponse", "v2/schema/types.gen.ts:DiffChange", "v2/schema/types.gen.ts:ElicitationPropertySchema", + "v2/schema/types.gen.ts:IdleStateUpdate", "v2/schema/types.gen.ts:McpServer", "v2/schema/types.gen.ts:MultiSelectItems", "v2/schema/types.gen.ts:NesSuggestion", diff --git a/src/v2/acp.ts b/src/v2/acp.ts index b8a0755d..fe003fac 100644 --- a/src/v2/acp.ts +++ b/src/v2/acp.ts @@ -32,6 +32,7 @@ export { CreateElicitationResponse, DiffChange, ElicitationPropertySchema, + IdleStateUpdate, McpServer, MultiSelectItems, NesSuggestion, @@ -1718,7 +1719,7 @@ export type ActiveSessionMessage = /** * Stop reason reported by the idle state, when provided. */ - stopReason: schema.StopReason | null | undefined; + stopReason: schema.IdleStateUpdate["stopReason"]; }; /** diff --git a/src/v2/schema/guards.gen.ts b/src/v2/schema/guards.gen.ts index 5afbf066..c2c47e47 100644 --- a/src/v2/schema/guards.gen.ts +++ b/src/v2/schema/guards.gen.ts @@ -15,34 +15,43 @@ const zGuardRequestPermissionSubjectToolCall = validate.zToolCallPermissionSubject.and( z.object({ type: z.literal("tool_call") }), ); -const zGuardRequestPermissionSubjectCommand = - validate.zCommandPermissionSubject.and( - z.object({ type: z.literal("command") }), - ); +const zGuardRequestPermissionSubjectCommand = validate.zCommandPermissionSubject + .and( + z.object({ + toolCallId: validate.zToolCallId.nullish(), + terminalId: validate.zTerminalId.nullish(), + }), + ) + .and(z.object({ type: z.literal("command") })); const zGuardToolCallContentContent = validate.zContent.and( z.object({ type: z.literal("content") }), ); -const zGuardToolCallContentDiff = validate.zDiff.and( - z.object({ type: z.literal("diff") }), -); +const zGuardToolCallContentDiff = validate.zDiff + .and(z.object({ patch: validate.zDiffPatch.nullish() })) + .and(z.object({ type: z.literal("diff") })); const zGuardToolCallContentTerminal = validate.zTerminal.and( z.object({ type: z.literal("terminal") }), ); -const zGuardContentBlockText = validate.zTextContent.and( - z.object({ type: z.literal("text") }), -); -const zGuardContentBlockImage = validate.zImageContent.and( - z.object({ type: z.literal("image") }), -); -const zGuardContentBlockAudio = validate.zAudioContent.and( - z.object({ type: z.literal("audio") }), -); -const zGuardContentBlockResourceLink = validate.zResourceLink.and( - z.object({ type: z.literal("resource_link") }), -); -const zGuardContentBlockResource = validate.zEmbeddedResource.and( - z.object({ type: z.literal("resource") }), -); +const zGuardContentBlockText = validate.zTextContent + .and(z.object({ annotations: validate.zAnnotations.nullish() })) + .and(z.object({ type: z.literal("text") })); +const zGuardContentBlockImage = validate.zImageContent + .and(z.object({ annotations: validate.zAnnotations.nullish() })) + .and(z.object({ type: z.literal("image") })); +const zGuardContentBlockAudio = validate.zAudioContent + .and(z.object({ annotations: validate.zAnnotations.nullish() })) + .and(z.object({ type: z.literal("audio") })); +const zGuardContentBlockResourceLink = validate.zResourceLink + .and( + z.object({ + mimeType: validate.zMediaType.nullish(), + annotations: validate.zAnnotations.nullish(), + }), + ) + .and(z.object({ type: z.literal("resource_link") })); +const zGuardContentBlockResource = validate.zEmbeddedResource + .and(z.object({ annotations: validate.zAnnotations.nullish() })) + .and(z.object({ type: z.literal("resource") })); const zGuardDiffChangeAdd = validate.zDiffPathChange.and( z.object({ operation: z.literal("add") }), ); @@ -106,9 +115,9 @@ const zGuardSessionConfigOptionCustom = z.object({ const zGuardAvailableCommandInputText = validate.zTextCommandInput.and( z.object({ type: z.literal("text") }), ); -const zGuardNesSuggestionEdit = validate.zNesEditSuggestion.and( - z.object({ kind: z.literal("edit") }), -); +const zGuardNesSuggestionEdit = validate.zNesEditSuggestion + .and(z.object({ cursorPosition: validate.zPosition.nullish() })) + .and(z.object({ kind: z.literal("edit") })); const zGuardNesSuggestionJump = validate.zNesJumpSuggestion.and( z.object({ kind: z.literal("jump") }), ); @@ -147,12 +156,23 @@ const zGuardSessionUpdateToolCallContentChunk = validate.zToolCallContentChunk.and( z.object({ sessionUpdate: z.literal("tool_call_content_chunk") }), ); -const zGuardSessionUpdateToolCallUpdate = validate.zToolCallUpdate.and( - z.object({ sessionUpdate: z.literal("tool_call_update") }), -); -const zGuardSessionUpdateTerminalUpdate = validate.zTerminalUpdate.and( - z.object({ sessionUpdate: z.literal("terminal_update") }), -); +const zGuardSessionUpdateToolCallUpdate = validate.zToolCallUpdate + .and( + z.object({ + kind: validate.zToolKind.nullish(), + status: validate.zToolCallStatus.nullish(), + }), + ) + .and(z.object({ sessionUpdate: z.literal("tool_call_update") })); +const zGuardSessionUpdateTerminalUpdate = validate.zTerminalUpdate + .and( + z.object({ + cwd: validate.zAbsolutePath.nullish(), + output: validate.zTerminalOutput.nullish(), + exitStatus: validate.zTerminalExitStatus.nullish(), + }), + ) + .and(z.object({ sessionUpdate: z.literal("terminal_update") })); const zGuardSessionUpdateTerminalOutputChunk = validate.zTerminalOutputChunk.and( z.object({ sessionUpdate: z.literal("terminal_output_chunk") }), @@ -173,9 +193,9 @@ const zGuardSessionUpdateConfigOptionUpdate = validate.zConfigOptionUpdate.and( const zGuardSessionUpdateSessionInfoUpdate = validate.zSessionInfoUpdate.and( z.object({ sessionUpdate: z.literal("session_info_update") }), ); -const zGuardSessionUpdateUsageUpdate = validate.zUsageUpdate.and( - z.object({ sessionUpdate: z.literal("usage_update") }), -); +const zGuardSessionUpdateUsageUpdate = validate.zUsageUpdate + .and(z.object({ cost: validate.zCost.nullish() })) + .and(z.object({ sessionUpdate: z.literal("usage_update") })); const zGuardSessionUpdateNotice = validate.zNotice.and( z.object({ sessionUpdate: z.literal("notice") }), ); @@ -186,22 +206,55 @@ const zGuardSessionUpdateCompactionSummaryChunk = validate.zCompactionSummaryChunk.and( z.object({ sessionUpdate: z.literal("compaction_summary_chunk") }), ); -const zGuardSessionUpdateSubagentUpdate = validate.zSubagentUpdate.and( - z.object({ sessionUpdate: z.literal("subagent_update") }), -); -const zGuardSessionUpdateSessionMessage = validate.zSessionMessage.and( - z.object({ sessionUpdate: z.literal("session_message") }), -); -const zGuardSessionUpdateSessionMessageChunk = - validate.zSessionMessageChunk.and( - z.object({ sessionUpdate: z.literal("session_message_chunk") }), - ); +const zGuardSessionUpdateSubagentUpdate = validate.zSubagentUpdate + .and( + z.object({ + capabilities: validate.zSubagentSessionCapabilities.nullish(), + state: validate.zStateUpdate.nullish(), + }), + ) + .and(z.object({ sessionUpdate: z.literal("subagent_update") })); +const zGuardSessionUpdateSessionMessage = validate.zSessionMessage + .and( + z.object({ + senderSessionId: validate.zSessionId.nullish(), + recipientSessionId: validate.zSessionId.nullish(), + }), + ) + .and(z.object({ sessionUpdate: z.literal("session_message") })); +const zGuardSessionUpdateSessionMessageChunk = validate.zSessionMessageChunk + .and( + z.object({ + senderSessionId: validate.zSessionId.nullish(), + recipientSessionId: validate.zSessionId.nullish(), + }), + ) + .and(z.object({ sessionUpdate: z.literal("session_message_chunk") })); +const zGuardIdleStateUpdateEndTurn = z.object({ + stopReason: z.literal("end_turn"), +}); +const zGuardIdleStateUpdateMaxTokens = z.object({ + stopReason: z.literal("max_tokens"), +}); +const zGuardIdleStateUpdateMaxTurnRequests = z.object({ + stopReason: z.literal("max_turn_requests"), +}); +const zGuardIdleStateUpdateRefusal = z.object({ + stopReason: z.literal("refusal"), +}); +const zGuardIdleStateUpdateCancelled = z.object({ + stopReason: z.literal("cancelled"), +}); +const zGuardIdleStateUpdateError = validate.zErrorStopReason + .and(z.object({ error: validate.zError.nullish() })) + .and(z.object({ stopReason: z.literal("error") })); +const zGuardIdleStateUpdateNone = z.object({ stopReason: z.null().optional() }); const zGuardStateUpdateRunning = validate.zRunningStateUpdate.and( z.object({ state: z.literal("running") }), ); -const zGuardStateUpdateIdle = validate.zIdleStateUpdate.and( - z.object({ state: z.literal("idle") }), -); +const zGuardStateUpdateIdle = validate.zIdleStateUpdate + .and(z.object({ usage: validate.zUsage.nullish() })) + .and(z.object({ state: z.literal("idle") })); const zGuardStateUpdateRequiresAction = validate.zRequiresActionStateUpdate.and( z.object({ state: z.literal("requires_action") }), ); @@ -1409,6 +1462,149 @@ export const SessionUpdate = { }, } as const; +/** + * The agent is ready to process a new prompt. + * + * Agents SHOULD include a `stopReason` when the idle transition ends foreground + * work. An omitted, `null`, or malformed `stopReason` means the agent is not + * reporting one. + */ +export type IdleStateUpdate = types.IdleStateUpdate; +/** + * Validated type guards for `IdleStateUpdate`'s known variants. + * + * Each guard validates the variant's payload, not just its discriminant + * tag: a malformed known variant (right tag, wrong payload) matches no + * guard — mirroring wire validation, which rejects such values instead + * of classifying them as custom. + * + * Guards check the value as given: fields that wire deserialization + * salvages to a default (e.g. a malformed `_meta`) are only normalized + * by parsing, and for ambiguous raw shapes (a known tag combined with + * another variant's payload) guards are conservative where wire parsing + * may still accept the value — narrow wire-parsed values when exact + * parity matters. + */ +export const IdleStateUpdate = { + /** Narrow to the `end_turn` variant, validating its payload. */ + isEndTurn( + value: types.IdleStateUpdate, + ): value is { stopReason: "end_turn" } & Pick< + types.IdleStateUpdate, + "usage" | "_meta" | "stopReason" + > { + return ( + tagOf(value, "stopReason") === "end_turn" && + zGuardIdleStateUpdateEndTurn.safeParse(value).success + ); + }, + + /** Narrow to the `max_tokens` variant, validating its payload. */ + isMaxTokens( + value: types.IdleStateUpdate, + ): value is { stopReason: "max_tokens" } & Pick< + types.IdleStateUpdate, + "usage" | "_meta" | "stopReason" + > { + return ( + tagOf(value, "stopReason") === "max_tokens" && + zGuardIdleStateUpdateMaxTokens.safeParse(value).success + ); + }, + + /** Narrow to the `max_turn_requests` variant, validating its payload. */ + isMaxTurnRequests( + value: types.IdleStateUpdate, + ): value is { stopReason: "max_turn_requests" } & Pick< + types.IdleStateUpdate, + "usage" | "_meta" | "stopReason" + > { + return ( + tagOf(value, "stopReason") === "max_turn_requests" && + zGuardIdleStateUpdateMaxTurnRequests.safeParse(value).success + ); + }, + + /** Narrow to the `refusal` variant, validating its payload. */ + isRefusal( + value: types.IdleStateUpdate, + ): value is { stopReason: "refusal" } & Pick< + types.IdleStateUpdate, + "usage" | "_meta" | "stopReason" + > { + return ( + tagOf(value, "stopReason") === "refusal" && + zGuardIdleStateUpdateRefusal.safeParse(value).success + ); + }, + + /** Narrow to the `cancelled` variant, validating its payload. */ + isCancelled( + value: types.IdleStateUpdate, + ): value is { stopReason: "cancelled" } & Pick< + types.IdleStateUpdate, + "usage" | "_meta" | "stopReason" + > { + return ( + tagOf(value, "stopReason") === "cancelled" && + zGuardIdleStateUpdateCancelled.safeParse(value).success + ); + }, + + /** Narrow to the `error` variant, validating its payload. */ + isError( + value: types.IdleStateUpdate, + ): value is (types.ErrorStopReason & { stopReason: "error" }) & + Pick { + return ( + tagOf(value, "stopReason") === "error" && + zGuardIdleStateUpdateError.safeParse(value).success + ); + }, + + /** Narrow to the `none` variant, validating its payload. */ + isNone( + value: types.IdleStateUpdate, + ): value is { stopReason?: null } & Pick< + types.IdleStateUpdate, + "usage" | "_meta" | "stopReason" + > { + return ( + (tagOf(value, "stopReason") === null || + tagOf(value, "stopReason") === undefined) && + zGuardIdleStateUpdateNone.safeParse(value).success + ); + }, + + /** + * Narrow to a custom or future variant: the `stopReason` tag matches no known variant. + * + * TypeScript keeps the known variants in the narrowed union (they are + * structural subtypes of the catch-all), so read vendor payload keys + * via a widening cast: `(value as Record).someKey`. + */ + isCustom( + value: types.IdleStateUpdate, + ): value is ( + | { stopReason: `_${string}`; [key: string]: unknown } + | types.UnknownVariant<{ stopReason: string; [key: string]: unknown }> + ) & + Pick { + const tag = tagOf(value, "stopReason"); + return ( + typeof tag === "string" && + ![ + "cancelled", + "end_turn", + "error", + "max_tokens", + "max_turn_requests", + "refusal", + ].includes(tag) + ); + }, +} as const; + /** * The state of the agent's foreground work has changed. * diff --git a/src/v2/schema/index.ts b/src/v2/schema/index.ts index 8ab327ad..48e23a21 100644 --- a/src/v2/schema/index.ts +++ b/src/v2/schema/index.ts @@ -81,6 +81,7 @@ export type { EnvVariable, Error, ErrorCode, + ErrorStopReason, ExtNotification, ExtRequest, ExtResponse, @@ -236,7 +237,6 @@ export type { StartNesRequest, StartNesResponse, StateUpdate, - StopReason, StringFormat, StringMultiSelectItems, StringPropertySchema, diff --git a/src/v2/schema/types.gen.ts b/src/v2/schema/types.gen.ts index 6a1199bc..a192d910 100644 --- a/src/v2/schema/types.gen.ts +++ b/src/v2/schema/types.gen.ts @@ -3455,7 +3455,11 @@ export type PromptResponse = { }; /** - * Unique identifier for a message within a session. + * Identifier for a message, unique among messages of the same type within a session. + * + * Each message type, such as user messages, agent messages, and agent thoughts, + * has its own ID space: messages of different types may share an ID and remain + * distinct messages. */ export type MessageId = string; @@ -4129,17 +4133,17 @@ export type RunningStateUpdate = { }; /** - * Reasons why an agent stops active session work. - * - * See protocol docs: [Stop Reasons](https://agentclientprotocol.com/protocol/v2/draft/prompt-lifecycle#stop-reasons) + * Details of a failure that ended active work. */ -export type StopReason = - | "end_turn" - | "max_tokens" - | "max_turn_requests" - | "refusal" - | "cancelled" - | string; +export type ErrorStopReason = { + /** + * The failure, as a JSON-RPC error object. + * + * Optional. Omitted or `null` both mean the agent is not reporting failure details. + * Agents SHOULD include it. + */ + error?: Error | null; +}; /** * **UNSTABLE** @@ -4189,15 +4193,58 @@ export type Usage = { /** * The agent is ready to process a new prompt. + * + * Agents SHOULD include a `stopReason` when the idle transition ends foreground + * work. An omitted, `null`, or malformed `stopReason` means the agent is not + * reporting one. */ -export type IdleStateUpdate = { - /** - * Indicates why foreground work stopped. - * - * Optional. Omitted or `null` both mean the agent is not reporting a stop reason. - * Agents SHOULD include this when the idle transition ends foreground work. - */ - stopReason?: StopReason | null; +export type IdleStateUpdate = ( + | { + stopReason: "end_turn"; + } + | { + stopReason: "max_tokens"; + } + | { + stopReason: "max_turn_requests"; + } + | { + stopReason: "refusal"; + } + | { + stopReason: "cancelled"; + } + | (ErrorStopReason & { + stopReason: "error"; + }) + | ( + | { + /** + * Custom or future stop reason. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + stopReason: `_${string}`; + [key: string]: unknown; + } + | UnknownVariant<{ + /** + * Custom or future stop reason. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + stopReason: string; + [key: string]: unknown; + }> + ) + | { + stopReason?: null; + } +) & { /** * **UNSTABLE** * @@ -4221,6 +4268,14 @@ export type IdleStateUpdate = { _meta?: { [key: string]: unknown; } | null; + /** + * Why foreground work stopped. The value selects one of this type's variants, which may add fields of their own. + * + * Optional. Omitted or `null` both mean the agent is not reporting a stop reason; a malformed value is treated the same way. + * + * See protocol docs: [Stop Reasons](https://agentclientprotocol.com/protocol/v2/draft/prompt-lifecycle#stop-reasons) + */ + stopReason?: string | null; }; /** @@ -4817,30 +4872,18 @@ export type UsageUpdate = { }; /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * Severity hint for a session notice. - * - * @experimental */ export type NoticeSeverity = "info" | "warning" | "error" | string; /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * - * Fire-and-forget advisory information for the user. + * Fire-and-forget information for the user. * * Notices are live events rather than session history. Agents must not rely on * a notice being received, displayed, or seen by the user. * No Client capability is required, and unsupported Clients may ignore notices. * * See RFD: [Session Notices](https://agentclientprotocol.com/rfds/session-notices) - * - * @experimental */ export type Notice = { /** @@ -4868,42 +4911,23 @@ export type Notice = { }; /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * Unique identifier for a context compaction within a session. - * - * @experimental */ export type CompactionId = string; /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * Lifecycle state of a context compaction. - * - * @experimental */ export type CompactionStatus = "in_progress" | "completed" | "failed" | "cancelled" | string; /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * - * A context compaction upsert. The first update fixes the compaction's + * A context compaction upsert. The first notification fixes the compaction's * timeline position. Later updates with the same ID patch that entity in place. * * `summary`, `error`, and `_meta` have patch semantics: omission leaves the * stored value unchanged, `null` clears it, and a concrete value replaces it. - * `summary: []` also clears the retained summary. A non-empty summary is only - * valid with `completed`; `error` is only valid with `failed`. - * - * @experimental + * `summary: []` also clears the summary. */ export type CompactionUpdate = { /** @@ -4915,11 +4939,11 @@ export type CompactionUpdate = { */ status: CompactionStatus; /** - * Complete replacement user-displayable summary retained by the compaction. + * Complete replacement user-displayable summary content for the compaction. */ summary?: Array | null; /** - * Human-readable description of why the compaction failed. + * Human-readable error details for the compaction. */ error?: string | null; /** @@ -4931,15 +4955,8 @@ export type CompactionUpdate = { }; /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * - * A content block appended to the retained summary of an in-progress - * compaction. Agents send chunks only after an `in_progress` update and before - * the terminal update for the same ID. - * - * @experimental + * A content block appended to a compaction's summary. A first-seen ID creates + * an in-progress compaction. Chunks append in receive order. */ export type CompactionSummaryChunk = { /** diff --git a/src/v2/schema/zod.gen.ts b/src/v2/schema/zod.gen.ts index 007e69c8..74a30374 100644 --- a/src/v2/schema/zod.gen.ts +++ b/src/v2/schema/zod.gen.ts @@ -2,6 +2,7 @@ import { defaultOnError, + defaultOnErrorOptionalStringTag, excludeKnownTags, preserveCustomPayload, requiredDefaultOnError, @@ -1603,8 +1604,8 @@ export const zAuthMethodTerminal = z.object({ methodId: zAuthMethodId, name: z.string(), description: defaultOnError(z.string().nullish(), () => undefined), - args: defaultOnError(vecSkipError(z.string()).optional(), () => []), - env: defaultOnError(vecSkipError(zEnvVariable).optional(), () => []), + args: z.array(z.string()).optional(), + env: z.array(zEnvVariable).optional(), _meta: defaultOnError( z.record(z.string(), z.unknown()).nullish(), () => undefined, @@ -2147,7 +2148,11 @@ export const zSetSessionConfigOptionResponse = z.object({ }); /** - * Unique identifier for a message within a session. + * Identifier for a message, unique among messages of the same type within a session. + * + * Each message type, such as user messages, agent messages, and agent thoughts, + * has its own ID space: messages of different types may share an ID and remain + * distinct messages. */ export const zMessageId = z.string(); @@ -2536,18 +2541,11 @@ export const zRunningStateUpdate = z.object({ }); /** - * Reasons why an agent stops active session work. - * - * See protocol docs: [Stop Reasons](https://agentclientprotocol.com/protocol/v2/draft/prompt-lifecycle#stop-reasons) + * Details of a failure that ended active work. */ -export const zStopReason = z.union([ - z.literal("end_turn"), - z.literal("max_tokens"), - z.literal("max_turn_requests"), - z.literal("refusal"), - z.literal("cancelled"), - z.string(), -]); +export const zErrorStopReason = z.object({ + error: defaultOnError(zError.nullish(), () => undefined), +}); /** * **UNSTABLE** @@ -2573,15 +2571,78 @@ export const zUsage = z.object({ /** * The agent is ready to process a new prompt. + * + * Agents SHOULD include a `stopReason` when the idle transition ends foreground + * work. An omitted, `null`, or malformed `stopReason` means the agent is not + * reporting one. + * + * Custom variants (unknown `stopReason` values) keep their extra + * properties exactly as received; unlike known variants, those keys + * bypass lenient-field salvage and arrive unvalidated. */ -export const zIdleStateUpdate = z.object({ - stopReason: defaultOnError(zStopReason.nullish(), () => undefined), - usage: defaultOnError(zUsage.nullish(), () => undefined), - _meta: defaultOnError( - z.record(z.string(), z.unknown()).nullish(), - () => undefined, - ), -}); +export const zIdleStateUpdate = defaultOnErrorOptionalStringTag( + preserveCustomPayload( + z.intersection( + z.union([ + z.object({ + stopReason: z.literal("end_turn"), + }), + z.object({ + stopReason: z.literal("max_tokens"), + }), + z.object({ + stopReason: z.literal("max_turn_requests"), + }), + z.object({ + stopReason: z.literal("refusal"), + }), + z.object({ + stopReason: z.literal("cancelled"), + }), + zErrorStopReason.and( + z.object({ + stopReason: z.literal("error"), + }), + ), + excludeKnownTags( + z.object({ + stopReason: z.string(), + }), + "stopReason", + [ + "cancelled", + "end_turn", + "error", + "max_tokens", + "max_turn_requests", + "refusal", + ], + ), + z.object({ + stopReason: z.null().optional(), + }), + ]), + z.object({ + usage: defaultOnError(zUsage.nullish(), () => undefined), + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), + stopReason: defaultOnError(z.string().nullish(), () => undefined), + }), + ), + "stopReason", + [ + "cancelled", + "end_turn", + "error", + "max_tokens", + "max_turn_requests", + "refusal", + ], + ), + "stopReason", +); /** * Foreground work is blocked on user action. @@ -2983,13 +3044,7 @@ export const zUsageUpdate = z.object({ }); /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * Severity hint for a session notice. - * - * @experimental */ export const zNoticeSeverity = z.union([ z.literal("info"), @@ -2999,19 +3054,13 @@ export const zNoticeSeverity = z.union([ ]); /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * - * Fire-and-forget advisory information for the user. + * Fire-and-forget information for the user. * * Notices are live events rather than session history. Agents must not rely on * a notice being received, displayed, or seen by the user. * No Client capability is required, and unsupported Clients may ignore notices. * * See RFD: [Session Notices](https://agentclientprotocol.com/rfds/session-notices) - * - * @experimental */ export const zNotice = z.object({ severity: zNoticeSeverity, @@ -3024,24 +3073,12 @@ export const zNotice = z.object({ }); /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * Unique identifier for a context compaction within a session. - * - * @experimental */ export const zCompactionId = z.string(); /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * Lifecycle state of a context compaction. - * - * @experimental */ export const zCompactionStatus = z.union([ z.literal("in_progress"), @@ -3052,19 +3089,12 @@ export const zCompactionStatus = z.union([ ]); /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * - * A context compaction upsert. The first update fixes the compaction's + * A context compaction upsert. The first notification fixes the compaction's * timeline position. Later updates with the same ID patch that entity in place. * * `summary`, `error`, and `_meta` have patch semantics: omission leaves the * stored value unchanged, `null` clears it, and a concrete value replaces it. - * `summary: []` also clears the retained summary. A non-empty summary is only - * valid with `completed`; `error` is only valid with `failed`. - * - * @experimental + * `summary: []` also clears the summary. */ export const zCompactionUpdate = z.object({ compactionId: zCompactionId, @@ -3081,15 +3111,8 @@ export const zCompactionUpdate = z.object({ }); /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * - * A content block appended to the retained summary of an in-progress - * compaction. Agents send chunks only after an `in_progress` update and before - * the terminal update for the same ID. - * - * @experimental + * A content block appended to a compaction's summary. A first-seen ID creates + * an in-progress compaction. Chunks append in receive order. */ export const zCompactionSummaryChunk = z.object({ compactionId: zCompactionId, @@ -3826,11 +3849,8 @@ export const zMcpServer = preserveCustomPayload( */ export const zNewSessionRequest = z.object({ cwd: zAbsolutePath, - additionalDirectories: defaultOnError( - vecSkipError(zAbsolutePath).optional(), - () => [], - ), - mcpServers: defaultOnError(vecSkipError(zMcpServer).optional(), () => []), + additionalDirectories: z.array(zAbsolutePath).optional(), + mcpServers: z.array(zMcpServer).optional(), _meta: defaultOnError( z.record(z.string(), z.unknown()).nullish(), () => undefined, @@ -3879,11 +3899,8 @@ export const zDeleteSessionRequest = z.object({ export const zForkSessionRequest = z.object({ sessionId: zSessionId, cwd: zAbsolutePath, - additionalDirectories: defaultOnError( - vecSkipError(zAbsolutePath).optional(), - () => [], - ), - mcpServers: defaultOnError(vecSkipError(zMcpServer).optional(), () => []), + additionalDirectories: z.array(zAbsolutePath).optional(), + mcpServers: z.array(zMcpServer).optional(), _meta: defaultOnError( z.record(z.string(), z.unknown()).nullish(), () => undefined, @@ -3941,12 +3958,9 @@ export const zReplayFrom = preserveCustomPayload( export const zResumeSessionRequest = z.object({ sessionId: zSessionId, cwd: zAbsolutePath, - additionalDirectories: defaultOnError( - vecSkipError(zAbsolutePath).optional(), - () => [], - ), - mcpServers: defaultOnError(vecSkipError(zMcpServer).optional(), () => []), - replayFrom: defaultOnError(zReplayFrom.nullish(), () => undefined), + additionalDirectories: z.array(zAbsolutePath).optional(), + mcpServers: z.array(zMcpServer).optional(), + replayFrom: zReplayFrom.nullish(), _meta: defaultOnError( z.record(z.string(), z.unknown()).nullish(), () => undefined, @@ -4488,10 +4502,7 @@ export const zDidChangeDocumentNotification = z.object({ sessionId: zSessionId, uri: z.url(), version: z.number(), - contentChanges: requiredDefaultOnError( - vecSkipError(zTextDocumentContentChangeEvent), - () => [], - ), + contentChanges: z.array(zTextDocumentContentChangeEvent), _meta: defaultOnError( z.record(z.string(), z.unknown()).nullish(), () => undefined,