From 40499959b705162d2a6c4eee8444fdd61788f5bd Mon Sep 17 00:00:00 2001 From: cccc <132337734+2725244134@users.noreply.github.com> Date: Sun, 2 Nov 2025 23:20:55 +0800 Subject: [PATCH 1/3] refactor: specify type annotation for error handling methods in RequestError class (#17) --- src/acp/exceptions.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/acp/exceptions.py b/src/acp/exceptions.py index c9a4dd9..898ca40 100644 --- a/src/acp/exceptions.py +++ b/src/acp/exceptions.py @@ -14,11 +14,11 @@ def __init__(self, code: int, message: str, data: Any | None = None) -> None: self.data = data @staticmethod - def parse_error(data: dict | None = None) -> RequestError: + def parse_error(data: dict[str, Any] | None = None) -> RequestError: return RequestError(-32700, "Parse error", data) @staticmethod - def invalid_request(data: dict | None = None) -> RequestError: + def invalid_request(data: dict[str, Any] | None = None) -> RequestError: return RequestError(-32600, "Invalid request", data) @staticmethod @@ -26,15 +26,15 @@ def method_not_found(method: str) -> RequestError: return RequestError(-32601, "Method not found", {"method": method}) @staticmethod - def invalid_params(data: dict | None = None) -> RequestError: + def invalid_params(data: dict[str, Any] | None = None) -> RequestError: return RequestError(-32602, "Invalid params", data) @staticmethod - def internal_error(data: dict | None = None) -> RequestError: + def internal_error(data: dict[str, Any] | None = None) -> RequestError: return RequestError(-32603, "Internal error", data) @staticmethod - def auth_required(data: dict | None = None) -> RequestError: + def auth_required(data: dict[str, Any] | None = None) -> RequestError: return RequestError(-32000, "Authentication required", data) @staticmethod From 893fcf46af5d8734dd6014c8ad11295625d930b6 Mon Sep 17 00:00:00 2001 From: Chojan Shang Date: Sun, 2 Nov 2025 23:59:47 +0800 Subject: [PATCH 2/3] fix: preserve _meta during ACP serialization (#20) Signed-off-by: Chojan Shang --- examples/echo_agent.py | 16 +++++++++------- scripts/gen_schema.py | 32 ++++++++++++++++++++++++++++++++ src/acp/connection.py | 7 ++++++- src/acp/schema.py | 7 +++++-- src/acp/utils.py | 2 +- tests/test_utils.py | 38 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 91 insertions(+), 11 deletions(-) create mode 100644 tests/test_utils.py diff --git a/examples/echo_agent.py b/examples/echo_agent.py index 1bf04ff..657eb28 100644 --- a/examples/echo_agent.py +++ b/examples/echo_agent.py @@ -29,13 +29,15 @@ async def newSession(self, params: NewSessionRequest) -> NewSessionResponse: async def prompt(self, params: PromptRequest) -> PromptResponse: for block in params.prompt: - text = getattr(block, "text", "") - await self._conn.sessionUpdate( - session_notification( - params.sessionId, - update_agent_message(text_block(text)), - ) - ) + text = block.get("text", "") if isinstance(block, dict) else getattr(block, "text", "") + chunk = update_agent_message(text_block(text)) + chunk.field_meta = {"echo": True} + chunk.content.field_meta = {"echo": True} + + notification = session_notification(params.sessionId, chunk) + notification.field_meta = {"source": "echo_agent"} + + await self._conn.sessionUpdate(notification) return PromptResponse(stopReason="end_turn") diff --git a/scripts/gen_schema.py b/scripts/gen_schema.py index c61e3c3..b95362e 100644 --- a/scripts/gen_schema.py +++ b/scripts/gen_schema.py @@ -199,6 +199,7 @@ def rename_types(output_path: Path) -> list[str]: content = _apply_field_overrides(content) content = _apply_default_overrides(content) content = _add_description_comments(content) + content = _ensure_custom_base_model(content) alias_lines = [f"{old} = {new}" for old, new in sorted(RENAME_MAP.items())] alias_block = BACKCOMPAT_MARKER + "\n" + "\n".join(alias_lines) + "\n" @@ -220,6 +221,37 @@ def rename_types(output_path: Path) -> list[str]: return warnings +def _ensure_custom_base_model(content: str) -> str: + if "class BaseModel(_BaseModel):" in content: + return content + lines = content.splitlines() + for idx, line in enumerate(lines): + if not line.startswith("from pydantic import "): + continue + imports = [part.strip() for part in line[len("from pydantic import ") :].split(",")] + has_alias = any(part == "BaseModel as _BaseModel" for part in imports) + has_config = any(part == "ConfigDict" for part in imports) + new_imports = [] + for part in imports: + if part == "BaseModel": + new_imports.append("BaseModel as _BaseModel") + has_alias = True + else: + new_imports.append(part) + if not has_alias: + new_imports.append("BaseModel as _BaseModel") + if not has_config: + new_imports.append("ConfigDict") + lines[idx] = "from pydantic import " + ", ".join(new_imports) + insert_idx = idx + 1 + lines.insert(insert_idx, "") + lines.insert(insert_idx + 1, "class BaseModel(_BaseModel):") + lines.insert(insert_idx + 2, " model_config = ConfigDict(populate_by_name=True)") + lines.insert(insert_idx + 3, "") + break + return "\n".join(lines) + "\n" + + def _apply_field_overrides(content: str) -> str: for class_name, field_name, new_type, optional in FIELD_TYPE_OVERRIDES: if optional: diff --git a/src/acp/connection.py b/src/acp/connection.py index 0b3230e..34142d7 100644 --- a/src/acp/connection.py +++ b/src/acp/connection.py @@ -192,7 +192,12 @@ async def _run_request(self, message: dict[str, Any]) -> Any: try: result = await self._handler(method, message.get("params"), False) if isinstance(result, BaseModel): - result = result.model_dump() + result = result.model_dump( + mode="json", + by_alias=True, + exclude_none=True, + exclude_unset=True, + ) payload["result"] = result if result is not None else None await self._sender.send(payload) self._notify_observers(StreamDirection.OUTGOING, payload) diff --git a/src/acp/schema.py b/src/acp/schema.py index b65a63e..d9e2da1 100644 --- a/src/acp/schema.py +++ b/src/acp/schema.py @@ -6,8 +6,7 @@ from enum import Enum from typing import Annotated, Any, List, Literal, Optional, Union -from pydantic import BaseModel, Field, RootModel - +from pydantic import BaseModel as _BaseModel, Field, RootModel, ConfigDict PermissionOptionKind = Literal["allow_once", "allow_always", "reject_once", "reject_always"] PlanEntryPriority = Literal["high", "medium", "low"] @@ -17,6 +16,10 @@ ToolKind = Literal["read", "edit", "delete", "move", "search", "execute", "think", "fetch", "switch_mode", "other"] +class BaseModel(_BaseModel): + model_config = ConfigDict(populate_by_name=True) + + class Jsonrpc(Enum): field_2_0 = "2.0" diff --git a/src/acp/utils.py b/src/acp/utils.py index b84a5a7..e81d7ba 100644 --- a/src/acp/utils.py +++ b/src/acp/utils.py @@ -24,7 +24,7 @@ def serialize_params(params: BaseModel) -> dict[str, Any]: """Return a JSON-serializable representation used for RPC calls.""" - return params.model_dump(exclude_none=True, exclude_defaults=True) + return params.model_dump(by_alias=True, exclude_none=True, exclude_defaults=True) def normalize_result(payload: Any) -> dict[str, Any]: diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..fbbf08e --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,38 @@ +from acp.schema import AgentMessageChunk, TextContentBlock +from acp.utils import serialize_params + + +def test_serialize_params_uses_meta_aliases() -> None: + chunk = AgentMessageChunk( + sessionUpdate="agent_message_chunk", + content=TextContentBlock(type="text", text="demo", field_meta={"inner": "value"}), + field_meta={"outer": "value"}, + ) + + payload = serialize_params(chunk) + + assert payload["_meta"] == {"outer": "value"} + assert payload["content"]["_meta"] == {"inner": "value"} + + +def test_serialize_params_omits_meta_when_absent() -> None: + chunk = AgentMessageChunk( + sessionUpdate="agent_message_chunk", + content=TextContentBlock(type="text", text="demo"), + ) + + payload = serialize_params(chunk) + + assert "_meta" not in payload + assert "_meta" not in payload["content"] + + +def test_field_meta_can_be_set_by_name_on_models() -> None: + chunk = AgentMessageChunk( + sessionUpdate="agent_message_chunk", + content=TextContentBlock(type="text", text="demo", field_meta={"inner": "value"}), + field_meta={"outer": "value"}, + ) + + assert chunk.field_meta == {"outer": "value"} + assert chunk.content.field_meta == {"inner": "value"} From e203729dbdf23f2512d699610d97c9f72991a86e Mon Sep 17 00:00:00 2001 From: Chojan Shang Date: Tue, 4 Nov 2025 04:05:38 +0800 Subject: [PATCH 3/3] feat: follow agent-client-protocol 0.6.3 (#21) * feat: bump protocol to 0.6.3 Signed-off-by: Chojan Shang * fix: make everything happy Signed-off-by: Chojan Shang * refactor: make gen_schema step by step Signed-off-by: Chojan Shang * chore: bump to 0.6.3 Signed-off-by: Chojan Shang --------- Signed-off-by: Chojan Shang --- pyproject.toml | 2 +- schema/VERSION | 2 +- schema/schema.json | 19 ++++- scripts/gen_schema.py | 172 +++++++++++++++++++++++++++++++++++------- src/acp/meta.py | 2 +- src/acp/schema.py | 18 +++-- uv.lock | 2 +- 7 files changed, 178 insertions(+), 39 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b71a716..2b1ff29 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "agent-client-protocol" -version = "0.6.2" +version = "0.6.3" description = "A Python implement of Agent Client Protocol (ACP, by Zed Industries)" authors = [{ name = "Chojan Shang", email = "psiace@apache.org" }] readme = "README.md" diff --git a/schema/VERSION b/schema/VERSION index 0e55109..75451e3 100644 --- a/schema/VERSION +++ b/schema/VERSION @@ -1 +1 @@ -refs/tags/v0.6.2 +refs/tags/v0.6.3 diff --git a/schema/schema.json b/schema/schema.json index e94b521..9b39020 100644 --- a/schema/schema.json +++ b/schema/schema.json @@ -708,9 +708,12 @@ }, "ContentBlock": { "description": "Content blocks represent displayable information in the Agent Client Protocol.\n\nThey provide a structured way to handle various types of user-facing content\u2014whether\nit's text from language models, images for analysis, or embedded resources for context.\n\nContent blocks appear in:\n- User prompts sent via `session/prompt`\n- Language model output streamed through `session/update` notifications\n- Progress updates and results from tool calls\n\nThis structure is compatible with the Model Context Protocol (MCP), enabling\nagents to seamlessly forward content from MCP tool outputs without transformation.\n\nSee protocol docs: [Content](https://agentclientprotocol.com/protocol/content)", + "discriminator": { + "propertyName": "type" + }, "oneOf": [ { - "description": "Plain text content\n\nAll agents MUST support text content blocks in prompts.", + "description": "Text content. May be plain text or formatted with Markdown.\n\nAll agents MUST support text content blocks in prompts.\nClients SHOULD render this text as Markdown.", "properties": { "_meta": { "description": "Extension point for implementations" @@ -1406,7 +1409,10 @@ "type": "object" } ], - "description": "Configuration for connecting to an MCP (Model Context Protocol) server.\n\nMCP servers provide tools and context that the agent can use when\nprocessing prompts.\n\nSee protocol docs: [MCP Servers](https://agentclientprotocol.com/protocol/session-setup#mcp-servers)" + "description": "Configuration for connecting to an MCP (Model Context Protocol) server.\n\nMCP servers provide tools and context that the agent can use when\nprocessing prompts.\n\nSee protocol docs: [MCP Servers](https://agentclientprotocol.com/protocol/session-setup#mcp-servers)", + "discriminator": { + "propertyName": "type" + } }, "ModelId": { "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA unique identifier for a model.", @@ -1796,6 +1802,9 @@ }, "RequestPermissionOutcome": { "description": "The outcome of a permission request.", + "discriminator": { + "propertyName": "outcome" + }, "oneOf": [ { "description": "The prompt turn was cancelled before the user responded.\n\nWhen a client sends a `session/cancel` notification to cancel an ongoing\nprompt turn, it MUST respond to all pending `session/request_permission`\nrequests with this `Cancelled` outcome.\n\nSee protocol docs: [Cancellation](https://agentclientprotocol.com/protocol/prompt-turn#cancellation)", @@ -2060,6 +2069,9 @@ }, "SessionUpdate": { "description": "Different types of updates that can be sent during session processing.\n\nThese updates provide real-time feedback about the agent's progress.\n\nSee protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protocol/prompt-turn#3-agent-reports-output)", + "discriminator": { + "propertyName": "sessionUpdate" + }, "oneOf": [ { "description": "A chunk of the user's message being streamed.", @@ -2530,6 +2542,9 @@ }, "ToolCallContent": { "description": "Content produced by a tool call.\n\nTool calls can produce different types of content including\nstandard content blocks (text, images) or file diffs.\n\nSee protocol docs: [Content](https://agentclientprotocol.com/protocol/tool-calls#content)", + "discriminator": { + "propertyName": "type" + }, "oneOf": [ { "description": "Standard content block (text, images, resources).", diff --git a/scripts/gen_schema.py b/scripts/gen_schema.py index b95362e..0badf30 100644 --- a/scripts/gen_schema.py +++ b/scripts/gen_schema.py @@ -3,11 +3,13 @@ import argparse import ast +import json import re import shutil import subprocess import sys from collections.abc import Callable +from dataclasses import dataclass from pathlib import Path ROOT = Path(__file__).resolve().parents[1] @@ -25,6 +27,13 @@ re.DOTALL, ) +STDIO_TYPE_LITERAL = 'Literal["2#-datamodel-code-generator-#-object-#-special-#"]' +STDIO_TYPE_PATTERN = re.compile( + r"^ type:\s*Literal\[['\"]2#-datamodel-code-generator-#-object-#-special-#['\"]\]" + r"(?:\s*=\s*['\"][^'\"]+['\"])?\s*$", + re.MULTILINE, +) + # Map of numbered classes produced by datamodel-code-generator to descriptive names. # Keep this in sync with the Rust/TypeScript SDK nomenclature. RENAME_MAP: dict[str, str] = { @@ -109,6 +118,14 @@ ) +@dataclass(frozen=True) +class _ProcessingStep: + """A named transformation applied to the generated schema content.""" + + name: str + apply: Callable[[str], str] + + def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Generate src/acp/schema.py from the ACP JSON schema.") parser.add_argument( @@ -159,7 +176,7 @@ def generate_schema(*, format_output: bool = True) -> None: ] subprocess.check_call(cmd) # noqa: S603 - warnings = rename_types(SCHEMA_OUT) + warnings = postprocess_generated_schema(SCHEMA_OUT) for warning in warnings: print(f"Warning: {warning}", file=sys.stderr) @@ -167,60 +184,150 @@ def generate_schema(*, format_output: bool = True) -> None: format_with_ruff(SCHEMA_OUT) -def rename_types(output_path: Path) -> list[str]: +def postprocess_generated_schema(output_path: Path) -> list[str]: if not output_path.exists(): raise RuntimeError(f"Generated schema not found at {output_path}") # noqa: TRY003 - content = output_path.read_text(encoding="utf-8") + raw_content = output_path.read_text(encoding="utf-8") + header_block = _build_header_block() + + content = _strip_existing_header(raw_content) + content = _remove_backcompat_block(content) + content, leftover_classes = _rename_numbered_models(content) + + processing_steps: tuple[_ProcessingStep, ...] = ( + _ProcessingStep("apply field overrides", _apply_field_overrides), + _ProcessingStep("apply default overrides", _apply_default_overrides), + _ProcessingStep("normalize stdio literal", _normalize_stdio_model), + _ProcessingStep("attach description comments", _add_description_comments), + _ProcessingStep("ensure custom BaseModel", _ensure_custom_base_model), + ) + + for step in processing_steps: + content = step.apply(content) + + missing_targets = _find_missing_targets(content) + + content = _inject_enum_aliases(content) + alias_block = _build_alias_block() + final_content = header_block + content.rstrip() + "\n\n" + alias_block + if not final_content.endswith("\n"): + final_content += "\n" + output_path.write_text(final_content, encoding="utf-8") + + warnings: list[str] = [] + if leftover_classes: + warnings.append( + "Unrenamed schema models detected: " + + ", ".join(leftover_classes) + + ". Update RENAME_MAP in scripts/gen_schema.py." + ) + if missing_targets: + warnings.append( + "Renamed schema targets not found after generation: " + + ", ".join(sorted(missing_targets)) + + ". Check RENAME_MAP or upstream schema changes." + ) + warnings.extend(_validate_schema_alignment()) + + return warnings + +def _build_header_block() -> str: header_lines = ["# Generated from schema/schema.json. Do not edit by hand."] if VERSION_FILE.exists(): ref = VERSION_FILE.read_text(encoding="utf-8").strip() if ref: header_lines.append(f"# Schema ref: {ref}") + return "\n".join(header_lines) + "\n\n" + + +def _build_alias_block() -> str: + alias_lines = [f"{old} = {new}" for old, new in sorted(RENAME_MAP.items())] + return BACKCOMPAT_MARKER + "\n" + "\n".join(alias_lines) + "\n" + +def _strip_existing_header(content: str) -> str: existing_header = re.match(r"(#.*\n)+", content) if existing_header: - content = content[existing_header.end() :] - content = content.lstrip("\n") + return content[existing_header.end() :].lstrip("\n") + return content.lstrip("\n") + +def _remove_backcompat_block(content: str) -> str: marker_index = content.find(BACKCOMPAT_MARKER) if marker_index != -1: - content = content[:marker_index].rstrip() + return content[:marker_index].rstrip() + return content + +def _rename_numbered_models(content: str) -> tuple[str, list[str]]: + renamed = content for old, new in sorted(RENAME_MAP.items(), key=lambda item: len(item[0]), reverse=True): pattern = re.compile(rf"\b{re.escape(old)}\b") - content = pattern.sub(new, content) + renamed = pattern.sub(new, renamed) leftover_class_pattern = re.compile(r"^class (\w+\d+)\(", re.MULTILINE) - leftover_classes = sorted(set(leftover_class_pattern.findall(content))) + leftover_classes = sorted(set(leftover_class_pattern.findall(renamed))) + return renamed, leftover_classes - header_block = "\n".join(header_lines) + "\n\n" - content = _apply_field_overrides(content) - content = _apply_default_overrides(content) - content = _add_description_comments(content) - content = _ensure_custom_base_model(content) - alias_lines = [f"{old} = {new}" for old, new in sorted(RENAME_MAP.items())] - alias_block = BACKCOMPAT_MARKER + "\n" + "\n".join(alias_lines) + "\n" +def _find_missing_targets(content: str) -> list[str]: + missing: list[str] = [] + for new_name in RENAME_MAP.values(): + pattern = re.compile(rf"^class {re.escape(new_name)}\(", re.MULTILINE) + if not pattern.search(content): + missing.append(new_name) + return missing - content = _inject_enum_aliases(content) - content = header_block + content.rstrip() + "\n\n" + alias_block - if not content.endswith("\n"): - content += "\n" - output_path.write_text(content, encoding="utf-8") +def _validate_schema_alignment() -> list[str]: warnings: list[str] = [] - if leftover_classes: - warnings.append( - "Unrenamed schema models detected: " - + ", ".join(leftover_classes) - + ". Update RENAME_MAP in scripts/gen_schema.py." - ) + if not SCHEMA_JSON.exists(): + warnings.append("schema/schema.json missing; unable to validate enum aliases.") + return warnings + try: + schema_enums = _load_schema_enum_literals() + except json.JSONDecodeError as exc: + warnings.append(f"Failed to parse schema/schema.json: {exc}") + return warnings + + for enum_name, expected_values in ENUM_LITERAL_MAP.items(): + schema_values = schema_enums.get(enum_name) + if schema_values is None: + warnings.append( + f"Enum '{enum_name}' not found in schema.json; update ENUM_LITERAL_MAP or investigate schema changes." + ) + continue + if tuple(schema_values) != expected_values: + warnings.append( + f"Enum mismatch for '{enum_name}': schema.json -> {schema_values}, generated aliases -> {expected_values}" + ) return warnings +def _load_schema_enum_literals() -> dict[str, tuple[str, ...]]: + schema_data = json.loads(SCHEMA_JSON.read_text(encoding="utf-8")) + defs = schema_data.get("$defs", {}) + enum_literals: dict[str, tuple[str, ...]] = {} + + for name, definition in defs.items(): + values: list[str] = [] + if "enum" in definition: + values = [str(item) for item in definition["enum"]] + elif "oneOf" in definition: + values = [ + str(option["const"]) + for option in definition.get("oneOf", []) + if isinstance(option, dict) and "const" in option + ] + if values: + enum_literals[name] = tuple(values) + + return enum_literals + + def _ensure_custom_base_model(content: str) -> str: if "class BaseModel(_BaseModel):" in content: return content @@ -323,6 +430,19 @@ def replace_block( return content +def _normalize_stdio_model(content: str) -> str: + replacement_line = ' type: Literal["stdio"] = "stdio"' + new_content, count = STDIO_TYPE_PATTERN.subn(replacement_line, content) + if count == 0: + return content + if count > 1: + print( + "Warning: multiple stdio type placeholders detected; manual review required.", + file=sys.stderr, + ) + return new_content + + def _add_description_comments(content: str) -> str: lines = content.splitlines() new_lines: list[str] = [] diff --git a/src/acp/meta.py b/src/acp/meta.py index dad9daf..2b67512 100644 --- a/src/acp/meta.py +++ b/src/acp/meta.py @@ -1,5 +1,5 @@ # Generated from schema/meta.json. Do not edit by hand. -# Schema ref: refs/tags/v0.6.2 +# Schema ref: refs/tags/v0.6.3 AGENT_METHODS = {'authenticate': 'authenticate', 'initialize': 'initialize', 'session_cancel': 'session/cancel', 'session_load': 'session/load', 'session_new': 'session/new', 'session_prompt': 'session/prompt', 'session_set_mode': 'session/set_mode', 'session_set_model': 'session/set_model'} CLIENT_METHODS = {'fs_read_text_file': 'fs/read_text_file', 'fs_write_text_file': 'fs/write_text_file', 'session_request_permission': 'session/request_permission', 'session_update': 'session/update', 'terminal_create': 'terminal/create', 'terminal_kill': 'terminal/kill', 'terminal_output': 'terminal/output', 'terminal_release': 'terminal/release', 'terminal_wait_for_exit': 'terminal/wait_for_exit'} PROTOCOL_VERSION = 1 diff --git a/src/acp/schema.py b/src/acp/schema.py index d9e2da1..4814f08 100644 --- a/src/acp/schema.py +++ b/src/acp/schema.py @@ -1,5 +1,5 @@ # Generated from schema/schema.json. Do not edit by hand. -# Schema ref: refs/tags/v0.6.2 +# Schema ref: refs/tags/v0.6.3 from __future__ import annotations @@ -244,6 +244,7 @@ class StdioMcpServer(BaseModel): ] # Human-readable name identifying this MCP server. name: Annotated[str, Field(description="Human-readable name identifying this MCP server.")] + type: Literal["stdio"] = "stdio" class ModelInfo(BaseModel): @@ -336,7 +337,10 @@ class RequestPermissionResponse(BaseModel): # The user's decision on the permission request. outcome: Annotated[ Union[DeniedOutcome, AllowedOutcome], - Field(description="The user's decision on the permission request."), + Field( + description="The user's decision on the permission request.", + discriminator="outcome", + ), ] @@ -1195,7 +1199,7 @@ class UserMessageChunk(BaseModel): Union[ TextContentBlock, ImageContentBlock, AudioContentBlock, ResourceContentBlock, EmbeddedResourceContentBlock ], - Field(description="A single item of content"), + Field(description="A single item of content", discriminator="type"), ] sessionUpdate: Literal["user_message_chunk"] @@ -1211,7 +1215,7 @@ class AgentMessageChunk(BaseModel): Union[ TextContentBlock, ImageContentBlock, AudioContentBlock, ResourceContentBlock, EmbeddedResourceContentBlock ], - Field(description="A single item of content"), + Field(description="A single item of content", discriminator="type"), ] sessionUpdate: Literal["agent_message_chunk"] @@ -1227,7 +1231,7 @@ class AgentThoughtChunk(BaseModel): Union[ TextContentBlock, ImageContentBlock, AudioContentBlock, ResourceContentBlock, EmbeddedResourceContentBlock ], - Field(description="A single item of content"), + Field(description="A single item of content", discriminator="type"), ] sessionUpdate: Literal["agent_thought_chunk"] @@ -1238,7 +1242,7 @@ class ContentToolCallContent(BaseModel): Union[ TextContentBlock, ImageContentBlock, AudioContentBlock, ResourceContentBlock, EmbeddedResourceContentBlock ], - Field(description="The actual content block."), + Field(description="The actual content block.", discriminator="type"), ] type: Literal["content"] @@ -1457,7 +1461,7 @@ class SessionNotification(BaseModel): AvailableCommandsUpdate, CurrentModeUpdate, ], - Field(description="The actual update content."), + Field(description="The actual update content.", discriminator="sessionUpdate"), ] diff --git a/uv.lock b/uv.lock index 304d15a..ff5c1a4 100644 --- a/uv.lock +++ b/uv.lock @@ -4,7 +4,7 @@ requires-python = ">=3.10, <4.0" [[package]] name = "agent-client-protocol" -version = "0.6.2" +version = "0.6.3" source = { editable = "." } dependencies = [ { name = "pydantic" },