diff --git a/docs/setup/local-cli.md b/docs/setup/local-cli.md index 48092b735..b7f8de04c 100644 --- a/docs/setup/local-cli.md +++ b/docs/setup/local-cli.md @@ -99,7 +99,11 @@ func main() { session, _ := client.CreateSession(ctx, &copilot.SessionConfig{Model: "gpt-4.1"}) response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Hello!"}) - fmt.Println(*response.Data.Content) + if response != nil { + if d, ok := response.Data.(*copilot.AssistantMessageData); ok { + fmt.Println(d.Content) + } + } } ``` @@ -115,7 +119,11 @@ defer client.Stop() session, _ := client.CreateSession(ctx, &copilot.SessionConfig{Model: "gpt-4.1"}) response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Hello!"}) -fmt.Println(*response.Data.Content) +if response != nil { + if d, ok := response.Data.(*copilot.AssistantMessageData); ok { + fmt.Println(d.Content) + } +} ``` diff --git a/python/README.md b/python/README.md index a023c6102..b303ab9bc 100644 --- a/python/README.md +++ b/python/README.md @@ -25,8 +25,10 @@ python chat.py ```python import asyncio +from typing import cast + from copilot import CopilotClient -from copilot.session import PermissionHandler +from copilot.generated.session_events import AssistantMessageData async def main(): # Client automatically starts on enter and cleans up on exit @@ -38,7 +40,7 @@ async def main(): def on_event(event): if event.type.value == "assistant.message": - print(event.data.content) + print(cast(AssistantMessageData, event.data).content) elif event.type.value == "session.idle": done.set() @@ -57,7 +59,10 @@ If you need more control over the lifecycle, you can call `start()`, `stop()`, a ```python import asyncio +from typing import cast + from copilot import CopilotClient +from copilot.generated.session_events import AssistantMessageData from copilot.session import PermissionHandler async def main(): @@ -74,7 +79,7 @@ async def main(): def on_event(event): if event.type.value == "assistant.message": - print(event.data.content) + print(cast(AssistantMessageData, event.data).content) elif event.type.value == "session.idle": done.set() @@ -333,7 +338,15 @@ Enable streaming to receive assistant response chunks as they're generated: ```python import asyncio +from typing import cast + from copilot import CopilotClient +from copilot.generated.session_events import ( + AssistantMessageData, + AssistantMessageDeltaData, + AssistantReasoningData, + AssistantReasoningDeltaData, +) from copilot.session import PermissionHandler async def main(): @@ -350,20 +363,20 @@ async def main(): match event.type.value: case "assistant.message_delta": # Streaming message chunk - print incrementally - delta = event.data.delta_content or "" + delta = cast(AssistantMessageDeltaData, event.data).delta_content or "" print(delta, end="", flush=True) case "assistant.reasoning_delta": # Streaming reasoning chunk (if model supports reasoning) - delta = event.data.delta_content or "" + delta = cast(AssistantReasoningDeltaData, event.data).delta_content or "" print(delta, end="", flush=True) case "assistant.message": # Final message - complete content print("\n--- Final message ---") - print(event.data.content) + print(cast(AssistantMessageData, event.data).content) case "assistant.reasoning": # Final reasoning content (if model supports reasoning) print("--- Reasoning ---") - print(event.data.content) + print(cast(AssistantReasoningData, event.data).content) case "session.idle": # Session finished processing done.set() diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index dea0e79fd..d07ae6c50 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -3,13 +3,16 @@ Generated from: session-events.schema.json """ -from enum import Enum +from __future__ import annotations + +from collections.abc import Callable from dataclasses import dataclass -from typing import Any, TypeVar, Callable, cast from datetime import datetime +from enum import Enum +from typing import Any, TypeVar, cast from uuid import UUID -import dateutil.parser +import dateutil.parser T = TypeVar("T") EnumT = TypeVar("EnumT", bound=Enum) @@ -20,9 +23,24 @@ def from_str(x: Any) -> str: return x -def from_list(f: Callable[[Any], T], x: Any) -> list[T]: - assert isinstance(x, list) - return [f(y) for y in x] +def from_int(x: Any) -> int: + assert isinstance(x, int) and not isinstance(x, bool) + return x + + +def to_int(x: Any) -> int: + assert isinstance(x, int) and not isinstance(x, bool) + return x + + +def from_float(x: Any) -> float: + assert isinstance(x, (float, int)) and not isinstance(x, bool) + return float(x) + + +def to_float(x: Any) -> float: + assert isinstance(x, (float, int)) and not isinstance(x, bool) + return float(x) def from_bool(x: Any) -> bool: @@ -35,7 +53,7 @@ def from_none(x: Any) -> Any: return x -def from_union(fs, x): +def from_union(fs: list[Callable[[Any], T]], x: Any) -> T: for f in fs: try: return f(x) @@ -44,14 +62,35 @@ def from_union(fs, x): assert False -def from_float(x: Any) -> float: - assert isinstance(x, (float, int)) and not isinstance(x, bool) - return float(x) +def from_list(f: Callable[[Any], T], x: Any) -> list[T]: + assert isinstance(x, list) + return [f(item) for item in x] -def to_float(x: Any) -> float: - assert isinstance(x, (int, float)) - return x +def from_dict(f: Callable[[Any], T], x: Any) -> dict[str, T]: + assert isinstance(x, dict) + return {key: f(value) for key, value in x.items()} + + +def from_datetime(x: Any) -> datetime: + return dateutil.parser.parse(from_str(x)) + + +def to_datetime(x: datetime) -> str: + return x.isoformat() + + +def from_uuid(x: Any) -> UUID: + return UUID(from_str(x)) + + +def to_uuid(x: UUID) -> str: + return str(x) + + +def parse_enum(c: type[EnumT], x: Any) -> EnumT: + assert isinstance(x, str) + return c(x) def to_class(c: type[T], x: Any) -> dict: @@ -59,3322 +98,4096 @@ def to_class(c: type[T], x: Any) -> dict: return cast(Any, x).to_dict() -def to_enum(c: type[EnumT], x: Any) -> EnumT: +def to_enum(c: type[EnumT], x: Any) -> str: assert isinstance(x, c) - return x.value + return cast(str, x.value) -def from_dict(f: Callable[[Any], T], x: Any) -> dict[str, T]: - assert isinstance(x, dict) - return { k: f(v) for (k, v) in x.items() } +class SessionEventType(Enum): + SESSION_START = "session.start" + SESSION_RESUME = "session.resume" + SESSION_REMOTE_STEERABLE_CHANGED = "session.remote_steerable_changed" + SESSION_ERROR = "session.error" + SESSION_IDLE = "session.idle" + SESSION_TITLE_CHANGED = "session.title_changed" + SESSION_INFO = "session.info" + SESSION_WARNING = "session.warning" + SESSION_MODEL_CHANGE = "session.model_change" + SESSION_MODE_CHANGED = "session.mode_changed" + SESSION_PLAN_CHANGED = "session.plan_changed" + SESSION_WORKSPACE_FILE_CHANGED = "session.workspace_file_changed" + SESSION_HANDOFF = "session.handoff" + SESSION_TRUNCATION = "session.truncation" + SESSION_SNAPSHOT_REWIND = "session.snapshot_rewind" + SESSION_SHUTDOWN = "session.shutdown" + SESSION_CONTEXT_CHANGED = "session.context_changed" + SESSION_USAGE_INFO = "session.usage_info" + SESSION_COMPACTION_START = "session.compaction_start" + SESSION_COMPACTION_COMPLETE = "session.compaction_complete" + SESSION_TASK_COMPLETE = "session.task_complete" + USER_MESSAGE = "user.message" + PENDING_MESSAGES_MODIFIED = "pending_messages.modified" + ASSISTANT_TURN_START = "assistant.turn_start" + ASSISTANT_INTENT = "assistant.intent" + ASSISTANT_REASONING = "assistant.reasoning" + ASSISTANT_REASONING_DELTA = "assistant.reasoning_delta" + ASSISTANT_STREAMING_DELTA = "assistant.streaming_delta" + ASSISTANT_MESSAGE = "assistant.message" + ASSISTANT_MESSAGE_DELTA = "assistant.message_delta" + ASSISTANT_TURN_END = "assistant.turn_end" + ASSISTANT_USAGE = "assistant.usage" + ABORT = "abort" + TOOL_USER_REQUESTED = "tool.user_requested" + TOOL_EXECUTION_START = "tool.execution_start" + TOOL_EXECUTION_PARTIAL_RESULT = "tool.execution_partial_result" + TOOL_EXECUTION_PROGRESS = "tool.execution_progress" + TOOL_EXECUTION_COMPLETE = "tool.execution_complete" + SKILL_INVOKED = "skill.invoked" + SUBAGENT_STARTED = "subagent.started" + SUBAGENT_COMPLETED = "subagent.completed" + SUBAGENT_FAILED = "subagent.failed" + SUBAGENT_SELECTED = "subagent.selected" + SUBAGENT_DESELECTED = "subagent.deselected" + HOOK_START = "hook.start" + HOOK_END = "hook.end" + SYSTEM_MESSAGE = "system.message" + SYSTEM_NOTIFICATION = "system.notification" + PERMISSION_REQUESTED = "permission.requested" + PERMISSION_COMPLETED = "permission.completed" + USER_INPUT_REQUESTED = "user_input.requested" + USER_INPUT_COMPLETED = "user_input.completed" + ELICITATION_REQUESTED = "elicitation.requested" + ELICITATION_COMPLETED = "elicitation.completed" + SAMPLING_REQUESTED = "sampling.requested" + SAMPLING_COMPLETED = "sampling.completed" + MCP_OAUTH_REQUIRED = "mcp.oauth_required" + MCP_OAUTH_COMPLETED = "mcp.oauth_completed" + EXTERNAL_TOOL_REQUESTED = "external_tool.requested" + EXTERNAL_TOOL_COMPLETED = "external_tool.completed" + COMMAND_QUEUED = "command.queued" + COMMAND_EXECUTE = "command.execute" + COMMAND_COMPLETED = "command.completed" + COMMANDS_CHANGED = "commands.changed" + CAPABILITIES_CHANGED = "capabilities.changed" + EXIT_PLAN_MODE_REQUESTED = "exit_plan_mode.requested" + EXIT_PLAN_MODE_COMPLETED = "exit_plan_mode.completed" + SESSION_TOOLS_UPDATED = "session.tools_updated" + SESSION_BACKGROUND_TASKS_CHANGED = "session.background_tasks_changed" + SESSION_SKILLS_LOADED = "session.skills_loaded" + SESSION_CUSTOM_AGENTS_UPDATED = "session.custom_agents_updated" + SESSION_MCP_SERVERS_LOADED = "session.mcp_servers_loaded" + SESSION_MCP_SERVER_STATUS_CHANGED = "session.mcp_server_status_changed" + SESSION_EXTENSIONS_LOADED = "session.extensions_loaded" + UNKNOWN = "unknown" + + @classmethod + def _missing_(cls, value: object) -> "SessionEventType": + return cls.UNKNOWN -def from_datetime(x: Any) -> datetime: - return dateutil.parser.parse(x) +@dataclass +class RawSessionEventData: + raw: Any + @staticmethod + def from_dict(obj: Any) -> "RawSessionEventData": + return RawSessionEventData(obj) -def from_int(x: Any) -> int: - assert isinstance(x, int) and not isinstance(x, bool) - return x + def to_dict(self) -> Any: + return self.raw -class Action(Enum): - """The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" - (dismissed) - """ - ACCEPT = "accept" - CANCEL = "cancel" - DECLINE = "decline" +def _compat_to_python_key(name: str) -> str: + normalized = name.replace(".", "_") + result: list[str] = [] + for index, char in enumerate(normalized): + if char.isupper() and index > 0 and (not normalized[index - 1].isupper() or (index + 1 < len(normalized) and normalized[index + 1].islower())): + result.append("_") + result.append(char.lower()) + return "".join(result) -class AgentMode(Enum): - """The agent mode that was active when this message was sent""" +def _compat_to_json_key(name: str) -> str: + parts = name.split("_") + if not parts: + return name + return parts[0] + "".join(part[:1].upper() + part[1:] for part in parts[1:]) - AUTOPILOT = "autopilot" - INTERACTIVE = "interactive" - PLAN = "plan" - SHELL = "shell" +def _compat_to_json_value(value: Any) -> Any: + if hasattr(value, "to_dict"): + return cast(Any, value).to_dict() + if isinstance(value, Enum): + return value.value + if isinstance(value, datetime): + return value.isoformat() + if isinstance(value, UUID): + return str(value) + if isinstance(value, list): + return [_compat_to_json_value(item) for item in value] + if isinstance(value, dict): + return {key: _compat_to_json_value(item) for key, item in value.items()} + return value -@dataclass -class Agent: - description: str - """Description of what the agent does""" - display_name: str - """Human-readable display name""" +def _compat_from_json_value(value: Any) -> Any: + return value - id: str - """Unique identifier for the agent""" - name: str - """Internal name of the agent""" +class Data: + """Backward-compatible shim for manually constructed event payloads.""" - source: str - """Source location: user, project, inherited, remote, or plugin""" + def __init__(self, **kwargs: Any): + self._values = {key: _compat_from_json_value(value) for key, value in kwargs.items()} + for key, value in self._values.items(): + setattr(self, key, value) - tools: list[str] - """List of tool names available to this agent""" + @staticmethod + def from_dict(obj: Any) -> "Data": + assert isinstance(obj, dict) + return Data(**{_compat_to_python_key(key): _compat_from_json_value(value) for key, value in obj.items()}) - user_invocable: bool - """Whether the agent can be selected by the user""" + def to_dict(self) -> dict: + return {_compat_to_json_key(key): _compat_to_json_value(value) for key, value in self._values.items() if value is not None} - model: str | None = None - """Model override for this agent, if set""" + +@dataclass +class SessionStartDataContext: + "Working directory and git context at session start" + cwd: str + git_root: str | None = None + repository: str | None = None + host_type: SessionStartDataContextHostType | None = None + branch: str | None = None + head_commit: str | None = None + base_commit: str | None = None @staticmethod - def from_dict(obj: Any) -> 'Agent': + def from_dict(obj: Any) -> "SessionStartDataContext": assert isinstance(obj, dict) - description = from_str(obj.get("description")) - display_name = from_str(obj.get("displayName")) - id = from_str(obj.get("id")) - name = from_str(obj.get("name")) - source = from_str(obj.get("source")) - tools = from_list(from_str, obj.get("tools")) - user_invocable = from_bool(obj.get("userInvocable")) - model = from_union([from_str, from_none], obj.get("model")) - return Agent(description, display_name, id, name, source, tools, user_invocable, model) + cwd = from_str(obj.get("cwd")) + git_root = from_union([from_none, lambda x: from_str(x)], obj.get("gitRoot")) + repository = from_union([from_none, lambda x: from_str(x)], obj.get("repository")) + host_type = from_union([from_none, lambda x: parse_enum(SessionStartDataContextHostType, x)], obj.get("hostType")) + branch = from_union([from_none, lambda x: from_str(x)], obj.get("branch")) + head_commit = from_union([from_none, lambda x: from_str(x)], obj.get("headCommit")) + base_commit = from_union([from_none, lambda x: from_str(x)], obj.get("baseCommit")) + return SessionStartDataContext( + cwd=cwd, + git_root=git_root, + repository=repository, + host_type=host_type, + branch=branch, + head_commit=head_commit, + base_commit=base_commit, + ) def to_dict(self) -> dict: result: dict = {} - result["description"] = from_str(self.description) - result["displayName"] = from_str(self.display_name) - result["id"] = from_str(self.id) - result["name"] = from_str(self.name) - result["source"] = from_str(self.source) - result["tools"] = from_list(from_str, self.tools) - result["userInvocable"] = from_bool(self.user_invocable) - if self.model is not None: - result["model"] = from_union([from_str, from_none], self.model) + result["cwd"] = from_str(self.cwd) + if self.git_root is not None: + result["gitRoot"] = from_union([from_none, lambda x: from_str(x)], self.git_root) + if self.repository is not None: + result["repository"] = from_union([from_none, lambda x: from_str(x)], self.repository) + if self.host_type is not None: + result["hostType"] = from_union([from_none, lambda x: to_enum(SessionStartDataContextHostType, x)], self.host_type) + if self.branch is not None: + result["branch"] = from_union([from_none, lambda x: from_str(x)], self.branch) + if self.head_commit is not None: + result["headCommit"] = from_union([from_none, lambda x: from_str(x)], self.head_commit) + if self.base_commit is not None: + result["baseCommit"] = from_union([from_none, lambda x: from_str(x)], self.base_commit) return result @dataclass -class LineRange: - """Optional line range to scope the attachment to a specific section of the file""" - - end: float - """End line number (1-based, inclusive)""" - - start: float - """Start line number (1-based)""" +class SessionStartData: + "Session initialization metadata including context and configuration" + session_id: str + version: float + producer: str + copilot_version: str + start_time: datetime + selected_model: str | None = None + reasoning_effort: str | None = None + context: SessionStartDataContext | None = None + already_in_use: bool | None = None + remote_steerable: bool | None = None @staticmethod - def from_dict(obj: Any) -> 'LineRange': + def from_dict(obj: Any) -> "SessionStartData": assert isinstance(obj, dict) - end = from_float(obj.get("end")) - start = from_float(obj.get("start")) - return LineRange(end, start) + session_id = from_str(obj.get("sessionId")) + version = from_float(obj.get("version")) + producer = from_str(obj.get("producer")) + copilot_version = from_str(obj.get("copilotVersion")) + start_time = from_datetime(obj.get("startTime")) + selected_model = from_union([from_none, lambda x: from_str(x)], obj.get("selectedModel")) + reasoning_effort = from_union([from_none, lambda x: from_str(x)], obj.get("reasoningEffort")) + context = from_union([from_none, lambda x: SessionStartDataContext.from_dict(x)], obj.get("context")) + already_in_use = from_union([from_none, lambda x: from_bool(x)], obj.get("alreadyInUse")) + remote_steerable = from_union([from_none, lambda x: from_bool(x)], obj.get("remoteSteerable")) + return SessionStartData( + session_id=session_id, + version=version, + producer=producer, + copilot_version=copilot_version, + start_time=start_time, + selected_model=selected_model, + reasoning_effort=reasoning_effort, + context=context, + already_in_use=already_in_use, + remote_steerable=remote_steerable, + ) def to_dict(self) -> dict: result: dict = {} - result["end"] = to_float(self.end) - result["start"] = to_float(self.start) + result["sessionId"] = from_str(self.session_id) + result["version"] = to_float(self.version) + result["producer"] = from_str(self.producer) + result["copilotVersion"] = from_str(self.copilot_version) + result["startTime"] = to_datetime(self.start_time) + if self.selected_model is not None: + result["selectedModel"] = from_union([from_none, lambda x: from_str(x)], self.selected_model) + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_none, lambda x: from_str(x)], self.reasoning_effort) + if self.context is not None: + result["context"] = from_union([from_none, lambda x: to_class(SessionStartDataContext, x)], self.context) + if self.already_in_use is not None: + result["alreadyInUse"] = from_union([from_none, lambda x: from_bool(x)], self.already_in_use) + if self.remote_steerable is not None: + result["remoteSteerable"] = from_union([from_none, lambda x: from_bool(x)], self.remote_steerable) return result -class ReferenceType(Enum): - """Type of GitHub reference""" - - DISCUSSION = "discussion" - ISSUE = "issue" - PR = "pr" - - @dataclass -class End: - """End position of the selection""" - - character: float - """End character offset within the line (0-based)""" - - line: float - """End line number (0-based)""" +class SessionResumeDataContext: + "Updated working directory and git context at resume time" + cwd: str + git_root: str | None = None + repository: str | None = None + host_type: SessionStartDataContextHostType | None = None + branch: str | None = None + head_commit: str | None = None + base_commit: str | None = None @staticmethod - def from_dict(obj: Any) -> 'End': + def from_dict(obj: Any) -> "SessionResumeDataContext": assert isinstance(obj, dict) - character = from_float(obj.get("character")) - line = from_float(obj.get("line")) - return End(character, line) + cwd = from_str(obj.get("cwd")) + git_root = from_union([from_none, lambda x: from_str(x)], obj.get("gitRoot")) + repository = from_union([from_none, lambda x: from_str(x)], obj.get("repository")) + host_type = from_union([from_none, lambda x: parse_enum(SessionStartDataContextHostType, x)], obj.get("hostType")) + branch = from_union([from_none, lambda x: from_str(x)], obj.get("branch")) + head_commit = from_union([from_none, lambda x: from_str(x)], obj.get("headCommit")) + base_commit = from_union([from_none, lambda x: from_str(x)], obj.get("baseCommit")) + return SessionResumeDataContext( + cwd=cwd, + git_root=git_root, + repository=repository, + host_type=host_type, + branch=branch, + head_commit=head_commit, + base_commit=base_commit, + ) def to_dict(self) -> dict: result: dict = {} - result["character"] = to_float(self.character) - result["line"] = to_float(self.line) + result["cwd"] = from_str(self.cwd) + if self.git_root is not None: + result["gitRoot"] = from_union([from_none, lambda x: from_str(x)], self.git_root) + if self.repository is not None: + result["repository"] = from_union([from_none, lambda x: from_str(x)], self.repository) + if self.host_type is not None: + result["hostType"] = from_union([from_none, lambda x: to_enum(SessionStartDataContextHostType, x)], self.host_type) + if self.branch is not None: + result["branch"] = from_union([from_none, lambda x: from_str(x)], self.branch) + if self.head_commit is not None: + result["headCommit"] = from_union([from_none, lambda x: from_str(x)], self.head_commit) + if self.base_commit is not None: + result["baseCommit"] = from_union([from_none, lambda x: from_str(x)], self.base_commit) return result @dataclass -class Start: - """Start position of the selection""" - - character: float - """Start character offset within the line (0-based)""" - - line: float - """Start line number (0-based)""" +class SessionResumeData: + "Session resume metadata including current context and event count" + resume_time: datetime + event_count: float + selected_model: str | None = None + reasoning_effort: str | None = None + context: SessionResumeDataContext | None = None + already_in_use: bool | None = None + remote_steerable: bool | None = None @staticmethod - def from_dict(obj: Any) -> 'Start': + def from_dict(obj: Any) -> "SessionResumeData": assert isinstance(obj, dict) - character = from_float(obj.get("character")) - line = from_float(obj.get("line")) - return Start(character, line) + resume_time = from_datetime(obj.get("resumeTime")) + event_count = from_float(obj.get("eventCount")) + selected_model = from_union([from_none, lambda x: from_str(x)], obj.get("selectedModel")) + reasoning_effort = from_union([from_none, lambda x: from_str(x)], obj.get("reasoningEffort")) + context = from_union([from_none, lambda x: SessionResumeDataContext.from_dict(x)], obj.get("context")) + already_in_use = from_union([from_none, lambda x: from_bool(x)], obj.get("alreadyInUse")) + remote_steerable = from_union([from_none, lambda x: from_bool(x)], obj.get("remoteSteerable")) + return SessionResumeData( + resume_time=resume_time, + event_count=event_count, + selected_model=selected_model, + reasoning_effort=reasoning_effort, + context=context, + already_in_use=already_in_use, + remote_steerable=remote_steerable, + ) def to_dict(self) -> dict: result: dict = {} - result["character"] = to_float(self.character) - result["line"] = to_float(self.line) + result["resumeTime"] = to_datetime(self.resume_time) + result["eventCount"] = to_float(self.event_count) + if self.selected_model is not None: + result["selectedModel"] = from_union([from_none, lambda x: from_str(x)], self.selected_model) + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_none, lambda x: from_str(x)], self.reasoning_effort) + if self.context is not None: + result["context"] = from_union([from_none, lambda x: to_class(SessionResumeDataContext, x)], self.context) + if self.already_in_use is not None: + result["alreadyInUse"] = from_union([from_none, lambda x: from_bool(x)], self.already_in_use) + if self.remote_steerable is not None: + result["remoteSteerable"] = from_union([from_none, lambda x: from_bool(x)], self.remote_steerable) return result @dataclass -class Selection: - """Position range of the selection within the file""" - - end: End - """End position of the selection""" - - start: Start - """Start position of the selection""" +class SessionRemoteSteerableChangedData: + "Notifies Mission Control that the session's remote steering capability has changed" + remote_steerable: bool @staticmethod - def from_dict(obj: Any) -> 'Selection': + def from_dict(obj: Any) -> "SessionRemoteSteerableChangedData": assert isinstance(obj, dict) - end = End.from_dict(obj.get("end")) - start = Start.from_dict(obj.get("start")) - return Selection(end, start) + remote_steerable = from_bool(obj.get("remoteSteerable")) + return SessionRemoteSteerableChangedData( + remote_steerable=remote_steerable, + ) def to_dict(self) -> dict: result: dict = {} - result["end"] = to_class(End, self.end) - result["start"] = to_class(Start, self.start) + result["remoteSteerable"] = from_bool(self.remote_steerable) return result -class AttachmentType(Enum): - BLOB = "blob" - DIRECTORY = "directory" - FILE = "file" - GITHUB_REFERENCE = "github_reference" - SELECTION = "selection" - - @dataclass -class Attachment: - """A user message attachment — a file, directory, code selection, blob, or GitHub reference - - File attachment - - Directory attachment - - Code selection attachment from an editor - - GitHub issue, pull request, or discussion reference - - Blob attachment with inline base64-encoded data - """ - type: AttachmentType - """Attachment type discriminator""" - - display_name: str | None = None - """User-facing display name for the attachment - - User-facing display name for the selection - """ - line_range: LineRange | None = None - """Optional line range to scope the attachment to a specific section of the file""" - - path: str | None = None - """Absolute file path - - Absolute directory path - """ - file_path: str | None = None - """Absolute path to the file containing the selection""" - - selection: Selection | None = None - """Position range of the selection within the file""" - - text: str | None = None - """The selected text content""" - - number: float | None = None - """Issue, pull request, or discussion number""" - - reference_type: ReferenceType | None = None - """Type of GitHub reference""" - - state: str | None = None - """Current state of the referenced item (e.g., open, closed, merged)""" - - title: str | None = None - """Title of the referenced item""" - +class SessionErrorData: + "Error details for timeline display including message and optional diagnostic information" + error_type: str + message: str + stack: str | None = None + status_code: int | None = None + provider_call_id: str | None = None url: str | None = None - """URL to the referenced item on GitHub""" - - data: str | None = None - """Base64-encoded content""" - - mime_type: str | None = None - """MIME type of the inline data""" @staticmethod - def from_dict(obj: Any) -> 'Attachment': + def from_dict(obj: Any) -> "SessionErrorData": assert isinstance(obj, dict) - type = AttachmentType(obj.get("type")) - display_name = from_union([from_str, from_none], obj.get("displayName")) - line_range = from_union([LineRange.from_dict, from_none], obj.get("lineRange")) - path = from_union([from_str, from_none], obj.get("path")) - file_path = from_union([from_str, from_none], obj.get("filePath")) - selection = from_union([Selection.from_dict, from_none], obj.get("selection")) - text = from_union([from_str, from_none], obj.get("text")) - number = from_union([from_float, from_none], obj.get("number")) - reference_type = from_union([ReferenceType, from_none], obj.get("referenceType")) - state = from_union([from_str, from_none], obj.get("state")) - title = from_union([from_str, from_none], obj.get("title")) - url = from_union([from_str, from_none], obj.get("url")) - data = from_union([from_str, from_none], obj.get("data")) - mime_type = from_union([from_str, from_none], obj.get("mimeType")) - return Attachment(type, display_name, line_range, path, file_path, selection, text, number, reference_type, state, title, url, data, mime_type) + error_type = from_str(obj.get("errorType")) + message = from_str(obj.get("message")) + stack = from_union([from_none, lambda x: from_str(x)], obj.get("stack")) + status_code = from_union([from_none, lambda x: from_int(x)], obj.get("statusCode")) + provider_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("providerCallId")) + url = from_union([from_none, lambda x: from_str(x)], obj.get("url")) + return SessionErrorData( + error_type=error_type, + message=message, + stack=stack, + status_code=status_code, + provider_call_id=provider_call_id, + url=url, + ) def to_dict(self) -> dict: result: dict = {} - result["type"] = to_enum(AttachmentType, self.type) - if self.display_name is not None: - result["displayName"] = from_union([from_str, from_none], self.display_name) - if self.line_range is not None: - result["lineRange"] = from_union([lambda x: to_class(LineRange, x), from_none], self.line_range) - if self.path is not None: - result["path"] = from_union([from_str, from_none], self.path) - if self.file_path is not None: - result["filePath"] = from_union([from_str, from_none], self.file_path) - if self.selection is not None: - result["selection"] = from_union([lambda x: to_class(Selection, x), from_none], self.selection) - if self.text is not None: - result["text"] = from_union([from_str, from_none], self.text) - if self.number is not None: - result["number"] = from_union([to_float, from_none], self.number) - if self.reference_type is not None: - result["referenceType"] = from_union([lambda x: to_enum(ReferenceType, x), from_none], self.reference_type) - if self.state is not None: - result["state"] = from_union([from_str, from_none], self.state) - if self.title is not None: - result["title"] = from_union([from_str, from_none], self.title) + result["errorType"] = from_str(self.error_type) + result["message"] = from_str(self.message) + if self.stack is not None: + result["stack"] = from_union([from_none, lambda x: from_str(x)], self.stack) + if self.status_code is not None: + result["statusCode"] = from_union([from_none, lambda x: to_int(x)], self.status_code) + if self.provider_call_id is not None: + result["providerCallId"] = from_union([from_none, lambda x: from_str(x)], self.provider_call_id) if self.url is not None: - result["url"] = from_union([from_str, from_none], self.url) - if self.data is not None: - result["data"] = from_union([from_str, from_none], self.data) - if self.mime_type is not None: - result["mimeType"] = from_union([from_str, from_none], self.mime_type) + result["url"] = from_union([from_none, lambda x: from_str(x)], self.url) return result @dataclass -class CodeChanges: - """Aggregate code change metrics for the session""" - - files_modified: list[str] - """List of file paths that were modified during the session""" - - lines_added: float - """Total number of lines added during the session""" - - lines_removed: float - """Total number of lines removed during the session""" +class SessionIdleData: + "Payload indicating the session is idle with no background agents in flight" + aborted: bool | None = None @staticmethod - def from_dict(obj: Any) -> 'CodeChanges': + def from_dict(obj: Any) -> "SessionIdleData": assert isinstance(obj, dict) - files_modified = from_list(from_str, obj.get("filesModified")) - lines_added = from_float(obj.get("linesAdded")) - lines_removed = from_float(obj.get("linesRemoved")) - return CodeChanges(files_modified, lines_added, lines_removed) + aborted = from_union([from_none, lambda x: from_bool(x)], obj.get("aborted")) + return SessionIdleData( + aborted=aborted, + ) def to_dict(self) -> dict: result: dict = {} - result["filesModified"] = from_list(from_str, self.files_modified) - result["linesAdded"] = to_float(self.lines_added) - result["linesRemoved"] = to_float(self.lines_removed) + if self.aborted is not None: + result["aborted"] = from_union([from_none, lambda x: from_bool(x)], self.aborted) return result @dataclass -class DataCommand: - name: str - description: str | None = None +class SessionTitleChangedData: + "Session title change payload containing the new display title" + title: str @staticmethod - def from_dict(obj: Any) -> 'DataCommand': + def from_dict(obj: Any) -> "SessionTitleChangedData": assert isinstance(obj, dict) - name = from_str(obj.get("name")) - description = from_union([from_str, from_none], obj.get("description")) - return DataCommand(name, description) + title = from_str(obj.get("title")) + return SessionTitleChangedData( + title=title, + ) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) - if self.description is not None: - result["description"] = from_union([from_str, from_none], self.description) + result["title"] = from_str(self.title) return result @dataclass -class CompactionTokensUsed: - """Token usage breakdown for the compaction LLM call""" - - cached_input: float - """Cached input tokens reused in the compaction LLM call""" - - input: float - """Input tokens consumed by the compaction LLM call""" - - output: float - """Output tokens produced by the compaction LLM call""" +class SessionInfoData: + "Informational message for timeline display with categorization" + info_type: str + message: str + url: str | None = None @staticmethod - def from_dict(obj: Any) -> 'CompactionTokensUsed': + def from_dict(obj: Any) -> "SessionInfoData": assert isinstance(obj, dict) - cached_input = from_float(obj.get("cachedInput")) - input = from_float(obj.get("input")) - output = from_float(obj.get("output")) - return CompactionTokensUsed(cached_input, input, output) + info_type = from_str(obj.get("infoType")) + message = from_str(obj.get("message")) + url = from_union([from_none, lambda x: from_str(x)], obj.get("url")) + return SessionInfoData( + info_type=info_type, + message=message, + url=url, + ) def to_dict(self) -> dict: result: dict = {} - result["cachedInput"] = to_float(self.cached_input) - result["input"] = to_float(self.input) - result["output"] = to_float(self.output) + result["infoType"] = from_str(self.info_type) + result["message"] = from_str(self.message) + if self.url is not None: + result["url"] = from_union([from_none, lambda x: from_str(x)], self.url) return result -class HostType(Enum): - """Hosting platform type of the repository (github or ado)""" - - ADO = "ado" - GITHUB = "github" - - @dataclass -class ContextClass: - """Working directory and git context at session start - - Updated working directory and git context at resume time - """ - cwd: str - """Current working directory path""" - - base_commit: str | None = None - """Base commit of current git branch at session start time""" - - branch: str | None = None - """Current git branch name""" - - git_root: str | None = None - """Root directory of the git repository, resolved via git rev-parse""" - - head_commit: str | None = None - """Head commit of current git branch at session start time""" - - host_type: HostType | None = None - """Hosting platform type of the repository (github or ado)""" - - repository: str | None = None - """Repository identifier derived from the git remote URL ("owner/name" for GitHub, - "org/project/repo" for Azure DevOps) - """ +class SessionWarningData: + "Warning message for timeline display with categorization" + warning_type: str + message: str + url: str | None = None @staticmethod - def from_dict(obj: Any) -> 'ContextClass': + def from_dict(obj: Any) -> "SessionWarningData": assert isinstance(obj, dict) - cwd = from_str(obj.get("cwd")) - base_commit = from_union([from_str, from_none], obj.get("baseCommit")) - branch = from_union([from_str, from_none], obj.get("branch")) - git_root = from_union([from_str, from_none], obj.get("gitRoot")) - head_commit = from_union([from_str, from_none], obj.get("headCommit")) - host_type = from_union([HostType, from_none], obj.get("hostType")) - repository = from_union([from_str, from_none], obj.get("repository")) - return ContextClass(cwd, base_commit, branch, git_root, head_commit, host_type, repository) + warning_type = from_str(obj.get("warningType")) + message = from_str(obj.get("message")) + url = from_union([from_none, lambda x: from_str(x)], obj.get("url")) + return SessionWarningData( + warning_type=warning_type, + message=message, + url=url, + ) def to_dict(self) -> dict: result: dict = {} - result["cwd"] = from_str(self.cwd) - if self.base_commit is not None: - result["baseCommit"] = from_union([from_str, from_none], self.base_commit) - if self.branch is not None: - result["branch"] = from_union([from_str, from_none], self.branch) - if self.git_root is not None: - result["gitRoot"] = from_union([from_str, from_none], self.git_root) - if self.head_commit is not None: - result["headCommit"] = from_union([from_str, from_none], self.head_commit) - if self.host_type is not None: - result["hostType"] = from_union([lambda x: to_enum(HostType, x), from_none], self.host_type) - if self.repository is not None: - result["repository"] = from_union([from_str, from_none], self.repository) + result["warningType"] = from_str(self.warning_type) + result["message"] = from_str(self.message) + if self.url is not None: + result["url"] = from_union([from_none, lambda x: from_str(x)], self.url) return result @dataclass -class TokenDetail: - """Token usage detail for a single billing category""" - - batch_size: float - """Number of tokens in this billing batch""" - - cost_per_batch: float - """Cost per batch of tokens""" - - token_count: float - """Total token count for this entry""" - - token_type: str - """Token category (e.g., "input", "output")""" +class SessionModelChangeData: + "Model change details including previous and new model identifiers" + new_model: str + previous_model: str | None = None + previous_reasoning_effort: str | None = None + reasoning_effort: str | None = None @staticmethod - def from_dict(obj: Any) -> 'TokenDetail': + def from_dict(obj: Any) -> "SessionModelChangeData": assert isinstance(obj, dict) - batch_size = from_float(obj.get("batchSize")) - cost_per_batch = from_float(obj.get("costPerBatch")) - token_count = from_float(obj.get("tokenCount")) - token_type = from_str(obj.get("tokenType")) - return TokenDetail(batch_size, cost_per_batch, token_count, token_type) + new_model = from_str(obj.get("newModel")) + previous_model = from_union([from_none, lambda x: from_str(x)], obj.get("previousModel")) + previous_reasoning_effort = from_union([from_none, lambda x: from_str(x)], obj.get("previousReasoningEffort")) + reasoning_effort = from_union([from_none, lambda x: from_str(x)], obj.get("reasoningEffort")) + return SessionModelChangeData( + new_model=new_model, + previous_model=previous_model, + previous_reasoning_effort=previous_reasoning_effort, + reasoning_effort=reasoning_effort, + ) def to_dict(self) -> dict: result: dict = {} - result["batchSize"] = to_float(self.batch_size) - result["costPerBatch"] = to_float(self.cost_per_batch) - result["tokenCount"] = to_float(self.token_count) - result["tokenType"] = from_str(self.token_type) + result["newModel"] = from_str(self.new_model) + if self.previous_model is not None: + result["previousModel"] = from_union([from_none, lambda x: from_str(x)], self.previous_model) + if self.previous_reasoning_effort is not None: + result["previousReasoningEffort"] = from_union([from_none, lambda x: from_str(x)], self.previous_reasoning_effort) + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_none, lambda x: from_str(x)], self.reasoning_effort) return result @dataclass -class CopilotUsage: - """Per-request cost and usage data from the CAPI copilot_usage response field""" - - token_details: list[TokenDetail] - """Itemized token usage breakdown""" - - total_nano_aiu: float - """Total cost in nano-AIU (AI Units) for this request""" +class SessionModeChangedData: + "Agent mode change details including previous and new modes" + previous_mode: str + new_mode: str @staticmethod - def from_dict(obj: Any) -> 'CopilotUsage': + def from_dict(obj: Any) -> "SessionModeChangedData": assert isinstance(obj, dict) - token_details = from_list(TokenDetail.from_dict, obj.get("tokenDetails")) - total_nano_aiu = from_float(obj.get("totalNanoAiu")) - return CopilotUsage(token_details, total_nano_aiu) + previous_mode = from_str(obj.get("previousMode")) + new_mode = from_str(obj.get("newMode")) + return SessionModeChangedData( + previous_mode=previous_mode, + new_mode=new_mode, + ) def to_dict(self) -> dict: result: dict = {} - result["tokenDetails"] = from_list(lambda x: to_class(TokenDetail, x), self.token_details) - result["totalNanoAiu"] = to_float(self.total_nano_aiu) + result["previousMode"] = from_str(self.previous_mode) + result["newMode"] = from_str(self.new_mode) return result @dataclass -class ErrorClass: - """Error details when the tool execution failed - - Error details when the hook failed - """ - message: str - """Human-readable error message""" - - code: str | None = None - """Machine-readable error code""" - - stack: str | None = None - """Error stack trace, when available""" +class SessionPlanChangedData: + "Plan file operation details indicating what changed" + operation: SessionPlanChangedDataOperation @staticmethod - def from_dict(obj: Any) -> 'ErrorClass': + def from_dict(obj: Any) -> "SessionPlanChangedData": assert isinstance(obj, dict) - message = from_str(obj.get("message")) - code = from_union([from_str, from_none], obj.get("code")) - stack = from_union([from_str, from_none], obj.get("stack")) - return ErrorClass(message, code, stack) + operation = parse_enum(SessionPlanChangedDataOperation, obj.get("operation")) + return SessionPlanChangedData( + operation=operation, + ) def to_dict(self) -> dict: result: dict = {} - result["message"] = from_str(self.message) - if self.code is not None: - result["code"] = from_union([from_str, from_none], self.code) - if self.stack is not None: - result["stack"] = from_union([from_str, from_none], self.stack) + result["operation"] = to_enum(SessionPlanChangedDataOperation, self.operation) return result -class Source(Enum): - """Discovery source""" - - PROJECT = "project" - USER = "user" - +@dataclass +class SessionWorkspaceFileChangedData: + "Workspace file change details including path and operation type" + path: str + operation: SessionWorkspaceFileChangedDataOperation -class ExtensionStatus(Enum): - """Current status: running, disabled, failed, or starting""" + @staticmethod + def from_dict(obj: Any) -> "SessionWorkspaceFileChangedData": + assert isinstance(obj, dict) + path = from_str(obj.get("path")) + operation = parse_enum(SessionWorkspaceFileChangedDataOperation, obj.get("operation")) + return SessionWorkspaceFileChangedData( + path=path, + operation=operation, + ) - DISABLED = "disabled" - FAILED = "failed" - RUNNING = "running" - STARTING = "starting" + def to_dict(self) -> dict: + result: dict = {} + result["path"] = from_str(self.path) + result["operation"] = to_enum(SessionWorkspaceFileChangedDataOperation, self.operation) + return result @dataclass -class Extension: - id: str - """Source-qualified extension ID (e.g., 'project:my-ext', 'user:auth-helper')""" - +class SessionHandoffDataRepository: + "Repository context for the handed-off session" + owner: str name: str - """Extension name (directory name)""" - - source: Source - """Discovery source""" - - status: ExtensionStatus - """Current status: running, disabled, failed, or starting""" + branch: str | None = None @staticmethod - def from_dict(obj: Any) -> 'Extension': + def from_dict(obj: Any) -> "SessionHandoffDataRepository": assert isinstance(obj, dict) - id = from_str(obj.get("id")) + owner = from_str(obj.get("owner")) name = from_str(obj.get("name")) - source = Source(obj.get("source")) - status = ExtensionStatus(obj.get("status")) - return Extension(id, name, source, status) + branch = from_union([from_none, lambda x: from_str(x)], obj.get("branch")) + return SessionHandoffDataRepository( + owner=owner, + name=name, + branch=branch, + ) def to_dict(self) -> dict: result: dict = {} - result["id"] = from_str(self.id) + result["owner"] = from_str(self.owner) result["name"] = from_str(self.name) - result["source"] = to_enum(Source, self.source) - result["status"] = to_enum(ExtensionStatus, self.status) + if self.branch is not None: + result["branch"] = from_union([from_none, lambda x: from_str(x)], self.branch) return result -class KindStatus(Enum): - """Whether the agent completed successfully or failed""" - - COMPLETED = "completed" - FAILED = "failed" +@dataclass +class SessionHandoffData: + "Session handoff metadata including source, context, and repository information" + handoff_time: datetime + source_type: SessionHandoffDataSourceType + repository: SessionHandoffDataRepository | None = None + context: str | None = None + summary: str | None = None + remote_session_id: str | None = None + host: str | None = None + @staticmethod + def from_dict(obj: Any) -> "SessionHandoffData": + assert isinstance(obj, dict) + handoff_time = from_datetime(obj.get("handoffTime")) + source_type = parse_enum(SessionHandoffDataSourceType, obj.get("sourceType")) + repository = from_union([from_none, lambda x: SessionHandoffDataRepository.from_dict(x)], obj.get("repository")) + context = from_union([from_none, lambda x: from_str(x)], obj.get("context")) + summary = from_union([from_none, lambda x: from_str(x)], obj.get("summary")) + remote_session_id = from_union([from_none, lambda x: from_str(x)], obj.get("remoteSessionId")) + host = from_union([from_none, lambda x: from_str(x)], obj.get("host")) + return SessionHandoffData( + handoff_time=handoff_time, + source_type=source_type, + repository=repository, + context=context, + summary=summary, + remote_session_id=remote_session_id, + host=host, + ) -class KindType(Enum): - AGENT_COMPLETED = "agent_completed" - AGENT_IDLE = "agent_idle" - SHELL_COMPLETED = "shell_completed" - SHELL_DETACHED_COMPLETED = "shell_detached_completed" + def to_dict(self) -> dict: + result: dict = {} + result["handoffTime"] = to_datetime(self.handoff_time) + result["sourceType"] = to_enum(SessionHandoffDataSourceType, self.source_type) + if self.repository is not None: + result["repository"] = from_union([from_none, lambda x: to_class(SessionHandoffDataRepository, x)], self.repository) + if self.context is not None: + result["context"] = from_union([from_none, lambda x: from_str(x)], self.context) + if self.summary is not None: + result["summary"] = from_union([from_none, lambda x: from_str(x)], self.summary) + if self.remote_session_id is not None: + result["remoteSessionId"] = from_union([from_none, lambda x: from_str(x)], self.remote_session_id) + if self.host is not None: + result["host"] = from_union([from_none, lambda x: from_str(x)], self.host) + return result @dataclass -class KindClass: - """Structured metadata identifying what triggered this notification""" - - type: KindType - agent_id: str | None = None - """Unique identifier of the background agent""" - - agent_type: str | None = None - """Type of the agent (e.g., explore, task, general-purpose)""" +class SessionTruncationData: + "Conversation truncation statistics including token counts and removed content metrics" + token_limit: float + pre_truncation_tokens_in_messages: float + pre_truncation_messages_length: float + post_truncation_tokens_in_messages: float + post_truncation_messages_length: float + tokens_removed_during_truncation: float + messages_removed_during_truncation: float + performed_by: str - description: str | None = None - """Human-readable description of the agent task - - Human-readable description of the command - """ - prompt: str | None = None - """The full prompt given to the background agent""" + @staticmethod + def from_dict(obj: Any) -> "SessionTruncationData": + assert isinstance(obj, dict) + token_limit = from_float(obj.get("tokenLimit")) + pre_truncation_tokens_in_messages = from_float(obj.get("preTruncationTokensInMessages")) + pre_truncation_messages_length = from_float(obj.get("preTruncationMessagesLength")) + post_truncation_tokens_in_messages = from_float(obj.get("postTruncationTokensInMessages")) + post_truncation_messages_length = from_float(obj.get("postTruncationMessagesLength")) + tokens_removed_during_truncation = from_float(obj.get("tokensRemovedDuringTruncation")) + messages_removed_during_truncation = from_float(obj.get("messagesRemovedDuringTruncation")) + performed_by = from_str(obj.get("performedBy")) + return SessionTruncationData( + token_limit=token_limit, + pre_truncation_tokens_in_messages=pre_truncation_tokens_in_messages, + pre_truncation_messages_length=pre_truncation_messages_length, + post_truncation_tokens_in_messages=post_truncation_tokens_in_messages, + post_truncation_messages_length=post_truncation_messages_length, + tokens_removed_during_truncation=tokens_removed_during_truncation, + messages_removed_during_truncation=messages_removed_during_truncation, + performed_by=performed_by, + ) - status: KindStatus | None = None - """Whether the agent completed successfully or failed""" + def to_dict(self) -> dict: + result: dict = {} + result["tokenLimit"] = to_float(self.token_limit) + result["preTruncationTokensInMessages"] = to_float(self.pre_truncation_tokens_in_messages) + result["preTruncationMessagesLength"] = to_float(self.pre_truncation_messages_length) + result["postTruncationTokensInMessages"] = to_float(self.post_truncation_tokens_in_messages) + result["postTruncationMessagesLength"] = to_float(self.post_truncation_messages_length) + result["tokensRemovedDuringTruncation"] = to_float(self.tokens_removed_during_truncation) + result["messagesRemovedDuringTruncation"] = to_float(self.messages_removed_during_truncation) + result["performedBy"] = from_str(self.performed_by) + return result - exit_code: float | None = None - """Exit code of the shell command, if available""" - shell_id: str | None = None - """Unique identifier of the shell session - - Unique identifier of the detached shell session - """ +@dataclass +class SessionSnapshotRewindData: + "Session rewind details including target event and count of removed events" + up_to_event_id: str + events_removed: float @staticmethod - def from_dict(obj: Any) -> 'KindClass': + def from_dict(obj: Any) -> "SessionSnapshotRewindData": assert isinstance(obj, dict) - type = KindType(obj.get("type")) - agent_id = from_union([from_str, from_none], obj.get("agentId")) - agent_type = from_union([from_str, from_none], obj.get("agentType")) - description = from_union([from_str, from_none], obj.get("description")) - prompt = from_union([from_str, from_none], obj.get("prompt")) - status = from_union([KindStatus, from_none], obj.get("status")) - exit_code = from_union([from_float, from_none], obj.get("exitCode")) - shell_id = from_union([from_str, from_none], obj.get("shellId")) - return KindClass(type, agent_id, agent_type, description, prompt, status, exit_code, shell_id) + up_to_event_id = from_str(obj.get("upToEventId")) + events_removed = from_float(obj.get("eventsRemoved")) + return SessionSnapshotRewindData( + up_to_event_id=up_to_event_id, + events_removed=events_removed, + ) def to_dict(self) -> dict: result: dict = {} - result["type"] = to_enum(KindType, self.type) - if self.agent_id is not None: - result["agentId"] = from_union([from_str, from_none], self.agent_id) - if self.agent_type is not None: - result["agentType"] = from_union([from_str, from_none], self.agent_type) - if self.description is not None: - result["description"] = from_union([from_str, from_none], self.description) - if self.prompt is not None: - result["prompt"] = from_union([from_str, from_none], self.prompt) - if self.status is not None: - result["status"] = from_union([lambda x: to_enum(KindStatus, x), from_none], self.status) - if self.exit_code is not None: - result["exitCode"] = from_union([to_float, from_none], self.exit_code) - if self.shell_id is not None: - result["shellId"] = from_union([from_str, from_none], self.shell_id) + result["upToEventId"] = from_str(self.up_to_event_id) + result["eventsRemoved"] = to_float(self.events_removed) return result @dataclass -class Metadata: - """Metadata about the prompt template and its construction""" - - prompt_version: str | None = None - """Version identifier of the prompt template used""" - - variables: dict[str, Any] | None = None - """Template variables used when constructing the prompt""" +class SessionShutdownDataCodeChanges: + "Aggregate code change metrics for the session" + lines_added: float + lines_removed: float + files_modified: list[str] @staticmethod - def from_dict(obj: Any) -> 'Metadata': + def from_dict(obj: Any) -> "SessionShutdownDataCodeChanges": assert isinstance(obj, dict) - prompt_version = from_union([from_str, from_none], obj.get("promptVersion")) - variables = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("variables")) - return Metadata(prompt_version, variables) + lines_added = from_float(obj.get("linesAdded")) + lines_removed = from_float(obj.get("linesRemoved")) + files_modified = from_list(lambda x: from_str(x), obj.get("filesModified")) + return SessionShutdownDataCodeChanges( + lines_added=lines_added, + lines_removed=lines_removed, + files_modified=files_modified, + ) def to_dict(self) -> dict: result: dict = {} - if self.prompt_version is not None: - result["promptVersion"] = from_union([from_str, from_none], self.prompt_version) - if self.variables is not None: - result["variables"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.variables) + result["linesAdded"] = to_float(self.lines_added) + result["linesRemoved"] = to_float(self.lines_removed) + result["filesModified"] = from_list(lambda x: from_str(x), self.files_modified) return result -class Mode(Enum): - """Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to - "form" when absent. - """ - FORM = "form" - URL = "url" - - @dataclass -class Requests: - """Request count and cost metrics""" - - cost: float - """Cumulative cost multiplier for requests to this model""" - +class SessionShutdownDataModelMetricsValueRequests: + "Request count and cost metrics" count: float - """Total number of API requests made to this model""" + cost: float @staticmethod - def from_dict(obj: Any) -> 'Requests': + def from_dict(obj: Any) -> "SessionShutdownDataModelMetricsValueRequests": assert isinstance(obj, dict) - cost = from_float(obj.get("cost")) count = from_float(obj.get("count")) - return Requests(cost, count) + cost = from_float(obj.get("cost")) + return SessionShutdownDataModelMetricsValueRequests( + count=count, + cost=cost, + ) def to_dict(self) -> dict: result: dict = {} - result["cost"] = to_float(self.cost) result["count"] = to_float(self.count) + result["cost"] = to_float(self.cost) return result @dataclass -class Usage: - """Token usage breakdown""" - - cache_read_tokens: float - """Total tokens read from prompt cache across all requests""" - - cache_write_tokens: float - """Total tokens written to prompt cache across all requests""" - +class SessionShutdownDataModelMetricsValueUsage: + "Token usage breakdown" input_tokens: float - """Total input tokens consumed across all requests to this model""" - output_tokens: float - """Total output tokens produced across all requests to this model""" + cache_read_tokens: float + cache_write_tokens: float @staticmethod - def from_dict(obj: Any) -> 'Usage': + def from_dict(obj: Any) -> "SessionShutdownDataModelMetricsValueUsage": assert isinstance(obj, dict) - cache_read_tokens = from_float(obj.get("cacheReadTokens")) - cache_write_tokens = from_float(obj.get("cacheWriteTokens")) input_tokens = from_float(obj.get("inputTokens")) output_tokens = from_float(obj.get("outputTokens")) - return Usage(cache_read_tokens, cache_write_tokens, input_tokens, output_tokens) + cache_read_tokens = from_float(obj.get("cacheReadTokens")) + cache_write_tokens = from_float(obj.get("cacheWriteTokens")) + return SessionShutdownDataModelMetricsValueUsage( + input_tokens=input_tokens, + output_tokens=output_tokens, + cache_read_tokens=cache_read_tokens, + cache_write_tokens=cache_write_tokens, + ) def to_dict(self) -> dict: result: dict = {} - result["cacheReadTokens"] = to_float(self.cache_read_tokens) - result["cacheWriteTokens"] = to_float(self.cache_write_tokens) result["inputTokens"] = to_float(self.input_tokens) result["outputTokens"] = to_float(self.output_tokens) + result["cacheReadTokens"] = to_float(self.cache_read_tokens) + result["cacheWriteTokens"] = to_float(self.cache_write_tokens) return result @dataclass -class ModelMetric: - requests: Requests - """Request count and cost metrics""" - - usage: Usage - """Token usage breakdown""" +class SessionShutdownDataModelMetricsValue: + requests: SessionShutdownDataModelMetricsValueRequests + usage: SessionShutdownDataModelMetricsValueUsage @staticmethod - def from_dict(obj: Any) -> 'ModelMetric': + def from_dict(obj: Any) -> "SessionShutdownDataModelMetricsValue": assert isinstance(obj, dict) - requests = Requests.from_dict(obj.get("requests")) - usage = Usage.from_dict(obj.get("usage")) - return ModelMetric(requests, usage) + requests = SessionShutdownDataModelMetricsValueRequests.from_dict(obj.get("requests")) + usage = SessionShutdownDataModelMetricsValueUsage.from_dict(obj.get("usage")) + return SessionShutdownDataModelMetricsValue( + requests=requests, + usage=usage, + ) def to_dict(self) -> dict: result: dict = {} - result["requests"] = to_class(Requests, self.requests) - result["usage"] = to_class(Usage, self.usage) + result["requests"] = to_class(SessionShutdownDataModelMetricsValueRequests, self.requests) + result["usage"] = to_class(SessionShutdownDataModelMetricsValueUsage, self.usage) return result -class Operation(Enum): - """The type of operation performed on the plan file - - Whether the file was newly created or updated - """ - CREATE = "create" - DELETE = "delete" - UPDATE = "update" - - @dataclass -class PermissionRequestCommand: - identifier: str - """Command identifier (e.g., executable name)""" - - read_only: bool - """Whether this command is read-only (no side effects)""" +class SessionShutdownData: + "Session termination metrics including usage statistics, code changes, and shutdown reason" + shutdown_type: SessionShutdownDataShutdownType + total_premium_requests: float + total_api_duration_ms: float + session_start_time: float + code_changes: SessionShutdownDataCodeChanges + model_metrics: dict[str, SessionShutdownDataModelMetricsValue] + error_reason: str | None = None + current_model: str | None = None + current_tokens: float | None = None + system_tokens: float | None = None + conversation_tokens: float | None = None + tool_definitions_tokens: float | None = None @staticmethod - def from_dict(obj: Any) -> 'PermissionRequestCommand': + def from_dict(obj: Any) -> "SessionShutdownData": assert isinstance(obj, dict) - identifier = from_str(obj.get("identifier")) - read_only = from_bool(obj.get("readOnly")) - return PermissionRequestCommand(identifier, read_only) + shutdown_type = parse_enum(SessionShutdownDataShutdownType, obj.get("shutdownType")) + total_premium_requests = from_float(obj.get("totalPremiumRequests")) + total_api_duration_ms = from_float(obj.get("totalApiDurationMs")) + session_start_time = from_float(obj.get("sessionStartTime")) + code_changes = SessionShutdownDataCodeChanges.from_dict(obj.get("codeChanges")) + model_metrics = from_dict(lambda x: SessionShutdownDataModelMetricsValue.from_dict(x), obj.get("modelMetrics")) + error_reason = from_union([from_none, lambda x: from_str(x)], obj.get("errorReason")) + current_model = from_union([from_none, lambda x: from_str(x)], obj.get("currentModel")) + current_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("currentTokens")) + system_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("systemTokens")) + conversation_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("conversationTokens")) + tool_definitions_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("toolDefinitionsTokens")) + return SessionShutdownData( + shutdown_type=shutdown_type, + total_premium_requests=total_premium_requests, + total_api_duration_ms=total_api_duration_ms, + session_start_time=session_start_time, + code_changes=code_changes, + model_metrics=model_metrics, + error_reason=error_reason, + current_model=current_model, + current_tokens=current_tokens, + system_tokens=system_tokens, + conversation_tokens=conversation_tokens, + tool_definitions_tokens=tool_definitions_tokens, + ) def to_dict(self) -> dict: result: dict = {} - result["identifier"] = from_str(self.identifier) - result["readOnly"] = from_bool(self.read_only) + result["shutdownType"] = to_enum(SessionShutdownDataShutdownType, self.shutdown_type) + result["totalPremiumRequests"] = to_float(self.total_premium_requests) + result["totalApiDurationMs"] = to_float(self.total_api_duration_ms) + result["sessionStartTime"] = to_float(self.session_start_time) + result["codeChanges"] = to_class(SessionShutdownDataCodeChanges, self.code_changes) + result["modelMetrics"] = from_dict(lambda x: to_class(SessionShutdownDataModelMetricsValue, x), self.model_metrics) + if self.error_reason is not None: + result["errorReason"] = from_union([from_none, lambda x: from_str(x)], self.error_reason) + if self.current_model is not None: + result["currentModel"] = from_union([from_none, lambda x: from_str(x)], self.current_model) + if self.current_tokens is not None: + result["currentTokens"] = from_union([from_none, lambda x: to_float(x)], self.current_tokens) + if self.system_tokens is not None: + result["systemTokens"] = from_union([from_none, lambda x: to_float(x)], self.system_tokens) + if self.conversation_tokens is not None: + result["conversationTokens"] = from_union([from_none, lambda x: to_float(x)], self.conversation_tokens) + if self.tool_definitions_tokens is not None: + result["toolDefinitionsTokens"] = from_union([from_none, lambda x: to_float(x)], self.tool_definitions_tokens) return result -class PermissionRequestKind(Enum): - CUSTOM_TOOL = "custom-tool" - HOOK = "hook" - MCP = "mcp" - MEMORY = "memory" - READ = "read" - SHELL = "shell" - URL = "url" - WRITE = "write" - - @dataclass -class PossibleURL: - url: str - """URL that may be accessed by the command""" +class SessionContextChangedData: + "Updated working directory and git context after the change" + cwd: str + git_root: str | None = None + repository: str | None = None + host_type: SessionStartDataContextHostType | None = None + branch: str | None = None + head_commit: str | None = None + base_commit: str | None = None @staticmethod - def from_dict(obj: Any) -> 'PossibleURL': + def from_dict(obj: Any) -> "SessionContextChangedData": assert isinstance(obj, dict) - url = from_str(obj.get("url")) - return PossibleURL(url) + cwd = from_str(obj.get("cwd")) + git_root = from_union([from_none, lambda x: from_str(x)], obj.get("gitRoot")) + repository = from_union([from_none, lambda x: from_str(x)], obj.get("repository")) + host_type = from_union([from_none, lambda x: parse_enum(SessionStartDataContextHostType, x)], obj.get("hostType")) + branch = from_union([from_none, lambda x: from_str(x)], obj.get("branch")) + head_commit = from_union([from_none, lambda x: from_str(x)], obj.get("headCommit")) + base_commit = from_union([from_none, lambda x: from_str(x)], obj.get("baseCommit")) + return SessionContextChangedData( + cwd=cwd, + git_root=git_root, + repository=repository, + host_type=host_type, + branch=branch, + head_commit=head_commit, + base_commit=base_commit, + ) def to_dict(self) -> dict: result: dict = {} - result["url"] = from_str(self.url) + result["cwd"] = from_str(self.cwd) + if self.git_root is not None: + result["gitRoot"] = from_union([from_none, lambda x: from_str(x)], self.git_root) + if self.repository is not None: + result["repository"] = from_union([from_none, lambda x: from_str(x)], self.repository) + if self.host_type is not None: + result["hostType"] = from_union([from_none, lambda x: to_enum(SessionStartDataContextHostType, x)], self.host_type) + if self.branch is not None: + result["branch"] = from_union([from_none, lambda x: from_str(x)], self.branch) + if self.head_commit is not None: + result["headCommit"] = from_union([from_none, lambda x: from_str(x)], self.head_commit) + if self.base_commit is not None: + result["baseCommit"] = from_union([from_none, lambda x: from_str(x)], self.base_commit) return result @dataclass -class PermissionRequest: - """Details of the permission being requested - - Shell command permission request - - File write permission request - - File or directory read permission request - - MCP tool invocation permission request - - URL access permission request - - Memory storage permission request - - Custom tool invocation permission request - - Hook confirmation permission request - """ - kind: PermissionRequestKind - """Permission kind discriminator""" - - can_offer_session_approval: bool | None = None - """Whether the UI can offer session-wide approval for this command pattern""" - - commands: list[PermissionRequestCommand] | None = None - """Parsed command identifiers found in the command text""" - - full_command_text: str | None = None - """The complete shell command text to be executed""" - - has_write_file_redirection: bool | None = None - """Whether the command includes a file write redirection (e.g., > or >>)""" - - intention: str | None = None - """Human-readable description of what the command intends to do - - Human-readable description of the intended file change - - Human-readable description of why the file is being read - - Human-readable description of why the URL is being accessed - """ - possible_paths: list[str] | None = None - """File paths that may be read or written by the command""" +class SessionUsageInfoData: + "Current context window usage statistics including token and message counts" + token_limit: float + current_tokens: float + messages_length: float + system_tokens: float | None = None + conversation_tokens: float | None = None + tool_definitions_tokens: float | None = None + is_initial: bool | None = None - possible_urls: list[PossibleURL] | None = None - """URLs that may be accessed by the command""" + @staticmethod + def from_dict(obj: Any) -> "SessionUsageInfoData": + assert isinstance(obj, dict) + token_limit = from_float(obj.get("tokenLimit")) + current_tokens = from_float(obj.get("currentTokens")) + messages_length = from_float(obj.get("messagesLength")) + system_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("systemTokens")) + conversation_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("conversationTokens")) + tool_definitions_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("toolDefinitionsTokens")) + is_initial = from_union([from_none, lambda x: from_bool(x)], obj.get("isInitial")) + return SessionUsageInfoData( + token_limit=token_limit, + current_tokens=current_tokens, + messages_length=messages_length, + system_tokens=system_tokens, + conversation_tokens=conversation_tokens, + tool_definitions_tokens=tool_definitions_tokens, + is_initial=is_initial, + ) - tool_call_id: str | None = None - """Tool call ID that triggered this permission request""" + def to_dict(self) -> dict: + result: dict = {} + result["tokenLimit"] = to_float(self.token_limit) + result["currentTokens"] = to_float(self.current_tokens) + result["messagesLength"] = to_float(self.messages_length) + if self.system_tokens is not None: + result["systemTokens"] = from_union([from_none, lambda x: to_float(x)], self.system_tokens) + if self.conversation_tokens is not None: + result["conversationTokens"] = from_union([from_none, lambda x: to_float(x)], self.conversation_tokens) + if self.tool_definitions_tokens is not None: + result["toolDefinitionsTokens"] = from_union([from_none, lambda x: to_float(x)], self.tool_definitions_tokens) + if self.is_initial is not None: + result["isInitial"] = from_union([from_none, lambda x: from_bool(x)], self.is_initial) + return result - warning: str | None = None - """Optional warning message about risks of running this command""" - diff: str | None = None - """Unified diff showing the proposed changes""" +@dataclass +class SessionCompactionStartData: + "Context window breakdown at the start of LLM-powered conversation compaction" + system_tokens: float | None = None + conversation_tokens: float | None = None + tool_definitions_tokens: float | None = None - file_name: str | None = None - """Path of the file being written to""" + @staticmethod + def from_dict(obj: Any) -> "SessionCompactionStartData": + assert isinstance(obj, dict) + system_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("systemTokens")) + conversation_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("conversationTokens")) + tool_definitions_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("toolDefinitionsTokens")) + return SessionCompactionStartData( + system_tokens=system_tokens, + conversation_tokens=conversation_tokens, + tool_definitions_tokens=tool_definitions_tokens, + ) - new_file_contents: str | None = None - """Complete new file contents for newly created files""" + def to_dict(self) -> dict: + result: dict = {} + if self.system_tokens is not None: + result["systemTokens"] = from_union([from_none, lambda x: to_float(x)], self.system_tokens) + if self.conversation_tokens is not None: + result["conversationTokens"] = from_union([from_none, lambda x: to_float(x)], self.conversation_tokens) + if self.tool_definitions_tokens is not None: + result["toolDefinitionsTokens"] = from_union([from_none, lambda x: to_float(x)], self.tool_definitions_tokens) + return result - path: str | None = None - """Path of the file or directory being read""" - args: Any = None - """Arguments to pass to the MCP tool - - Arguments to pass to the custom tool - """ - read_only: bool | None = None - """Whether this MCP tool is read-only (no side effects)""" +@dataclass +class SessionCompactionCompleteDataCompactionTokensUsed: + "Token usage breakdown for the compaction LLM call" + input: float + output: float + cached_input: float - server_name: str | None = None - """Name of the MCP server providing the tool""" + @staticmethod + def from_dict(obj: Any) -> "SessionCompactionCompleteDataCompactionTokensUsed": + assert isinstance(obj, dict) + input = from_float(obj.get("input")) + output = from_float(obj.get("output")) + cached_input = from_float(obj.get("cachedInput")) + return SessionCompactionCompleteDataCompactionTokensUsed( + input=input, + output=output, + cached_input=cached_input, + ) - tool_name: str | None = None - """Internal name of the MCP tool - - Name of the custom tool - - Name of the tool the hook is gating - """ - tool_title: str | None = None - """Human-readable title of the MCP tool""" + def to_dict(self) -> dict: + result: dict = {} + result["input"] = to_float(self.input) + result["output"] = to_float(self.output) + result["cachedInput"] = to_float(self.cached_input) + return result - url: str | None = None - """URL to be fetched""" - citations: str | None = None - """Source references for the stored fact""" +@dataclass +class SessionCompactionCompleteData: + "Conversation compaction results including success status, metrics, and optional error details" + success: bool + error: str | None = None + pre_compaction_tokens: float | None = None + post_compaction_tokens: float | None = None + pre_compaction_messages_length: float | None = None + messages_removed: float | None = None + tokens_removed: float | None = None + summary_content: str | None = None + checkpoint_number: float | None = None + checkpoint_path: str | None = None + compaction_tokens_used: SessionCompactionCompleteDataCompactionTokensUsed | None = None + request_id: str | None = None + system_tokens: float | None = None + conversation_tokens: float | None = None + tool_definitions_tokens: float | None = None - fact: str | None = None - """The fact or convention being stored""" + @staticmethod + def from_dict(obj: Any) -> "SessionCompactionCompleteData": + assert isinstance(obj, dict) + success = from_bool(obj.get("success")) + error = from_union([from_none, lambda x: from_str(x)], obj.get("error")) + pre_compaction_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("preCompactionTokens")) + post_compaction_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("postCompactionTokens")) + pre_compaction_messages_length = from_union([from_none, lambda x: from_float(x)], obj.get("preCompactionMessagesLength")) + messages_removed = from_union([from_none, lambda x: from_float(x)], obj.get("messagesRemoved")) + tokens_removed = from_union([from_none, lambda x: from_float(x)], obj.get("tokensRemoved")) + summary_content = from_union([from_none, lambda x: from_str(x)], obj.get("summaryContent")) + checkpoint_number = from_union([from_none, lambda x: from_float(x)], obj.get("checkpointNumber")) + checkpoint_path = from_union([from_none, lambda x: from_str(x)], obj.get("checkpointPath")) + compaction_tokens_used = from_union([from_none, lambda x: SessionCompactionCompleteDataCompactionTokensUsed.from_dict(x)], obj.get("compactionTokensUsed")) + request_id = from_union([from_none, lambda x: from_str(x)], obj.get("requestId")) + system_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("systemTokens")) + conversation_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("conversationTokens")) + tool_definitions_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("toolDefinitionsTokens")) + return SessionCompactionCompleteData( + success=success, + error=error, + pre_compaction_tokens=pre_compaction_tokens, + post_compaction_tokens=post_compaction_tokens, + pre_compaction_messages_length=pre_compaction_messages_length, + messages_removed=messages_removed, + tokens_removed=tokens_removed, + summary_content=summary_content, + checkpoint_number=checkpoint_number, + checkpoint_path=checkpoint_path, + compaction_tokens_used=compaction_tokens_used, + request_id=request_id, + system_tokens=system_tokens, + conversation_tokens=conversation_tokens, + tool_definitions_tokens=tool_definitions_tokens, + ) - subject: str | None = None - """Topic or subject of the memory being stored""" + def to_dict(self) -> dict: + result: dict = {} + result["success"] = from_bool(self.success) + if self.error is not None: + result["error"] = from_union([from_none, lambda x: from_str(x)], self.error) + if self.pre_compaction_tokens is not None: + result["preCompactionTokens"] = from_union([from_none, lambda x: to_float(x)], self.pre_compaction_tokens) + if self.post_compaction_tokens is not None: + result["postCompactionTokens"] = from_union([from_none, lambda x: to_float(x)], self.post_compaction_tokens) + if self.pre_compaction_messages_length is not None: + result["preCompactionMessagesLength"] = from_union([from_none, lambda x: to_float(x)], self.pre_compaction_messages_length) + if self.messages_removed is not None: + result["messagesRemoved"] = from_union([from_none, lambda x: to_float(x)], self.messages_removed) + if self.tokens_removed is not None: + result["tokensRemoved"] = from_union([from_none, lambda x: to_float(x)], self.tokens_removed) + if self.summary_content is not None: + result["summaryContent"] = from_union([from_none, lambda x: from_str(x)], self.summary_content) + if self.checkpoint_number is not None: + result["checkpointNumber"] = from_union([from_none, lambda x: to_float(x)], self.checkpoint_number) + if self.checkpoint_path is not None: + result["checkpointPath"] = from_union([from_none, lambda x: from_str(x)], self.checkpoint_path) + if self.compaction_tokens_used is not None: + result["compactionTokensUsed"] = from_union([from_none, lambda x: to_class(SessionCompactionCompleteDataCompactionTokensUsed, x)], self.compaction_tokens_used) + if self.request_id is not None: + result["requestId"] = from_union([from_none, lambda x: from_str(x)], self.request_id) + if self.system_tokens is not None: + result["systemTokens"] = from_union([from_none, lambda x: to_float(x)], self.system_tokens) + if self.conversation_tokens is not None: + result["conversationTokens"] = from_union([from_none, lambda x: to_float(x)], self.conversation_tokens) + if self.tool_definitions_tokens is not None: + result["toolDefinitionsTokens"] = from_union([from_none, lambda x: to_float(x)], self.tool_definitions_tokens) + return result - tool_description: str | None = None - """Description of what the custom tool does""" - hook_message: str | None = None - """Optional message from the hook explaining why confirmation is needed""" +@dataclass +class SessionTaskCompleteData: + "Task completion notification with summary from the agent" + summary: str | None = None + success: bool | None = None - tool_args: Any = None - """Arguments of the tool call being gated""" - - @staticmethod - def from_dict(obj: Any) -> 'PermissionRequest': - assert isinstance(obj, dict) - kind = PermissionRequestKind(obj.get("kind")) - can_offer_session_approval = from_union([from_bool, from_none], obj.get("canOfferSessionApproval")) - commands = from_union([lambda x: from_list(PermissionRequestCommand.from_dict, x), from_none], obj.get("commands")) - full_command_text = from_union([from_str, from_none], obj.get("fullCommandText")) - has_write_file_redirection = from_union([from_bool, from_none], obj.get("hasWriteFileRedirection")) - intention = from_union([from_str, from_none], obj.get("intention")) - possible_paths = from_union([lambda x: from_list(from_str, x), from_none], obj.get("possiblePaths")) - possible_urls = from_union([lambda x: from_list(PossibleURL.from_dict, x), from_none], obj.get("possibleUrls")) - tool_call_id = from_union([from_str, from_none], obj.get("toolCallId")) - warning = from_union([from_str, from_none], obj.get("warning")) - diff = from_union([from_str, from_none], obj.get("diff")) - file_name = from_union([from_str, from_none], obj.get("fileName")) - new_file_contents = from_union([from_str, from_none], obj.get("newFileContents")) - path = from_union([from_str, from_none], obj.get("path")) - args = obj.get("args") - read_only = from_union([from_bool, from_none], obj.get("readOnly")) - server_name = from_union([from_str, from_none], obj.get("serverName")) - tool_name = from_union([from_str, from_none], obj.get("toolName")) - tool_title = from_union([from_str, from_none], obj.get("toolTitle")) - url = from_union([from_str, from_none], obj.get("url")) - citations = from_union([from_str, from_none], obj.get("citations")) - fact = from_union([from_str, from_none], obj.get("fact")) - subject = from_union([from_str, from_none], obj.get("subject")) - tool_description = from_union([from_str, from_none], obj.get("toolDescription")) - hook_message = from_union([from_str, from_none], obj.get("hookMessage")) - tool_args = obj.get("toolArgs") - return PermissionRequest(kind, can_offer_session_approval, commands, full_command_text, has_write_file_redirection, intention, possible_paths, possible_urls, tool_call_id, warning, diff, file_name, new_file_contents, path, args, read_only, server_name, tool_name, tool_title, url, citations, fact, subject, tool_description, hook_message, tool_args) + @staticmethod + def from_dict(obj: Any) -> "SessionTaskCompleteData": + assert isinstance(obj, dict) + summary = from_union([from_none, lambda x: from_str(x)], obj.get("summary")) + success = from_union([from_none, lambda x: from_bool(x)], obj.get("success")) + return SessionTaskCompleteData( + summary=summary, + success=success, + ) def to_dict(self) -> dict: result: dict = {} - result["kind"] = to_enum(PermissionRequestKind, self.kind) - if self.can_offer_session_approval is not None: - result["canOfferSessionApproval"] = from_union([from_bool, from_none], self.can_offer_session_approval) - if self.commands is not None: - result["commands"] = from_union([lambda x: from_list(lambda x: to_class(PermissionRequestCommand, x), x), from_none], self.commands) - if self.full_command_text is not None: - result["fullCommandText"] = from_union([from_str, from_none], self.full_command_text) - if self.has_write_file_redirection is not None: - result["hasWriteFileRedirection"] = from_union([from_bool, from_none], self.has_write_file_redirection) - if self.intention is not None: - result["intention"] = from_union([from_str, from_none], self.intention) - if self.possible_paths is not None: - result["possiblePaths"] = from_union([lambda x: from_list(from_str, x), from_none], self.possible_paths) - if self.possible_urls is not None: - result["possibleUrls"] = from_union([lambda x: from_list(lambda x: to_class(PossibleURL, x), x), from_none], self.possible_urls) - if self.tool_call_id is not None: - result["toolCallId"] = from_union([from_str, from_none], self.tool_call_id) - if self.warning is not None: - result["warning"] = from_union([from_str, from_none], self.warning) - if self.diff is not None: - result["diff"] = from_union([from_str, from_none], self.diff) - if self.file_name is not None: - result["fileName"] = from_union([from_str, from_none], self.file_name) - if self.new_file_contents is not None: - result["newFileContents"] = from_union([from_str, from_none], self.new_file_contents) - if self.path is not None: - result["path"] = from_union([from_str, from_none], self.path) - if self.args is not None: - result["args"] = self.args - if self.read_only is not None: - result["readOnly"] = from_union([from_bool, from_none], self.read_only) - if self.server_name is not None: - result["serverName"] = from_union([from_str, from_none], self.server_name) - if self.tool_name is not None: - result["toolName"] = from_union([from_str, from_none], self.tool_name) - if self.tool_title is not None: - result["toolTitle"] = from_union([from_str, from_none], self.tool_title) - if self.url is not None: - result["url"] = from_union([from_str, from_none], self.url) - if self.citations is not None: - result["citations"] = from_union([from_str, from_none], self.citations) - if self.fact is not None: - result["fact"] = from_union([from_str, from_none], self.fact) - if self.subject is not None: - result["subject"] = from_union([from_str, from_none], self.subject) - if self.tool_description is not None: - result["toolDescription"] = from_union([from_str, from_none], self.tool_description) - if self.hook_message is not None: - result["hookMessage"] = from_union([from_str, from_none], self.hook_message) - if self.tool_args is not None: - result["toolArgs"] = self.tool_args + if self.summary is not None: + result["summary"] = from_union([from_none, lambda x: from_str(x)], self.summary) + if self.success is not None: + result["success"] = from_union([from_none, lambda x: from_bool(x)], self.success) return result @dataclass -class QuotaSnapshot: - entitlement_requests: float - """Total requests allowed by the entitlement""" - - is_unlimited_entitlement: bool - """Whether the user has an unlimited usage entitlement""" - - overage: float - """Number of requests over the entitlement limit""" - - overage_allowed_with_exhausted_quota: bool - """Whether overage is allowed when quota is exhausted""" +class UserMessageDataAttachmentsItemLineRange: + "Optional line range to scope the attachment to a specific section of the file" + start: float + end: float - remaining_percentage: float - """Percentage of quota remaining (0.0 to 1.0)""" + @staticmethod + def from_dict(obj: Any) -> "UserMessageDataAttachmentsItemLineRange": + assert isinstance(obj, dict) + start = from_float(obj.get("start")) + end = from_float(obj.get("end")) + return UserMessageDataAttachmentsItemLineRange( + start=start, + end=end, + ) - usage_allowed_with_exhausted_quota: bool - """Whether usage is still permitted after quota exhaustion""" + def to_dict(self) -> dict: + result: dict = {} + result["start"] = to_float(self.start) + result["end"] = to_float(self.end) + return result - used_requests: float - """Number of requests already consumed""" - reset_date: datetime | None = None - """Date when the quota resets""" +@dataclass +class UserMessageDataAttachmentsItemSelectionStart: + "Start position of the selection" + line: float + character: float @staticmethod - def from_dict(obj: Any) -> 'QuotaSnapshot': + def from_dict(obj: Any) -> "UserMessageDataAttachmentsItemSelectionStart": assert isinstance(obj, dict) - entitlement_requests = from_float(obj.get("entitlementRequests")) - is_unlimited_entitlement = from_bool(obj.get("isUnlimitedEntitlement")) - overage = from_float(obj.get("overage")) - overage_allowed_with_exhausted_quota = from_bool(obj.get("overageAllowedWithExhaustedQuota")) - remaining_percentage = from_float(obj.get("remainingPercentage")) - usage_allowed_with_exhausted_quota = from_bool(obj.get("usageAllowedWithExhaustedQuota")) - used_requests = from_float(obj.get("usedRequests")) - reset_date = from_union([from_datetime, from_none], obj.get("resetDate")) - return QuotaSnapshot(entitlement_requests, is_unlimited_entitlement, overage, overage_allowed_with_exhausted_quota, remaining_percentage, usage_allowed_with_exhausted_quota, used_requests, reset_date) + line = from_float(obj.get("line")) + character = from_float(obj.get("character")) + return UserMessageDataAttachmentsItemSelectionStart( + line=line, + character=character, + ) def to_dict(self) -> dict: result: dict = {} - result["entitlementRequests"] = to_float(self.entitlement_requests) - result["isUnlimitedEntitlement"] = from_bool(self.is_unlimited_entitlement) - result["overage"] = to_float(self.overage) - result["overageAllowedWithExhaustedQuota"] = from_bool(self.overage_allowed_with_exhausted_quota) - result["remainingPercentage"] = to_float(self.remaining_percentage) - result["usageAllowedWithExhaustedQuota"] = from_bool(self.usage_allowed_with_exhausted_quota) - result["usedRequests"] = to_float(self.used_requests) - if self.reset_date is not None: - result["resetDate"] = from_union([lambda x: x.isoformat(), from_none], self.reset_date) + result["line"] = to_float(self.line) + result["character"] = to_float(self.character) return result @dataclass -class RepositoryClass: - """Repository context for the handed-off session""" - - name: str - """Repository name""" - - owner: str - """Repository owner (user or organization)""" - - branch: str | None = None - """Git branch name, if applicable""" +class UserMessageDataAttachmentsItemSelectionEnd: + "End position of the selection" + line: float + character: float @staticmethod - def from_dict(obj: Any) -> 'RepositoryClass': + def from_dict(obj: Any) -> "UserMessageDataAttachmentsItemSelectionEnd": assert isinstance(obj, dict) - name = from_str(obj.get("name")) - owner = from_str(obj.get("owner")) - branch = from_union([from_str, from_none], obj.get("branch")) - return RepositoryClass(name, owner, branch) + line = from_float(obj.get("line")) + character = from_float(obj.get("character")) + return UserMessageDataAttachmentsItemSelectionEnd( + line=line, + character=character, + ) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) - result["owner"] = from_str(self.owner) - if self.branch is not None: - result["branch"] = from_union([from_str, from_none], self.branch) + result["line"] = to_float(self.line) + result["character"] = to_float(self.character) return result -class RequestedSchemaType(Enum): - OBJECT = "object" - - @dataclass -class RequestedSchema: - """JSON Schema describing the form fields to present to the user (form mode only)""" - - properties: dict[str, Any] - """Form field definitions, keyed by field name""" - - type: RequestedSchemaType - """Schema type indicator (always 'object')""" - - required: list[str] | None = None - """List of required field names""" +class UserMessageDataAttachmentsItemSelection: + "Position range of the selection within the file" + start: UserMessageDataAttachmentsItemSelectionStart + end: UserMessageDataAttachmentsItemSelectionEnd @staticmethod - def from_dict(obj: Any) -> 'RequestedSchema': + def from_dict(obj: Any) -> "UserMessageDataAttachmentsItemSelection": assert isinstance(obj, dict) - properties = from_dict(lambda x: x, obj.get("properties")) - type = RequestedSchemaType(obj.get("type")) - required = from_union([lambda x: from_list(from_str, x), from_none], obj.get("required")) - return RequestedSchema(properties, type, required) + start = UserMessageDataAttachmentsItemSelectionStart.from_dict(obj.get("start")) + end = UserMessageDataAttachmentsItemSelectionEnd.from_dict(obj.get("end")) + return UserMessageDataAttachmentsItemSelection( + start=start, + end=end, + ) def to_dict(self) -> dict: result: dict = {} - result["properties"] = from_dict(lambda x: x, self.properties) - result["type"] = to_enum(RequestedSchemaType, self.type) - if self.required is not None: - result["required"] = from_union([lambda x: from_list(from_str, x), from_none], self.required) + result["start"] = to_class(UserMessageDataAttachmentsItemSelectionStart, self.start) + result["end"] = to_class(UserMessageDataAttachmentsItemSelectionEnd, self.end) return result -class Theme(Enum): - """Theme variant this icon is intended for""" - - DARK = "dark" - LIGHT = "light" - - @dataclass -class Icon: - """Icon image for a resource""" - - src: str - """URL or path to the icon image""" - +class UserMessageDataAttachmentsItem: + "A user message attachment — a file, directory, code selection, blob, or GitHub reference" + type: UserMessageDataAttachmentsItemType + path: str | None = None + display_name: str | None = None + line_range: UserMessageDataAttachmentsItemLineRange | None = None + file_path: str | None = None + text: str | None = None + selection: UserMessageDataAttachmentsItemSelection | None = None + number: float | None = None + title: str | None = None + reference_type: UserMessageDataAttachmentsItemReferenceType | None = None + state: str | None = None + url: str | None = None + data: str | None = None mime_type: str | None = None - """MIME type of the icon image""" - - sizes: list[str] | None = None - """Available icon sizes (e.g., ['16x16', '32x32'])""" - - theme: Theme | None = None - """Theme variant this icon is intended for""" @staticmethod - def from_dict(obj: Any) -> 'Icon': + def from_dict(obj: Any) -> "UserMessageDataAttachmentsItem": assert isinstance(obj, dict) - src = from_str(obj.get("src")) - mime_type = from_union([from_str, from_none], obj.get("mimeType")) - sizes = from_union([lambda x: from_list(from_str, x), from_none], obj.get("sizes")) - theme = from_union([Theme, from_none], obj.get("theme")) - return Icon(src, mime_type, sizes, theme) + type = parse_enum(UserMessageDataAttachmentsItemType, obj.get("type")) + path = from_union([from_none, lambda x: from_str(x)], obj.get("path")) + display_name = from_union([from_none, lambda x: from_str(x)], obj.get("displayName")) + line_range = from_union([from_none, lambda x: UserMessageDataAttachmentsItemLineRange.from_dict(x)], obj.get("lineRange")) + file_path = from_union([from_none, lambda x: from_str(x)], obj.get("filePath")) + text = from_union([from_none, lambda x: from_str(x)], obj.get("text")) + selection = from_union([from_none, lambda x: UserMessageDataAttachmentsItemSelection.from_dict(x)], obj.get("selection")) + number = from_union([from_none, lambda x: from_float(x)], obj.get("number")) + title = from_union([from_none, lambda x: from_str(x)], obj.get("title")) + reference_type = from_union([from_none, lambda x: parse_enum(UserMessageDataAttachmentsItemReferenceType, x)], obj.get("referenceType")) + state = from_union([from_none, lambda x: from_str(x)], obj.get("state")) + url = from_union([from_none, lambda x: from_str(x)], obj.get("url")) + data = from_union([from_none, lambda x: from_str(x)], obj.get("data")) + mime_type = from_union([from_none, lambda x: from_str(x)], obj.get("mimeType")) + return UserMessageDataAttachmentsItem( + type=type, + path=path, + display_name=display_name, + line_range=line_range, + file_path=file_path, + text=text, + selection=selection, + number=number, + title=title, + reference_type=reference_type, + state=state, + url=url, + data=data, + mime_type=mime_type, + ) def to_dict(self) -> dict: result: dict = {} - result["src"] = from_str(self.src) + result["type"] = to_enum(UserMessageDataAttachmentsItemType, self.type) + if self.path is not None: + result["path"] = from_union([from_none, lambda x: from_str(x)], self.path) + if self.display_name is not None: + result["displayName"] = from_union([from_none, lambda x: from_str(x)], self.display_name) + if self.line_range is not None: + result["lineRange"] = from_union([from_none, lambda x: to_class(UserMessageDataAttachmentsItemLineRange, x)], self.line_range) + if self.file_path is not None: + result["filePath"] = from_union([from_none, lambda x: from_str(x)], self.file_path) + if self.text is not None: + result["text"] = from_union([from_none, lambda x: from_str(x)], self.text) + if self.selection is not None: + result["selection"] = from_union([from_none, lambda x: to_class(UserMessageDataAttachmentsItemSelection, x)], self.selection) + if self.number is not None: + result["number"] = from_union([from_none, lambda x: to_float(x)], self.number) + if self.title is not None: + result["title"] = from_union([from_none, lambda x: from_str(x)], self.title) + if self.reference_type is not None: + result["referenceType"] = from_union([from_none, lambda x: to_enum(UserMessageDataAttachmentsItemReferenceType, x)], self.reference_type) + if self.state is not None: + result["state"] = from_union([from_none, lambda x: from_str(x)], self.state) + if self.url is not None: + result["url"] = from_union([from_none, lambda x: from_str(x)], self.url) + if self.data is not None: + result["data"] = from_union([from_none, lambda x: from_str(x)], self.data) if self.mime_type is not None: - result["mimeType"] = from_union([from_str, from_none], self.mime_type) - if self.sizes is not None: - result["sizes"] = from_union([lambda x: from_list(from_str, x), from_none], self.sizes) - if self.theme is not None: - result["theme"] = from_union([lambda x: to_enum(Theme, x), from_none], self.theme) + result["mimeType"] = from_union([from_none, lambda x: from_str(x)], self.mime_type) return result @dataclass -class Resource: - """The embedded resource contents, either text or base64-encoded binary""" - - uri: str - """URI identifying the resource""" - - mime_type: str | None = None - """MIME type of the text content - - MIME type of the blob content - """ - text: str | None = None - """Text content of the resource""" - - blob: str | None = None - """Base64-encoded binary content of the resource""" +class UserMessageData: + content: str + transformed_content: str | None = None + attachments: list[UserMessageDataAttachmentsItem] | None = None + source: str | None = None + agent_mode: UserMessageDataAgentMode | None = None + interaction_id: str | None = None @staticmethod - def from_dict(obj: Any) -> 'Resource': + def from_dict(obj: Any) -> "UserMessageData": assert isinstance(obj, dict) - uri = from_str(obj.get("uri")) - mime_type = from_union([from_str, from_none], obj.get("mimeType")) - text = from_union([from_str, from_none], obj.get("text")) - blob = from_union([from_str, from_none], obj.get("blob")) - return Resource(uri, mime_type, text, blob) + content = from_str(obj.get("content")) + transformed_content = from_union([from_none, lambda x: from_str(x)], obj.get("transformedContent")) + attachments = from_union([from_none, lambda x: from_list(UserMessageDataAttachmentsItem.from_dict, x)], obj.get("attachments")) + source = from_union([from_none, lambda x: from_str(x)], obj.get("source")) + agent_mode = from_union([from_none, lambda x: parse_enum(UserMessageDataAgentMode, x)], obj.get("agentMode")) + interaction_id = from_union([from_none, lambda x: from_str(x)], obj.get("interactionId")) + return UserMessageData( + content=content, + transformed_content=transformed_content, + attachments=attachments, + source=source, + agent_mode=agent_mode, + interaction_id=interaction_id, + ) def to_dict(self) -> dict: result: dict = {} - result["uri"] = from_str(self.uri) - if self.mime_type is not None: - result["mimeType"] = from_union([from_str, from_none], self.mime_type) - if self.text is not None: - result["text"] = from_union([from_str, from_none], self.text) - if self.blob is not None: - result["blob"] = from_union([from_str, from_none], self.blob) + result["content"] = from_str(self.content) + if self.transformed_content is not None: + result["transformedContent"] = from_union([from_none, lambda x: from_str(x)], self.transformed_content) + if self.attachments is not None: + result["attachments"] = from_union([from_none, lambda x: from_list(lambda x: to_class(UserMessageDataAttachmentsItem, x), x)], self.attachments) + if self.source is not None: + result["source"] = from_union([from_none, lambda x: from_str(x)], self.source) + if self.agent_mode is not None: + result["agentMode"] = from_union([from_none, lambda x: to_enum(UserMessageDataAgentMode, x)], self.agent_mode) + if self.interaction_id is not None: + result["interactionId"] = from_union([from_none, lambda x: from_str(x)], self.interaction_id) return result -class ContentType(Enum): - AUDIO = "audio" - IMAGE = "image" - RESOURCE = "resource" - RESOURCE_LINK = "resource_link" - TERMINAL = "terminal" - TEXT = "text" - - @dataclass -class ContentElement: - """A content block within a tool result, which may be text, terminal output, image, audio, - or a resource - - Plain text content block - - Terminal/shell output content block with optional exit code and working directory - - Image content block with base64-encoded data - - Audio content block with base64-encoded data - - Resource link content block referencing an external resource - - Embedded resource content block with inline text or binary data - """ - type: ContentType - """Content block type discriminator""" - - text: str | None = None - """The text content - - Terminal/shell output text - """ - cwd: str | None = None - """Working directory where the command was executed""" - - exit_code: float | None = None - """Process exit code, if the command has completed""" - - data: str | None = None - """Base64-encoded image data - - Base64-encoded audio data - """ - mime_type: str | None = None - """MIME type of the image (e.g., image/png, image/jpeg) - - MIME type of the audio (e.g., audio/wav, audio/mpeg) - - MIME type of the resource content - """ - description: str | None = None - """Human-readable description of the resource""" - - icons: list[Icon] | None = None - """Icons associated with this resource""" - - name: str | None = None - """Resource name identifier""" - - size: float | None = None - """Size of the resource in bytes""" +class PendingMessagesModifiedData: + "Empty payload; the event signals that the pending message queue has changed" + @staticmethod + def from_dict(obj: Any) -> "PendingMessagesModifiedData": + assert isinstance(obj, dict) + return PendingMessagesModifiedData() - title: str | None = None - """Human-readable display title for the resource""" + def to_dict(self) -> dict: + return {} - uri: str | None = None - """URI identifying the resource""" - resource: Resource | None = None - """The embedded resource contents, either text or base64-encoded binary""" +@dataclass +class AssistantTurnStartData: + "Turn initialization metadata including identifier and interaction tracking" + turn_id: str + interaction_id: str | None = None @staticmethod - def from_dict(obj: Any) -> 'ContentElement': + def from_dict(obj: Any) -> "AssistantTurnStartData": assert isinstance(obj, dict) - type = ContentType(obj.get("type")) - text = from_union([from_str, from_none], obj.get("text")) - cwd = from_union([from_str, from_none], obj.get("cwd")) - exit_code = from_union([from_float, from_none], obj.get("exitCode")) - data = from_union([from_str, from_none], obj.get("data")) - mime_type = from_union([from_str, from_none], obj.get("mimeType")) - description = from_union([from_str, from_none], obj.get("description")) - icons = from_union([lambda x: from_list(Icon.from_dict, x), from_none], obj.get("icons")) - name = from_union([from_str, from_none], obj.get("name")) - size = from_union([from_float, from_none], obj.get("size")) - title = from_union([from_str, from_none], obj.get("title")) - uri = from_union([from_str, from_none], obj.get("uri")) - resource = from_union([Resource.from_dict, from_none], obj.get("resource")) - return ContentElement(type, text, cwd, exit_code, data, mime_type, description, icons, name, size, title, uri, resource) + turn_id = from_str(obj.get("turnId")) + interaction_id = from_union([from_none, lambda x: from_str(x)], obj.get("interactionId")) + return AssistantTurnStartData( + turn_id=turn_id, + interaction_id=interaction_id, + ) def to_dict(self) -> dict: result: dict = {} - result["type"] = to_enum(ContentType, self.type) - if self.text is not None: - result["text"] = from_union([from_str, from_none], self.text) - if self.cwd is not None: - result["cwd"] = from_union([from_str, from_none], self.cwd) - if self.exit_code is not None: - result["exitCode"] = from_union([to_float, from_none], self.exit_code) - if self.data is not None: - result["data"] = from_union([from_str, from_none], self.data) - if self.mime_type is not None: - result["mimeType"] = from_union([from_str, from_none], self.mime_type) - if self.description is not None: - result["description"] = from_union([from_str, from_none], self.description) - if self.icons is not None: - result["icons"] = from_union([lambda x: from_list(lambda x: to_class(Icon, x), x), from_none], self.icons) - if self.name is not None: - result["name"] = from_union([from_str, from_none], self.name) - if self.size is not None: - result["size"] = from_union([to_float, from_none], self.size) - if self.title is not None: - result["title"] = from_union([from_str, from_none], self.title) - if self.uri is not None: - result["uri"] = from_union([from_str, from_none], self.uri) - if self.resource is not None: - result["resource"] = from_union([lambda x: to_class(Resource, x), from_none], self.resource) + result["turnId"] = from_str(self.turn_id) + if self.interaction_id is not None: + result["interactionId"] = from_union([from_none, lambda x: from_str(x)], self.interaction_id) return result -class ResultKind(Enum): - """The outcome of the permission request""" - - APPROVED = "approved" - DENIED_BY_CONTENT_EXCLUSION_POLICY = "denied-by-content-exclusion-policy" - DENIED_BY_PERMISSION_REQUEST_HOOK = "denied-by-permission-request-hook" - DENIED_BY_RULES = "denied-by-rules" - DENIED_INTERACTIVELY_BY_USER = "denied-interactively-by-user" - DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER = "denied-no-approval-rule-and-could-not-request-from-user" - - @dataclass -class Result: - """Tool execution result on success - - The result of the permission request - """ - content: str | None = None - """Concise tool result text sent to the LLM for chat completion, potentially truncated for - token efficiency - """ - contents: list[ContentElement] | None = None - """Structured content blocks (text, images, audio, resources) returned by the tool in their - native format - """ - detailed_content: str | None = None - """Full detailed tool result for UI/timeline display, preserving complete content such as - diffs. Falls back to content when absent. - """ - kind: ResultKind | None = None - """The outcome of the permission request""" +class AssistantIntentData: + "Agent intent description for current activity or plan" + intent: str @staticmethod - def from_dict(obj: Any) -> 'Result': + def from_dict(obj: Any) -> "AssistantIntentData": assert isinstance(obj, dict) - content = from_union([from_str, from_none], obj.get("content")) - contents = from_union([lambda x: from_list(ContentElement.from_dict, x), from_none], obj.get("contents")) - detailed_content = from_union([from_str, from_none], obj.get("detailedContent")) - kind = from_union([ResultKind, from_none], obj.get("kind")) - return Result(content, contents, detailed_content, kind) + intent = from_str(obj.get("intent")) + return AssistantIntentData( + intent=intent, + ) def to_dict(self) -> dict: result: dict = {} - if self.content is not None: - result["content"] = from_union([from_str, from_none], self.content) - if self.contents is not None: - result["contents"] = from_union([lambda x: from_list(lambda x: to_class(ContentElement, x), x), from_none], self.contents) - if self.detailed_content is not None: - result["detailedContent"] = from_union([from_str, from_none], self.detailed_content) - if self.kind is not None: - result["kind"] = from_union([lambda x: to_enum(ResultKind, x), from_none], self.kind) + result["intent"] = from_str(self.intent) return result -class Role(Enum): - """Message role: "system" for system prompts, "developer" for developer-injected instructions""" - - DEVELOPER = "developer" - SYSTEM = "system" - - -class ServerStatus(Enum): - """Connection status: connected, failed, needs-auth, pending, disabled, or not_configured - - New connection status: connected, failed, needs-auth, pending, disabled, or not_configured - """ - CONNECTED = "connected" - DISABLED = "disabled" - FAILED = "failed" - NEEDS_AUTH = "needs-auth" - NOT_CONFIGURED = "not_configured" - PENDING = "pending" - - @dataclass -class Server: - name: str - """Server name (config key)""" - - status: ServerStatus - """Connection status: connected, failed, needs-auth, pending, disabled, or not_configured""" - - error: str | None = None - """Error message if the server failed to connect""" - - source: str | None = None - """Configuration source: user, workspace, plugin, or builtin""" +class AssistantReasoningData: + "Assistant reasoning content for timeline display with complete thinking text" + reasoning_id: str + content: str @staticmethod - def from_dict(obj: Any) -> 'Server': + def from_dict(obj: Any) -> "AssistantReasoningData": assert isinstance(obj, dict) - name = from_str(obj.get("name")) - status = ServerStatus(obj.get("status")) - error = from_union([from_str, from_none], obj.get("error")) - source = from_union([from_str, from_none], obj.get("source")) - return Server(name, status, error, source) + reasoning_id = from_str(obj.get("reasoningId")) + content = from_str(obj.get("content")) + return AssistantReasoningData( + reasoning_id=reasoning_id, + content=content, + ) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) - result["status"] = to_enum(ServerStatus, self.status) - if self.error is not None: - result["error"] = from_union([from_str, from_none], self.error) - if self.source is not None: - result["source"] = from_union([from_str, from_none], self.source) + result["reasoningId"] = from_str(self.reasoning_id) + result["content"] = from_str(self.content) return result -class ShutdownType(Enum): - """Whether the session ended normally ("routine") or due to a crash/fatal error ("error")""" - - ERROR = "error" - ROUTINE = "routine" - - @dataclass -class Skill: - description: str - """Description of what the skill does""" - - enabled: bool - """Whether the skill is currently enabled""" - - name: str - """Unique identifier for the skill""" - - source: str - """Source location type of the skill (e.g., project, personal, plugin)""" - - user_invocable: bool - """Whether the skill can be invoked by the user as a slash command""" - - path: str | None = None - """Absolute path to the skill file, if available""" +class AssistantReasoningDeltaData: + "Streaming reasoning delta for incremental extended thinking updates" + reasoning_id: str + delta_content: str @staticmethod - def from_dict(obj: Any) -> 'Skill': + def from_dict(obj: Any) -> "AssistantReasoningDeltaData": assert isinstance(obj, dict) - description = from_str(obj.get("description")) - enabled = from_bool(obj.get("enabled")) - name = from_str(obj.get("name")) - source = from_str(obj.get("source")) - user_invocable = from_bool(obj.get("userInvocable")) - path = from_union([from_str, from_none], obj.get("path")) - return Skill(description, enabled, name, source, user_invocable, path) + reasoning_id = from_str(obj.get("reasoningId")) + delta_content = from_str(obj.get("deltaContent")) + return AssistantReasoningDeltaData( + reasoning_id=reasoning_id, + delta_content=delta_content, + ) def to_dict(self) -> dict: result: dict = {} - result["description"] = from_str(self.description) - result["enabled"] = from_bool(self.enabled) - result["name"] = from_str(self.name) - result["source"] = from_str(self.source) - result["userInvocable"] = from_bool(self.user_invocable) - if self.path is not None: - result["path"] = from_union([from_str, from_none], self.path) + result["reasoningId"] = from_str(self.reasoning_id) + result["deltaContent"] = from_str(self.delta_content) return result -class SourceType(Enum): - """Origin type of the session being handed off""" - - LOCAL = "local" - REMOTE = "remote" - - @dataclass -class StaticClientConfig: - """Static OAuth client configuration, if the server specifies one""" - - client_id: str - """OAuth client ID for the server""" - - public_client: bool | None = None - """Whether this is a public OAuth client""" +class AssistantStreamingDeltaData: + "Streaming response progress with cumulative byte count" + total_response_size_bytes: float @staticmethod - def from_dict(obj: Any) -> 'StaticClientConfig': + def from_dict(obj: Any) -> "AssistantStreamingDeltaData": assert isinstance(obj, dict) - client_id = from_str(obj.get("clientId")) - public_client = from_union([from_bool, from_none], obj.get("publicClient")) - return StaticClientConfig(client_id, public_client) + total_response_size_bytes = from_float(obj.get("totalResponseSizeBytes")) + return AssistantStreamingDeltaData( + total_response_size_bytes=total_response_size_bytes, + ) def to_dict(self) -> dict: result: dict = {} - result["clientId"] = from_str(self.client_id) - if self.public_client is not None: - result["publicClient"] = from_union([from_bool, from_none], self.public_client) + result["totalResponseSizeBytes"] = to_float(self.total_response_size_bytes) return result -class ToolRequestType(Enum): - """Tool call type: "function" for standard tool calls, "custom" for grammar-based tool - calls. Defaults to "function" when absent. - """ - CUSTOM = "custom" - FUNCTION = "function" - - @dataclass -class ToolRequest: - """A tool invocation request from the assistant""" - - name: str - """Name of the tool being invoked""" - +class AssistantMessageDataToolRequestsItem: + "A tool invocation request from the assistant" tool_call_id: str - """Unique identifier for this tool call""" - + name: str arguments: Any = None - """Arguments to pass to the tool, format depends on the tool""" - - intention_summary: str | None = None - """Resolved intention summary describing what this specific call does""" - - mcp_server_name: str | None = None - """Name of the MCP server hosting this tool, when the tool is an MCP tool""" - + type: AssistantMessageDataToolRequestsItemType | None = None tool_title: str | None = None - """Human-readable display title for the tool""" - - type: ToolRequestType | None = None - """Tool call type: "function" for standard tool calls, "custom" for grammar-based tool - calls. Defaults to "function" when absent. - """ + mcp_server_name: str | None = None + intention_summary: str | None = None @staticmethod - def from_dict(obj: Any) -> 'ToolRequest': + def from_dict(obj: Any) -> "AssistantMessageDataToolRequestsItem": assert isinstance(obj, dict) - name = from_str(obj.get("name")) tool_call_id = from_str(obj.get("toolCallId")) + name = from_str(obj.get("name")) arguments = obj.get("arguments") - intention_summary = from_union([from_none, from_str], obj.get("intentionSummary")) - mcp_server_name = from_union([from_str, from_none], obj.get("mcpServerName")) - tool_title = from_union([from_str, from_none], obj.get("toolTitle")) - type = from_union([ToolRequestType, from_none], obj.get("type")) - return ToolRequest(name, tool_call_id, arguments, intention_summary, mcp_server_name, tool_title, type) + type = from_union([from_none, lambda x: parse_enum(AssistantMessageDataToolRequestsItemType, x)], obj.get("type")) + tool_title = from_union([from_none, lambda x: from_str(x)], obj.get("toolTitle")) + mcp_server_name = from_union([from_none, lambda x: from_str(x)], obj.get("mcpServerName")) + intention_summary = from_union([from_none, lambda x: from_str(x)], obj.get("intentionSummary")) + return AssistantMessageDataToolRequestsItem( + tool_call_id=tool_call_id, + name=name, + arguments=arguments, + type=type, + tool_title=tool_title, + mcp_server_name=mcp_server_name, + intention_summary=intention_summary, + ) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) result["toolCallId"] = from_str(self.tool_call_id) + result["name"] = from_str(self.name) if self.arguments is not None: result["arguments"] = self.arguments - if self.intention_summary is not None: - result["intentionSummary"] = from_union([from_none, from_str], self.intention_summary) - if self.mcp_server_name is not None: - result["mcpServerName"] = from_union([from_str, from_none], self.mcp_server_name) - if self.tool_title is not None: - result["toolTitle"] = from_union([from_str, from_none], self.tool_title) if self.type is not None: - result["type"] = from_union([lambda x: to_enum(ToolRequestType, x), from_none], self.type) + result["type"] = from_union([from_none, lambda x: to_enum(AssistantMessageDataToolRequestsItemType, x)], self.type) + if self.tool_title is not None: + result["toolTitle"] = from_union([from_none, lambda x: from_str(x)], self.tool_title) + if self.mcp_server_name is not None: + result["mcpServerName"] = from_union([from_none, lambda x: from_str(x)], self.mcp_server_name) + if self.intention_summary is not None: + result["intentionSummary"] = from_union([from_none, lambda x: from_str(x)], self.intention_summary) return result @dataclass -class UI: - """UI capability changes""" - - elicitation: bool | None = None - """Whether elicitation is now supported""" +class AssistantMessageData: + "Assistant response containing text content, optional tool requests, and interaction metadata" + message_id: str + content: str + tool_requests: list[AssistantMessageDataToolRequestsItem] | None = None + reasoning_opaque: str | None = None + reasoning_text: str | None = None + encrypted_content: str | None = None + phase: str | None = None + output_tokens: float | None = None + interaction_id: str | None = None + request_id: str | None = None + parent_tool_call_id: str | None = None @staticmethod - def from_dict(obj: Any) -> 'UI': + def from_dict(obj: Any) -> "AssistantMessageData": assert isinstance(obj, dict) - elicitation = from_union([from_bool, from_none], obj.get("elicitation")) - return UI(elicitation) + message_id = from_str(obj.get("messageId")) + content = from_str(obj.get("content")) + tool_requests = from_union([from_none, lambda x: from_list(lambda x: AssistantMessageDataToolRequestsItem.from_dict(x), x)], obj.get("toolRequests")) + reasoning_opaque = from_union([from_none, lambda x: from_str(x)], obj.get("reasoningOpaque")) + reasoning_text = from_union([from_none, lambda x: from_str(x)], obj.get("reasoningText")) + encrypted_content = from_union([from_none, lambda x: from_str(x)], obj.get("encryptedContent")) + phase = from_union([from_none, lambda x: from_str(x)], obj.get("phase")) + output_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("outputTokens")) + interaction_id = from_union([from_none, lambda x: from_str(x)], obj.get("interactionId")) + request_id = from_union([from_none, lambda x: from_str(x)], obj.get("requestId")) + parent_tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("parentToolCallId")) + return AssistantMessageData( + message_id=message_id, + content=content, + tool_requests=tool_requests, + reasoning_opaque=reasoning_opaque, + reasoning_text=reasoning_text, + encrypted_content=encrypted_content, + phase=phase, + output_tokens=output_tokens, + interaction_id=interaction_id, + request_id=request_id, + parent_tool_call_id=parent_tool_call_id, + ) def to_dict(self) -> dict: result: dict = {} - if self.elicitation is not None: - result["elicitation"] = from_union([from_bool, from_none], self.elicitation) + result["messageId"] = from_str(self.message_id) + result["content"] = from_str(self.content) + if self.tool_requests is not None: + result["toolRequests"] = from_union([from_none, lambda x: from_list(lambda x: to_class(AssistantMessageDataToolRequestsItem, x), x)], self.tool_requests) + if self.reasoning_opaque is not None: + result["reasoningOpaque"] = from_union([from_none, lambda x: from_str(x)], self.reasoning_opaque) + if self.reasoning_text is not None: + result["reasoningText"] = from_union([from_none, lambda x: from_str(x)], self.reasoning_text) + if self.encrypted_content is not None: + result["encryptedContent"] = from_union([from_none, lambda x: from_str(x)], self.encrypted_content) + if self.phase is not None: + result["phase"] = from_union([from_none, lambda x: from_str(x)], self.phase) + if self.output_tokens is not None: + result["outputTokens"] = from_union([from_none, lambda x: to_float(x)], self.output_tokens) + if self.interaction_id is not None: + result["interactionId"] = from_union([from_none, lambda x: from_str(x)], self.interaction_id) + if self.request_id is not None: + result["requestId"] = from_union([from_none, lambda x: from_str(x)], self.request_id) + if self.parent_tool_call_id is not None: + result["parentToolCallId"] = from_union([from_none, lambda x: from_str(x)], self.parent_tool_call_id) return result @dataclass -class Data: - """Session initialization metadata including context and configuration - - Session resume metadata including current context and event count - - Notifies Mission Control that the session's remote steering capability has changed - - Error details for timeline display including message and optional diagnostic information - - Payload indicating the session is idle with no background agents in flight - - Session title change payload containing the new display title - - Informational message for timeline display with categorization - - Warning message for timeline display with categorization - - Model change details including previous and new model identifiers - - Agent mode change details including previous and new modes - - Plan file operation details indicating what changed - - Workspace file change details including path and operation type - - Session handoff metadata including source, context, and repository information - - Conversation truncation statistics including token counts and removed content metrics - - Session rewind details including target event and count of removed events - - Session termination metrics including usage statistics, code changes, and shutdown - reason - - Updated working directory and git context after the change - - Current context window usage statistics including token and message counts - - Context window breakdown at the start of LLM-powered conversation compaction - - Conversation compaction results including success status, metrics, and optional error - details - - Task completion notification with summary from the agent - - Empty payload; the event signals that the pending message queue has changed - - Turn initialization metadata including identifier and interaction tracking - - Agent intent description for current activity or plan - - Assistant reasoning content for timeline display with complete thinking text - - Streaming reasoning delta for incremental extended thinking updates - - Streaming response progress with cumulative byte count - - Assistant response containing text content, optional tool requests, and interaction - metadata - - Streaming assistant message delta for incremental response updates - - Turn completion metadata including the turn identifier - - LLM API call usage metrics including tokens, costs, quotas, and billing information - - Turn abort information including the reason for termination - - User-initiated tool invocation request with tool name and arguments - - Tool execution startup details including MCP server information when applicable - - Streaming tool execution output for incremental result display - - Tool execution progress notification with status message - - Tool execution completion results including success status, detailed output, and error - information - - Skill invocation details including content, allowed tools, and plugin metadata - - Sub-agent startup details including parent tool call and agent information - - Sub-agent completion details for successful execution - - Sub-agent failure details including error message and agent information - - Custom agent selection details including name and available tools - - Empty payload; the event signals that the custom agent was deselected, returning to the - default agent - - Hook invocation start details including type and input data - - Hook invocation completion details including output, success status, and error - information - - System or developer message content with role and optional template metadata - - System-generated notification for runtime events like background task completion - - Permission request notification requiring client approval with request details - - Permission request completion notification signaling UI dismissal - - User input request notification with question and optional predefined choices - - User input request completion with the user's response - - Elicitation request; may be form-based (structured input) or URL-based (browser - redirect) - - Elicitation request completion with the user's response - - Sampling request from an MCP server; contains the server name and a requestId for - correlation - - Sampling request completion notification signaling UI dismissal - - OAuth authentication request for an MCP server - - MCP OAuth request completion notification - - External tool invocation request for client-side tool execution - - External tool completion notification signaling UI dismissal - - Queued slash command dispatch request for client execution - - Registered command dispatch request routed to the owning client - - Queued command completion notification signaling UI dismissal - - SDK command registration change notification - - Session capability change notification - - Plan approval request with plan content and available user actions - - Plan mode exit completion with the user's approval decision and optional feedback - """ - already_in_use: bool | None = None - """Whether the session was already in use by another client at start time - - Whether the session was already in use by another client at resume time - """ - context: ContextClass | str | None = None - """Working directory and git context at session start - - Updated working directory and git context at resume time - - Additional context information for the handoff - """ - copilot_version: str | None = None - """Version string of the Copilot application""" - - producer: str | None = None - """Identifier of the software producing the events (e.g., "copilot-agent")""" - - reasoning_effort: str | None = None - """Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", - "xhigh") - - Reasoning effort level after the model change, if applicable - """ - remote_steerable: bool | None = None - """Whether this session supports remote steering via Mission Control - - Whether this session now supports remote steering via Mission Control - """ - selected_model: str | None = None - """Model selected at session creation time, if any - - Model currently selected at resume time - """ - session_id: str | None = None - """Unique identifier for the session - - Session ID that this external tool request belongs to - """ - start_time: datetime | None = None - """ISO 8601 timestamp when the session was created""" - - version: float | None = None - """Schema version number for the session event format""" - - event_count: float | None = None - """Total number of persisted events in the session at the time of resume""" - - resume_time: datetime | None = None - """ISO 8601 timestamp when the session was resumed""" - - error_type: str | None = None - """Category of error (e.g., "authentication", "authorization", "quota", "rate_limit", - "context_limit", "query") - """ - message: str | None = None - """Human-readable error message - - Human-readable informational message for display in the timeline - - Human-readable warning message for display in the timeline - - Message describing what information is needed from the user - """ - provider_call_id: str | None = None - """GitHub request tracing ID (x-github-request-id header) for correlating with server-side - logs - - GitHub request tracing ID (x-github-request-id header) for server-side log correlation - """ - stack: str | None = None - """Error stack trace, when available""" - - status_code: int | None = None - """HTTP status code from the upstream request, if applicable""" - - url: str | None = None - """Optional URL associated with this error that the user can open in a browser - - Optional URL associated with this message that the user can open in a browser - - Optional URL associated with this warning that the user can open in a browser - - URL to open in the user's browser (url mode only) - """ - aborted: bool | None = None - """True when the preceding agentic loop was cancelled via abort signal""" +class AssistantMessageDeltaData: + "Streaming assistant message delta for incremental response updates" + message_id: str + delta_content: str + parent_tool_call_id: str | None = None - title: str | None = None - """The new display title for the session""" + @staticmethod + def from_dict(obj: Any) -> "AssistantMessageDeltaData": + assert isinstance(obj, dict) + message_id = from_str(obj.get("messageId")) + delta_content = from_str(obj.get("deltaContent")) + parent_tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("parentToolCallId")) + return AssistantMessageDeltaData( + message_id=message_id, + delta_content=delta_content, + parent_tool_call_id=parent_tool_call_id, + ) - info_type: str | None = None - """Category of informational message (e.g., "notification", "timing", "context_window", - "mcp", "snapshot", "configuration", "authentication", "model") - """ - warning_type: str | None = None - """Category of warning (e.g., "subscription", "policy", "mcp")""" + def to_dict(self) -> dict: + result: dict = {} + result["messageId"] = from_str(self.message_id) + result["deltaContent"] = from_str(self.delta_content) + if self.parent_tool_call_id is not None: + result["parentToolCallId"] = from_union([from_none, lambda x: from_str(x)], self.parent_tool_call_id) + return result - new_model: str | None = None - """Newly selected model identifier""" - previous_model: str | None = None - """Model that was previously selected, if any""" +@dataclass +class AssistantTurnEndData: + "Turn completion metadata including the turn identifier" + turn_id: str - previous_reasoning_effort: str | None = None - """Reasoning effort level before the model change, if applicable""" + @staticmethod + def from_dict(obj: Any) -> "AssistantTurnEndData": + assert isinstance(obj, dict) + turn_id = from_str(obj.get("turnId")) + return AssistantTurnEndData( + turn_id=turn_id, + ) - new_mode: str | None = None - """Agent mode after the change (e.g., "interactive", "plan", "autopilot")""" + def to_dict(self) -> dict: + result: dict = {} + result["turnId"] = from_str(self.turn_id) + return result - previous_mode: str | None = None - """Agent mode before the change (e.g., "interactive", "plan", "autopilot")""" - operation: Operation | None = None - """The type of operation performed on the plan file - - Whether the file was newly created or updated - """ - path: str | None = None - """Relative path within the session workspace files directory - - File path to the SKILL.md definition - """ - handoff_time: datetime | None = None - """ISO 8601 timestamp when the handoff occurred""" - - host: str | None = None - """GitHub host URL for the source session (e.g., https://github.com or - https://tenant.ghe.com) - """ - remote_session_id: str | None = None - """Session ID of the remote session being handed off""" - - repository: RepositoryClass | str | None = None - """Repository context for the handed-off session - - Repository identifier derived from the git remote URL ("owner/name" for GitHub, - "org/project/repo" for Azure DevOps) - """ - source_type: SourceType | None = None - """Origin type of the session being handed off""" - - summary: str | None = None - """Summary of the work done in the source session - - Summary of the completed task, provided by the agent - - Summary of the plan that was created - """ - messages_removed_during_truncation: float | None = None - """Number of messages removed by truncation""" +@dataclass +class AssistantUsageDataQuotaSnapshotsValue: + is_unlimited_entitlement: bool + entitlement_requests: float + used_requests: float + usage_allowed_with_exhausted_quota: bool + overage: float + overage_allowed_with_exhausted_quota: bool + remaining_percentage: float + reset_date: datetime | None = None - performed_by: str | None = None - """Identifier of the component that performed truncation (e.g., "BasicTruncator")""" + @staticmethod + def from_dict(obj: Any) -> "AssistantUsageDataQuotaSnapshotsValue": + assert isinstance(obj, dict) + is_unlimited_entitlement = from_bool(obj.get("isUnlimitedEntitlement")) + entitlement_requests = from_float(obj.get("entitlementRequests")) + used_requests = from_float(obj.get("usedRequests")) + usage_allowed_with_exhausted_quota = from_bool(obj.get("usageAllowedWithExhaustedQuota")) + overage = from_float(obj.get("overage")) + overage_allowed_with_exhausted_quota = from_bool(obj.get("overageAllowedWithExhaustedQuota")) + remaining_percentage = from_float(obj.get("remainingPercentage")) + reset_date = from_union([from_none, lambda x: from_datetime(x)], obj.get("resetDate")) + return AssistantUsageDataQuotaSnapshotsValue( + is_unlimited_entitlement=is_unlimited_entitlement, + entitlement_requests=entitlement_requests, + used_requests=used_requests, + usage_allowed_with_exhausted_quota=usage_allowed_with_exhausted_quota, + overage=overage, + overage_allowed_with_exhausted_quota=overage_allowed_with_exhausted_quota, + remaining_percentage=remaining_percentage, + reset_date=reset_date, + ) - post_truncation_messages_length: float | None = None - """Number of conversation messages after truncation""" + def to_dict(self) -> dict: + result: dict = {} + result["isUnlimitedEntitlement"] = from_bool(self.is_unlimited_entitlement) + result["entitlementRequests"] = to_float(self.entitlement_requests) + result["usedRequests"] = to_float(self.used_requests) + result["usageAllowedWithExhaustedQuota"] = from_bool(self.usage_allowed_with_exhausted_quota) + result["overage"] = to_float(self.overage) + result["overageAllowedWithExhaustedQuota"] = from_bool(self.overage_allowed_with_exhausted_quota) + result["remainingPercentage"] = to_float(self.remaining_percentage) + if self.reset_date is not None: + result["resetDate"] = from_union([from_none, lambda x: to_datetime(x)], self.reset_date) + return result - post_truncation_tokens_in_messages: float | None = None - """Total tokens in conversation messages after truncation""" - pre_truncation_messages_length: float | None = None - """Number of conversation messages before truncation""" +@dataclass +class AssistantUsageDataCopilotUsageTokenDetailsItem: + "Token usage detail for a single billing category" + batch_size: float + cost_per_batch: float + token_count: float + token_type: str - pre_truncation_tokens_in_messages: float | None = None - """Total tokens in conversation messages before truncation""" + @staticmethod + def from_dict(obj: Any) -> "AssistantUsageDataCopilotUsageTokenDetailsItem": + assert isinstance(obj, dict) + batch_size = from_float(obj.get("batchSize")) + cost_per_batch = from_float(obj.get("costPerBatch")) + token_count = from_float(obj.get("tokenCount")) + token_type = from_str(obj.get("tokenType")) + return AssistantUsageDataCopilotUsageTokenDetailsItem( + batch_size=batch_size, + cost_per_batch=cost_per_batch, + token_count=token_count, + token_type=token_type, + ) - token_limit: float | None = None - """Maximum token count for the model's context window""" + def to_dict(self) -> dict: + result: dict = {} + result["batchSize"] = to_float(self.batch_size) + result["costPerBatch"] = to_float(self.cost_per_batch) + result["tokenCount"] = to_float(self.token_count) + result["tokenType"] = from_str(self.token_type) + return result - tokens_removed_during_truncation: float | None = None - """Number of tokens removed by truncation""" - events_removed: float | None = None - """Number of events that were removed by the rewind""" +@dataclass +class AssistantUsageDataCopilotUsage: + "Per-request cost and usage data from the CAPI copilot_usage response field" + token_details: list[AssistantUsageDataCopilotUsageTokenDetailsItem] + total_nano_aiu: float - up_to_event_id: str | None = None - """Event ID that was rewound to; this event and all after it were removed""" + @staticmethod + def from_dict(obj: Any) -> "AssistantUsageDataCopilotUsage": + assert isinstance(obj, dict) + token_details = from_list(lambda x: AssistantUsageDataCopilotUsageTokenDetailsItem.from_dict(x), obj.get("tokenDetails")) + total_nano_aiu = from_float(obj.get("totalNanoAiu")) + return AssistantUsageDataCopilotUsage( + token_details=token_details, + total_nano_aiu=total_nano_aiu, + ) - code_changes: CodeChanges | None = None - """Aggregate code change metrics for the session""" + def to_dict(self) -> dict: + result: dict = {} + result["tokenDetails"] = from_list(lambda x: to_class(AssistantUsageDataCopilotUsageTokenDetailsItem, x), self.token_details) + result["totalNanoAiu"] = to_float(self.total_nano_aiu) + return result - conversation_tokens: float | None = None - """Non-system message token count at shutdown - - Token count from non-system messages (user, assistant, tool) - - Token count from non-system messages (user, assistant, tool) at compaction start - - Token count from non-system messages (user, assistant, tool) after compaction - """ - current_model: str | None = None - """Model that was selected at the time of shutdown""" - current_tokens: float | None = None - """Total tokens in context window at shutdown - - Current number of tokens in the context window - """ - error_reason: str | None = None - """Error description when shutdownType is "error\"""" +@dataclass +class AssistantUsageData: + "LLM API call usage metrics including tokens, costs, quotas, and billing information" + model: str + input_tokens: float | None = None + output_tokens: float | None = None + cache_read_tokens: float | None = None + cache_write_tokens: float | None = None + cost: float | None = None + duration: float | None = None + ttft_ms: float | None = None + inter_token_latency_ms: float | None = None + initiator: str | None = None + api_call_id: str | None = None + provider_call_id: str | None = None + parent_tool_call_id: str | None = None + quota_snapshots: dict[str, AssistantUsageDataQuotaSnapshotsValue] | None = None + copilot_usage: AssistantUsageDataCopilotUsage | None = None + reasoning_effort: str | None = None - model_metrics: dict[str, ModelMetric] | None = None - """Per-model usage breakdown, keyed by model identifier""" + @staticmethod + def from_dict(obj: Any) -> "AssistantUsageData": + assert isinstance(obj, dict) + model = from_str(obj.get("model")) + input_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("inputTokens")) + output_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("outputTokens")) + cache_read_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("cacheReadTokens")) + cache_write_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("cacheWriteTokens")) + cost = from_union([from_none, lambda x: from_float(x)], obj.get("cost")) + duration = from_union([from_none, lambda x: from_float(x)], obj.get("duration")) + ttft_ms = from_union([from_none, lambda x: from_float(x)], obj.get("ttftMs")) + inter_token_latency_ms = from_union([from_none, lambda x: from_float(x)], obj.get("interTokenLatencyMs")) + initiator = from_union([from_none, lambda x: from_str(x)], obj.get("initiator")) + api_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("apiCallId")) + provider_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("providerCallId")) + parent_tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("parentToolCallId")) + quota_snapshots = from_union([from_none, lambda x: from_dict(lambda x: AssistantUsageDataQuotaSnapshotsValue.from_dict(x), x)], obj.get("quotaSnapshots")) + copilot_usage = from_union([from_none, lambda x: AssistantUsageDataCopilotUsage.from_dict(x)], obj.get("copilotUsage")) + reasoning_effort = from_union([from_none, lambda x: from_str(x)], obj.get("reasoningEffort")) + return AssistantUsageData( + model=model, + input_tokens=input_tokens, + output_tokens=output_tokens, + cache_read_tokens=cache_read_tokens, + cache_write_tokens=cache_write_tokens, + cost=cost, + duration=duration, + ttft_ms=ttft_ms, + inter_token_latency_ms=inter_token_latency_ms, + initiator=initiator, + api_call_id=api_call_id, + provider_call_id=provider_call_id, + parent_tool_call_id=parent_tool_call_id, + quota_snapshots=quota_snapshots, + copilot_usage=copilot_usage, + reasoning_effort=reasoning_effort, + ) - session_start_time: float | None = None - """Unix timestamp (milliseconds) when the session started""" + def to_dict(self) -> dict: + result: dict = {} + result["model"] = from_str(self.model) + if self.input_tokens is not None: + result["inputTokens"] = from_union([from_none, lambda x: to_float(x)], self.input_tokens) + if self.output_tokens is not None: + result["outputTokens"] = from_union([from_none, lambda x: to_float(x)], self.output_tokens) + if self.cache_read_tokens is not None: + result["cacheReadTokens"] = from_union([from_none, lambda x: to_float(x)], self.cache_read_tokens) + if self.cache_write_tokens is not None: + result["cacheWriteTokens"] = from_union([from_none, lambda x: to_float(x)], self.cache_write_tokens) + if self.cost is not None: + result["cost"] = from_union([from_none, lambda x: to_float(x)], self.cost) + if self.duration is not None: + result["duration"] = from_union([from_none, lambda x: to_float(x)], self.duration) + if self.ttft_ms is not None: + result["ttftMs"] = from_union([from_none, lambda x: to_float(x)], self.ttft_ms) + if self.inter_token_latency_ms is not None: + result["interTokenLatencyMs"] = from_union([from_none, lambda x: to_float(x)], self.inter_token_latency_ms) + if self.initiator is not None: + result["initiator"] = from_union([from_none, lambda x: from_str(x)], self.initiator) + if self.api_call_id is not None: + result["apiCallId"] = from_union([from_none, lambda x: from_str(x)], self.api_call_id) + if self.provider_call_id is not None: + result["providerCallId"] = from_union([from_none, lambda x: from_str(x)], self.provider_call_id) + if self.parent_tool_call_id is not None: + result["parentToolCallId"] = from_union([from_none, lambda x: from_str(x)], self.parent_tool_call_id) + if self.quota_snapshots is not None: + result["quotaSnapshots"] = from_union([from_none, lambda x: from_dict(lambda x: to_class(AssistantUsageDataQuotaSnapshotsValue, x), x)], self.quota_snapshots) + if self.copilot_usage is not None: + result["copilotUsage"] = from_union([from_none, lambda x: to_class(AssistantUsageDataCopilotUsage, x)], self.copilot_usage) + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_none, lambda x: from_str(x)], self.reasoning_effort) + return result - shutdown_type: ShutdownType | None = None - """Whether the session ended normally ("routine") or due to a crash/fatal error ("error")""" - system_tokens: float | None = None - """System message token count at shutdown - - Token count from system message(s) - - Token count from system message(s) at compaction start - - Token count from system message(s) after compaction - """ - tool_definitions_tokens: float | None = None - """Tool definitions token count at shutdown - - Token count from tool definitions - - Token count from tool definitions at compaction start - - Token count from tool definitions after compaction - """ - total_api_duration_ms: float | None = None - """Cumulative time spent in API calls during the session, in milliseconds""" - - total_premium_requests: float | None = None - """Total number of premium API requests used during the session""" +@dataclass +class AbortData: + "Turn abort information including the reason for termination" + reason: str - base_commit: str | None = None - """Base commit of current git branch at session start time""" + @staticmethod + def from_dict(obj: Any) -> "AbortData": + assert isinstance(obj, dict) + reason = from_str(obj.get("reason")) + return AbortData( + reason=reason, + ) - branch: str | None = None - """Current git branch name""" + def to_dict(self) -> dict: + result: dict = {} + result["reason"] = from_str(self.reason) + return result - cwd: str | None = None - """Current working directory path""" - git_root: str | None = None - """Root directory of the git repository, resolved via git rev-parse""" +@dataclass +class ToolUserRequestedData: + "User-initiated tool invocation request with tool name and arguments" + tool_call_id: str + tool_name: str + arguments: Any = None - head_commit: str | None = None - """Head commit of current git branch at session start time""" + @staticmethod + def from_dict(obj: Any) -> "ToolUserRequestedData": + assert isinstance(obj, dict) + tool_call_id = from_str(obj.get("toolCallId")) + tool_name = from_str(obj.get("toolName")) + arguments = obj.get("arguments") + return ToolUserRequestedData( + tool_call_id=tool_call_id, + tool_name=tool_name, + arguments=arguments, + ) - host_type: HostType | None = None - """Hosting platform type of the repository (github or ado)""" + def to_dict(self) -> dict: + result: dict = {} + result["toolCallId"] = from_str(self.tool_call_id) + result["toolName"] = from_str(self.tool_name) + if self.arguments is not None: + result["arguments"] = self.arguments + return result - is_initial: bool | None = None - """Whether this is the first usage_info event emitted in this session""" - messages_length: float | None = None - """Current number of messages in the conversation""" +@dataclass +class ToolExecutionStartData: + "Tool execution startup details including MCP server information when applicable" + tool_call_id: str + tool_name: str + arguments: Any = None + mcp_server_name: str | None = None + mcp_tool_name: str | None = None + parent_tool_call_id: str | None = None - checkpoint_number: float | None = None - """Checkpoint snapshot number created for recovery""" + @staticmethod + def from_dict(obj: Any) -> "ToolExecutionStartData": + assert isinstance(obj, dict) + tool_call_id = from_str(obj.get("toolCallId")) + tool_name = from_str(obj.get("toolName")) + arguments = obj.get("arguments") + mcp_server_name = from_union([from_none, lambda x: from_str(x)], obj.get("mcpServerName")) + mcp_tool_name = from_union([from_none, lambda x: from_str(x)], obj.get("mcpToolName")) + parent_tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("parentToolCallId")) + return ToolExecutionStartData( + tool_call_id=tool_call_id, + tool_name=tool_name, + arguments=arguments, + mcp_server_name=mcp_server_name, + mcp_tool_name=mcp_tool_name, + parent_tool_call_id=parent_tool_call_id, + ) - checkpoint_path: str | None = None - """File path where the checkpoint was stored""" - - compaction_tokens_used: CompactionTokensUsed | None = None - """Token usage breakdown for the compaction LLM call""" - - error: ErrorClass | str | None = None - """Error message if compaction failed - - Error details when the tool execution failed - - Error message describing why the sub-agent failed - - Error details when the hook failed - """ - messages_removed: float | None = None - """Number of messages removed during compaction""" + def to_dict(self) -> dict: + result: dict = {} + result["toolCallId"] = from_str(self.tool_call_id) + result["toolName"] = from_str(self.tool_name) + if self.arguments is not None: + result["arguments"] = self.arguments + if self.mcp_server_name is not None: + result["mcpServerName"] = from_union([from_none, lambda x: from_str(x)], self.mcp_server_name) + if self.mcp_tool_name is not None: + result["mcpToolName"] = from_union([from_none, lambda x: from_str(x)], self.mcp_tool_name) + if self.parent_tool_call_id is not None: + result["parentToolCallId"] = from_union([from_none, lambda x: from_str(x)], self.parent_tool_call_id) + return result - post_compaction_tokens: float | None = None - """Total tokens in conversation after compaction""" - pre_compaction_messages_length: float | None = None - """Number of messages before compaction""" +@dataclass +class ToolExecutionPartialResultData: + "Streaming tool execution output for incremental result display" + tool_call_id: str + partial_output: str - pre_compaction_tokens: float | None = None - """Total tokens in conversation before compaction""" + @staticmethod + def from_dict(obj: Any) -> "ToolExecutionPartialResultData": + assert isinstance(obj, dict) + tool_call_id = from_str(obj.get("toolCallId")) + partial_output = from_str(obj.get("partialOutput")) + return ToolExecutionPartialResultData( + tool_call_id=tool_call_id, + partial_output=partial_output, + ) - request_id: str | None = None - """GitHub request tracing ID (x-github-request-id header) for the compaction LLM call - - GitHub request tracing ID (x-github-request-id header) for correlating with server-side - logs - - Unique identifier for this permission request; used to respond via - session.respondToPermission() - - Request ID of the resolved permission request; clients should dismiss any UI for this - request - - Unique identifier for this input request; used to respond via - session.respondToUserInput() - - Request ID of the resolved user input request; clients should dismiss any UI for this - request - - Unique identifier for this elicitation request; used to respond via - session.respondToElicitation() - - Request ID of the resolved elicitation request; clients should dismiss any UI for this - request - - Unique identifier for this sampling request; used to respond via - session.respondToSampling() - - Request ID of the resolved sampling request; clients should dismiss any UI for this - request - - Unique identifier for this OAuth request; used to respond via - session.respondToMcpOAuth() - - Request ID of the resolved OAuth request - - Unique identifier for this request; used to respond via session.respondToExternalTool() - - Request ID of the resolved external tool request; clients should dismiss any UI for this - request - - Unique identifier for this request; used to respond via session.respondToQueuedCommand() - - Unique identifier; used to respond via session.commands.handlePendingCommand() - - Request ID of the resolved command request; clients should dismiss any UI for this - request - - Unique identifier for this request; used to respond via session.respondToExitPlanMode() - - Request ID of the resolved exit plan mode request; clients should dismiss any UI for this - request - """ - success: bool | None = None - """Whether compaction completed successfully - - Whether the tool call succeeded. False when validation failed (e.g., invalid arguments) - - Whether the tool execution completed successfully - - Whether the hook completed successfully - """ - summary_content: str | None = None - """LLM-generated summary of the compacted conversation history""" + def to_dict(self) -> dict: + result: dict = {} + result["toolCallId"] = from_str(self.tool_call_id) + result["partialOutput"] = from_str(self.partial_output) + return result - tokens_removed: float | None = None - """Number of tokens removed during compaction""" - - agent_mode: AgentMode | None = None - """The agent mode that was active when this message was sent""" - - attachments: list[Attachment] | None = None - """Files, selections, or GitHub references attached to the message""" - - content: str | dict[str, float | bool | list[str] | str] | None = None - """The user's message text as displayed in the timeline - - The complete extended thinking text from the model - - The assistant's text response content - - Full content of the skill file, injected into the conversation for the model - - The system or developer prompt text - - The notification text, typically wrapped in XML tags - - The submitted form data when action is 'accept'; keys match the requested schema fields - """ - interaction_id: str | None = None - """CAPI interaction ID for correlating this user message with its turn - - CAPI interaction ID for correlating this turn with upstream telemetry - - CAPI interaction ID for correlating this message with upstream telemetry - - CAPI interaction ID for correlating this tool execution with upstream telemetry - """ - source: str | None = None - """Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected - messages that should be hidden from the user) - """ - transformed_content: str | None = None - """Transformed version of the message sent to the model, with XML wrapping, timestamps, and - other augmentations for prompt caching - """ - turn_id: str | None = None - """Identifier for this turn within the agentic loop, typically a stringified turn number - - Identifier of the turn that has ended, matching the corresponding assistant.turn_start - event - """ - intent: str | None = None - """Short description of what the agent is currently doing or planning to do""" - - reasoning_id: str | None = None - """Unique identifier for this reasoning block - - Reasoning block ID this delta belongs to, matching the corresponding assistant.reasoning - event - """ - delta_content: str | None = None - """Incremental text chunk to append to the reasoning content - - Incremental text chunk to append to the message content - """ - total_response_size_bytes: float | None = None - """Cumulative total bytes received from the streaming response so far""" - encrypted_content: str | None = None - """Encrypted reasoning content from OpenAI models. Session-bound and stripped on resume.""" +@dataclass +class ToolExecutionProgressData: + "Tool execution progress notification with status message" + tool_call_id: str + progress_message: str - message_id: str | None = None - """Unique identifier for this assistant message - - Message ID this delta belongs to, matching the corresponding assistant.message event - """ - output_tokens: float | None = None - """Actual output token count from the API response (completion_tokens), used for accurate - token accounting - - Number of output tokens produced - """ - parent_tool_call_id: str | None = None - """Tool call ID of the parent tool invocation when this event originates from a sub-agent - - Parent tool call ID when this usage originates from a sub-agent - """ - phase: str | None = None - """Generation phase for phased-output models (e.g., thinking vs. response phases)""" + @staticmethod + def from_dict(obj: Any) -> "ToolExecutionProgressData": + assert isinstance(obj, dict) + tool_call_id = from_str(obj.get("toolCallId")) + progress_message = from_str(obj.get("progressMessage")) + return ToolExecutionProgressData( + tool_call_id=tool_call_id, + progress_message=progress_message, + ) - reasoning_opaque: str | None = None - """Opaque/encrypted extended thinking data from Anthropic models. Session-bound and stripped - on resume. - """ - reasoning_text: str | None = None - """Readable reasoning text from the model's extended thinking""" + def to_dict(self) -> dict: + result: dict = {} + result["toolCallId"] = from_str(self.tool_call_id) + result["progressMessage"] = from_str(self.progress_message) + return result - tool_requests: list[ToolRequest] | None = None - """Tool invocations requested by the assistant in this message""" - api_call_id: str | None = None - """Completion ID from the model provider (e.g., chatcmpl-abc123)""" +@dataclass +class ToolExecutionCompleteDataResultContentsItemIconsItem: + "Icon image for a resource" + src: str + mime_type: str | None = None + sizes: list[str] | None = None + theme: ToolExecutionCompleteDataResultContentsItemIconsItemTheme | None = None - cache_read_tokens: float | None = None - """Number of tokens read from prompt cache""" + @staticmethod + def from_dict(obj: Any) -> "ToolExecutionCompleteDataResultContentsItemIconsItem": + assert isinstance(obj, dict) + src = from_str(obj.get("src")) + mime_type = from_union([from_none, lambda x: from_str(x)], obj.get("mimeType")) + sizes = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], obj.get("sizes")) + theme = from_union([from_none, lambda x: parse_enum(ToolExecutionCompleteDataResultContentsItemIconsItemTheme, x)], obj.get("theme")) + return ToolExecutionCompleteDataResultContentsItemIconsItem( + src=src, + mime_type=mime_type, + sizes=sizes, + theme=theme, + ) - cache_write_tokens: float | None = None - """Number of tokens written to prompt cache""" + def to_dict(self) -> dict: + result: dict = {} + result["src"] = from_str(self.src) + if self.mime_type is not None: + result["mimeType"] = from_union([from_none, lambda x: from_str(x)], self.mime_type) + if self.sizes is not None: + result["sizes"] = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], self.sizes) + if self.theme is not None: + result["theme"] = from_union([from_none, lambda x: to_enum(ToolExecutionCompleteDataResultContentsItemIconsItemTheme, x)], self.theme) + return result - copilot_usage: CopilotUsage | None = None - """Per-request cost and usage data from the CAPI copilot_usage response field""" - cost: float | None = None - """Model multiplier cost for billing purposes""" +@dataclass +class ToolExecutionCompleteDataResultContentsItem: + "A content block within a tool result, which may be text, terminal output, image, audio, or a resource" + type: ToolExecutionCompleteDataResultContentsItemType + text: str | None = None + exit_code: float | None = None + cwd: str | None = None + data: str | None = None + mime_type: str | None = None + icons: list[ToolExecutionCompleteDataResultContentsItemIconsItem] | None = None + name: str | None = None + title: str | None = None + uri: str | None = None + description: str | None = None + size: float | None = None + resource: Any = None - duration: float | None = None - """Duration of the API call in milliseconds""" + @staticmethod + def from_dict(obj: Any) -> "ToolExecutionCompleteDataResultContentsItem": + assert isinstance(obj, dict) + type = parse_enum(ToolExecutionCompleteDataResultContentsItemType, obj.get("type")) + text = from_union([from_none, lambda x: from_str(x)], obj.get("text")) + exit_code = from_union([from_none, lambda x: from_float(x)], obj.get("exitCode")) + cwd = from_union([from_none, lambda x: from_str(x)], obj.get("cwd")) + data = from_union([from_none, lambda x: from_str(x)], obj.get("data")) + mime_type = from_union([from_none, lambda x: from_str(x)], obj.get("mimeType")) + icons = from_union([from_none, lambda x: from_list(lambda x: ToolExecutionCompleteDataResultContentsItemIconsItem.from_dict(x), x)], obj.get("icons")) + name = from_union([from_none, lambda x: from_str(x)], obj.get("name")) + title = from_union([from_none, lambda x: from_str(x)], obj.get("title")) + uri = from_union([from_none, lambda x: from_str(x)], obj.get("uri")) + description = from_union([from_none, lambda x: from_str(x)], obj.get("description")) + size = from_union([from_none, lambda x: from_float(x)], obj.get("size")) + resource = obj.get("resource") + return ToolExecutionCompleteDataResultContentsItem( + type=type, + text=text, + exit_code=exit_code, + cwd=cwd, + data=data, + mime_type=mime_type, + icons=icons, + name=name, + title=title, + uri=uri, + description=description, + size=size, + resource=resource, + ) - initiator: str | None = None - """What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for - user-initiated calls - """ - input_tokens: float | None = None - """Number of input tokens consumed""" + def to_dict(self) -> dict: + result: dict = {} + result["type"] = to_enum(ToolExecutionCompleteDataResultContentsItemType, self.type) + if self.text is not None: + result["text"] = from_union([from_none, lambda x: from_str(x)], self.text) + if self.exit_code is not None: + result["exitCode"] = from_union([from_none, lambda x: to_float(x)], self.exit_code) + if self.cwd is not None: + result["cwd"] = from_union([from_none, lambda x: from_str(x)], self.cwd) + if self.data is not None: + result["data"] = from_union([from_none, lambda x: from_str(x)], self.data) + if self.mime_type is not None: + result["mimeType"] = from_union([from_none, lambda x: from_str(x)], self.mime_type) + if self.icons is not None: + result["icons"] = from_union([from_none, lambda x: from_list(lambda x: to_class(ToolExecutionCompleteDataResultContentsItemIconsItem, x), x)], self.icons) + if self.name is not None: + result["name"] = from_union([from_none, lambda x: from_str(x)], self.name) + if self.title is not None: + result["title"] = from_union([from_none, lambda x: from_str(x)], self.title) + if self.uri is not None: + result["uri"] = from_union([from_none, lambda x: from_str(x)], self.uri) + if self.description is not None: + result["description"] = from_union([from_none, lambda x: from_str(x)], self.description) + if self.size is not None: + result["size"] = from_union([from_none, lambda x: to_float(x)], self.size) + if self.resource is not None: + result["resource"] = self.resource + return result - inter_token_latency_ms: float | None = None - """Average inter-token latency in milliseconds. Only available for streaming requests""" - model: str | None = None - """Model identifier used for this API call - - Model identifier that generated this tool call - - Model used by the sub-agent - - Model used by the sub-agent (if any model calls succeeded before failure) - """ - quota_snapshots: dict[str, QuotaSnapshot] | None = None - """Per-quota resource usage snapshots, keyed by quota identifier""" +@dataclass +class ToolExecutionCompleteDataResult: + "Tool execution result on success" + content: str + detailed_content: str | None = None + contents: list[ToolExecutionCompleteDataResultContentsItem] | None = None - ttft_ms: float | None = None - """Time to first token in milliseconds. Only available for streaming requests""" + @staticmethod + def from_dict(obj: Any) -> "ToolExecutionCompleteDataResult": + assert isinstance(obj, dict) + content = from_str(obj.get("content")) + detailed_content = from_union([from_none, lambda x: from_str(x)], obj.get("detailedContent")) + contents = from_union([from_none, lambda x: from_list(ToolExecutionCompleteDataResultContentsItem.from_dict, x)], obj.get("contents")) + return ToolExecutionCompleteDataResult( + content=content, + detailed_content=detailed_content, + contents=contents, + ) - reason: str | None = None - """Reason the current turn was aborted (e.g., "user initiated")""" + def to_dict(self) -> dict: + result: dict = {} + result["content"] = from_str(self.content) + if self.detailed_content is not None: + result["detailedContent"] = from_union([from_none, lambda x: from_str(x)], self.detailed_content) + if self.contents is not None: + result["contents"] = from_union([from_none, lambda x: from_list(lambda x: to_class(ToolExecutionCompleteDataResultContentsItem, x), x)], self.contents) + return result - arguments: Any = None - """Arguments for the tool invocation - - Arguments passed to the tool - - Arguments to pass to the external tool - """ - tool_call_id: str | None = None - """Unique identifier for this tool call - - Tool call ID this partial result belongs to - - Tool call ID this progress notification belongs to - - Unique identifier for the completed tool call - - Tool call ID of the parent tool invocation that spawned this sub-agent - - The LLM-assigned tool call ID that triggered this request; used by remote UIs to - correlate responses - - Tool call ID from the LLM completion; used to correlate with CompletionChunk.toolCall.id - for remote UIs - - Tool call ID assigned to this external tool invocation - """ - tool_name: str | None = None - """Name of the tool the user wants to invoke - - Name of the tool being executed - - Name of the external tool to invoke - """ - mcp_server_name: str | None = None - """Name of the MCP server hosting this tool, when the tool is an MCP tool""" - mcp_tool_name: str | None = None - """Original tool name on the MCP server, when the tool is an MCP tool""" +@dataclass +class ToolExecutionCompleteDataError: + "Error details when the tool execution failed" + message: str + code: str | None = None - partial_output: str | None = None - """Incremental output chunk from the running tool""" + @staticmethod + def from_dict(obj: Any) -> "ToolExecutionCompleteDataError": + assert isinstance(obj, dict) + message = from_str(obj.get("message")) + code = from_union([from_none, lambda x: from_str(x)], obj.get("code")) + return ToolExecutionCompleteDataError( + message=message, + code=code, + ) - progress_message: str | None = None - """Human-readable progress status message (e.g., from an MCP server)""" + def to_dict(self) -> dict: + result: dict = {} + result["message"] = from_str(self.message) + if self.code is not None: + result["code"] = from_union([from_none, lambda x: from_str(x)], self.code) + return result - is_user_requested: bool | None = None - """Whether this tool call was explicitly requested by the user rather than the assistant""" - result: Result | None = None - """Tool execution result on success - - The result of the permission request - """ +@dataclass +class ToolExecutionCompleteData: + "Tool execution completion results including success status, detailed output, and error information" + tool_call_id: str + success: bool + model: str | None = None + interaction_id: str | None = None + is_user_requested: bool | None = None + result: ToolExecutionCompleteDataResult | None = None + error: ToolExecutionCompleteDataError | None = None tool_telemetry: dict[str, Any] | None = None - """Tool-specific telemetry data (e.g., CodeQL check counts, grep match counts)""" + parent_tool_call_id: str | None = None - allowed_tools: list[str] | None = None - """Tool names that should be auto-approved when this skill is active""" + @staticmethod + def from_dict(obj: Any) -> "ToolExecutionCompleteData": + assert isinstance(obj, dict) + tool_call_id = from_str(obj.get("toolCallId")) + success = from_bool(obj.get("success")) + model = from_union([from_none, lambda x: from_str(x)], obj.get("model")) + interaction_id = from_union([from_none, lambda x: from_str(x)], obj.get("interactionId")) + is_user_requested = from_union([from_none, lambda x: from_bool(x)], obj.get("isUserRequested")) + result = from_union([from_none, lambda x: ToolExecutionCompleteDataResult.from_dict(x)], obj.get("result")) + error = from_union([from_none, lambda x: ToolExecutionCompleteDataError.from_dict(x)], obj.get("error")) + tool_telemetry = from_union([from_none, lambda x: from_dict(lambda x: x, x)], obj.get("toolTelemetry")) + parent_tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("parentToolCallId")) + return ToolExecutionCompleteData( + tool_call_id=tool_call_id, + success=success, + model=model, + interaction_id=interaction_id, + is_user_requested=is_user_requested, + result=result, + error=error, + tool_telemetry=tool_telemetry, + parent_tool_call_id=parent_tool_call_id, + ) - description: str | None = None - """Description of the skill from its SKILL.md frontmatter""" + def to_dict(self) -> dict: + result: dict = {} + result["toolCallId"] = from_str(self.tool_call_id) + result["success"] = from_bool(self.success) + if self.model is not None: + result["model"] = from_union([from_none, lambda x: from_str(x)], self.model) + if self.interaction_id is not None: + result["interactionId"] = from_union([from_none, lambda x: from_str(x)], self.interaction_id) + if self.is_user_requested is not None: + result["isUserRequested"] = from_union([from_none, lambda x: from_bool(x)], self.is_user_requested) + if self.result is not None: + result["result"] = from_union([from_none, lambda x: to_class(ToolExecutionCompleteDataResult, x)], self.result) + if self.error is not None: + result["error"] = from_union([from_none, lambda x: to_class(ToolExecutionCompleteDataError, x)], self.error) + if self.tool_telemetry is not None: + result["toolTelemetry"] = from_union([from_none, lambda x: from_dict(lambda x: x, x)], self.tool_telemetry) + if self.parent_tool_call_id is not None: + result["parentToolCallId"] = from_union([from_none, lambda x: from_str(x)], self.parent_tool_call_id) + return result - name: str | None = None - """Name of the invoked skill - - Optional name identifier for the message source - """ - plugin_name: str | None = None - """Name of the plugin this skill originated from, when applicable""" +@dataclass +class SkillInvokedData: + "Skill invocation details including content, allowed tools, and plugin metadata" + name: str + path: str + content: str + allowed_tools: list[str] | None = None + plugin_name: str | None = None plugin_version: str | None = None - """Version of the plugin this skill originated from, when applicable""" - - agent_description: str | None = None - """Description of what the sub-agent does""" - - agent_display_name: str | None = None - """Human-readable display name of the sub-agent - - Human-readable display name of the selected custom agent - """ - agent_name: str | None = None - """Internal name of the sub-agent - - Internal name of the selected custom agent - """ - duration_ms: float | None = None - """Wall-clock duration of the sub-agent execution in milliseconds""" - - total_tokens: float | None = None - """Total tokens (input + output) consumed by the sub-agent - - Total tokens (input + output) consumed before the sub-agent failed - """ - total_tool_calls: float | None = None - """Total number of tool calls made by the sub-agent - - Total number of tool calls made before the sub-agent failed - """ - tools: list[str] | None = None - """List of tool names available to this agent, or null for all tools""" - - hook_invocation_id: str | None = None - """Unique identifier for this hook invocation - - Identifier matching the corresponding hook.start event - """ - hook_type: str | None = None - """Type of hook being invoked (e.g., "preToolUse", "postToolUse", "sessionStart") - - Type of hook that was invoked (e.g., "preToolUse", "postToolUse", "sessionStart") - """ - input: Any = None - """Input data passed to the hook""" - - output: Any = None - """Output data produced by the hook""" + description: str | None = None - metadata: Metadata | None = None - """Metadata about the prompt template and its construction""" + @staticmethod + def from_dict(obj: Any) -> "SkillInvokedData": + assert isinstance(obj, dict) + name = from_str(obj.get("name")) + path = from_str(obj.get("path")) + content = from_str(obj.get("content")) + allowed_tools = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], obj.get("allowedTools")) + plugin_name = from_union([from_none, lambda x: from_str(x)], obj.get("pluginName")) + plugin_version = from_union([from_none, lambda x: from_str(x)], obj.get("pluginVersion")) + description = from_union([from_none, lambda x: from_str(x)], obj.get("description")) + return SkillInvokedData( + name=name, + path=path, + content=content, + allowed_tools=allowed_tools, + plugin_name=plugin_name, + plugin_version=plugin_version, + description=description, + ) - role: Role | None = None - """Message role: "system" for system prompts, "developer" for developer-injected instructions""" + def to_dict(self) -> dict: + result: dict = {} + result["name"] = from_str(self.name) + result["path"] = from_str(self.path) + result["content"] = from_str(self.content) + if self.allowed_tools is not None: + result["allowedTools"] = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], self.allowed_tools) + if self.plugin_name is not None: + result["pluginName"] = from_union([from_none, lambda x: from_str(x)], self.plugin_name) + if self.plugin_version is not None: + result["pluginVersion"] = from_union([from_none, lambda x: from_str(x)], self.plugin_version) + if self.description is not None: + result["description"] = from_union([from_none, lambda x: from_str(x)], self.description) + return result - kind: KindClass | None = None - """Structured metadata identifying what triggered this notification""" - permission_request: PermissionRequest | None = None - """Details of the permission being requested""" +@dataclass +class SubagentStartedData: + "Sub-agent startup details including parent tool call and agent information" + tool_call_id: str + agent_name: str + agent_display_name: str + agent_description: str - resolved_by_hook: bool | None = None - """When true, this permission was already resolved by a permissionRequest hook and requires - no client action - """ - allow_freeform: bool | None = None - """Whether the user can provide a free-form text response in addition to predefined choices""" + @staticmethod + def from_dict(obj: Any) -> "SubagentStartedData": + assert isinstance(obj, dict) + tool_call_id = from_str(obj.get("toolCallId")) + agent_name = from_str(obj.get("agentName")) + agent_display_name = from_str(obj.get("agentDisplayName")) + agent_description = from_str(obj.get("agentDescription")) + return SubagentStartedData( + tool_call_id=tool_call_id, + agent_name=agent_name, + agent_display_name=agent_display_name, + agent_description=agent_description, + ) - choices: list[str] | None = None - """Predefined choices for the user to select from, if applicable""" + def to_dict(self) -> dict: + result: dict = {} + result["toolCallId"] = from_str(self.tool_call_id) + result["agentName"] = from_str(self.agent_name) + result["agentDisplayName"] = from_str(self.agent_display_name) + result["agentDescription"] = from_str(self.agent_description) + return result - question: str | None = None - """The question or prompt to present to the user""" - answer: str | None = None - """The user's answer to the input request""" +@dataclass +class SubagentCompletedData: + "Sub-agent completion details for successful execution" + tool_call_id: str + agent_name: str + agent_display_name: str + model: str | None = None + total_tool_calls: float | None = None + total_tokens: float | None = None + duration_ms: float | None = None - was_freeform: bool | None = None - """Whether the answer was typed as free-form text rather than selected from choices""" + @staticmethod + def from_dict(obj: Any) -> "SubagentCompletedData": + assert isinstance(obj, dict) + tool_call_id = from_str(obj.get("toolCallId")) + agent_name = from_str(obj.get("agentName")) + agent_display_name = from_str(obj.get("agentDisplayName")) + model = from_union([from_none, lambda x: from_str(x)], obj.get("model")) + total_tool_calls = from_union([from_none, lambda x: from_float(x)], obj.get("totalToolCalls")) + total_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("totalTokens")) + duration_ms = from_union([from_none, lambda x: from_float(x)], obj.get("durationMs")) + return SubagentCompletedData( + tool_call_id=tool_call_id, + agent_name=agent_name, + agent_display_name=agent_display_name, + model=model, + total_tool_calls=total_tool_calls, + total_tokens=total_tokens, + duration_ms=duration_ms, + ) - elicitation_source: str | None = None - """The source that initiated the request (MCP server name, or absent for agent-initiated)""" - - mode: Mode | None = None - """Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to - "form" when absent. - """ - requested_schema: RequestedSchema | None = None - """JSON Schema describing the form fields to present to the user (form mode only)""" - - action: Action | None = None - """The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" - (dismissed) - """ - mcp_request_id: float | str | None = None - """The JSON-RPC request ID from the MCP protocol""" + def to_dict(self) -> dict: + result: dict = {} + result["toolCallId"] = from_str(self.tool_call_id) + result["agentName"] = from_str(self.agent_name) + result["agentDisplayName"] = from_str(self.agent_display_name) + if self.model is not None: + result["model"] = from_union([from_none, lambda x: from_str(x)], self.model) + if self.total_tool_calls is not None: + result["totalToolCalls"] = from_union([from_none, lambda x: to_float(x)], self.total_tool_calls) + if self.total_tokens is not None: + result["totalTokens"] = from_union([from_none, lambda x: to_float(x)], self.total_tokens) + if self.duration_ms is not None: + result["durationMs"] = from_union([from_none, lambda x: to_float(x)], self.duration_ms) + return result - server_name: str | None = None - """Name of the MCP server that initiated the sampling request - - Display name of the MCP server that requires OAuth - - Name of the MCP server whose status changed - """ - server_url: str | None = None - """URL of the MCP server that requires OAuth""" - - static_client_config: StaticClientConfig | None = None - """Static OAuth client configuration, if the server specifies one""" - traceparent: str | None = None - """W3C Trace Context traceparent header for the execute_tool span""" +@dataclass +class SubagentFailedData: + "Sub-agent failure details including error message and agent information" + tool_call_id: str + agent_name: str + agent_display_name: str + error: str + model: str | None = None + total_tool_calls: float | None = None + total_tokens: float | None = None + duration_ms: float | None = None - tracestate: str | None = None - """W3C Trace Context tracestate header for the execute_tool span""" + @staticmethod + def from_dict(obj: Any) -> "SubagentFailedData": + assert isinstance(obj, dict) + tool_call_id = from_str(obj.get("toolCallId")) + agent_name = from_str(obj.get("agentName")) + agent_display_name = from_str(obj.get("agentDisplayName")) + error = from_str(obj.get("error")) + model = from_union([from_none, lambda x: from_str(x)], obj.get("model")) + total_tool_calls = from_union([from_none, lambda x: from_float(x)], obj.get("totalToolCalls")) + total_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("totalTokens")) + duration_ms = from_union([from_none, lambda x: from_float(x)], obj.get("durationMs")) + return SubagentFailedData( + tool_call_id=tool_call_id, + agent_name=agent_name, + agent_display_name=agent_display_name, + error=error, + model=model, + total_tool_calls=total_tool_calls, + total_tokens=total_tokens, + duration_ms=duration_ms, + ) - command: str | None = None - """The slash command text to be executed (e.g., /help, /clear) - - The full command text (e.g., /deploy production) - """ - args: str | None = None - """Raw argument string after the command name""" + def to_dict(self) -> dict: + result: dict = {} + result["toolCallId"] = from_str(self.tool_call_id) + result["agentName"] = from_str(self.agent_name) + result["agentDisplayName"] = from_str(self.agent_display_name) + result["error"] = from_str(self.error) + if self.model is not None: + result["model"] = from_union([from_none, lambda x: from_str(x)], self.model) + if self.total_tool_calls is not None: + result["totalToolCalls"] = from_union([from_none, lambda x: to_float(x)], self.total_tool_calls) + if self.total_tokens is not None: + result["totalTokens"] = from_union([from_none, lambda x: to_float(x)], self.total_tokens) + if self.duration_ms is not None: + result["durationMs"] = from_union([from_none, lambda x: to_float(x)], self.duration_ms) + return result - command_name: str | None = None - """Command name without leading /""" - commands: list[DataCommand] | None = None - """Current list of registered SDK commands""" +@dataclass +class SubagentSelectedData: + "Custom agent selection details including name and available tools" + agent_name: str + agent_display_name: str + tools: list[str] | None - ui: UI | None = None - """UI capability changes""" + @staticmethod + def from_dict(obj: Any) -> "SubagentSelectedData": + assert isinstance(obj, dict) + agent_name = from_str(obj.get("agentName")) + agent_display_name = from_str(obj.get("agentDisplayName")) + tools = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], obj.get("tools")) + return SubagentSelectedData( + agent_name=agent_name, + agent_display_name=agent_display_name, + tools=tools, + ) - actions: list[str] | None = None - """Available actions the user can take (e.g., approve, edit, reject)""" + def to_dict(self) -> dict: + result: dict = {} + result["agentName"] = from_str(self.agent_name) + result["agentDisplayName"] = from_str(self.agent_display_name) + result["tools"] = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], self.tools) + return result - plan_content: str | None = None - """Full content of the plan file""" - recommended_action: str | None = None - """The recommended action for the user to take""" +@dataclass +class SubagentDeselectedData: + "Empty payload; the event signals that the custom agent was deselected, returning to the default agent" + @staticmethod + def from_dict(obj: Any) -> "SubagentDeselectedData": + assert isinstance(obj, dict) + return SubagentDeselectedData() - approved: bool | None = None - """Whether the plan was approved by the user""" + def to_dict(self) -> dict: + return {} - auto_approve_edits: bool | None = None - """Whether edits should be auto-approved without confirmation""" - feedback: str | None = None - """Free-form feedback from the user if they requested changes to the plan""" +@dataclass +class HookStartData: + "Hook invocation start details including type and input data" + hook_invocation_id: str + hook_type: str + input: Any = None - selected_action: str | None = None - """Which action the user selected (e.g. 'autopilot', 'interactive', 'exit_only')""" - - skills: list[Skill] | None = None - """Array of resolved skill metadata""" - - agents: list[Agent] | None = None - """Array of loaded custom agent metadata""" - - errors: list[str] | None = None - """Fatal errors from agent loading""" - - warnings: list[str] | None = None - """Non-fatal warnings from agent loading""" - - servers: list[Server] | None = None - """Array of MCP server status summaries""" - - status: ServerStatus | None = None - """New connection status: connected, failed, needs-auth, pending, disabled, or not_configured""" - - extensions: list[Extension] | None = None - """Array of discovered extensions and their status""" - - @staticmethod - def from_dict(obj: Any) -> 'Data': - assert isinstance(obj, dict) - already_in_use = from_union([from_bool, from_none], obj.get("alreadyInUse")) - context = from_union([ContextClass.from_dict, from_str, from_none], obj.get("context")) - copilot_version = from_union([from_str, from_none], obj.get("copilotVersion")) - producer = from_union([from_str, from_none], obj.get("producer")) - reasoning_effort = from_union([from_str, from_none], obj.get("reasoningEffort")) - remote_steerable = from_union([from_bool, from_none], obj.get("remoteSteerable")) - selected_model = from_union([from_str, from_none], obj.get("selectedModel")) - session_id = from_union([from_str, from_none], obj.get("sessionId")) - start_time = from_union([from_datetime, from_none], obj.get("startTime")) - version = from_union([from_float, from_none], obj.get("version")) - event_count = from_union([from_float, from_none], obj.get("eventCount")) - resume_time = from_union([from_datetime, from_none], obj.get("resumeTime")) - error_type = from_union([from_str, from_none], obj.get("errorType")) - message = from_union([from_str, from_none], obj.get("message")) - provider_call_id = from_union([from_str, from_none], obj.get("providerCallId")) - stack = from_union([from_str, from_none], obj.get("stack")) - status_code = from_union([from_int, from_none], obj.get("statusCode")) - url = from_union([from_str, from_none], obj.get("url")) - aborted = from_union([from_bool, from_none], obj.get("aborted")) - title = from_union([from_str, from_none], obj.get("title")) - info_type = from_union([from_str, from_none], obj.get("infoType")) - warning_type = from_union([from_str, from_none], obj.get("warningType")) - new_model = from_union([from_str, from_none], obj.get("newModel")) - previous_model = from_union([from_str, from_none], obj.get("previousModel")) - previous_reasoning_effort = from_union([from_str, from_none], obj.get("previousReasoningEffort")) - new_mode = from_union([from_str, from_none], obj.get("newMode")) - previous_mode = from_union([from_str, from_none], obj.get("previousMode")) - operation = from_union([Operation, from_none], obj.get("operation")) - path = from_union([from_str, from_none], obj.get("path")) - handoff_time = from_union([from_datetime, from_none], obj.get("handoffTime")) - host = from_union([from_str, from_none], obj.get("host")) - remote_session_id = from_union([from_str, from_none], obj.get("remoteSessionId")) - repository = from_union([RepositoryClass.from_dict, from_str, from_none], obj.get("repository")) - source_type = from_union([SourceType, from_none], obj.get("sourceType")) - summary = from_union([from_str, from_none], obj.get("summary")) - messages_removed_during_truncation = from_union([from_float, from_none], obj.get("messagesRemovedDuringTruncation")) - performed_by = from_union([from_str, from_none], obj.get("performedBy")) - post_truncation_messages_length = from_union([from_float, from_none], obj.get("postTruncationMessagesLength")) - post_truncation_tokens_in_messages = from_union([from_float, from_none], obj.get("postTruncationTokensInMessages")) - pre_truncation_messages_length = from_union([from_float, from_none], obj.get("preTruncationMessagesLength")) - pre_truncation_tokens_in_messages = from_union([from_float, from_none], obj.get("preTruncationTokensInMessages")) - token_limit = from_union([from_float, from_none], obj.get("tokenLimit")) - tokens_removed_during_truncation = from_union([from_float, from_none], obj.get("tokensRemovedDuringTruncation")) - events_removed = from_union([from_float, from_none], obj.get("eventsRemoved")) - up_to_event_id = from_union([from_str, from_none], obj.get("upToEventId")) - code_changes = from_union([CodeChanges.from_dict, from_none], obj.get("codeChanges")) - conversation_tokens = from_union([from_float, from_none], obj.get("conversationTokens")) - current_model = from_union([from_str, from_none], obj.get("currentModel")) - current_tokens = from_union([from_float, from_none], obj.get("currentTokens")) - error_reason = from_union([from_str, from_none], obj.get("errorReason")) - model_metrics = from_union([lambda x: from_dict(ModelMetric.from_dict, x), from_none], obj.get("modelMetrics")) - session_start_time = from_union([from_float, from_none], obj.get("sessionStartTime")) - shutdown_type = from_union([ShutdownType, from_none], obj.get("shutdownType")) - system_tokens = from_union([from_float, from_none], obj.get("systemTokens")) - tool_definitions_tokens = from_union([from_float, from_none], obj.get("toolDefinitionsTokens")) - total_api_duration_ms = from_union([from_float, from_none], obj.get("totalApiDurationMs")) - total_premium_requests = from_union([from_float, from_none], obj.get("totalPremiumRequests")) - base_commit = from_union([from_str, from_none], obj.get("baseCommit")) - branch = from_union([from_str, from_none], obj.get("branch")) - cwd = from_union([from_str, from_none], obj.get("cwd")) - git_root = from_union([from_str, from_none], obj.get("gitRoot")) - head_commit = from_union([from_str, from_none], obj.get("headCommit")) - host_type = from_union([HostType, from_none], obj.get("hostType")) - is_initial = from_union([from_bool, from_none], obj.get("isInitial")) - messages_length = from_union([from_float, from_none], obj.get("messagesLength")) - checkpoint_number = from_union([from_float, from_none], obj.get("checkpointNumber")) - checkpoint_path = from_union([from_str, from_none], obj.get("checkpointPath")) - compaction_tokens_used = from_union([CompactionTokensUsed.from_dict, from_none], obj.get("compactionTokensUsed")) - error = from_union([ErrorClass.from_dict, from_str, from_none], obj.get("error")) - messages_removed = from_union([from_float, from_none], obj.get("messagesRemoved")) - post_compaction_tokens = from_union([from_float, from_none], obj.get("postCompactionTokens")) - pre_compaction_messages_length = from_union([from_float, from_none], obj.get("preCompactionMessagesLength")) - pre_compaction_tokens = from_union([from_float, from_none], obj.get("preCompactionTokens")) - request_id = from_union([from_str, from_none], obj.get("requestId")) - success = from_union([from_bool, from_none], obj.get("success")) - summary_content = from_union([from_str, from_none], obj.get("summaryContent")) - tokens_removed = from_union([from_float, from_none], obj.get("tokensRemoved")) - agent_mode = from_union([AgentMode, from_none], obj.get("agentMode")) - attachments = from_union([lambda x: from_list(Attachment.from_dict, x), from_none], obj.get("attachments")) - content = from_union([from_str, lambda x: from_dict(lambda x: from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], obj.get("content")) - interaction_id = from_union([from_str, from_none], obj.get("interactionId")) - source = from_union([from_str, from_none], obj.get("source")) - transformed_content = from_union([from_str, from_none], obj.get("transformedContent")) - turn_id = from_union([from_str, from_none], obj.get("turnId")) - intent = from_union([from_str, from_none], obj.get("intent")) - reasoning_id = from_union([from_str, from_none], obj.get("reasoningId")) - delta_content = from_union([from_str, from_none], obj.get("deltaContent")) - total_response_size_bytes = from_union([from_float, from_none], obj.get("totalResponseSizeBytes")) - encrypted_content = from_union([from_str, from_none], obj.get("encryptedContent")) - message_id = from_union([from_str, from_none], obj.get("messageId")) - output_tokens = from_union([from_float, from_none], obj.get("outputTokens")) - parent_tool_call_id = from_union([from_str, from_none], obj.get("parentToolCallId")) - phase = from_union([from_str, from_none], obj.get("phase")) - reasoning_opaque = from_union([from_str, from_none], obj.get("reasoningOpaque")) - reasoning_text = from_union([from_str, from_none], obj.get("reasoningText")) - tool_requests = from_union([lambda x: from_list(ToolRequest.from_dict, x), from_none], obj.get("toolRequests")) - api_call_id = from_union([from_str, from_none], obj.get("apiCallId")) - cache_read_tokens = from_union([from_float, from_none], obj.get("cacheReadTokens")) - cache_write_tokens = from_union([from_float, from_none], obj.get("cacheWriteTokens")) - copilot_usage = from_union([CopilotUsage.from_dict, from_none], obj.get("copilotUsage")) - cost = from_union([from_float, from_none], obj.get("cost")) - duration = from_union([from_float, from_none], obj.get("duration")) - initiator = from_union([from_str, from_none], obj.get("initiator")) - input_tokens = from_union([from_float, from_none], obj.get("inputTokens")) - inter_token_latency_ms = from_union([from_float, from_none], obj.get("interTokenLatencyMs")) - model = from_union([from_str, from_none], obj.get("model")) - quota_snapshots = from_union([lambda x: from_dict(QuotaSnapshot.from_dict, x), from_none], obj.get("quotaSnapshots")) - ttft_ms = from_union([from_float, from_none], obj.get("ttftMs")) - reason = from_union([from_str, from_none], obj.get("reason")) - arguments = obj.get("arguments") - tool_call_id = from_union([from_str, from_none], obj.get("toolCallId")) - tool_name = from_union([from_str, from_none], obj.get("toolName")) - mcp_server_name = from_union([from_str, from_none], obj.get("mcpServerName")) - mcp_tool_name = from_union([from_str, from_none], obj.get("mcpToolName")) - partial_output = from_union([from_str, from_none], obj.get("partialOutput")) - progress_message = from_union([from_str, from_none], obj.get("progressMessage")) - is_user_requested = from_union([from_bool, from_none], obj.get("isUserRequested")) - result = from_union([Result.from_dict, from_none], obj.get("result")) - tool_telemetry = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("toolTelemetry")) - allowed_tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("allowedTools")) - description = from_union([from_str, from_none], obj.get("description")) - name = from_union([from_str, from_none], obj.get("name")) - plugin_name = from_union([from_str, from_none], obj.get("pluginName")) - plugin_version = from_union([from_str, from_none], obj.get("pluginVersion")) - agent_description = from_union([from_str, from_none], obj.get("agentDescription")) - agent_display_name = from_union([from_str, from_none], obj.get("agentDisplayName")) - agent_name = from_union([from_str, from_none], obj.get("agentName")) - duration_ms = from_union([from_float, from_none], obj.get("durationMs")) - total_tokens = from_union([from_float, from_none], obj.get("totalTokens")) - total_tool_calls = from_union([from_float, from_none], obj.get("totalToolCalls")) - tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) - hook_invocation_id = from_union([from_str, from_none], obj.get("hookInvocationId")) - hook_type = from_union([from_str, from_none], obj.get("hookType")) + @staticmethod + def from_dict(obj: Any) -> "HookStartData": + assert isinstance(obj, dict) + hook_invocation_id = from_str(obj.get("hookInvocationId")) + hook_type = from_str(obj.get("hookType")) input = obj.get("input") - output = obj.get("output") - metadata = from_union([Metadata.from_dict, from_none], obj.get("metadata")) - role = from_union([Role, from_none], obj.get("role")) - kind = from_union([KindClass.from_dict, from_none], obj.get("kind")) - permission_request = from_union([PermissionRequest.from_dict, from_none], obj.get("permissionRequest")) - resolved_by_hook = from_union([from_bool, from_none], obj.get("resolvedByHook")) - allow_freeform = from_union([from_bool, from_none], obj.get("allowFreeform")) - choices = from_union([lambda x: from_list(from_str, x), from_none], obj.get("choices")) - question = from_union([from_str, from_none], obj.get("question")) - answer = from_union([from_str, from_none], obj.get("answer")) - was_freeform = from_union([from_bool, from_none], obj.get("wasFreeform")) - elicitation_source = from_union([from_str, from_none], obj.get("elicitationSource")) - mode = from_union([Mode, from_none], obj.get("mode")) - requested_schema = from_union([RequestedSchema.from_dict, from_none], obj.get("requestedSchema")) - action = from_union([Action, from_none], obj.get("action")) - mcp_request_id = from_union([from_float, from_str, from_none], obj.get("mcpRequestId")) - server_name = from_union([from_str, from_none], obj.get("serverName")) - server_url = from_union([from_str, from_none], obj.get("serverUrl")) - static_client_config = from_union([StaticClientConfig.from_dict, from_none], obj.get("staticClientConfig")) - traceparent = from_union([from_str, from_none], obj.get("traceparent")) - tracestate = from_union([from_str, from_none], obj.get("tracestate")) - command = from_union([from_str, from_none], obj.get("command")) - args = from_union([from_str, from_none], obj.get("args")) - command_name = from_union([from_str, from_none], obj.get("commandName")) - commands = from_union([lambda x: from_list(DataCommand.from_dict, x), from_none], obj.get("commands")) - ui = from_union([UI.from_dict, from_none], obj.get("ui")) - actions = from_union([lambda x: from_list(from_str, x), from_none], obj.get("actions")) - plan_content = from_union([from_str, from_none], obj.get("planContent")) - recommended_action = from_union([from_str, from_none], obj.get("recommendedAction")) - approved = from_union([from_bool, from_none], obj.get("approved")) - auto_approve_edits = from_union([from_bool, from_none], obj.get("autoApproveEdits")) - feedback = from_union([from_str, from_none], obj.get("feedback")) - selected_action = from_union([from_str, from_none], obj.get("selectedAction")) - skills = from_union([lambda x: from_list(Skill.from_dict, x), from_none], obj.get("skills")) - agents = from_union([lambda x: from_list(Agent.from_dict, x), from_none], obj.get("agents")) - errors = from_union([lambda x: from_list(from_str, x), from_none], obj.get("errors")) - warnings = from_union([lambda x: from_list(from_str, x), from_none], obj.get("warnings")) - servers = from_union([lambda x: from_list(Server.from_dict, x), from_none], obj.get("servers")) - status = from_union([ServerStatus, from_none], obj.get("status")) - extensions = from_union([lambda x: from_list(Extension.from_dict, x), from_none], obj.get("extensions")) - return Data(already_in_use, context, copilot_version, producer, reasoning_effort, remote_steerable, selected_model, session_id, start_time, version, event_count, resume_time, error_type, message, provider_call_id, stack, status_code, url, aborted, title, info_type, warning_type, new_model, previous_model, previous_reasoning_effort, new_mode, previous_mode, operation, path, handoff_time, host, remote_session_id, repository, source_type, summary, messages_removed_during_truncation, performed_by, post_truncation_messages_length, post_truncation_tokens_in_messages, pre_truncation_messages_length, pre_truncation_tokens_in_messages, token_limit, tokens_removed_during_truncation, events_removed, up_to_event_id, code_changes, conversation_tokens, current_model, current_tokens, error_reason, model_metrics, session_start_time, shutdown_type, system_tokens, tool_definitions_tokens, total_api_duration_ms, total_premium_requests, base_commit, branch, cwd, git_root, head_commit, host_type, is_initial, messages_length, checkpoint_number, checkpoint_path, compaction_tokens_used, error, messages_removed, post_compaction_tokens, pre_compaction_messages_length, pre_compaction_tokens, request_id, success, summary_content, tokens_removed, agent_mode, attachments, content, interaction_id, source, transformed_content, turn_id, intent, reasoning_id, delta_content, total_response_size_bytes, encrypted_content, message_id, output_tokens, parent_tool_call_id, phase, reasoning_opaque, reasoning_text, tool_requests, api_call_id, cache_read_tokens, cache_write_tokens, copilot_usage, cost, duration, initiator, input_tokens, inter_token_latency_ms, model, quota_snapshots, ttft_ms, reason, arguments, tool_call_id, tool_name, mcp_server_name, mcp_tool_name, partial_output, progress_message, is_user_requested, result, tool_telemetry, allowed_tools, description, name, plugin_name, plugin_version, agent_description, agent_display_name, agent_name, duration_ms, total_tokens, total_tool_calls, tools, hook_invocation_id, hook_type, input, output, metadata, role, kind, permission_request, resolved_by_hook, allow_freeform, choices, question, answer, was_freeform, elicitation_source, mode, requested_schema, action, mcp_request_id, server_name, server_url, static_client_config, traceparent, tracestate, command, args, command_name, commands, ui, actions, plan_content, recommended_action, approved, auto_approve_edits, feedback, selected_action, skills, agents, errors, warnings, servers, status, extensions) + return HookStartData( + hook_invocation_id=hook_invocation_id, + hook_type=hook_type, + input=input, + ) def to_dict(self) -> dict: result: dict = {} - if self.already_in_use is not None: - result["alreadyInUse"] = from_union([from_bool, from_none], self.already_in_use) - if self.context is not None: - result["context"] = from_union([lambda x: to_class(ContextClass, x), from_str, from_none], self.context) - if self.copilot_version is not None: - result["copilotVersion"] = from_union([from_str, from_none], self.copilot_version) - if self.producer is not None: - result["producer"] = from_union([from_str, from_none], self.producer) - if self.reasoning_effort is not None: - result["reasoningEffort"] = from_union([from_str, from_none], self.reasoning_effort) - if self.remote_steerable is not None: - result["remoteSteerable"] = from_union([from_bool, from_none], self.remote_steerable) - if self.selected_model is not None: - result["selectedModel"] = from_union([from_str, from_none], self.selected_model) - if self.session_id is not None: - result["sessionId"] = from_union([from_str, from_none], self.session_id) - if self.start_time is not None: - result["startTime"] = from_union([lambda x: x.isoformat(), from_none], self.start_time) - if self.version is not None: - result["version"] = from_union([to_float, from_none], self.version) - if self.event_count is not None: - result["eventCount"] = from_union([to_float, from_none], self.event_count) - if self.resume_time is not None: - result["resumeTime"] = from_union([lambda x: x.isoformat(), from_none], self.resume_time) - if self.error_type is not None: - result["errorType"] = from_union([from_str, from_none], self.error_type) - if self.message is not None: - result["message"] = from_union([from_str, from_none], self.message) - if self.provider_call_id is not None: - result["providerCallId"] = from_union([from_str, from_none], self.provider_call_id) - if self.stack is not None: - result["stack"] = from_union([from_str, from_none], self.stack) - if self.status_code is not None: - result["statusCode"] = from_union([from_int, from_none], self.status_code) - if self.url is not None: - result["url"] = from_union([from_str, from_none], self.url) - if self.aborted is not None: - result["aborted"] = from_union([from_bool, from_none], self.aborted) - if self.title is not None: - result["title"] = from_union([from_str, from_none], self.title) - if self.info_type is not None: - result["infoType"] = from_union([from_str, from_none], self.info_type) - if self.warning_type is not None: - result["warningType"] = from_union([from_str, from_none], self.warning_type) - if self.new_model is not None: - result["newModel"] = from_union([from_str, from_none], self.new_model) - if self.previous_model is not None: - result["previousModel"] = from_union([from_str, from_none], self.previous_model) - if self.previous_reasoning_effort is not None: - result["previousReasoningEffort"] = from_union([from_str, from_none], self.previous_reasoning_effort) - if self.new_mode is not None: - result["newMode"] = from_union([from_str, from_none], self.new_mode) - if self.previous_mode is not None: - result["previousMode"] = from_union([from_str, from_none], self.previous_mode) - if self.operation is not None: - result["operation"] = from_union([lambda x: to_enum(Operation, x), from_none], self.operation) - if self.path is not None: - result["path"] = from_union([from_str, from_none], self.path) - if self.handoff_time is not None: - result["handoffTime"] = from_union([lambda x: x.isoformat(), from_none], self.handoff_time) - if self.host is not None: - result["host"] = from_union([from_str, from_none], self.host) - if self.remote_session_id is not None: - result["remoteSessionId"] = from_union([from_str, from_none], self.remote_session_id) - if self.repository is not None: - result["repository"] = from_union([lambda x: to_class(RepositoryClass, x), from_str, from_none], self.repository) - if self.source_type is not None: - result["sourceType"] = from_union([lambda x: to_enum(SourceType, x), from_none], self.source_type) - if self.summary is not None: - result["summary"] = from_union([from_str, from_none], self.summary) - if self.messages_removed_during_truncation is not None: - result["messagesRemovedDuringTruncation"] = from_union([to_float, from_none], self.messages_removed_during_truncation) - if self.performed_by is not None: - result["performedBy"] = from_union([from_str, from_none], self.performed_by) - if self.post_truncation_messages_length is not None: - result["postTruncationMessagesLength"] = from_union([to_float, from_none], self.post_truncation_messages_length) - if self.post_truncation_tokens_in_messages is not None: - result["postTruncationTokensInMessages"] = from_union([to_float, from_none], self.post_truncation_tokens_in_messages) - if self.pre_truncation_messages_length is not None: - result["preTruncationMessagesLength"] = from_union([to_float, from_none], self.pre_truncation_messages_length) - if self.pre_truncation_tokens_in_messages is not None: - result["preTruncationTokensInMessages"] = from_union([to_float, from_none], self.pre_truncation_tokens_in_messages) - if self.token_limit is not None: - result["tokenLimit"] = from_union([to_float, from_none], self.token_limit) - if self.tokens_removed_during_truncation is not None: - result["tokensRemovedDuringTruncation"] = from_union([to_float, from_none], self.tokens_removed_during_truncation) - if self.events_removed is not None: - result["eventsRemoved"] = from_union([to_float, from_none], self.events_removed) - if self.up_to_event_id is not None: - result["upToEventId"] = from_union([from_str, from_none], self.up_to_event_id) - if self.code_changes is not None: - result["codeChanges"] = from_union([lambda x: to_class(CodeChanges, x), from_none], self.code_changes) - if self.conversation_tokens is not None: - result["conversationTokens"] = from_union([to_float, from_none], self.conversation_tokens) - if self.current_model is not None: - result["currentModel"] = from_union([from_str, from_none], self.current_model) - if self.current_tokens is not None: - result["currentTokens"] = from_union([to_float, from_none], self.current_tokens) - if self.error_reason is not None: - result["errorReason"] = from_union([from_str, from_none], self.error_reason) - if self.model_metrics is not None: - result["modelMetrics"] = from_union([lambda x: from_dict(lambda x: to_class(ModelMetric, x), x), from_none], self.model_metrics) - if self.session_start_time is not None: - result["sessionStartTime"] = from_union([to_float, from_none], self.session_start_time) - if self.shutdown_type is not None: - result["shutdownType"] = from_union([lambda x: to_enum(ShutdownType, x), from_none], self.shutdown_type) - if self.system_tokens is not None: - result["systemTokens"] = from_union([to_float, from_none], self.system_tokens) - if self.tool_definitions_tokens is not None: - result["toolDefinitionsTokens"] = from_union([to_float, from_none], self.tool_definitions_tokens) - if self.total_api_duration_ms is not None: - result["totalApiDurationMs"] = from_union([to_float, from_none], self.total_api_duration_ms) - if self.total_premium_requests is not None: - result["totalPremiumRequests"] = from_union([to_float, from_none], self.total_premium_requests) - if self.base_commit is not None: - result["baseCommit"] = from_union([from_str, from_none], self.base_commit) - if self.branch is not None: - result["branch"] = from_union([from_str, from_none], self.branch) - if self.cwd is not None: - result["cwd"] = from_union([from_str, from_none], self.cwd) - if self.git_root is not None: - result["gitRoot"] = from_union([from_str, from_none], self.git_root) - if self.head_commit is not None: - result["headCommit"] = from_union([from_str, from_none], self.head_commit) - if self.host_type is not None: - result["hostType"] = from_union([lambda x: to_enum(HostType, x), from_none], self.host_type) - if self.is_initial is not None: - result["isInitial"] = from_union([from_bool, from_none], self.is_initial) - if self.messages_length is not None: - result["messagesLength"] = from_union([to_float, from_none], self.messages_length) - if self.checkpoint_number is not None: - result["checkpointNumber"] = from_union([to_float, from_none], self.checkpoint_number) - if self.checkpoint_path is not None: - result["checkpointPath"] = from_union([from_str, from_none], self.checkpoint_path) - if self.compaction_tokens_used is not None: - result["compactionTokensUsed"] = from_union([lambda x: to_class(CompactionTokensUsed, x), from_none], self.compaction_tokens_used) - if self.error is not None: - result["error"] = from_union([lambda x: to_class(ErrorClass, x), from_str, from_none], self.error) - if self.messages_removed is not None: - result["messagesRemoved"] = from_union([to_float, from_none], self.messages_removed) - if self.post_compaction_tokens is not None: - result["postCompactionTokens"] = from_union([to_float, from_none], self.post_compaction_tokens) - if self.pre_compaction_messages_length is not None: - result["preCompactionMessagesLength"] = from_union([to_float, from_none], self.pre_compaction_messages_length) - if self.pre_compaction_tokens is not None: - result["preCompactionTokens"] = from_union([to_float, from_none], self.pre_compaction_tokens) - if self.request_id is not None: - result["requestId"] = from_union([from_str, from_none], self.request_id) - if self.success is not None: - result["success"] = from_union([from_bool, from_none], self.success) - if self.summary_content is not None: - result["summaryContent"] = from_union([from_str, from_none], self.summary_content) - if self.tokens_removed is not None: - result["tokensRemoved"] = from_union([to_float, from_none], self.tokens_removed) - if self.agent_mode is not None: - result["agentMode"] = from_union([lambda x: to_enum(AgentMode, x), from_none], self.agent_mode) - if self.attachments is not None: - result["attachments"] = from_union([lambda x: from_list(lambda x: to_class(Attachment, x), x), from_none], self.attachments) - if self.content is not None: - result["content"] = from_union([from_str, lambda x: from_dict(lambda x: from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], self.content) - if self.interaction_id is not None: - result["interactionId"] = from_union([from_str, from_none], self.interaction_id) - if self.source is not None: - result["source"] = from_union([from_str, from_none], self.source) - if self.transformed_content is not None: - result["transformedContent"] = from_union([from_str, from_none], self.transformed_content) - if self.turn_id is not None: - result["turnId"] = from_union([from_str, from_none], self.turn_id) - if self.intent is not None: - result["intent"] = from_union([from_str, from_none], self.intent) - if self.reasoning_id is not None: - result["reasoningId"] = from_union([from_str, from_none], self.reasoning_id) - if self.delta_content is not None: - result["deltaContent"] = from_union([from_str, from_none], self.delta_content) - if self.total_response_size_bytes is not None: - result["totalResponseSizeBytes"] = from_union([to_float, from_none], self.total_response_size_bytes) - if self.encrypted_content is not None: - result["encryptedContent"] = from_union([from_str, from_none], self.encrypted_content) - if self.message_id is not None: - result["messageId"] = from_union([from_str, from_none], self.message_id) - if self.output_tokens is not None: - result["outputTokens"] = from_union([to_float, from_none], self.output_tokens) - if self.parent_tool_call_id is not None: - result["parentToolCallId"] = from_union([from_str, from_none], self.parent_tool_call_id) - if self.phase is not None: - result["phase"] = from_union([from_str, from_none], self.phase) - if self.reasoning_opaque is not None: - result["reasoningOpaque"] = from_union([from_str, from_none], self.reasoning_opaque) - if self.reasoning_text is not None: - result["reasoningText"] = from_union([from_str, from_none], self.reasoning_text) - if self.tool_requests is not None: - result["toolRequests"] = from_union([lambda x: from_list(lambda x: to_class(ToolRequest, x), x), from_none], self.tool_requests) - if self.api_call_id is not None: - result["apiCallId"] = from_union([from_str, from_none], self.api_call_id) - if self.cache_read_tokens is not None: - result["cacheReadTokens"] = from_union([to_float, from_none], self.cache_read_tokens) - if self.cache_write_tokens is not None: - result["cacheWriteTokens"] = from_union([to_float, from_none], self.cache_write_tokens) - if self.copilot_usage is not None: - result["copilotUsage"] = from_union([lambda x: to_class(CopilotUsage, x), from_none], self.copilot_usage) - if self.cost is not None: - result["cost"] = from_union([to_float, from_none], self.cost) - if self.duration is not None: - result["duration"] = from_union([to_float, from_none], self.duration) - if self.initiator is not None: - result["initiator"] = from_union([from_str, from_none], self.initiator) - if self.input_tokens is not None: - result["inputTokens"] = from_union([to_float, from_none], self.input_tokens) - if self.inter_token_latency_ms is not None: - result["interTokenLatencyMs"] = from_union([to_float, from_none], self.inter_token_latency_ms) - if self.model is not None: - result["model"] = from_union([from_str, from_none], self.model) - if self.quota_snapshots is not None: - result["quotaSnapshots"] = from_union([lambda x: from_dict(lambda x: to_class(QuotaSnapshot, x), x), from_none], self.quota_snapshots) - if self.ttft_ms is not None: - result["ttftMs"] = from_union([to_float, from_none], self.ttft_ms) - if self.reason is not None: - result["reason"] = from_union([from_str, from_none], self.reason) - if self.arguments is not None: - result["arguments"] = self.arguments - if self.tool_call_id is not None: - result["toolCallId"] = from_union([from_str, from_none], self.tool_call_id) - if self.tool_name is not None: - result["toolName"] = from_union([from_str, from_none], self.tool_name) - if self.mcp_server_name is not None: - result["mcpServerName"] = from_union([from_str, from_none], self.mcp_server_name) - if self.mcp_tool_name is not None: - result["mcpToolName"] = from_union([from_str, from_none], self.mcp_tool_name) - if self.partial_output is not None: - result["partialOutput"] = from_union([from_str, from_none], self.partial_output) - if self.progress_message is not None: - result["progressMessage"] = from_union([from_str, from_none], self.progress_message) - if self.is_user_requested is not None: - result["isUserRequested"] = from_union([from_bool, from_none], self.is_user_requested) - if self.result is not None: - result["result"] = from_union([lambda x: to_class(Result, x), from_none], self.result) - if self.tool_telemetry is not None: - result["toolTelemetry"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.tool_telemetry) - if self.allowed_tools is not None: - result["allowedTools"] = from_union([lambda x: from_list(from_str, x), from_none], self.allowed_tools) - if self.description is not None: - result["description"] = from_union([from_str, from_none], self.description) - if self.name is not None: - result["name"] = from_union([from_str, from_none], self.name) - if self.plugin_name is not None: - result["pluginName"] = from_union([from_str, from_none], self.plugin_name) - if self.plugin_version is not None: - result["pluginVersion"] = from_union([from_str, from_none], self.plugin_version) - if self.agent_description is not None: - result["agentDescription"] = from_union([from_str, from_none], self.agent_description) - if self.agent_display_name is not None: - result["agentDisplayName"] = from_union([from_str, from_none], self.agent_display_name) - if self.agent_name is not None: - result["agentName"] = from_union([from_str, from_none], self.agent_name) - if self.duration_ms is not None: - result["durationMs"] = from_union([to_float, from_none], self.duration_ms) - if self.total_tokens is not None: - result["totalTokens"] = from_union([to_float, from_none], self.total_tokens) - if self.total_tool_calls is not None: - result["totalToolCalls"] = from_union([to_float, from_none], self.total_tool_calls) - if self.tools is not None: - result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) - if self.hook_invocation_id is not None: - result["hookInvocationId"] = from_union([from_str, from_none], self.hook_invocation_id) - if self.hook_type is not None: - result["hookType"] = from_union([from_str, from_none], self.hook_type) + result["hookInvocationId"] = from_str(self.hook_invocation_id) + result["hookType"] = from_str(self.hook_type) if self.input is not None: result["input"] = self.input - if self.output is not None: - result["output"] = self.output - if self.metadata is not None: - result["metadata"] = from_union([lambda x: to_class(Metadata, x), from_none], self.metadata) - if self.role is not None: - result["role"] = from_union([lambda x: to_enum(Role, x), from_none], self.role) - if self.kind is not None: - result["kind"] = from_union([lambda x: to_class(KindClass, x), from_none], self.kind) - if self.permission_request is not None: - result["permissionRequest"] = from_union([lambda x: to_class(PermissionRequest, x), from_none], self.permission_request) - if self.resolved_by_hook is not None: - result["resolvedByHook"] = from_union([from_bool, from_none], self.resolved_by_hook) - if self.allow_freeform is not None: - result["allowFreeform"] = from_union([from_bool, from_none], self.allow_freeform) - if self.choices is not None: - result["choices"] = from_union([lambda x: from_list(from_str, x), from_none], self.choices) - if self.question is not None: - result["question"] = from_union([from_str, from_none], self.question) - if self.answer is not None: - result["answer"] = from_union([from_str, from_none], self.answer) - if self.was_freeform is not None: - result["wasFreeform"] = from_union([from_bool, from_none], self.was_freeform) - if self.elicitation_source is not None: - result["elicitationSource"] = from_union([from_str, from_none], self.elicitation_source) - if self.mode is not None: - result["mode"] = from_union([lambda x: to_enum(Mode, x), from_none], self.mode) - if self.requested_schema is not None: - result["requestedSchema"] = from_union([lambda x: to_class(RequestedSchema, x), from_none], self.requested_schema) - if self.action is not None: - result["action"] = from_union([lambda x: to_enum(Action, x), from_none], self.action) - if self.mcp_request_id is not None: - result["mcpRequestId"] = from_union([to_float, from_str, from_none], self.mcp_request_id) - if self.server_name is not None: - result["serverName"] = from_union([from_str, from_none], self.server_name) - if self.server_url is not None: - result["serverUrl"] = from_union([from_str, from_none], self.server_url) - if self.static_client_config is not None: - result["staticClientConfig"] = from_union([lambda x: to_class(StaticClientConfig, x), from_none], self.static_client_config) - if self.traceparent is not None: - result["traceparent"] = from_union([from_str, from_none], self.traceparent) - if self.tracestate is not None: - result["tracestate"] = from_union([from_str, from_none], self.tracestate) - if self.command is not None: - result["command"] = from_union([from_str, from_none], self.command) - if self.args is not None: - result["args"] = from_union([from_str, from_none], self.args) - if self.command_name is not None: - result["commandName"] = from_union([from_str, from_none], self.command_name) - if self.commands is not None: - result["commands"] = from_union([lambda x: from_list(lambda x: to_class(DataCommand, x), x), from_none], self.commands) - if self.ui is not None: - result["ui"] = from_union([lambda x: to_class(UI, x), from_none], self.ui) - if self.actions is not None: - result["actions"] = from_union([lambda x: from_list(from_str, x), from_none], self.actions) - if self.plan_content is not None: - result["planContent"] = from_union([from_str, from_none], self.plan_content) - if self.recommended_action is not None: - result["recommendedAction"] = from_union([from_str, from_none], self.recommended_action) - if self.approved is not None: - result["approved"] = from_union([from_bool, from_none], self.approved) - if self.auto_approve_edits is not None: - result["autoApproveEdits"] = from_union([from_bool, from_none], self.auto_approve_edits) - if self.feedback is not None: - result["feedback"] = from_union([from_str, from_none], self.feedback) - if self.selected_action is not None: - result["selectedAction"] = from_union([from_str, from_none], self.selected_action) - if self.skills is not None: - result["skills"] = from_union([lambda x: from_list(lambda x: to_class(Skill, x), x), from_none], self.skills) - if self.agents is not None: - result["agents"] = from_union([lambda x: from_list(lambda x: to_class(Agent, x), x), from_none], self.agents) - if self.errors is not None: - result["errors"] = from_union([lambda x: from_list(from_str, x), from_none], self.errors) - if self.warnings is not None: - result["warnings"] = from_union([lambda x: from_list(from_str, x), from_none], self.warnings) - if self.servers is not None: - result["servers"] = from_union([lambda x: from_list(lambda x: to_class(Server, x), x), from_none], self.servers) - if self.status is not None: - result["status"] = from_union([lambda x: to_enum(ServerStatus, x), from_none], self.status) - if self.extensions is not None: - result["extensions"] = from_union([lambda x: from_list(lambda x: to_class(Extension, x), x), from_none], self.extensions) return result -class SessionEventType(Enum): - ABORT = "abort" - ASSISTANT_INTENT = "assistant.intent" - ASSISTANT_MESSAGE = "assistant.message" - ASSISTANT_MESSAGE_DELTA = "assistant.message_delta" - ASSISTANT_REASONING = "assistant.reasoning" - ASSISTANT_REASONING_DELTA = "assistant.reasoning_delta" - ASSISTANT_STREAMING_DELTA = "assistant.streaming_delta" - ASSISTANT_TURN_END = "assistant.turn_end" - ASSISTANT_TURN_START = "assistant.turn_start" - ASSISTANT_USAGE = "assistant.usage" - CAPABILITIES_CHANGED = "capabilities.changed" - COMMANDS_CHANGED = "commands.changed" - COMMAND_COMPLETED = "command.completed" - COMMAND_EXECUTE = "command.execute" - COMMAND_QUEUED = "command.queued" - ELICITATION_COMPLETED = "elicitation.completed" - ELICITATION_REQUESTED = "elicitation.requested" - EXIT_PLAN_MODE_COMPLETED = "exit_plan_mode.completed" - EXIT_PLAN_MODE_REQUESTED = "exit_plan_mode.requested" - EXTERNAL_TOOL_COMPLETED = "external_tool.completed" - EXTERNAL_TOOL_REQUESTED = "external_tool.requested" - HOOK_END = "hook.end" - HOOK_START = "hook.start" - MCP_OAUTH_COMPLETED = "mcp.oauth_completed" - MCP_OAUTH_REQUIRED = "mcp.oauth_required" - PENDING_MESSAGES_MODIFIED = "pending_messages.modified" - PERMISSION_COMPLETED = "permission.completed" - PERMISSION_REQUESTED = "permission.requested" - SAMPLING_COMPLETED = "sampling.completed" - SAMPLING_REQUESTED = "sampling.requested" - SESSION_BACKGROUND_TASKS_CHANGED = "session.background_tasks_changed" - SESSION_COMPACTION_COMPLETE = "session.compaction_complete" - SESSION_COMPACTION_START = "session.compaction_start" - SESSION_CONTEXT_CHANGED = "session.context_changed" - SESSION_CUSTOM_AGENTS_UPDATED = "session.custom_agents_updated" - SESSION_ERROR = "session.error" - SESSION_EXTENSIONS_LOADED = "session.extensions_loaded" - SESSION_HANDOFF = "session.handoff" - SESSION_IDLE = "session.idle" - SESSION_INFO = "session.info" - SESSION_MCP_SERVERS_LOADED = "session.mcp_servers_loaded" - SESSION_MCP_SERVER_STATUS_CHANGED = "session.mcp_server_status_changed" - SESSION_MODEL_CHANGE = "session.model_change" - SESSION_MODE_CHANGED = "session.mode_changed" - SESSION_PLAN_CHANGED = "session.plan_changed" - SESSION_REMOTE_STEERABLE_CHANGED = "session.remote_steerable_changed" - SESSION_RESUME = "session.resume" - SESSION_SHUTDOWN = "session.shutdown" - SESSION_SKILLS_LOADED = "session.skills_loaded" - SESSION_SNAPSHOT_REWIND = "session.snapshot_rewind" - SESSION_START = "session.start" - SESSION_TASK_COMPLETE = "session.task_complete" - SESSION_TITLE_CHANGED = "session.title_changed" - SESSION_TOOLS_UPDATED = "session.tools_updated" - SESSION_TRUNCATION = "session.truncation" - SESSION_USAGE_INFO = "session.usage_info" - SESSION_WARNING = "session.warning" - SESSION_WORKSPACE_FILE_CHANGED = "session.workspace_file_changed" - SKILL_INVOKED = "skill.invoked" - SUBAGENT_COMPLETED = "subagent.completed" - SUBAGENT_DESELECTED = "subagent.deselected" - SUBAGENT_FAILED = "subagent.failed" - SUBAGENT_SELECTED = "subagent.selected" - SUBAGENT_STARTED = "subagent.started" - SYSTEM_MESSAGE = "system.message" - SYSTEM_NOTIFICATION = "system.notification" - TOOL_EXECUTION_COMPLETE = "tool.execution_complete" - TOOL_EXECUTION_PARTIAL_RESULT = "tool.execution_partial_result" - TOOL_EXECUTION_PROGRESS = "tool.execution_progress" - TOOL_EXECUTION_START = "tool.execution_start" - TOOL_USER_REQUESTED = "tool.user_requested" - USER_INPUT_COMPLETED = "user_input.completed" - USER_INPUT_REQUESTED = "user_input.requested" - USER_MESSAGE = "user.message" - # UNKNOWN is used for forward compatibility - UNKNOWN = "unknown" +@dataclass +class HookEndDataError: + "Error details when the hook failed" + message: str + stack: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "HookEndDataError": + assert isinstance(obj, dict) + message = from_str(obj.get("message")) + stack = from_union([from_none, lambda x: from_str(x)], obj.get("stack")) + return HookEndDataError( + message=message, + stack=stack, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["message"] = from_str(self.message) + if self.stack is not None: + result["stack"] = from_union([from_none, lambda x: from_str(x)], self.stack) + return result + + +@dataclass +class HookEndData: + "Hook invocation completion details including output, success status, and error information" + hook_invocation_id: str + hook_type: str + success: bool + output: Any = None + error: HookEndDataError | None = None + + @staticmethod + def from_dict(obj: Any) -> "HookEndData": + assert isinstance(obj, dict) + hook_invocation_id = from_str(obj.get("hookInvocationId")) + hook_type = from_str(obj.get("hookType")) + success = from_bool(obj.get("success")) + output = obj.get("output") + error = from_union([from_none, lambda x: HookEndDataError.from_dict(x)], obj.get("error")) + return HookEndData( + hook_invocation_id=hook_invocation_id, + hook_type=hook_type, + success=success, + output=output, + error=error, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["hookInvocationId"] = from_str(self.hook_invocation_id) + result["hookType"] = from_str(self.hook_type) + result["success"] = from_bool(self.success) + if self.output is not None: + result["output"] = self.output + if self.error is not None: + result["error"] = from_union([from_none, lambda x: to_class(HookEndDataError, x)], self.error) + return result + + +@dataclass +class SystemMessageDataMetadata: + "Metadata about the prompt template and its construction" + prompt_version: str | None = None + variables: dict[str, Any] | None = None + + @staticmethod + def from_dict(obj: Any) -> "SystemMessageDataMetadata": + assert isinstance(obj, dict) + prompt_version = from_union([from_none, lambda x: from_str(x)], obj.get("promptVersion")) + variables = from_union([from_none, lambda x: from_dict(lambda x: x, x)], obj.get("variables")) + return SystemMessageDataMetadata( + prompt_version=prompt_version, + variables=variables, + ) + + def to_dict(self) -> dict: + result: dict = {} + if self.prompt_version is not None: + result["promptVersion"] = from_union([from_none, lambda x: from_str(x)], self.prompt_version) + if self.variables is not None: + result["variables"] = from_union([from_none, lambda x: from_dict(lambda x: x, x)], self.variables) + return result + + +@dataclass +class SystemMessageData: + "System or developer message content with role and optional template metadata" + content: str + role: SystemMessageDataRole + name: str | None = None + metadata: SystemMessageDataMetadata | None = None + + @staticmethod + def from_dict(obj: Any) -> "SystemMessageData": + assert isinstance(obj, dict) + content = from_str(obj.get("content")) + role = parse_enum(SystemMessageDataRole, obj.get("role")) + name = from_union([from_none, lambda x: from_str(x)], obj.get("name")) + metadata = from_union([from_none, lambda x: SystemMessageDataMetadata.from_dict(x)], obj.get("metadata")) + return SystemMessageData( + content=content, + role=role, + name=name, + metadata=metadata, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["content"] = from_str(self.content) + result["role"] = to_enum(SystemMessageDataRole, self.role) + if self.name is not None: + result["name"] = from_union([from_none, lambda x: from_str(x)], self.name) + if self.metadata is not None: + result["metadata"] = from_union([from_none, lambda x: to_class(SystemMessageDataMetadata, x)], self.metadata) + return result + + +@dataclass +class SystemNotificationDataKind: + "Structured metadata identifying what triggered this notification" + type: SystemNotificationDataKindType + agent_id: str | None = None + agent_type: str | None = None + status: SystemNotificationDataKindStatus | None = None + description: str | None = None + prompt: str | None = None + shell_id: str | None = None + exit_code: float | None = None + + @staticmethod + def from_dict(obj: Any) -> "SystemNotificationDataKind": + assert isinstance(obj, dict) + type = parse_enum(SystemNotificationDataKindType, obj.get("type")) + agent_id = from_union([from_none, lambda x: from_str(x)], obj.get("agentId")) + agent_type = from_union([from_none, lambda x: from_str(x)], obj.get("agentType")) + status = from_union([from_none, lambda x: parse_enum(SystemNotificationDataKindStatus, x)], obj.get("status")) + description = from_union([from_none, lambda x: from_str(x)], obj.get("description")) + prompt = from_union([from_none, lambda x: from_str(x)], obj.get("prompt")) + shell_id = from_union([from_none, lambda x: from_str(x)], obj.get("shellId")) + exit_code = from_union([from_none, lambda x: from_float(x)], obj.get("exitCode")) + return SystemNotificationDataKind( + type=type, + agent_id=agent_id, + agent_type=agent_type, + status=status, + description=description, + prompt=prompt, + shell_id=shell_id, + exit_code=exit_code, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["type"] = to_enum(SystemNotificationDataKindType, self.type) + if self.agent_id is not None: + result["agentId"] = from_union([from_none, lambda x: from_str(x)], self.agent_id) + if self.agent_type is not None: + result["agentType"] = from_union([from_none, lambda x: from_str(x)], self.agent_type) + if self.status is not None: + result["status"] = from_union([from_none, lambda x: to_enum(SystemNotificationDataKindStatus, x)], self.status) + if self.description is not None: + result["description"] = from_union([from_none, lambda x: from_str(x)], self.description) + if self.prompt is not None: + result["prompt"] = from_union([from_none, lambda x: from_str(x)], self.prompt) + if self.shell_id is not None: + result["shellId"] = from_union([from_none, lambda x: from_str(x)], self.shell_id) + if self.exit_code is not None: + result["exitCode"] = from_union([from_none, lambda x: to_float(x)], self.exit_code) + return result + + +@dataclass +class SystemNotificationData: + "System-generated notification for runtime events like background task completion" + content: str + kind: SystemNotificationDataKind + + @staticmethod + def from_dict(obj: Any) -> "SystemNotificationData": + assert isinstance(obj, dict) + content = from_str(obj.get("content")) + kind = SystemNotificationDataKind.from_dict(obj.get("kind")) + return SystemNotificationData( + content=content, + kind=kind, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["content"] = from_str(self.content) + result["kind"] = to_class(SystemNotificationDataKind, self.kind) + return result + + +@dataclass +class PermissionRequestedDataPermissionRequestCommandsItem: + identifier: str + read_only: bool + + @staticmethod + def from_dict(obj: Any) -> "PermissionRequestedDataPermissionRequestCommandsItem": + assert isinstance(obj, dict) + identifier = from_str(obj.get("identifier")) + read_only = from_bool(obj.get("readOnly")) + return PermissionRequestedDataPermissionRequestCommandsItem( + identifier=identifier, + read_only=read_only, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["identifier"] = from_str(self.identifier) + result["readOnly"] = from_bool(self.read_only) + return result + + +@dataclass +class PermissionRequestedDataPermissionRequestPossibleUrlsItem: + url: str + + @staticmethod + def from_dict(obj: Any) -> "PermissionRequestedDataPermissionRequestPossibleUrlsItem": + assert isinstance(obj, dict) + url = from_str(obj.get("url")) + return PermissionRequestedDataPermissionRequestPossibleUrlsItem( + url=url, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["url"] = from_str(self.url) + return result + + +@dataclass +class PermissionRequestedDataPermissionRequest: + "Details of the permission being requested" + kind: PermissionRequestedDataPermissionRequestKind + tool_call_id: str | None = None + full_command_text: str | None = None + intention: str | None = None + commands: list[PermissionRequestedDataPermissionRequestCommandsItem] | None = None + possible_paths: list[str] | None = None + possible_urls: list[PermissionRequestedDataPermissionRequestPossibleUrlsItem] | None = None + has_write_file_redirection: bool | None = None + can_offer_session_approval: bool | None = None + warning: str | None = None + file_name: str | None = None + diff: str | None = None + new_file_contents: str | None = None + path: str | None = None + server_name: str | None = None + tool_name: str | None = None + tool_title: str | None = None + args: Any = None + read_only: bool | None = None + url: str | None = None + subject: str | None = None + fact: str | None = None + citations: str | None = None + tool_description: str | None = None + tool_args: Any = None + hook_message: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "PermissionRequestedDataPermissionRequest": + assert isinstance(obj, dict) + kind = parse_enum(PermissionRequestedDataPermissionRequestKind, obj.get("kind")) + tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("toolCallId")) + full_command_text = from_union([from_none, lambda x: from_str(x)], obj.get("fullCommandText")) + intention = from_union([from_none, lambda x: from_str(x)], obj.get("intention")) + commands = from_union([from_none, lambda x: from_list(lambda x: PermissionRequestedDataPermissionRequestCommandsItem.from_dict(x), x)], obj.get("commands")) + possible_paths = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], obj.get("possiblePaths")) + possible_urls = from_union([from_none, lambda x: from_list(lambda x: PermissionRequestedDataPermissionRequestPossibleUrlsItem.from_dict(x), x)], obj.get("possibleUrls")) + has_write_file_redirection = from_union([from_none, lambda x: from_bool(x)], obj.get("hasWriteFileRedirection")) + can_offer_session_approval = from_union([from_none, lambda x: from_bool(x)], obj.get("canOfferSessionApproval")) + warning = from_union([from_none, lambda x: from_str(x)], obj.get("warning")) + file_name = from_union([from_none, lambda x: from_str(x)], obj.get("fileName")) + diff = from_union([from_none, lambda x: from_str(x)], obj.get("diff")) + new_file_contents = from_union([from_none, lambda x: from_str(x)], obj.get("newFileContents")) + path = from_union([from_none, lambda x: from_str(x)], obj.get("path")) + server_name = from_union([from_none, lambda x: from_str(x)], obj.get("serverName")) + tool_name = from_union([from_none, lambda x: from_str(x)], obj.get("toolName")) + tool_title = from_union([from_none, lambda x: from_str(x)], obj.get("toolTitle")) + args = obj.get("args") + read_only = from_union([from_none, lambda x: from_bool(x)], obj.get("readOnly")) + url = from_union([from_none, lambda x: from_str(x)], obj.get("url")) + subject = from_union([from_none, lambda x: from_str(x)], obj.get("subject")) + fact = from_union([from_none, lambda x: from_str(x)], obj.get("fact")) + citations = from_union([from_none, lambda x: from_str(x)], obj.get("citations")) + tool_description = from_union([from_none, lambda x: from_str(x)], obj.get("toolDescription")) + tool_args = obj.get("toolArgs") + hook_message = from_union([from_none, lambda x: from_str(x)], obj.get("hookMessage")) + return PermissionRequestedDataPermissionRequest( + kind=kind, + tool_call_id=tool_call_id, + full_command_text=full_command_text, + intention=intention, + commands=commands, + possible_paths=possible_paths, + possible_urls=possible_urls, + has_write_file_redirection=has_write_file_redirection, + can_offer_session_approval=can_offer_session_approval, + warning=warning, + file_name=file_name, + diff=diff, + new_file_contents=new_file_contents, + path=path, + server_name=server_name, + tool_name=tool_name, + tool_title=tool_title, + args=args, + read_only=read_only, + url=url, + subject=subject, + fact=fact, + citations=citations, + tool_description=tool_description, + tool_args=tool_args, + hook_message=hook_message, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(PermissionRequestedDataPermissionRequestKind, self.kind) + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_none, lambda x: from_str(x)], self.tool_call_id) + if self.full_command_text is not None: + result["fullCommandText"] = from_union([from_none, lambda x: from_str(x)], self.full_command_text) + if self.intention is not None: + result["intention"] = from_union([from_none, lambda x: from_str(x)], self.intention) + if self.commands is not None: + result["commands"] = from_union([from_none, lambda x: from_list(lambda x: to_class(PermissionRequestedDataPermissionRequestCommandsItem, x), x)], self.commands) + if self.possible_paths is not None: + result["possiblePaths"] = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], self.possible_paths) + if self.possible_urls is not None: + result["possibleUrls"] = from_union([from_none, lambda x: from_list(lambda x: to_class(PermissionRequestedDataPermissionRequestPossibleUrlsItem, x), x)], self.possible_urls) + if self.has_write_file_redirection is not None: + result["hasWriteFileRedirection"] = from_union([from_none, lambda x: from_bool(x)], self.has_write_file_redirection) + if self.can_offer_session_approval is not None: + result["canOfferSessionApproval"] = from_union([from_none, lambda x: from_bool(x)], self.can_offer_session_approval) + if self.warning is not None: + result["warning"] = from_union([from_none, lambda x: from_str(x)], self.warning) + if self.file_name is not None: + result["fileName"] = from_union([from_none, lambda x: from_str(x)], self.file_name) + if self.diff is not None: + result["diff"] = from_union([from_none, lambda x: from_str(x)], self.diff) + if self.new_file_contents is not None: + result["newFileContents"] = from_union([from_none, lambda x: from_str(x)], self.new_file_contents) + if self.path is not None: + result["path"] = from_union([from_none, lambda x: from_str(x)], self.path) + if self.server_name is not None: + result["serverName"] = from_union([from_none, lambda x: from_str(x)], self.server_name) + if self.tool_name is not None: + result["toolName"] = from_union([from_none, lambda x: from_str(x)], self.tool_name) + if self.tool_title is not None: + result["toolTitle"] = from_union([from_none, lambda x: from_str(x)], self.tool_title) + if self.args is not None: + result["args"] = self.args + if self.read_only is not None: + result["readOnly"] = from_union([from_none, lambda x: from_bool(x)], self.read_only) + if self.url is not None: + result["url"] = from_union([from_none, lambda x: from_str(x)], self.url) + if self.subject is not None: + result["subject"] = from_union([from_none, lambda x: from_str(x)], self.subject) + if self.fact is not None: + result["fact"] = from_union([from_none, lambda x: from_str(x)], self.fact) + if self.citations is not None: + result["citations"] = from_union([from_none, lambda x: from_str(x)], self.citations) + if self.tool_description is not None: + result["toolDescription"] = from_union([from_none, lambda x: from_str(x)], self.tool_description) + if self.tool_args is not None: + result["toolArgs"] = self.tool_args + if self.hook_message is not None: + result["hookMessage"] = from_union([from_none, lambda x: from_str(x)], self.hook_message) + return result + + +@dataclass +class PermissionRequestedData: + "Permission request notification requiring client approval with request details" + request_id: str + permission_request: PermissionRequestedDataPermissionRequest + resolved_by_hook: bool | None = None + + @staticmethod + def from_dict(obj: Any) -> "PermissionRequestedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + permission_request = PermissionRequestedDataPermissionRequest.from_dict(obj.get("permissionRequest")) + resolved_by_hook = from_union([from_none, lambda x: from_bool(x)], obj.get("resolvedByHook")) + return PermissionRequestedData( + request_id=request_id, + permission_request=permission_request, + resolved_by_hook=resolved_by_hook, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["permissionRequest"] = to_class(PermissionRequestedDataPermissionRequest, self.permission_request) + if self.resolved_by_hook is not None: + result["resolvedByHook"] = from_union([from_none, lambda x: from_bool(x)], self.resolved_by_hook) + return result + + +@dataclass +class PermissionCompletedDataResult: + "The result of the permission request" + kind: PermissionCompletedDataResultKind + + @staticmethod + def from_dict(obj: Any) -> "PermissionCompletedDataResult": + assert isinstance(obj, dict) + kind = parse_enum(PermissionCompletedDataResultKind, obj.get("kind")) + return PermissionCompletedDataResult( + kind=kind, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(PermissionCompletedDataResultKind, self.kind) + return result + + +@dataclass +class PermissionCompletedData: + "Permission request completion notification signaling UI dismissal" + request_id: str + result: PermissionCompletedDataResult + + @staticmethod + def from_dict(obj: Any) -> "PermissionCompletedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + result = PermissionCompletedDataResult.from_dict(obj.get("result")) + return PermissionCompletedData( + request_id=request_id, + result=result, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["result"] = to_class(PermissionCompletedDataResult, self.result) + return result + + +@dataclass +class UserInputRequestedData: + "User input request notification with question and optional predefined choices" + request_id: str + question: str + choices: list[str] | None = None + allow_freeform: bool | None = None + tool_call_id: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "UserInputRequestedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + question = from_str(obj.get("question")) + choices = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], obj.get("choices")) + allow_freeform = from_union([from_none, lambda x: from_bool(x)], obj.get("allowFreeform")) + tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("toolCallId")) + return UserInputRequestedData( + request_id=request_id, + question=question, + choices=choices, + allow_freeform=allow_freeform, + tool_call_id=tool_call_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["question"] = from_str(self.question) + if self.choices is not None: + result["choices"] = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], self.choices) + if self.allow_freeform is not None: + result["allowFreeform"] = from_union([from_none, lambda x: from_bool(x)], self.allow_freeform) + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_none, lambda x: from_str(x)], self.tool_call_id) + return result + + +@dataclass +class UserInputCompletedData: + "User input request completion with the user's response" + request_id: str + answer: str | None = None + was_freeform: bool | None = None + + @staticmethod + def from_dict(obj: Any) -> "UserInputCompletedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + answer = from_union([from_none, lambda x: from_str(x)], obj.get("answer")) + was_freeform = from_union([from_none, lambda x: from_bool(x)], obj.get("wasFreeform")) + return UserInputCompletedData( + request_id=request_id, + answer=answer, + was_freeform=was_freeform, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + if self.answer is not None: + result["answer"] = from_union([from_none, lambda x: from_str(x)], self.answer) + if self.was_freeform is not None: + result["wasFreeform"] = from_union([from_none, lambda x: from_bool(x)], self.was_freeform) + return result + + +@dataclass +class ElicitationRequestedDataRequestedSchema: + "JSON Schema describing the form fields to present to the user (form mode only)" + type: str + properties: dict[str, Any] + required: list[str] | None = None + + @staticmethod + def from_dict(obj: Any) -> "ElicitationRequestedDataRequestedSchema": + assert isinstance(obj, dict) + type = from_str(obj.get("type")) + properties = from_dict(lambda x: x, obj.get("properties")) + required = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], obj.get("required")) + return ElicitationRequestedDataRequestedSchema( + type=type, + properties=properties, + required=required, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["type"] = from_str(self.type) + result["properties"] = from_dict(lambda x: x, self.properties) + if self.required is not None: + result["required"] = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], self.required) + return result + + +@dataclass +class ElicitationRequestedData: + "Elicitation request; may be form-based (structured input) or URL-based (browser redirect)" + request_id: str + message: str + tool_call_id: str | None = None + elicitation_source: str | None = None + mode: ElicitationRequestedDataMode | None = None + requested_schema: ElicitationRequestedDataRequestedSchema | None = None + url: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "ElicitationRequestedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + message = from_str(obj.get("message")) + tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("toolCallId")) + elicitation_source = from_union([from_none, lambda x: from_str(x)], obj.get("elicitationSource")) + mode = from_union([from_none, lambda x: parse_enum(ElicitationRequestedDataMode, x)], obj.get("mode")) + requested_schema = from_union([from_none, lambda x: ElicitationRequestedDataRequestedSchema.from_dict(x)], obj.get("requestedSchema")) + url = from_union([from_none, lambda x: from_str(x)], obj.get("url")) + return ElicitationRequestedData( + request_id=request_id, + message=message, + tool_call_id=tool_call_id, + elicitation_source=elicitation_source, + mode=mode, + requested_schema=requested_schema, + url=url, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["message"] = from_str(self.message) + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_none, lambda x: from_str(x)], self.tool_call_id) + if self.elicitation_source is not None: + result["elicitationSource"] = from_union([from_none, lambda x: from_str(x)], self.elicitation_source) + if self.mode is not None: + result["mode"] = from_union([from_none, lambda x: to_enum(ElicitationRequestedDataMode, x)], self.mode) + if self.requested_schema is not None: + result["requestedSchema"] = from_union([from_none, lambda x: to_class(ElicitationRequestedDataRequestedSchema, x)], self.requested_schema) + if self.url is not None: + result["url"] = from_union([from_none, lambda x: from_str(x)], self.url) + return result + + +@dataclass +class ElicitationCompletedData: + "Elicitation request completion with the user's response" + request_id: str + action: ElicitationCompletedDataAction | None = None + content: dict[str, Any] | None = None + + @staticmethod + def from_dict(obj: Any) -> "ElicitationCompletedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + action = from_union([from_none, lambda x: parse_enum(ElicitationCompletedDataAction, x)], obj.get("action")) + content = from_union([from_none, lambda x: from_dict(lambda x: x, x)], obj.get("content")) + return ElicitationCompletedData( + request_id=request_id, + action=action, + content=content, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + if self.action is not None: + result["action"] = from_union([from_none, lambda x: to_enum(ElicitationCompletedDataAction, x)], self.action) + if self.content is not None: + result["content"] = from_union([from_none, lambda x: from_dict(lambda x: x, x)], self.content) + return result + + +@dataclass +class SamplingRequestedData: + "Sampling request from an MCP server; contains the server name and a requestId for correlation" + request_id: str + server_name: str + mcp_request_id: Any + + @staticmethod + def from_dict(obj: Any) -> "SamplingRequestedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + server_name = from_str(obj.get("serverName")) + mcp_request_id = obj.get("mcpRequestId") + return SamplingRequestedData( + request_id=request_id, + server_name=server_name, + mcp_request_id=mcp_request_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["serverName"] = from_str(self.server_name) + result["mcpRequestId"] = self.mcp_request_id + return result + + +@dataclass +class SamplingCompletedData: + "Sampling request completion notification signaling UI dismissal" + request_id: str + + @staticmethod + def from_dict(obj: Any) -> "SamplingCompletedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + return SamplingCompletedData( + request_id=request_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + return result + + +@dataclass +class McpOauthRequiredDataStaticClientConfig: + "Static OAuth client configuration, if the server specifies one" + client_id: str + public_client: bool | None = None + + @staticmethod + def from_dict(obj: Any) -> "McpOauthRequiredDataStaticClientConfig": + assert isinstance(obj, dict) + client_id = from_str(obj.get("clientId")) + public_client = from_union([from_none, lambda x: from_bool(x)], obj.get("publicClient")) + return McpOauthRequiredDataStaticClientConfig( + client_id=client_id, + public_client=public_client, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["clientId"] = from_str(self.client_id) + if self.public_client is not None: + result["publicClient"] = from_union([from_none, lambda x: from_bool(x)], self.public_client) + return result + + +@dataclass +class McpOauthRequiredData: + "OAuth authentication request for an MCP server" + request_id: str + server_name: str + server_url: str + static_client_config: McpOauthRequiredDataStaticClientConfig | None = None + + @staticmethod + def from_dict(obj: Any) -> "McpOauthRequiredData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + server_name = from_str(obj.get("serverName")) + server_url = from_str(obj.get("serverUrl")) + static_client_config = from_union([from_none, lambda x: McpOauthRequiredDataStaticClientConfig.from_dict(x)], obj.get("staticClientConfig")) + return McpOauthRequiredData( + request_id=request_id, + server_name=server_name, + server_url=server_url, + static_client_config=static_client_config, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["serverName"] = from_str(self.server_name) + result["serverUrl"] = from_str(self.server_url) + if self.static_client_config is not None: + result["staticClientConfig"] = from_union([from_none, lambda x: to_class(McpOauthRequiredDataStaticClientConfig, x)], self.static_client_config) + return result + + +@dataclass +class McpOauthCompletedData: + "MCP OAuth request completion notification" + request_id: str + + @staticmethod + def from_dict(obj: Any) -> "McpOauthCompletedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + return McpOauthCompletedData( + request_id=request_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + return result + + +@dataclass +class ExternalToolRequestedData: + "External tool invocation request for client-side tool execution" + request_id: str + session_id: str + tool_call_id: str + tool_name: str + arguments: Any = None + traceparent: str | None = None + tracestate: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "ExternalToolRequestedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + session_id = from_str(obj.get("sessionId")) + tool_call_id = from_str(obj.get("toolCallId")) + tool_name = from_str(obj.get("toolName")) + arguments = obj.get("arguments") + traceparent = from_union([from_none, lambda x: from_str(x)], obj.get("traceparent")) + tracestate = from_union([from_none, lambda x: from_str(x)], obj.get("tracestate")) + return ExternalToolRequestedData( + request_id=request_id, + session_id=session_id, + tool_call_id=tool_call_id, + tool_name=tool_name, + arguments=arguments, + traceparent=traceparent, + tracestate=tracestate, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["sessionId"] = from_str(self.session_id) + result["toolCallId"] = from_str(self.tool_call_id) + result["toolName"] = from_str(self.tool_name) + if self.arguments is not None: + result["arguments"] = self.arguments + if self.traceparent is not None: + result["traceparent"] = from_union([from_none, lambda x: from_str(x)], self.traceparent) + if self.tracestate is not None: + result["tracestate"] = from_union([from_none, lambda x: from_str(x)], self.tracestate) + return result + + +@dataclass +class ExternalToolCompletedData: + "External tool completion notification signaling UI dismissal" + request_id: str + + @staticmethod + def from_dict(obj: Any) -> "ExternalToolCompletedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + return ExternalToolCompletedData( + request_id=request_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + return result + + +@dataclass +class CommandQueuedData: + "Queued slash command dispatch request for client execution" + request_id: str + command: str + + @staticmethod + def from_dict(obj: Any) -> "CommandQueuedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + command = from_str(obj.get("command")) + return CommandQueuedData( + request_id=request_id, + command=command, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["command"] = from_str(self.command) + return result + + +@dataclass +class CommandExecuteData: + "Registered command dispatch request routed to the owning client" + request_id: str + command: str + command_name: str + args: str + + @staticmethod + def from_dict(obj: Any) -> "CommandExecuteData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + command = from_str(obj.get("command")) + command_name = from_str(obj.get("commandName")) + args = from_str(obj.get("args")) + return CommandExecuteData( + request_id=request_id, + command=command, + command_name=command_name, + args=args, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["command"] = from_str(self.command) + result["commandName"] = from_str(self.command_name) + result["args"] = from_str(self.args) + return result + + +@dataclass +class CommandCompletedData: + "Queued command completion notification signaling UI dismissal" + request_id: str + + @staticmethod + def from_dict(obj: Any) -> "CommandCompletedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + return CommandCompletedData( + request_id=request_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + return result + + +@dataclass +class CommandsChangedDataCommandsItem: + name: str + description: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "CommandsChangedDataCommandsItem": + assert isinstance(obj, dict) + name = from_str(obj.get("name")) + description = from_union([from_none, lambda x: from_str(x)], obj.get("description")) + return CommandsChangedDataCommandsItem( + name=name, + description=description, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["name"] = from_str(self.name) + if self.description is not None: + result["description"] = from_union([from_none, lambda x: from_str(x)], self.description) + return result + + +@dataclass +class CommandsChangedData: + "SDK command registration change notification" + commands: list[CommandsChangedDataCommandsItem] + + @staticmethod + def from_dict(obj: Any) -> "CommandsChangedData": + assert isinstance(obj, dict) + commands = from_list(lambda x: CommandsChangedDataCommandsItem.from_dict(x), obj.get("commands")) + return CommandsChangedData( + commands=commands, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["commands"] = from_list(lambda x: to_class(CommandsChangedDataCommandsItem, x), self.commands) + return result + + +@dataclass +class CapabilitiesChangedDataUi: + "UI capability changes" + elicitation: bool | None = None + + @staticmethod + def from_dict(obj: Any) -> "CapabilitiesChangedDataUi": + assert isinstance(obj, dict) + elicitation = from_union([from_none, lambda x: from_bool(x)], obj.get("elicitation")) + return CapabilitiesChangedDataUi( + elicitation=elicitation, + ) + + def to_dict(self) -> dict: + result: dict = {} + if self.elicitation is not None: + result["elicitation"] = from_union([from_none, lambda x: from_bool(x)], self.elicitation) + return result + + +@dataclass +class CapabilitiesChangedData: + "Session capability change notification" + ui: CapabilitiesChangedDataUi | None = None + + @staticmethod + def from_dict(obj: Any) -> "CapabilitiesChangedData": + assert isinstance(obj, dict) + ui = from_union([from_none, lambda x: CapabilitiesChangedDataUi.from_dict(x)], obj.get("ui")) + return CapabilitiesChangedData( + ui=ui, + ) + + def to_dict(self) -> dict: + result: dict = {} + if self.ui is not None: + result["ui"] = from_union([from_none, lambda x: to_class(CapabilitiesChangedDataUi, x)], self.ui) + return result + + +@dataclass +class ExitPlanModeRequestedData: + "Plan approval request with plan content and available user actions" + request_id: str + summary: str + plan_content: str + actions: list[str] + recommended_action: str + + @staticmethod + def from_dict(obj: Any) -> "ExitPlanModeRequestedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + summary = from_str(obj.get("summary")) + plan_content = from_str(obj.get("planContent")) + actions = from_list(lambda x: from_str(x), obj.get("actions")) + recommended_action = from_str(obj.get("recommendedAction")) + return ExitPlanModeRequestedData( + request_id=request_id, + summary=summary, + plan_content=plan_content, + actions=actions, + recommended_action=recommended_action, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["summary"] = from_str(self.summary) + result["planContent"] = from_str(self.plan_content) + result["actions"] = from_list(lambda x: from_str(x), self.actions) + result["recommendedAction"] = from_str(self.recommended_action) + return result + + +@dataclass +class ExitPlanModeCompletedData: + "Plan mode exit completion with the user's approval decision and optional feedback" + request_id: str + approved: bool | None = None + selected_action: str | None = None + auto_approve_edits: bool | None = None + feedback: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "ExitPlanModeCompletedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + approved = from_union([from_none, lambda x: from_bool(x)], obj.get("approved")) + selected_action = from_union([from_none, lambda x: from_str(x)], obj.get("selectedAction")) + auto_approve_edits = from_union([from_none, lambda x: from_bool(x)], obj.get("autoApproveEdits")) + feedback = from_union([from_none, lambda x: from_str(x)], obj.get("feedback")) + return ExitPlanModeCompletedData( + request_id=request_id, + approved=approved, + selected_action=selected_action, + auto_approve_edits=auto_approve_edits, + feedback=feedback, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + if self.approved is not None: + result["approved"] = from_union([from_none, lambda x: from_bool(x)], self.approved) + if self.selected_action is not None: + result["selectedAction"] = from_union([from_none, lambda x: from_str(x)], self.selected_action) + if self.auto_approve_edits is not None: + result["autoApproveEdits"] = from_union([from_none, lambda x: from_bool(x)], self.auto_approve_edits) + if self.feedback is not None: + result["feedback"] = from_union([from_none, lambda x: from_str(x)], self.feedback) + return result + + +@dataclass +class SessionToolsUpdatedData: + model: str + + @staticmethod + def from_dict(obj: Any) -> "SessionToolsUpdatedData": + assert isinstance(obj, dict) + model = from_str(obj.get("model")) + return SessionToolsUpdatedData( + model=model, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["model"] = from_str(self.model) + return result + + +@dataclass +class SessionBackgroundTasksChangedData: + @staticmethod + def from_dict(obj: Any) -> "SessionBackgroundTasksChangedData": + assert isinstance(obj, dict) + return SessionBackgroundTasksChangedData() + + def to_dict(self) -> dict: + return {} + + +@dataclass +class SessionSkillsLoadedDataSkillsItem: + name: str + description: str + source: str + user_invocable: bool + enabled: bool + path: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "SessionSkillsLoadedDataSkillsItem": + assert isinstance(obj, dict) + name = from_str(obj.get("name")) + description = from_str(obj.get("description")) + source = from_str(obj.get("source")) + user_invocable = from_bool(obj.get("userInvocable")) + enabled = from_bool(obj.get("enabled")) + path = from_union([from_none, lambda x: from_str(x)], obj.get("path")) + return SessionSkillsLoadedDataSkillsItem( + name=name, + description=description, + source=source, + user_invocable=user_invocable, + enabled=enabled, + path=path, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["name"] = from_str(self.name) + result["description"] = from_str(self.description) + result["source"] = from_str(self.source) + result["userInvocable"] = from_bool(self.user_invocable) + result["enabled"] = from_bool(self.enabled) + if self.path is not None: + result["path"] = from_union([from_none, lambda x: from_str(x)], self.path) + return result + + +@dataclass +class SessionSkillsLoadedData: + skills: list[SessionSkillsLoadedDataSkillsItem] + + @staticmethod + def from_dict(obj: Any) -> "SessionSkillsLoadedData": + assert isinstance(obj, dict) + skills = from_list(lambda x: SessionSkillsLoadedDataSkillsItem.from_dict(x), obj.get("skills")) + return SessionSkillsLoadedData( + skills=skills, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["skills"] = from_list(lambda x: to_class(SessionSkillsLoadedDataSkillsItem, x), self.skills) + return result + + +@dataclass +class SessionCustomAgentsUpdatedDataAgentsItem: + id: str + name: str + display_name: str + description: str + source: str + tools: list[str] + user_invocable: bool + model: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "SessionCustomAgentsUpdatedDataAgentsItem": + assert isinstance(obj, dict) + id = from_str(obj.get("id")) + name = from_str(obj.get("name")) + display_name = from_str(obj.get("displayName")) + description = from_str(obj.get("description")) + source = from_str(obj.get("source")) + tools = from_list(lambda x: from_str(x), obj.get("tools")) + user_invocable = from_bool(obj.get("userInvocable")) + model = from_union([from_none, lambda x: from_str(x)], obj.get("model")) + return SessionCustomAgentsUpdatedDataAgentsItem( + id=id, + name=name, + display_name=display_name, + description=description, + source=source, + tools=tools, + user_invocable=user_invocable, + model=model, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["id"] = from_str(self.id) + result["name"] = from_str(self.name) + result["displayName"] = from_str(self.display_name) + result["description"] = from_str(self.description) + result["source"] = from_str(self.source) + result["tools"] = from_list(lambda x: from_str(x), self.tools) + result["userInvocable"] = from_bool(self.user_invocable) + if self.model is not None: + result["model"] = from_union([from_none, lambda x: from_str(x)], self.model) + return result + + +@dataclass +class SessionCustomAgentsUpdatedData: + agents: list[SessionCustomAgentsUpdatedDataAgentsItem] + warnings: list[str] + errors: list[str] + + @staticmethod + def from_dict(obj: Any) -> "SessionCustomAgentsUpdatedData": + assert isinstance(obj, dict) + agents = from_list(lambda x: SessionCustomAgentsUpdatedDataAgentsItem.from_dict(x), obj.get("agents")) + warnings = from_list(lambda x: from_str(x), obj.get("warnings")) + errors = from_list(lambda x: from_str(x), obj.get("errors")) + return SessionCustomAgentsUpdatedData( + agents=agents, + warnings=warnings, + errors=errors, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["agents"] = from_list(lambda x: to_class(SessionCustomAgentsUpdatedDataAgentsItem, x), self.agents) + result["warnings"] = from_list(lambda x: from_str(x), self.warnings) + result["errors"] = from_list(lambda x: from_str(x), self.errors) + return result + + +@dataclass +class SessionMcpServersLoadedDataServersItem: + name: str + status: SessionMcpServersLoadedDataServersItemStatus + source: str | None = None + error: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "SessionMcpServersLoadedDataServersItem": + assert isinstance(obj, dict) + name = from_str(obj.get("name")) + status = parse_enum(SessionMcpServersLoadedDataServersItemStatus, obj.get("status")) + source = from_union([from_none, lambda x: from_str(x)], obj.get("source")) + error = from_union([from_none, lambda x: from_str(x)], obj.get("error")) + return SessionMcpServersLoadedDataServersItem( + name=name, + status=status, + source=source, + error=error, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["name"] = from_str(self.name) + result["status"] = to_enum(SessionMcpServersLoadedDataServersItemStatus, self.status) + if self.source is not None: + result["source"] = from_union([from_none, lambda x: from_str(x)], self.source) + if self.error is not None: + result["error"] = from_union([from_none, lambda x: from_str(x)], self.error) + return result + + +@dataclass +class SessionMcpServersLoadedData: + servers: list[SessionMcpServersLoadedDataServersItem] + + @staticmethod + def from_dict(obj: Any) -> "SessionMcpServersLoadedData": + assert isinstance(obj, dict) + servers = from_list(lambda x: SessionMcpServersLoadedDataServersItem.from_dict(x), obj.get("servers")) + return SessionMcpServersLoadedData( + servers=servers, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["servers"] = from_list(lambda x: to_class(SessionMcpServersLoadedDataServersItem, x), self.servers) + return result + - @classmethod - def _missing_(cls, value: object) -> "SessionEventType": - """Handle unknown event types gracefully for forward compatibility.""" - return cls.UNKNOWN +@dataclass +class SessionMcpServerStatusChangedData: + server_name: str + status: SessionMcpServersLoadedDataServersItemStatus + + @staticmethod + def from_dict(obj: Any) -> "SessionMcpServerStatusChangedData": + assert isinstance(obj, dict) + server_name = from_str(obj.get("serverName")) + status = parse_enum(SessionMcpServersLoadedDataServersItemStatus, obj.get("status")) + return SessionMcpServerStatusChangedData( + server_name=server_name, + status=status, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["serverName"] = from_str(self.server_name) + result["status"] = to_enum(SessionMcpServersLoadedDataServersItemStatus, self.status) + return result + + +@dataclass +class SessionExtensionsLoadedDataExtensionsItem: + id: str + name: str + source: SessionExtensionsLoadedDataExtensionsItemSource + status: SessionExtensionsLoadedDataExtensionsItemStatus + + @staticmethod + def from_dict(obj: Any) -> "SessionExtensionsLoadedDataExtensionsItem": + assert isinstance(obj, dict) + id = from_str(obj.get("id")) + name = from_str(obj.get("name")) + source = parse_enum(SessionExtensionsLoadedDataExtensionsItemSource, obj.get("source")) + status = parse_enum(SessionExtensionsLoadedDataExtensionsItemStatus, obj.get("status")) + return SessionExtensionsLoadedDataExtensionsItem( + id=id, + name=name, + source=source, + status=status, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["id"] = from_str(self.id) + result["name"] = from_str(self.name) + result["source"] = to_enum(SessionExtensionsLoadedDataExtensionsItemSource, self.source) + result["status"] = to_enum(SessionExtensionsLoadedDataExtensionsItemStatus, self.status) + return result + + +@dataclass +class SessionExtensionsLoadedData: + extensions: list[SessionExtensionsLoadedDataExtensionsItem] + + @staticmethod + def from_dict(obj: Any) -> "SessionExtensionsLoadedData": + assert isinstance(obj, dict) + extensions = from_list(lambda x: SessionExtensionsLoadedDataExtensionsItem.from_dict(x), obj.get("extensions")) + return SessionExtensionsLoadedData( + extensions=extensions, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["extensions"] = from_list(lambda x: to_class(SessionExtensionsLoadedDataExtensionsItem, x), self.extensions) + return result + + +class SessionStartDataContextHostType(Enum): + "Hosting platform type of the repository (github or ado)" + GITHUB = "github" + ADO = "ado" + + +class SessionPlanChangedDataOperation(Enum): + "The type of operation performed on the plan file" + CREATE = "create" + UPDATE = "update" + DELETE = "delete" + + +class SessionWorkspaceFileChangedDataOperation(Enum): + "Whether the file was newly created or updated" + CREATE = "create" + UPDATE = "update" + + +class SessionHandoffDataSourceType(Enum): + "Origin type of the session being handed off" + REMOTE = "remote" + LOCAL = "local" + + +class SessionShutdownDataShutdownType(Enum): + "Whether the session ended normally (\"routine\") or due to a crash/fatal error (\"error\")" + ROUTINE = "routine" + ERROR = "error" + + +class UserMessageDataAttachmentsItemType(Enum): + "A user message attachment — a file, directory, code selection, blob, or GitHub reference discriminator" + FILE = "file" + DIRECTORY = "directory" + SELECTION = "selection" + GITHUB_REFERENCE = "github_reference" + BLOB = "blob" + + +class UserMessageDataAttachmentsItemReferenceType(Enum): + "Type of GitHub reference" + ISSUE = "issue" + PR = "pr" + DISCUSSION = "discussion" + + +class UserMessageDataAgentMode(Enum): + "The agent mode that was active when this message was sent" + INTERACTIVE = "interactive" + PLAN = "plan" + AUTOPILOT = "autopilot" + SHELL = "shell" + + +class AssistantMessageDataToolRequestsItemType(Enum): + "Tool call type: \"function\" for standard tool calls, \"custom\" for grammar-based tool calls. Defaults to \"function\" when absent." + FUNCTION = "function" + CUSTOM = "custom" + + +class ToolExecutionCompleteDataResultContentsItemType(Enum): + "A content block within a tool result, which may be text, terminal output, image, audio, or a resource discriminator" + TEXT = "text" + TERMINAL = "terminal" + IMAGE = "image" + AUDIO = "audio" + RESOURCE_LINK = "resource_link" + RESOURCE = "resource" + + +class ToolExecutionCompleteDataResultContentsItemIconsItemTheme(Enum): + "Theme variant this icon is intended for" + LIGHT = "light" + DARK = "dark" + + +class SystemMessageDataRole(Enum): + "Message role: \"system\" for system prompts, \"developer\" for developer-injected instructions" + SYSTEM = "system" + DEVELOPER = "developer" + + +class SystemNotificationDataKindType(Enum): + "Structured metadata identifying what triggered this notification discriminator" + AGENT_COMPLETED = "agent_completed" + AGENT_IDLE = "agent_idle" + SHELL_COMPLETED = "shell_completed" + SHELL_DETACHED_COMPLETED = "shell_detached_completed" + + +class SystemNotificationDataKindStatus(Enum): + "Whether the agent completed successfully or failed" + COMPLETED = "completed" + FAILED = "failed" + + +class PermissionRequestedDataPermissionRequestKind(Enum): + "Details of the permission being requested discriminator" + SHELL = "shell" + WRITE = "write" + READ = "read" + MCP = "mcp" + URL = "url" + MEMORY = "memory" + CUSTOM_TOOL = "custom-tool" + HOOK = "hook" + + +class PermissionCompletedDataResultKind(Enum): + "The outcome of the permission request" + APPROVED = "approved" + DENIED_BY_RULES = "denied-by-rules" + DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER = "denied-no-approval-rule-and-could-not-request-from-user" + DENIED_INTERACTIVELY_BY_USER = "denied-interactively-by-user" + DENIED_BY_CONTENT_EXCLUSION_POLICY = "denied-by-content-exclusion-policy" + DENIED_BY_PERMISSION_REQUEST_HOOK = "denied-by-permission-request-hook" + + +class ElicitationRequestedDataMode(Enum): + "Elicitation mode; \"form\" for structured input, \"url\" for browser-based. Defaults to \"form\" when absent." + FORM = "form" + URL = "url" +class ElicitationCompletedDataAction(Enum): + "The user action: \"accept\" (submitted form), \"decline\" (explicitly refused), or \"cancel\" (dismissed)" + ACCEPT = "accept" + DECLINE = "decline" + CANCEL = "cancel" + + +class SessionMcpServersLoadedDataServersItemStatus(Enum): + "Connection status: connected, failed, needs-auth, pending, disabled, or not_configured" + CONNECTED = "connected" + FAILED = "failed" + NEEDS_AUTH = "needs-auth" + PENDING = "pending" + DISABLED = "disabled" + NOT_CONFIGURED = "not_configured" + + +class SessionExtensionsLoadedDataExtensionsItemSource(Enum): + "Discovery source" + PROJECT = "project" + USER = "user" + + +class SessionExtensionsLoadedDataExtensionsItemStatus(Enum): + "Current status: running, disabled, failed, or starting" + RUNNING = "running" + DISABLED = "disabled" + FAILED = "failed" + STARTING = "starting" + + +SessionEventData = SessionStartData | SessionResumeData | SessionRemoteSteerableChangedData | SessionErrorData | SessionIdleData | SessionTitleChangedData | SessionInfoData | SessionWarningData | SessionModelChangeData | SessionModeChangedData | SessionPlanChangedData | SessionWorkspaceFileChangedData | SessionHandoffData | SessionTruncationData | SessionSnapshotRewindData | SessionShutdownData | SessionContextChangedData | SessionUsageInfoData | SessionCompactionStartData | SessionCompactionCompleteData | SessionTaskCompleteData | UserMessageData | PendingMessagesModifiedData | AssistantTurnStartData | AssistantIntentData | AssistantReasoningData | AssistantReasoningDeltaData | AssistantStreamingDeltaData | AssistantMessageData | AssistantMessageDeltaData | AssistantTurnEndData | AssistantUsageData | AbortData | ToolUserRequestedData | ToolExecutionStartData | ToolExecutionPartialResultData | ToolExecutionProgressData | ToolExecutionCompleteData | SkillInvokedData | SubagentStartedData | SubagentCompletedData | SubagentFailedData | SubagentSelectedData | SubagentDeselectedData | HookStartData | HookEndData | SystemMessageData | SystemNotificationData | PermissionRequestedData | PermissionCompletedData | UserInputRequestedData | UserInputCompletedData | ElicitationRequestedData | ElicitationCompletedData | SamplingRequestedData | SamplingCompletedData | McpOauthRequiredData | McpOauthCompletedData | ExternalToolRequestedData | ExternalToolCompletedData | CommandQueuedData | CommandExecuteData | CommandCompletedData | CommandsChangedData | CapabilitiesChangedData | ExitPlanModeRequestedData | ExitPlanModeCompletedData | SessionToolsUpdatedData | SessionBackgroundTasksChangedData | SessionSkillsLoadedData | SessionCustomAgentsUpdatedData | SessionMcpServersLoadedData | SessionMcpServerStatusChangedData | SessionExtensionsLoadedData | RawSessionEventData | Data + @dataclass class SessionEvent: - data: Data - """Session initialization metadata including context and configuration - - Session resume metadata including current context and event count - - Notifies Mission Control that the session's remote steering capability has changed - - Error details for timeline display including message and optional diagnostic information - - Payload indicating the session is idle with no background agents in flight - - Session title change payload containing the new display title - - Informational message for timeline display with categorization - - Warning message for timeline display with categorization - - Model change details including previous and new model identifiers - - Agent mode change details including previous and new modes - - Plan file operation details indicating what changed - - Workspace file change details including path and operation type - - Session handoff metadata including source, context, and repository information - - Conversation truncation statistics including token counts and removed content metrics - - Session rewind details including target event and count of removed events - - Session termination metrics including usage statistics, code changes, and shutdown - reason - - Updated working directory and git context after the change - - Current context window usage statistics including token and message counts - - Context window breakdown at the start of LLM-powered conversation compaction - - Conversation compaction results including success status, metrics, and optional error - details - - Task completion notification with summary from the agent - - Empty payload; the event signals that the pending message queue has changed - - Turn initialization metadata including identifier and interaction tracking - - Agent intent description for current activity or plan - - Assistant reasoning content for timeline display with complete thinking text - - Streaming reasoning delta for incremental extended thinking updates - - Streaming response progress with cumulative byte count - - Assistant response containing text content, optional tool requests, and interaction - metadata - - Streaming assistant message delta for incremental response updates - - Turn completion metadata including the turn identifier - - LLM API call usage metrics including tokens, costs, quotas, and billing information - - Turn abort information including the reason for termination - - User-initiated tool invocation request with tool name and arguments - - Tool execution startup details including MCP server information when applicable - - Streaming tool execution output for incremental result display - - Tool execution progress notification with status message - - Tool execution completion results including success status, detailed output, and error - information - - Skill invocation details including content, allowed tools, and plugin metadata - - Sub-agent startup details including parent tool call and agent information - - Sub-agent completion details for successful execution - - Sub-agent failure details including error message and agent information - - Custom agent selection details including name and available tools - - Empty payload; the event signals that the custom agent was deselected, returning to the - default agent - - Hook invocation start details including type and input data - - Hook invocation completion details including output, success status, and error - information - - System or developer message content with role and optional template metadata - - System-generated notification for runtime events like background task completion - - Permission request notification requiring client approval with request details - - Permission request completion notification signaling UI dismissal - - User input request notification with question and optional predefined choices - - User input request completion with the user's response - - Elicitation request; may be form-based (structured input) or URL-based (browser - redirect) - - Elicitation request completion with the user's response - - Sampling request from an MCP server; contains the server name and a requestId for - correlation - - Sampling request completion notification signaling UI dismissal - - OAuth authentication request for an MCP server - - MCP OAuth request completion notification - - External tool invocation request for client-side tool execution - - External tool completion notification signaling UI dismissal - - Queued slash command dispatch request for client execution - - Registered command dispatch request routed to the owning client - - Queued command completion notification signaling UI dismissal - - SDK command registration change notification - - Session capability change notification - - Plan approval request with plan content and available user actions - - Plan mode exit completion with the user's approval decision and optional feedback - """ + data: SessionEventData id: UUID - """Unique event identifier (UUID v4), generated when the event is emitted""" - timestamp: datetime - """ISO 8601 timestamp when the event was created""" - type: SessionEventType ephemeral: bool | None = None - """When true, the event is transient and not persisted to the session event log on disk""" - parent_id: UUID | None = None - """ID of the chronologically preceding event in the session, forming a linked chain. Null - for the first event. - """ + raw_type: str | None = None @staticmethod - def from_dict(obj: Any) -> 'SessionEvent': + def from_dict(obj: Any) -> "SessionEvent": assert isinstance(obj, dict) - data = Data.from_dict(obj.get("data")) - id = UUID(obj.get("id")) + raw_type = from_str(obj.get("type")) + event_type = SessionEventType(raw_type) + event_id = from_uuid(obj.get("id")) timestamp = from_datetime(obj.get("timestamp")) - type = SessionEventType(obj.get("type")) ephemeral = from_union([from_bool, from_none], obj.get("ephemeral")) - parent_id = from_union([from_none, lambda x: UUID(x)], obj.get("parentId")) - return SessionEvent(data, id, timestamp, type, ephemeral, parent_id) + parent_id = from_union([from_none, from_uuid], obj.get("parentId")) + data_obj = obj.get("data") + match event_type: + case SessionEventType.SESSION_START: data = SessionStartData.from_dict(data_obj) + case SessionEventType.SESSION_RESUME: data = SessionResumeData.from_dict(data_obj) + case SessionEventType.SESSION_REMOTE_STEERABLE_CHANGED: data = SessionRemoteSteerableChangedData.from_dict(data_obj) + case SessionEventType.SESSION_ERROR: data = SessionErrorData.from_dict(data_obj) + case SessionEventType.SESSION_IDLE: data = SessionIdleData.from_dict(data_obj) + case SessionEventType.SESSION_TITLE_CHANGED: data = SessionTitleChangedData.from_dict(data_obj) + case SessionEventType.SESSION_INFO: data = SessionInfoData.from_dict(data_obj) + case SessionEventType.SESSION_WARNING: data = SessionWarningData.from_dict(data_obj) + case SessionEventType.SESSION_MODEL_CHANGE: data = SessionModelChangeData.from_dict(data_obj) + case SessionEventType.SESSION_MODE_CHANGED: data = SessionModeChangedData.from_dict(data_obj) + case SessionEventType.SESSION_PLAN_CHANGED: data = SessionPlanChangedData.from_dict(data_obj) + case SessionEventType.SESSION_WORKSPACE_FILE_CHANGED: data = SessionWorkspaceFileChangedData.from_dict(data_obj) + case SessionEventType.SESSION_HANDOFF: data = SessionHandoffData.from_dict(data_obj) + case SessionEventType.SESSION_TRUNCATION: data = SessionTruncationData.from_dict(data_obj) + case SessionEventType.SESSION_SNAPSHOT_REWIND: data = SessionSnapshotRewindData.from_dict(data_obj) + case SessionEventType.SESSION_SHUTDOWN: data = SessionShutdownData.from_dict(data_obj) + case SessionEventType.SESSION_CONTEXT_CHANGED: data = SessionContextChangedData.from_dict(data_obj) + case SessionEventType.SESSION_USAGE_INFO: data = SessionUsageInfoData.from_dict(data_obj) + case SessionEventType.SESSION_COMPACTION_START: data = SessionCompactionStartData.from_dict(data_obj) + case SessionEventType.SESSION_COMPACTION_COMPLETE: data = SessionCompactionCompleteData.from_dict(data_obj) + case SessionEventType.SESSION_TASK_COMPLETE: data = SessionTaskCompleteData.from_dict(data_obj) + case SessionEventType.USER_MESSAGE: data = UserMessageData.from_dict(data_obj) + case SessionEventType.PENDING_MESSAGES_MODIFIED: data = PendingMessagesModifiedData.from_dict(data_obj) + case SessionEventType.ASSISTANT_TURN_START: data = AssistantTurnStartData.from_dict(data_obj) + case SessionEventType.ASSISTANT_INTENT: data = AssistantIntentData.from_dict(data_obj) + case SessionEventType.ASSISTANT_REASONING: data = AssistantReasoningData.from_dict(data_obj) + case SessionEventType.ASSISTANT_REASONING_DELTA: data = AssistantReasoningDeltaData.from_dict(data_obj) + case SessionEventType.ASSISTANT_STREAMING_DELTA: data = AssistantStreamingDeltaData.from_dict(data_obj) + case SessionEventType.ASSISTANT_MESSAGE: data = AssistantMessageData.from_dict(data_obj) + case SessionEventType.ASSISTANT_MESSAGE_DELTA: data = AssistantMessageDeltaData.from_dict(data_obj) + case SessionEventType.ASSISTANT_TURN_END: data = AssistantTurnEndData.from_dict(data_obj) + case SessionEventType.ASSISTANT_USAGE: data = AssistantUsageData.from_dict(data_obj) + case SessionEventType.ABORT: data = AbortData.from_dict(data_obj) + case SessionEventType.TOOL_USER_REQUESTED: data = ToolUserRequestedData.from_dict(data_obj) + case SessionEventType.TOOL_EXECUTION_START: data = ToolExecutionStartData.from_dict(data_obj) + case SessionEventType.TOOL_EXECUTION_PARTIAL_RESULT: data = ToolExecutionPartialResultData.from_dict(data_obj) + case SessionEventType.TOOL_EXECUTION_PROGRESS: data = ToolExecutionProgressData.from_dict(data_obj) + case SessionEventType.TOOL_EXECUTION_COMPLETE: data = ToolExecutionCompleteData.from_dict(data_obj) + case SessionEventType.SKILL_INVOKED: data = SkillInvokedData.from_dict(data_obj) + case SessionEventType.SUBAGENT_STARTED: data = SubagentStartedData.from_dict(data_obj) + case SessionEventType.SUBAGENT_COMPLETED: data = SubagentCompletedData.from_dict(data_obj) + case SessionEventType.SUBAGENT_FAILED: data = SubagentFailedData.from_dict(data_obj) + case SessionEventType.SUBAGENT_SELECTED: data = SubagentSelectedData.from_dict(data_obj) + case SessionEventType.SUBAGENT_DESELECTED: data = SubagentDeselectedData.from_dict(data_obj) + case SessionEventType.HOOK_START: data = HookStartData.from_dict(data_obj) + case SessionEventType.HOOK_END: data = HookEndData.from_dict(data_obj) + case SessionEventType.SYSTEM_MESSAGE: data = SystemMessageData.from_dict(data_obj) + case SessionEventType.SYSTEM_NOTIFICATION: data = SystemNotificationData.from_dict(data_obj) + case SessionEventType.PERMISSION_REQUESTED: data = PermissionRequestedData.from_dict(data_obj) + case SessionEventType.PERMISSION_COMPLETED: data = PermissionCompletedData.from_dict(data_obj) + case SessionEventType.USER_INPUT_REQUESTED: data = UserInputRequestedData.from_dict(data_obj) + case SessionEventType.USER_INPUT_COMPLETED: data = UserInputCompletedData.from_dict(data_obj) + case SessionEventType.ELICITATION_REQUESTED: data = ElicitationRequestedData.from_dict(data_obj) + case SessionEventType.ELICITATION_COMPLETED: data = ElicitationCompletedData.from_dict(data_obj) + case SessionEventType.SAMPLING_REQUESTED: data = SamplingRequestedData.from_dict(data_obj) + case SessionEventType.SAMPLING_COMPLETED: data = SamplingCompletedData.from_dict(data_obj) + case SessionEventType.MCP_OAUTH_REQUIRED: data = McpOauthRequiredData.from_dict(data_obj) + case SessionEventType.MCP_OAUTH_COMPLETED: data = McpOauthCompletedData.from_dict(data_obj) + case SessionEventType.EXTERNAL_TOOL_REQUESTED: data = ExternalToolRequestedData.from_dict(data_obj) + case SessionEventType.EXTERNAL_TOOL_COMPLETED: data = ExternalToolCompletedData.from_dict(data_obj) + case SessionEventType.COMMAND_QUEUED: data = CommandQueuedData.from_dict(data_obj) + case SessionEventType.COMMAND_EXECUTE: data = CommandExecuteData.from_dict(data_obj) + case SessionEventType.COMMAND_COMPLETED: data = CommandCompletedData.from_dict(data_obj) + case SessionEventType.COMMANDS_CHANGED: data = CommandsChangedData.from_dict(data_obj) + case SessionEventType.CAPABILITIES_CHANGED: data = CapabilitiesChangedData.from_dict(data_obj) + case SessionEventType.EXIT_PLAN_MODE_REQUESTED: data = ExitPlanModeRequestedData.from_dict(data_obj) + case SessionEventType.EXIT_PLAN_MODE_COMPLETED: data = ExitPlanModeCompletedData.from_dict(data_obj) + case SessionEventType.SESSION_TOOLS_UPDATED: data = SessionToolsUpdatedData.from_dict(data_obj) + case SessionEventType.SESSION_BACKGROUND_TASKS_CHANGED: data = SessionBackgroundTasksChangedData.from_dict(data_obj) + case SessionEventType.SESSION_SKILLS_LOADED: data = SessionSkillsLoadedData.from_dict(data_obj) + case SessionEventType.SESSION_CUSTOM_AGENTS_UPDATED: data = SessionCustomAgentsUpdatedData.from_dict(data_obj) + case SessionEventType.SESSION_MCP_SERVERS_LOADED: data = SessionMcpServersLoadedData.from_dict(data_obj) + case SessionEventType.SESSION_MCP_SERVER_STATUS_CHANGED: data = SessionMcpServerStatusChangedData.from_dict(data_obj) + case SessionEventType.SESSION_EXTENSIONS_LOADED: data = SessionExtensionsLoadedData.from_dict(data_obj) + case _: data = RawSessionEventData.from_dict(data_obj) + return SessionEvent( + data=data, + id=event_id, + timestamp=timestamp, + type=event_type, + ephemeral=ephemeral, + parent_id=parent_id, + raw_type=raw_type if event_type == SessionEventType.UNKNOWN else None, + ) def to_dict(self) -> dict: result: dict = {} - result["data"] = to_class(Data, self.data) - result["id"] = str(self.id) - result["timestamp"] = self.timestamp.isoformat() - result["type"] = to_enum(SessionEventType, self.type) + result["data"] = self.data.to_dict() + result["id"] = to_uuid(self.id) + result["timestamp"] = to_datetime(self.timestamp) + result["type"] = self.raw_type if self.type == SessionEventType.UNKNOWN and self.raw_type is not None else to_enum(SessionEventType, self.type) if self.ephemeral is not None: - result["ephemeral"] = from_union([from_bool, from_none], self.ephemeral) - result["parentId"] = from_union([from_none, lambda x: str(x)], self.parent_id) + result["ephemeral"] = from_bool(self.ephemeral) + result["parentId"] = from_union([from_none, to_uuid], self.parent_id) return result @@ -3383,4 +4196,241 @@ def session_event_from_dict(s: Any) -> SessionEvent: def session_event_to_dict(x: SessionEvent) -> Any: - return to_class(SessionEvent, x) + return x.to_dict() + + +# Compatibility shims for pre-refactor top-level generated types. +class RequestedSchemaType(str, Enum): + OBJECT = "object" + + +@dataclass +class ErrorClass: + """Backward-compatible shim for generic error payloads.""" + message: str + code: str | None = None + stack: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "ErrorClass": + assert isinstance(obj, dict) + message = from_str(obj.get("message")) + code = from_union([from_none, lambda x: from_str(x)], obj.get("code")) + stack = from_union([from_none, lambda x: from_str(x)], obj.get("stack")) + return ErrorClass( + message=message, + code=code, + stack=stack, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["message"] = from_str(self.message) + if self.code is not None: + result["code"] = from_union([from_none, lambda x: from_str(x)], self.code) + if self.stack is not None: + result["stack"] = from_union([from_none, lambda x: from_str(x)], self.stack) + return result + + +@dataclass +class Resource: + """Backward-compatible shim for embedded tool result resources.""" + uri: str + mime_type: str | None = None + text: str | None = None + blob: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "Resource": + assert isinstance(obj, dict) + uri = from_str(obj.get("uri")) + mime_type = from_union([from_none, lambda x: from_str(x)], obj.get("mimeType")) + text = from_union([from_none, lambda x: from_str(x)], obj.get("text")) + blob = from_union([from_none, lambda x: from_str(x)], obj.get("blob")) + return Resource( + uri=uri, + mime_type=mime_type, + text=text, + blob=blob, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["uri"] = from_str(self.uri) + if self.mime_type is not None: + result["mimeType"] = from_union([from_none, lambda x: from_str(x)], self.mime_type) + if self.text is not None: + result["text"] = from_union([from_none, lambda x: from_str(x)], self.text) + if self.blob is not None: + result["blob"] = from_union([from_none, lambda x: from_str(x)], self.blob) + return result + + +ContentType = ToolExecutionCompleteDataResultContentsItemType +Theme = ToolExecutionCompleteDataResultContentsItemIconsItemTheme +Icon = ToolExecutionCompleteDataResultContentsItemIconsItem +ResultKind = PermissionCompletedDataResultKind + + +@dataclass +class ContentElement: + """Backward-compatible shim for tool result content blocks.""" + type: ContentType + text: str | None = None + cwd: str | None = None + exit_code: float | None = None + data: str | None = None + mime_type: str | None = None + description: str | None = None + icons: list[Icon] | None = None + name: str | None = None + size: float | None = None + title: str | None = None + uri: str | None = None + resource: Resource | None = None + + @staticmethod + def from_dict(obj: Any) -> "ContentElement": + assert isinstance(obj, dict) + type = parse_enum(ContentType, obj.get("type")) + text = from_union([from_none, lambda x: from_str(x)], obj.get("text")) + cwd = from_union([from_none, lambda x: from_str(x)], obj.get("cwd")) + exit_code = from_union([from_none, lambda x: from_float(x)], obj.get("exitCode")) + data = from_union([from_none, lambda x: from_str(x)], obj.get("data")) + mime_type = from_union([from_none, lambda x: from_str(x)], obj.get("mimeType")) + description = from_union([from_none, lambda x: from_str(x)], obj.get("description")) + icons = from_union([from_none, lambda x: from_list(Icon.from_dict, x)], obj.get("icons")) + name = from_union([from_none, lambda x: from_str(x)], obj.get("name")) + size = from_union([from_none, lambda x: from_float(x)], obj.get("size")) + title = from_union([from_none, lambda x: from_str(x)], obj.get("title")) + uri = from_union([from_none, lambda x: from_str(x)], obj.get("uri")) + resource = from_union([from_none, lambda x: Resource.from_dict(x)], obj.get("resource")) + return ContentElement( + type=type, + text=text, + cwd=cwd, + exit_code=exit_code, + data=data, + mime_type=mime_type, + description=description, + icons=icons, + name=name, + size=size, + title=title, + uri=uri, + resource=resource, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["type"] = to_enum(ContentType, self.type) + if self.text is not None: + result["text"] = from_union([from_none, lambda x: from_str(x)], self.text) + if self.cwd is not None: + result["cwd"] = from_union([from_none, lambda x: from_str(x)], self.cwd) + if self.exit_code is not None: + result["exitCode"] = from_union([from_none, lambda x: to_float(x)], self.exit_code) + if self.data is not None: + result["data"] = from_union([from_none, lambda x: from_str(x)], self.data) + if self.mime_type is not None: + result["mimeType"] = from_union([from_none, lambda x: from_str(x)], self.mime_type) + if self.description is not None: + result["description"] = from_union([from_none, lambda x: from_str(x)], self.description) + if self.icons is not None: + result["icons"] = from_union([from_none, lambda x: from_list(lambda x: to_class(Icon, x), x)], self.icons) + if self.name is not None: + result["name"] = from_union([from_none, lambda x: from_str(x)], self.name) + if self.size is not None: + result["size"] = from_union([from_none, lambda x: to_float(x)], self.size) + if self.title is not None: + result["title"] = from_union([from_none, lambda x: from_str(x)], self.title) + if self.uri is not None: + result["uri"] = from_union([from_none, lambda x: from_str(x)], self.uri) + if self.resource is not None: + result["resource"] = from_union([from_none, lambda x: to_class(Resource, x)], self.resource) + return result + + +@dataclass +class Result: + """Backward-compatible shim for generic result payloads.""" + content: str | None = None + contents: list[ContentElement] | None = None + detailed_content: str | None = None + kind: ResultKind | None = None + + @staticmethod + def from_dict(obj: Any) -> "Result": + assert isinstance(obj, dict) + content = from_union([from_none, lambda x: from_str(x)], obj.get("content")) + contents = from_union([from_none, lambda x: from_list(ContentElement.from_dict, x)], obj.get("contents")) + detailed_content = from_union([from_none, lambda x: from_str(x)], obj.get("detailedContent")) + kind = from_union([from_none, lambda x: parse_enum(ResultKind, x)], obj.get("kind")) + return Result( + content=content, + contents=contents, + detailed_content=detailed_content, + kind=kind, + ) + + def to_dict(self) -> dict: + result: dict = {} + if self.content is not None: + result["content"] = from_union([from_none, lambda x: from_str(x)], self.content) + if self.contents is not None: + result["contents"] = from_union([from_none, lambda x: from_list(lambda x: to_class(ContentElement, x), x)], self.contents) + if self.detailed_content is not None: + result["detailedContent"] = from_union([from_none, lambda x: from_str(x)], self.detailed_content) + if self.kind is not None: + result["kind"] = from_union([from_none, lambda x: to_enum(ResultKind, x)], self.kind) + return result + + +# Convenience aliases for commonly used nested event types. +Action = ElicitationCompletedDataAction +Agent = SessionCustomAgentsUpdatedDataAgentsItem +AgentMode = UserMessageDataAgentMode +Attachment = UserMessageDataAttachmentsItem +AttachmentType = UserMessageDataAttachmentsItemType +CodeChanges = SessionShutdownDataCodeChanges +CompactionTokensUsed = SessionCompactionCompleteDataCompactionTokensUsed +ContextClass = SessionStartDataContext +CopilotUsage = AssistantUsageDataCopilotUsage +DataCommand = CommandsChangedDataCommandsItem +End = UserMessageDataAttachmentsItemSelectionEnd +Extension = SessionExtensionsLoadedDataExtensionsItem +ExtensionStatus = SessionExtensionsLoadedDataExtensionsItemStatus +HostType = SessionStartDataContextHostType +KindClass = SystemNotificationDataKind +KindStatus = SystemNotificationDataKindStatus +KindType = SystemNotificationDataKindType +LineRange = UserMessageDataAttachmentsItemLineRange +Metadata = SystemMessageDataMetadata +Mode = ElicitationRequestedDataMode +ModelMetric = SessionShutdownDataModelMetricsValue +Operation = SessionPlanChangedDataOperation +PermissionRequest = PermissionRequestedDataPermissionRequest +PermissionRequestKind = PermissionRequestedDataPermissionRequestKind +PermissionRequestCommand = PermissionRequestedDataPermissionRequestCommandsItem +PossibleURL = PermissionRequestedDataPermissionRequestPossibleUrlsItem +QuotaSnapshot = AssistantUsageDataQuotaSnapshotsValue +ReferenceType = UserMessageDataAttachmentsItemReferenceType +RepositoryClass = SessionHandoffDataRepository +RequestedSchema = ElicitationRequestedDataRequestedSchema +Requests = SessionShutdownDataModelMetricsValueRequests +Role = SystemMessageDataRole +Selection = UserMessageDataAttachmentsItemSelection +Server = SessionMcpServersLoadedDataServersItem +ServerStatus = SessionMcpServersLoadedDataServersItemStatus +ShutdownType = SessionShutdownDataShutdownType +Skill = SessionSkillsLoadedDataSkillsItem +Source = SessionExtensionsLoadedDataExtensionsItemSource +SourceType = SessionHandoffDataSourceType +Start = UserMessageDataAttachmentsItemSelectionStart +StaticClientConfig = McpOauthRequiredDataStaticClientConfig +TokenDetail = AssistantUsageDataCopilotUsageTokenDetailsItem +ToolRequest = AssistantMessageDataToolRequestsItem +ToolRequestType = AssistantMessageDataToolRequestsItemType +UI = CapabilitiesChangedDataUi +Usage = SessionShutdownDataModelMetricsValueUsage \ No newline at end of file diff --git a/python/copilot/session.py b/python/copilot/session.py index 45e8826b7..3d1511457 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -47,7 +47,12 @@ ModelCapabilitiesOverride as _RpcModelCapabilitiesOverride, ) from .generated.session_events import ( + CapabilitiesChangedData, + CommandExecuteData, + ElicitationRequestedData, + ExternalToolRequestedData, PermissionRequest, + PermissionRequestedData, SessionEvent, SessionEventType, session_event_from_dict, @@ -1123,9 +1128,11 @@ async def send_and_wait( Exception: If the session has been disconnected or the connection fails. Example: + >>> from typing import cast + >>> from copilot.generated.session_events import AssistantMessageData >>> response = await session.send_and_wait("What is 2+2?") >>> if response: - ... print(response.data.content) + ... print(cast(AssistantMessageData, response.data).content) """ idle_event = asyncio.Event() error_event: Exception | None = None @@ -1171,11 +1178,13 @@ def on(self, handler: Callable[[SessionEvent], None]) -> Callable[[], None]: A function that, when called, unsubscribes the handler. Example: + >>> from typing import cast + >>> from copilot.generated.session_events import AssistantMessageData, SessionErrorData >>> def handle_event(event): - ... if event.type == "assistant.message": - ... print(f"Assistant: {event.data.content}") - ... elif event.type == "session.error": - ... print(f"Error: {event.data.message}") + ... if event.type.value == "assistant.message": + ... print(f"Assistant: {cast(AssistantMessageData, event.data).content}") + ... elif event.type.value == "session.error": + ... print(f"Error: {cast(SessionErrorData, event.data).message}") >>> unsubscribe = session.on(handle_event) >>> # Later, to stop receiving events: >>> unsubscribe() @@ -1222,8 +1231,9 @@ def _handle_broadcast_event(self, event: SessionEvent) -> None: are broadcast as session events to all clients. """ if event.type == SessionEventType.EXTERNAL_TOOL_REQUESTED: - request_id = event.data.request_id - tool_name = event.data.tool_name + data = cast(ExternalToolRequestedData, event.data) + request_id = data.request_id + tool_name = data.tool_name if not request_id or not tool_name: return @@ -1231,10 +1241,10 @@ def _handle_broadcast_event(self, event: SessionEvent) -> None: if not handler: return # This client doesn't handle this tool; another client will. - tool_call_id = event.data.tool_call_id or "" - arguments = event.data.arguments - tp = getattr(event.data, "traceparent", None) - ts = getattr(event.data, "tracestate", None) + tool_call_id = data.tool_call_id or "" + arguments = data.arguments + tp = getattr(data, "traceparent", None) + ts = getattr(data, "tracestate", None) asyncio.ensure_future( self._execute_tool_and_respond( request_id, tool_name, tool_call_id, arguments, handler, tp, ts @@ -1242,12 +1252,13 @@ def _handle_broadcast_event(self, event: SessionEvent) -> None: ) elif event.type == SessionEventType.PERMISSION_REQUESTED: - request_id = event.data.request_id - permission_request = event.data.permission_request + data = cast(PermissionRequestedData, event.data) + request_id = data.request_id + permission_request = data.permission_request if not request_id or not permission_request: return - resolved_by_hook = getattr(event.data, "resolved_by_hook", None) + resolved_by_hook = getattr(data, "resolved_by_hook", None) if resolved_by_hook: return # Already resolved by a permissionRequest hook; no client action needed. @@ -1261,10 +1272,11 @@ def _handle_broadcast_event(self, event: SessionEvent) -> None: ) elif event.type == SessionEventType.COMMAND_EXECUTE: - request_id = event.data.request_id - command_name = event.data.command_name - command = event.data.command - args = event.data.args + data = cast(CommandExecuteData, event.data) + request_id = data.request_id + command_name = data.command_name + command = data.command + args = data.args if not request_id or not command_name: return asyncio.ensure_future( @@ -1274,33 +1286,35 @@ def _handle_broadcast_event(self, event: SessionEvent) -> None: ) elif event.type == SessionEventType.ELICITATION_REQUESTED: + data = cast(ElicitationRequestedData, event.data) with self._elicitation_handler_lock: handler = self._elicitation_handler if not handler: return - request_id = event.data.request_id + request_id = data.request_id if not request_id: return context: ElicitationContext = { "session_id": self.session_id, - "message": event.data.message or "", + "message": data.message or "", } - if event.data.requested_schema is not None: - context["requestedSchema"] = event.data.requested_schema.to_dict() - if event.data.mode is not None: - context["mode"] = event.data.mode.value - if event.data.elicitation_source is not None: - context["elicitationSource"] = event.data.elicitation_source - if event.data.url is not None: - context["url"] = event.data.url + if data.requested_schema is not None: + context["requestedSchema"] = data.requested_schema.to_dict() + if data.mode is not None: + context["mode"] = data.mode.value + if data.elicitation_source is not None: + context["elicitationSource"] = data.elicitation_source + if data.url is not None: + context["url"] = data.url asyncio.ensure_future(self._handle_elicitation_request(context, request_id)) elif event.type == SessionEventType.CAPABILITIES_CHANGED: + data = cast(CapabilitiesChangedData, event.data) cap: SessionCapabilities = {} - if event.data.ui is not None: + if data.ui is not None: ui_cap: SessionUiCapabilities = {} - if event.data.ui.elicitation is not None: - ui_cap["elicitation"] = event.data.ui.elicitation + if data.ui.elicitation is not None: + ui_cap["elicitation"] = data.ui.elicitation cap["ui"] = ui_cap self._capabilities = {**self._capabilities, **cap} @@ -1791,10 +1805,12 @@ async def get_messages(self) -> list[SessionEvent]: Exception: If the session has been disconnected or the connection fails. Example: + >>> from typing import cast + >>> from copilot.generated.session_events import AssistantMessageData >>> events = await session.get_messages() >>> for event in events: - ... if event.type == "assistant.message": - ... print(f"Assistant: {event.data.content}") + ... if event.type.value == "assistant.message": + ... print(f"Assistant: {cast(AssistantMessageData, event.data).content}") """ response = await self._client.request("session.getMessages", {"sessionId": self.session_id}) # Convert dict events to SessionEvent objects diff --git a/python/test_commands_and_elicitation.py b/python/test_commands_and_elicitation.py index 9ee710fe0..bab9d0b98 100644 --- a/python/test_commands_and_elicitation.py +++ b/python/test_commands_and_elicitation.py @@ -136,13 +136,13 @@ async def mock_request(method, params): # Simulate a command.execute broadcast event from copilot.generated.session_events import ( - Data, + CommandExecuteData, SessionEvent, SessionEventType, ) event = SessionEvent( - data=Data( + data=CommandExecuteData( request_id="req-1", command="/deploy production", command_name="deploy", @@ -203,13 +203,13 @@ async def mock_request(method, params): client._client.request = mock_request from copilot.generated.session_events import ( - Data, + CommandExecuteData, SessionEvent, SessionEventType, ) event = SessionEvent( - data=Data( + data=CommandExecuteData( request_id="req-2", command="/fail", command_name="fail", @@ -257,13 +257,13 @@ async def mock_request(method, params): client._client.request = mock_request from copilot.generated.session_events import ( - Data, + CommandExecuteData, SessionEvent, SessionEventType, ) event = SessionEvent( - data=Data( + data=CommandExecuteData( request_id="req-3", command="/unknown", command_name="unknown", @@ -519,13 +519,13 @@ async def mock_request(method, params): client._client.request = mock_request from copilot.generated.session_events import ( - Data, + ElicitationRequestedData, SessionEvent, SessionEventType, ) event = SessionEvent( - data=Data( + data=ElicitationRequestedData( request_id="req-elicit-1", message="Pick a color", ), @@ -578,19 +578,18 @@ async def mock_request(method, params): client._client.request = mock_request from copilot.generated.session_events import ( - Data, + ElicitationRequestedData, RequestedSchema, - RequestedSchemaType, SessionEvent, SessionEventType, ) event = SessionEvent( - data=Data( + data=ElicitationRequestedData( request_id="req-schema-1", message="Fill in your details", requested_schema=RequestedSchema( - type=RequestedSchemaType.OBJECT, + type="object", properties={ "name": {"type": "string"}, "age": {"type": "number"}, @@ -639,13 +638,13 @@ async def test_capabilities_changed_event_updates_session(self): from copilot.generated.session_events import ( UI, - Data, + CapabilitiesChangedData, SessionEvent, SessionEventType, ) event = SessionEvent( - data=Data(ui=UI(elicitation=True)), + data=CapabilitiesChangedData(ui=UI(elicitation=True)), id="evt-cap-1", timestamp="2025-01-01T00:00:00Z", type=SessionEventType.CAPABILITIES_CHANGED, diff --git a/python/test_event_forward_compatibility.py b/python/test_event_forward_compatibility.py index 017cff2e8..f0a4bedef 100644 --- a/python/test_event_forward_compatibility.py +++ b/python/test_event_forward_compatibility.py @@ -12,7 +12,21 @@ import pytest -from copilot.generated.session_events import SessionEventType, session_event_from_dict +from copilot.generated.session_events import ( + Action, + AgentMode, + ContentElement, + Data, + Mode, + ReferenceType, + RequestedSchema, + RequestedSchemaType, + Resource, + Result, + ResultKind, + SessionEventType, + session_event_from_dict, +) class TestEventForwardCompatibility: @@ -62,3 +76,57 @@ def test_malformed_timestamp_raises_error(self): # This should raise an error and NOT be silently suppressed with pytest.raises((ValueError, TypeError)): session_event_from_dict(malformed_event) + + def test_legacy_top_level_generated_symbols_remain_available(self): + """Previously top-level generated helper symbols should remain importable.""" + assert Action.ACCEPT.value == "accept" + assert AgentMode.INTERACTIVE.value == "interactive" + assert Mode.FORM.value == "form" + assert ReferenceType.PR.value == "pr" + + schema = RequestedSchema( + properties={"answer": {"type": "string"}}, type=RequestedSchemaType.OBJECT + ) + assert schema.to_dict()["type"] == "object" + + result = Result( + content="Approved", + kind=ResultKind.APPROVED, + contents=[ + ContentElement( + type=ContentElement.from_dict({"type": "text", "text": "hello"}).type, + text="hello", + resource=Resource(uri="file://artifact.txt", text="artifact"), + ) + ], + ) + assert result.to_dict() == { + "content": "Approved", + "kind": "approved", + "contents": [ + { + "type": "text", + "text": "hello", + "resource": { + "uri": "file://artifact.txt", + "text": "artifact", + }, + } + ], + } + + def test_data_shim_preserves_raw_mapping_values(self): + """Compatibility Data should keep arbitrary nested mappings as plain dicts.""" + parsed = Data.from_dict( + { + "arguments": {"toolCallId": "call-1"}, + "input": {"step_name": "build"}, + } + ) + assert parsed.arguments == {"toolCallId": "call-1"} + assert isinstance(parsed.arguments, dict) + assert parsed.input == {"step_name": "build"} + assert isinstance(parsed.input, dict) + + constructed = Data(arguments={"tool_call_id": "call-1"}) + assert constructed.to_dict() == {"arguments": {"tool_call_id": "call-1"}} diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts index 2aa593c5d..9fbaf0bac 100644 --- a/scripts/codegen/python.ts +++ b/scripts/codegen/python.ts @@ -10,13 +10,13 @@ import fs from "fs/promises"; import type { JSONSchema7 } from "json-schema"; import { FetchingJSONSchemaStore, InputData, JSONSchemaInput, quicktype } from "quicktype-core"; import { + EXCLUDED_EVENT_TYPES, getApiSchemaPath, getSessionEventsSchemaPath, isRpcMethod, + isNodeFullyExperimental, postProcessSchema, writeGeneratedFile, - isRpcMethod, - isNodeFullyExperimental, type ApiSchema, type RpcMethod, } from "./utils.js"; @@ -74,6 +74,10 @@ function splitTopLevelCommas(s: string): string[] { return parts; } +function pyDocstringLiteral(text: string): string { + return JSON.stringify(text); +} + function modernizePython(code: string): string { // Replace Optional[X] with X | None (handles arbitrarily nested brackets) code = replaceBalancedBrackets(code, "Optional", (inner) => `${inner} | None`); @@ -144,59 +148,1146 @@ function collectRpcMethods(node: Record): RpcMethod[] { return results; } -// ── Session Events ────────────────────────────────────────────────────────── +// ── Session Events (custom codegen — dedicated per-event payload types) ───── -async function generateSessionEvents(schemaPath?: string): Promise { - console.log("Python: generating session-events..."); +interface PyEventVariant { + typeName: string; + dataClassName: string; + dataSchema: JSONSchema7; + dataDescription?: string; +} - const resolvedPath = schemaPath ?? (await getSessionEventsSchemaPath()); - const schema = JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as JSONSchema7; - const resolvedSchema = (schema.definitions?.SessionEvent as JSONSchema7) || schema; - const processed = postProcessSchema(resolvedSchema); +interface PyResolvedType { + annotation: string; + fromExpr: (expr: string) => string; + toExpr: (expr: string) => string; +} - const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore()); - await schemaInput.addSource({ name: "SessionEvent", schema: JSON.stringify(processed) }); +interface PyCodegenCtx { + classes: string[]; + enums: string[]; + enumsByValues: Map; + generatedNames: Set; +} - const inputData = new InputData(); - inputData.addInput(schemaInput); +function toEnumMemberName(value: string): string { + const cleaned = value + .replace(/([a-z])([A-Z])/g, "$1_$2") + .replace(/[^A-Za-z0-9]+/g, "_") + .replace(/^_+|_+$/g, "") + .toUpperCase(); + if (!cleaned) { + return "VALUE"; + } + return /^[0-9]/.test(cleaned) ? `VALUE_${cleaned}` : cleaned; +} - const result = await quicktype({ - inputData, - lang: "python", - rendererOptions: { "python-version": "3.7" }, +function wrapParser(resolved: PyResolvedType, arg = "x"): string { + return `lambda ${arg}: ${resolved.fromExpr(arg)}`; +} + +function wrapSerializer(resolved: PyResolvedType, arg = "x"): string { + return `lambda ${arg}: ${resolved.toExpr(arg)}`; +} + +function pyPrimitiveResolvedType(annotation: string, fromFn: string, toFn = fromFn): PyResolvedType { + return { + annotation, + fromExpr: (expr) => `${fromFn}(${expr})`, + toExpr: (expr) => `${toFn}(${expr})`, + }; +} + +function pyOptionalResolvedType(inner: PyResolvedType): PyResolvedType { + return { + annotation: `${inner.annotation} | None`, + fromExpr: (expr) => `from_union([from_none, ${wrapParser(inner)}], ${expr})`, + toExpr: (expr) => `from_union([from_none, ${wrapSerializer(inner)}], ${expr})`, + }; +} + +function pyAnyResolvedType(): PyResolvedType { + return { + annotation: "Any", + fromExpr: (expr) => expr, + toExpr: (expr) => expr, + }; +} + +function extractPyEventVariants(schema: JSONSchema7): PyEventVariant[] { + const sessionEvent = schema.definitions?.SessionEvent as JSONSchema7; + if (!sessionEvent?.anyOf) { + throw new Error("Schema must have SessionEvent definition with anyOf"); + } + + return (sessionEvent.anyOf as JSONSchema7[]) + .map((variant) => { + if (typeof variant !== "object" || !variant.properties) { + throw new Error("Invalid event variant"); + } + + const typeSchema = variant.properties.type as JSONSchema7; + const typeName = typeSchema?.const as string; + if (!typeName) { + throw new Error("Event variant must define type.const"); + } + + const dataSchema = (variant.properties.data as JSONSchema7) || {}; + return { + typeName, + dataClassName: `${toPascalCase(typeName)}Data`, + dataSchema, + dataDescription: dataSchema.description, + }; + }) + .filter((variant) => !EXCLUDED_EVENT_TYPES.has(variant.typeName)); +} + +function findPyDiscriminator( + variants: JSONSchema7[] +): { property: string; mapping: Map } | null { + if (variants.length === 0) { + return null; + } + + const firstVariant = variants[0]; + if (!firstVariant.properties) { + return null; + } + + for (const [propName, propSchema] of Object.entries(firstVariant.properties)) { + if (typeof propSchema !== "object") { + continue; + } + if ((propSchema as JSONSchema7).const === undefined) { + continue; + } + + const mapping = new Map(); + let valid = true; + for (const variant of variants) { + if (!variant.properties) { + valid = false; + break; + } + + const variantProp = variant.properties[propName]; + if (typeof variantProp !== "object" || (variantProp as JSONSchema7).const === undefined) { + valid = false; + break; + } + + mapping.set(String((variantProp as JSONSchema7).const), variant); + } + + if (valid && mapping.size === variants.length) { + return { property: propName, mapping }; + } + } + + return null; +} + +function getOrCreatePyEnum( + enumName: string, + values: string[], + ctx: PyCodegenCtx, + description?: string +): string { + const valuesKey = [...values].sort().join("|"); + const existing = ctx.enumsByValues.get(valuesKey); + if (existing) { + return existing; + } + + const lines: string[] = []; + if (description) { + lines.push(`class ${enumName}(Enum):`); + lines.push(` ${pyDocstringLiteral(description)}`); + } else { + lines.push(`class ${enumName}(Enum):`); + } + for (const value of values) { + lines.push(` ${toEnumMemberName(value)} = ${JSON.stringify(value)}`); + } + ctx.enumsByValues.set(valuesKey, enumName); + ctx.enums.push(lines.join("\n")); + return enumName; +} + +function resolvePyPropertyType( + propSchema: JSONSchema7, + parentTypeName: string, + jsonPropName: string, + isRequired: boolean, + ctx: PyCodegenCtx +): PyResolvedType { + const nestedName = parentTypeName + toPascalCase(jsonPropName); + + if (propSchema.allOf && propSchema.allOf.length === 1 && typeof propSchema.allOf[0] === "object") { + return resolvePyPropertyType( + propSchema.allOf[0] as JSONSchema7, + parentTypeName, + jsonPropName, + isRequired, + ctx + ); + } + + if (propSchema.anyOf) { + const variants = (propSchema.anyOf as JSONSchema7[]).filter((item) => typeof item === "object"); + const nonNull = variants.filter((item) => item.type !== "null"); + const hasNull = variants.length !== nonNull.length; + + if (nonNull.length === 1) { + const inner = resolvePyPropertyType(nonNull[0], parentTypeName, jsonPropName, true, ctx); + return hasNull || !isRequired ? pyOptionalResolvedType(inner) : inner; + } + + if (nonNull.length > 1) { + const discriminator = findPyDiscriminator(nonNull); + if (discriminator) { + emitPyFlatDiscriminatedUnion( + nestedName, + discriminator.property, + discriminator.mapping, + ctx, + propSchema.description + ); + const resolved: PyResolvedType = { + annotation: nestedName, + fromExpr: (expr) => `${nestedName}.from_dict(${expr})`, + toExpr: (expr) => `to_class(${nestedName}, ${expr})`, + }; + return hasNull || !isRequired ? pyOptionalResolvedType(resolved) : resolved; + } + + return pyAnyResolvedType(); + } + } + + if (propSchema.enum && Array.isArray(propSchema.enum) && propSchema.enum.every((value) => typeof value === "string")) { + const enumType = getOrCreatePyEnum( + nestedName, + propSchema.enum as string[], + ctx, + propSchema.description + ); + const resolved: PyResolvedType = { + annotation: enumType, + fromExpr: (expr) => `parse_enum(${enumType}, ${expr})`, + toExpr: (expr) => `to_enum(${enumType}, ${expr})`, + }; + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + + if (propSchema.const !== undefined) { + if (typeof propSchema.const === "string") { + const resolved = pyPrimitiveResolvedType("str", "from_str"); + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + if (typeof propSchema.const === "boolean") { + const resolved = pyPrimitiveResolvedType("bool", "from_bool"); + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + if (typeof propSchema.const === "number") { + const resolved = Number.isInteger(propSchema.const) + ? pyPrimitiveResolvedType("int", "from_int", "to_int") + : pyPrimitiveResolvedType("float", "from_float", "to_float"); + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + } + + const type = propSchema.type; + const format = propSchema.format; + + if (Array.isArray(type)) { + const nonNullTypes = type.filter((value) => value !== "null"); + if (nonNullTypes.length === 1) { + const inner = resolvePyPropertyType( + { ...propSchema, type: nonNullTypes[0] as JSONSchema7["type"] }, + parentTypeName, + jsonPropName, + true, + ctx + ); + return pyOptionalResolvedType(inner); + } + } + + if (type === "string") { + if (format === "date-time") { + const resolved = pyPrimitiveResolvedType("datetime", "from_datetime", "to_datetime"); + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + if (format === "uuid") { + const resolved = pyPrimitiveResolvedType("UUID", "from_uuid", "to_uuid"); + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + const resolved = pyPrimitiveResolvedType("str", "from_str"); + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + + if (type === "integer") { + const resolved = pyPrimitiveResolvedType("int", "from_int", "to_int"); + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + + if (type === "number") { + const resolved = pyPrimitiveResolvedType("float", "from_float", "to_float"); + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + + if (type === "boolean") { + const resolved = pyPrimitiveResolvedType("bool", "from_bool"); + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + + if (type === "array") { + const items = propSchema.items as JSONSchema7 | undefined; + if (!items) { + const resolved: PyResolvedType = { + annotation: "list[Any]", + fromExpr: (expr) => `from_list(lambda x: x, ${expr})`, + toExpr: (expr) => `from_list(lambda x: x, ${expr})`, + }; + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + + if (items.allOf && items.allOf.length === 1 && typeof items.allOf[0] === "object") { + return resolvePyPropertyType( + { ...propSchema, items: items.allOf[0] as JSONSchema7 }, + parentTypeName, + jsonPropName, + isRequired, + ctx + ); + } + + if (items.anyOf) { + const itemVariants = (items.anyOf as JSONSchema7[]) + .filter((variant) => typeof variant === "object") + .filter((variant) => variant.type !== "null"); + const discriminator = findPyDiscriminator(itemVariants); + if (discriminator) { + const itemTypeName = nestedName + "Item"; + emitPyFlatDiscriminatedUnion( + itemTypeName, + discriminator.property, + discriminator.mapping, + ctx, + items.description + ); + const resolved: PyResolvedType = { + annotation: `list[${itemTypeName}]`, + fromExpr: (expr) => `from_list(${itemTypeName}.from_dict, ${expr})`, + toExpr: (expr) => `from_list(lambda x: to_class(${itemTypeName}, x), ${expr})`, + }; + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + } + + const itemType = resolvePyPropertyType(items, parentTypeName, jsonPropName + "Item", true, ctx); + const resolved: PyResolvedType = { + annotation: `list[${itemType.annotation}]`, + fromExpr: (expr) => `from_list(${wrapParser(itemType)}, ${expr})`, + toExpr: (expr) => `from_list(${wrapSerializer(itemType)}, ${expr})`, + }; + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + + if (type === "object" || (propSchema.properties && !type)) { + if (propSchema.properties) { + emitPyClass(nestedName, propSchema, ctx, propSchema.description); + const resolved: PyResolvedType = { + annotation: nestedName, + fromExpr: (expr) => `${nestedName}.from_dict(${expr})`, + toExpr: (expr) => `to_class(${nestedName}, ${expr})`, + }; + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + + if (propSchema.additionalProperties) { + if ( + typeof propSchema.additionalProperties === "object" && + Object.keys(propSchema.additionalProperties as Record).length > 0 + ) { + const valueType = resolvePyPropertyType( + propSchema.additionalProperties as JSONSchema7, + parentTypeName, + jsonPropName + "Value", + true, + ctx + ); + const resolved: PyResolvedType = { + annotation: `dict[str, ${valueType.annotation}]`, + fromExpr: (expr) => `from_dict(${wrapParser(valueType)}, ${expr})`, + toExpr: (expr) => `from_dict(${wrapSerializer(valueType)}, ${expr})`, + }; + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + + const resolved: PyResolvedType = { + annotation: "dict[str, Any]", + fromExpr: (expr) => `from_dict(lambda x: x, ${expr})`, + toExpr: (expr) => `from_dict(lambda x: x, ${expr})`, + }; + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + + return pyAnyResolvedType(); + } + + return pyAnyResolvedType(); +} + +function emitPyClass( + typeName: string, + schema: JSONSchema7, + ctx: PyCodegenCtx, + description?: string +): void { + if (ctx.generatedNames.has(typeName)) { + return; + } + ctx.generatedNames.add(typeName); + + const required = new Set(schema.required || []); + const fieldEntries = Object.entries(schema.properties || {}).filter( + ([, value]) => typeof value === "object" + ) as Array<[string, JSONSchema7]>; + const orderedFieldEntries = [ + ...fieldEntries.filter(([name]) => required.has(name)), + ...fieldEntries.filter(([name]) => !required.has(name)), + ]; + + const fieldInfos = orderedFieldEntries.map(([propName, propSchema]) => { + const isRequired = required.has(propName); + const resolved = resolvePyPropertyType(propSchema, typeName, propName, isRequired, ctx); + return { + jsonName: propName, + fieldName: toSnakeCase(propName), + isRequired, + resolved, + }; }); - let code = result.lines.join("\n"); + const lines: string[] = []; + lines.push(`@dataclass`); + lines.push(`class ${typeName}:`); + if (description || schema.description) { + lines.push(` ${pyDocstringLiteral(description || schema.description || "")}`); + } + + if (fieldInfos.length === 0) { + lines.push(` @staticmethod`); + lines.push(` def from_dict(obj: Any) -> "${typeName}":`); + lines.push(` assert isinstance(obj, dict)`); + lines.push(` return ${typeName}()`); + lines.push(``); + lines.push(` def to_dict(self) -> dict:`); + lines.push(` return {}`); + ctx.classes.push(lines.join("\n")); + return; + } - // Fix dataclass field ordering (Any fields need defaults) - code = code.replace(/: Any$/gm, ": Any = None"); - // Fix bare except: to use Exception (required by ruff/pylint) - code = code.replace(/except:/g, "except Exception:"); - // Modernize to Python 3.11+ syntax - code = modernizePython(code); + for (const field of fieldInfos) { + const suffix = field.isRequired ? "" : " = None"; + lines.push(` ${field.fieldName}: ${field.resolved.annotation}${suffix}`); + } - // Add UNKNOWN enum value for forward compatibility - code = code.replace( - /^(class SessionEventType\(Enum\):.*?)(^\s*\n@dataclass)/ms, - `$1 # UNKNOWN is used for forward compatibility - UNKNOWN = "unknown" + lines.push(``); + lines.push(` @staticmethod`); + lines.push(` def from_dict(obj: Any) -> "${typeName}":`); + lines.push(` assert isinstance(obj, dict)`); + for (const field of fieldInfos) { + lines.push( + ` ${field.fieldName} = ${field.resolved.fromExpr(`obj.get(${JSON.stringify(field.jsonName)})`)}` + ); + } + lines.push(` return ${typeName}(`); + for (const field of fieldInfos) { + lines.push(` ${field.fieldName}=${field.fieldName},`); + } + lines.push(` )`); + lines.push(``); + lines.push(` def to_dict(self) -> dict:`); + lines.push(` result: dict = {}`); + for (const field of fieldInfos) { + const valueExpr = field.resolved.toExpr(`self.${field.fieldName}`); + if (field.isRequired) { + lines.push(` result[${JSON.stringify(field.jsonName)}] = ${valueExpr}`); + } else { + lines.push(` if self.${field.fieldName} is not None:`); + lines.push(` result[${JSON.stringify(field.jsonName)}] = ${valueExpr}`); + } + } + lines.push(` return result`); - @classmethod - def _missing_(cls, value: object) -> "SessionEventType": - """Handle unknown event types gracefully for forward compatibility.""" - return cls.UNKNOWN + ctx.classes.push(lines.join("\n")); +} -$2` +function emitPyFlatDiscriminatedUnion( + typeName: string, + discriminatorProp: string, + mapping: Map, + ctx: PyCodegenCtx, + description?: string +): void { + if (ctx.generatedNames.has(typeName)) { + return; + } + ctx.generatedNames.add(typeName); + + const allProps = new Map(); + for (const [, variant] of mapping) { + const required = new Set(variant.required || []); + for (const [propName, propSchema] of Object.entries(variant.properties || {})) { + if (typeof propSchema !== "object") { + continue; + } + if (!allProps.has(propName)) { + allProps.set(propName, { + schema: propSchema as JSONSchema7, + requiredInAll: required.has(propName), + }); + } else if (!required.has(propName)) { + allProps.get(propName)!.requiredInAll = false; + } + } + } + + const variantCount = mapping.size; + for (const [propName, info] of allProps) { + let presentCount = 0; + for (const [, variant] of mapping) { + if (variant.properties && propName in variant.properties) { + presentCount++; + } + } + if (presentCount < variantCount) { + info.requiredInAll = false; + } + } + + const discriminatorEnumName = getOrCreatePyEnum( + typeName + toPascalCase(discriminatorProp), + [...mapping.keys()], + ctx, + description ? `${description} discriminator` : `${typeName} discriminator` ); - const banner = `""" -AUTO-GENERATED FILE - DO NOT EDIT -Generated from: session-events.schema.json -""" + const fieldEntries: Array<[string, JSONSchema7, boolean]> = [ + [ + discriminatorProp, + { + type: "string", + enum: [...mapping.keys()], + }, + true, + ], + ...[...allProps.entries()] + .filter(([propName]) => propName !== discriminatorProp) + .map(([propName, info]) => [propName, info.schema, info.requiredInAll] as [string, JSONSchema7, boolean]), + ]; + + const orderedFieldEntries = [ + ...fieldEntries.filter(([, , requiredInAll]) => requiredInAll), + ...fieldEntries.filter(([, , requiredInAll]) => !requiredInAll), + ]; + + const fieldInfos = orderedFieldEntries.map(([propName, propSchema, requiredInAll]) => { + let resolved: PyResolvedType; + if (propName === discriminatorProp) { + resolved = { + annotation: discriminatorEnumName, + fromExpr: (expr) => `parse_enum(${discriminatorEnumName}, ${expr})`, + toExpr: (expr) => `to_enum(${discriminatorEnumName}, ${expr})`, + }; + } else { + resolved = resolvePyPropertyType(propSchema, typeName, propName, requiredInAll, ctx); + } + + return { + jsonName: propName, + fieldName: toSnakeCase(propName), + isRequired: requiredInAll, + resolved, + }; + }); -`; + const lines: string[] = []; + lines.push(`@dataclass`); + lines.push(`class ${typeName}:`); + if (description) { + lines.push(` ${pyDocstringLiteral(description)}`); + } + for (const field of fieldInfos) { + const suffix = field.isRequired ? "" : " = None"; + lines.push(` ${field.fieldName}: ${field.resolved.annotation}${suffix}`); + } + lines.push(``); + lines.push(` @staticmethod`); + lines.push(` def from_dict(obj: Any) -> "${typeName}":`); + lines.push(` assert isinstance(obj, dict)`); + for (const field of fieldInfos) { + lines.push( + ` ${field.fieldName} = ${field.resolved.fromExpr(`obj.get(${JSON.stringify(field.jsonName)})`)}` + ); + } + lines.push(` return ${typeName}(`); + for (const field of fieldInfos) { + lines.push(` ${field.fieldName}=${field.fieldName},`); + } + lines.push(` )`); + lines.push(``); + lines.push(` def to_dict(self) -> dict:`); + lines.push(` result: dict = {}`); + for (const field of fieldInfos) { + const valueExpr = field.resolved.toExpr(`self.${field.fieldName}`); + if (field.isRequired) { + lines.push(` result[${JSON.stringify(field.jsonName)}] = ${valueExpr}`); + } else { + lines.push(` if self.${field.fieldName} is not None:`); + lines.push(` result[${JSON.stringify(field.jsonName)}] = ${valueExpr}`); + } + } + lines.push(` return result`); + + ctx.classes.push(lines.join("\n")); +} + +function generatePythonSessionEventsCode(schema: JSONSchema7): string { + const variants = extractPyEventVariants(schema); + const ctx: PyCodegenCtx = { + classes: [], + enums: [], + enumsByValues: new Map(), + generatedNames: new Set(), + }; + + for (const variant of variants) { + emitPyClass(variant.dataClassName, variant.dataSchema, ctx, variant.dataDescription); + } + + const eventTypeLines: string[] = []; + eventTypeLines.push(`class SessionEventType(Enum):`); + for (const variant of variants) { + eventTypeLines.push(` ${toEnumMemberName(variant.typeName)} = ${JSON.stringify(variant.typeName)}`); + } + eventTypeLines.push(` UNKNOWN = "unknown"`); + eventTypeLines.push(``); + eventTypeLines.push(` @classmethod`); + eventTypeLines.push(` def _missing_(cls, value: object) -> "SessionEventType":`); + eventTypeLines.push(` return cls.UNKNOWN`); + + const out: string[] = []; + out.push(`"""`); + out.push(`AUTO-GENERATED FILE - DO NOT EDIT`); + out.push(`Generated from: session-events.schema.json`); + out.push(`"""`); + out.push(``); + out.push(`from __future__ import annotations`); + out.push(``); + out.push(`from collections.abc import Callable`); + out.push(`from dataclasses import dataclass`); + out.push(`from datetime import datetime`); + out.push(`from enum import Enum`); + out.push(`from typing import Any, TypeVar, cast`); + out.push(`from uuid import UUID`); + out.push(``); + out.push(`import dateutil.parser`); + out.push(``); + out.push(`T = TypeVar("T")`); + out.push(`EnumT = TypeVar("EnumT", bound=Enum)`); + out.push(``); + out.push(``); + out.push(`def from_str(x: Any) -> str:`); + out.push(` assert isinstance(x, str)`); + out.push(` return x`); + out.push(``); + out.push(``); + out.push(`def from_int(x: Any) -> int:`); + out.push(` assert isinstance(x, int) and not isinstance(x, bool)`); + out.push(` return x`); + out.push(``); + out.push(``); + out.push(`def to_int(x: Any) -> int:`); + out.push(` assert isinstance(x, int) and not isinstance(x, bool)`); + out.push(` return x`); + out.push(``); + out.push(``); + out.push(`def from_float(x: Any) -> float:`); + out.push(` assert isinstance(x, (float, int)) and not isinstance(x, bool)`); + out.push(` return float(x)`); + out.push(``); + out.push(``); + out.push(`def to_float(x: Any) -> float:`); + out.push(` assert isinstance(x, (float, int)) and not isinstance(x, bool)`); + out.push(` return float(x)`); + out.push(``); + out.push(``); + out.push(`def from_bool(x: Any) -> bool:`); + out.push(` assert isinstance(x, bool)`); + out.push(` return x`); + out.push(``); + out.push(``); + out.push(`def from_none(x: Any) -> Any:`); + out.push(` assert x is None`); + out.push(` return x`); + out.push(``); + out.push(``); + out.push(`def from_union(fs: list[Callable[[Any], T]], x: Any) -> T:`); + out.push(` for f in fs:`); + out.push(` try:`); + out.push(` return f(x)`); + out.push(` except Exception:`); + out.push(` pass`); + out.push(` assert False`); + out.push(``); + out.push(``); + out.push(`def from_list(f: Callable[[Any], T], x: Any) -> list[T]:`); + out.push(` assert isinstance(x, list)`); + out.push(` return [f(item) for item in x]`); + out.push(``); + out.push(``); + out.push(`def from_dict(f: Callable[[Any], T], x: Any) -> dict[str, T]:`); + out.push(` assert isinstance(x, dict)`); + out.push(` return {key: f(value) for key, value in x.items()}`); + out.push(``); + out.push(``); + out.push(`def from_datetime(x: Any) -> datetime:`); + out.push(` return dateutil.parser.parse(from_str(x))`); + out.push(``); + out.push(``); + out.push(`def to_datetime(x: datetime) -> str:`); + out.push(` return x.isoformat()`); + out.push(``); + out.push(``); + out.push(`def from_uuid(x: Any) -> UUID:`); + out.push(` return UUID(from_str(x))`); + out.push(``); + out.push(``); + out.push(`def to_uuid(x: UUID) -> str:`); + out.push(` return str(x)`); + out.push(``); + out.push(``); + out.push(`def parse_enum(c: type[EnumT], x: Any) -> EnumT:`); + out.push(` assert isinstance(x, str)`); + out.push(` return c(x)`); + out.push(``); + out.push(``); + out.push(`def to_class(c: type[T], x: Any) -> dict:`); + out.push(` assert isinstance(x, c)`); + out.push(` return cast(Any, x).to_dict()`); + out.push(``); + out.push(``); + out.push(`def to_enum(c: type[EnumT], x: Any) -> str:`); + out.push(` assert isinstance(x, c)`); + out.push(` return cast(str, x.value)`); + out.push(``); + out.push(``); + out.push(eventTypeLines.join("\n")); + out.push(``); + out.push(``); + out.push(`@dataclass`); + out.push(`class RawSessionEventData:`); + out.push(` raw: Any`); + out.push(``); + out.push(` @staticmethod`); + out.push(` def from_dict(obj: Any) -> "RawSessionEventData":`); + out.push(` return RawSessionEventData(obj)`); + out.push(``); + out.push(` def to_dict(self) -> Any:`); + out.push(` return self.raw`); + out.push(``); + out.push(``); + out.push(`def _compat_to_python_key(name: str) -> str:`); + out.push(` normalized = name.replace(".", "_")`); + out.push(` result: list[str] = []`); + out.push(` for index, char in enumerate(normalized):`); + out.push( + ` if char.isupper() and index > 0 and (not normalized[index - 1].isupper() or (index + 1 < len(normalized) and normalized[index + 1].islower())):` + ); + out.push(` result.append("_")`); + out.push(` result.append(char.lower())`); + out.push(` return "".join(result)`); + out.push(``); + out.push(``); + out.push(`def _compat_to_json_key(name: str) -> str:`); + out.push(` parts = name.split("_")`); + out.push(` if not parts:`); + out.push(` return name`); + out.push(` return parts[0] + "".join(part[:1].upper() + part[1:] for part in parts[1:])`); + out.push(``); + out.push(``); + out.push(`def _compat_to_json_value(value: Any) -> Any:`); + out.push(` if hasattr(value, "to_dict"):`); + out.push(` return cast(Any, value).to_dict()`); + out.push(` if isinstance(value, Enum):`); + out.push(` return value.value`); + out.push(` if isinstance(value, datetime):`); + out.push(` return value.isoformat()`); + out.push(` if isinstance(value, UUID):`); + out.push(` return str(value)`); + out.push(` if isinstance(value, list):`); + out.push(` return [_compat_to_json_value(item) for item in value]`); + out.push(` if isinstance(value, dict):`); + out.push(` return {key: _compat_to_json_value(item) for key, item in value.items()}`); + out.push(` return value`); + out.push(``); + out.push(``); + out.push(`def _compat_from_json_value(value: Any) -> Any:`); + out.push(` return value`); + out.push(``); + out.push(``); + out.push(`class Data:`); + out.push(` """Backward-compatible shim for manually constructed event payloads."""`); + out.push(``); + out.push(` def __init__(self, **kwargs: Any):`); + out.push(` self._values = {key: _compat_from_json_value(value) for key, value in kwargs.items()}`); + out.push(` for key, value in self._values.items():`); + out.push(` setattr(self, key, value)`); + out.push(``); + out.push(` @staticmethod`); + out.push(` def from_dict(obj: Any) -> "Data":`); + out.push(` assert isinstance(obj, dict)`); + out.push( + ` return Data(**{_compat_to_python_key(key): _compat_from_json_value(value) for key, value in obj.items()})` + ); + out.push(``); + out.push(` def to_dict(self) -> dict:`); + out.push( + ` return {_compat_to_json_key(key): _compat_to_json_value(value) for key, value in self._values.items() if value is not None}` + ); + out.push(``); + out.push(``); + for (const classDef of ctx.classes) { + out.push(classDef); + out.push(``); + out.push(``); + } + for (const enumDef of ctx.enums) { + out.push(enumDef); + out.push(``); + out.push(``); + } + + const sessionEventDataTypes = [ + ...variants.map((variant) => variant.dataClassName), + "RawSessionEventData", + "Data", + ]; + out.push(`SessionEventData = ${sessionEventDataTypes.join(" | ")}`); + out.push(``); + out.push(``); + out.push(`@dataclass`); + out.push(`class SessionEvent:`); + out.push(` data: SessionEventData`); + out.push(` id: UUID`); + out.push(` timestamp: datetime`); + out.push(` type: SessionEventType`); + out.push(` ephemeral: bool | None = None`); + out.push(` parent_id: UUID | None = None`); + out.push(` raw_type: str | None = None`); + out.push(``); + out.push(` @staticmethod`); + out.push(` def from_dict(obj: Any) -> "SessionEvent":`); + out.push(` assert isinstance(obj, dict)`); + out.push(` raw_type = from_str(obj.get("type"))`); + out.push(` event_type = SessionEventType(raw_type)`); + out.push(` event_id = from_uuid(obj.get("id"))`); + out.push(` timestamp = from_datetime(obj.get("timestamp"))`); + out.push(` ephemeral = from_union([from_bool, from_none], obj.get("ephemeral"))`); + out.push(` parent_id = from_union([from_none, from_uuid], obj.get("parentId"))`); + out.push(` data_obj = obj.get("data")`); + out.push(` match event_type:`); + for (const variant of variants) { + out.push( + ` case SessionEventType.${toEnumMemberName(variant.typeName)}: data = ${variant.dataClassName}.from_dict(data_obj)` + ); + } + out.push(` case _: data = RawSessionEventData.from_dict(data_obj)`); + out.push(` return SessionEvent(`); + out.push(` data=data,`); + out.push(` id=event_id,`); + out.push(` timestamp=timestamp,`); + out.push(` type=event_type,`); + out.push(` ephemeral=ephemeral,`); + out.push(` parent_id=parent_id,`); + out.push(` raw_type=raw_type if event_type == SessionEventType.UNKNOWN else None,`); + out.push(` )`); + out.push(``); + out.push(` def to_dict(self) -> dict:`); + out.push(` result: dict = {}`); + out.push(` result["data"] = self.data.to_dict()`); + out.push(` result["id"] = to_uuid(self.id)`); + out.push(` result["timestamp"] = to_datetime(self.timestamp)`); + out.push( + ` result["type"] = self.raw_type if self.type == SessionEventType.UNKNOWN and self.raw_type is not None else to_enum(SessionEventType, self.type)` + ); + out.push(` if self.ephemeral is not None:`); + out.push(` result["ephemeral"] = from_bool(self.ephemeral)`); + out.push(` result["parentId"] = from_union([from_none, to_uuid], self.parent_id)`); + out.push(` return result`); + out.push(``); + out.push(``); + out.push(`def session_event_from_dict(s: Any) -> SessionEvent:`); + out.push(` return SessionEvent.from_dict(s)`); + out.push(``); + out.push(``); + out.push(`def session_event_to_dict(x: SessionEvent) -> Any:`); + out.push(` return x.to_dict()`); + out.push(``); + out.push(``); + out.push(`# Compatibility shims for pre-refactor top-level generated types.`); + out.push(`class RequestedSchemaType(str, Enum):`); + out.push(` OBJECT = "object"`); + out.push(``); + out.push(``); + out.push(`@dataclass`); + out.push(`class ErrorClass:`); + out.push(` """Backward-compatible shim for generic error payloads."""`); + out.push(` message: str`); + out.push(` code: str | None = None`); + out.push(` stack: str | None = None`); + out.push(``); + out.push(` @staticmethod`); + out.push(` def from_dict(obj: Any) -> "ErrorClass":`); + out.push(` assert isinstance(obj, dict)`); + out.push(` message = from_str(obj.get("message"))`); + out.push(` code = from_union([from_none, lambda x: from_str(x)], obj.get("code"))`); + out.push(` stack = from_union([from_none, lambda x: from_str(x)], obj.get("stack"))`); + out.push(` return ErrorClass(`); + out.push(` message=message,`); + out.push(` code=code,`); + out.push(` stack=stack,`); + out.push(` )`); + out.push(``); + out.push(` def to_dict(self) -> dict:`); + out.push(` result: dict = {}`); + out.push(` result["message"] = from_str(self.message)`); + out.push(` if self.code is not None:`); + out.push(` result["code"] = from_union([from_none, lambda x: from_str(x)], self.code)`); + out.push(` if self.stack is not None:`); + out.push(` result["stack"] = from_union([from_none, lambda x: from_str(x)], self.stack)`); + out.push(` return result`); + out.push(``); + out.push(``); + out.push(`@dataclass`); + out.push(`class Resource:`); + out.push(` """Backward-compatible shim for embedded tool result resources."""`); + out.push(` uri: str`); + out.push(` mime_type: str | None = None`); + out.push(` text: str | None = None`); + out.push(` blob: str | None = None`); + out.push(``); + out.push(` @staticmethod`); + out.push(` def from_dict(obj: Any) -> "Resource":`); + out.push(` assert isinstance(obj, dict)`); + out.push(` uri = from_str(obj.get("uri"))`); + out.push(` mime_type = from_union([from_none, lambda x: from_str(x)], obj.get("mimeType"))`); + out.push(` text = from_union([from_none, lambda x: from_str(x)], obj.get("text"))`); + out.push(` blob = from_union([from_none, lambda x: from_str(x)], obj.get("blob"))`); + out.push(` return Resource(`); + out.push(` uri=uri,`); + out.push(` mime_type=mime_type,`); + out.push(` text=text,`); + out.push(` blob=blob,`); + out.push(` )`); + out.push(``); + out.push(` def to_dict(self) -> dict:`); + out.push(` result: dict = {}`); + out.push(` result["uri"] = from_str(self.uri)`); + out.push(` if self.mime_type is not None:`); + out.push(` result["mimeType"] = from_union([from_none, lambda x: from_str(x)], self.mime_type)`); + out.push(` if self.text is not None:`); + out.push(` result["text"] = from_union([from_none, lambda x: from_str(x)], self.text)`); + out.push(` if self.blob is not None:`); + out.push(` result["blob"] = from_union([from_none, lambda x: from_str(x)], self.blob)`); + out.push(` return result`); + out.push(``); + out.push(``); + out.push(`ContentType = ToolExecutionCompleteDataResultContentsItemType`); + out.push(`Theme = ToolExecutionCompleteDataResultContentsItemIconsItemTheme`); + out.push(`Icon = ToolExecutionCompleteDataResultContentsItemIconsItem`); + out.push(`ResultKind = PermissionCompletedDataResultKind`); + out.push(``); + out.push(``); + out.push(`@dataclass`); + out.push(`class ContentElement:`); + out.push(` """Backward-compatible shim for tool result content blocks."""`); + out.push(` type: ContentType`); + out.push(` text: str | None = None`); + out.push(` cwd: str | None = None`); + out.push(` exit_code: float | None = None`); + out.push(` data: str | None = None`); + out.push(` mime_type: str | None = None`); + out.push(` description: str | None = None`); + out.push(` icons: list[Icon] | None = None`); + out.push(` name: str | None = None`); + out.push(` size: float | None = None`); + out.push(` title: str | None = None`); + out.push(` uri: str | None = None`); + out.push(` resource: Resource | None = None`); + out.push(``); + out.push(` @staticmethod`); + out.push(` def from_dict(obj: Any) -> "ContentElement":`); + out.push(` assert isinstance(obj, dict)`); + out.push(` type = parse_enum(ContentType, obj.get("type"))`); + out.push(` text = from_union([from_none, lambda x: from_str(x)], obj.get("text"))`); + out.push(` cwd = from_union([from_none, lambda x: from_str(x)], obj.get("cwd"))`); + out.push(` exit_code = from_union([from_none, lambda x: from_float(x)], obj.get("exitCode"))`); + out.push(` data = from_union([from_none, lambda x: from_str(x)], obj.get("data"))`); + out.push(` mime_type = from_union([from_none, lambda x: from_str(x)], obj.get("mimeType"))`); + out.push(` description = from_union([from_none, lambda x: from_str(x)], obj.get("description"))`); + out.push(` icons = from_union([from_none, lambda x: from_list(Icon.from_dict, x)], obj.get("icons"))`); + out.push(` name = from_union([from_none, lambda x: from_str(x)], obj.get("name"))`); + out.push(` size = from_union([from_none, lambda x: from_float(x)], obj.get("size"))`); + out.push(` title = from_union([from_none, lambda x: from_str(x)], obj.get("title"))`); + out.push(` uri = from_union([from_none, lambda x: from_str(x)], obj.get("uri"))`); + out.push(` resource = from_union([from_none, lambda x: Resource.from_dict(x)], obj.get("resource"))`); + out.push(` return ContentElement(`); + out.push(` type=type,`); + out.push(` text=text,`); + out.push(` cwd=cwd,`); + out.push(` exit_code=exit_code,`); + out.push(` data=data,`); + out.push(` mime_type=mime_type,`); + out.push(` description=description,`); + out.push(` icons=icons,`); + out.push(` name=name,`); + out.push(` size=size,`); + out.push(` title=title,`); + out.push(` uri=uri,`); + out.push(` resource=resource,`); + out.push(` )`); + out.push(``); + out.push(` def to_dict(self) -> dict:`); + out.push(` result: dict = {}`); + out.push(` result["type"] = to_enum(ContentType, self.type)`); + out.push(` if self.text is not None:`); + out.push(` result["text"] = from_union([from_none, lambda x: from_str(x)], self.text)`); + out.push(` if self.cwd is not None:`); + out.push(` result["cwd"] = from_union([from_none, lambda x: from_str(x)], self.cwd)`); + out.push(` if self.exit_code is not None:`); + out.push(` result["exitCode"] = from_union([from_none, lambda x: to_float(x)], self.exit_code)`); + out.push(` if self.data is not None:`); + out.push(` result["data"] = from_union([from_none, lambda x: from_str(x)], self.data)`); + out.push(` if self.mime_type is not None:`); + out.push(` result["mimeType"] = from_union([from_none, lambda x: from_str(x)], self.mime_type)`); + out.push(` if self.description is not None:`); + out.push(` result["description"] = from_union([from_none, lambda x: from_str(x)], self.description)`); + out.push(` if self.icons is not None:`); + out.push(` result["icons"] = from_union([from_none, lambda x: from_list(lambda x: to_class(Icon, x), x)], self.icons)`); + out.push(` if self.name is not None:`); + out.push(` result["name"] = from_union([from_none, lambda x: from_str(x)], self.name)`); + out.push(` if self.size is not None:`); + out.push(` result["size"] = from_union([from_none, lambda x: to_float(x)], self.size)`); + out.push(` if self.title is not None:`); + out.push(` result["title"] = from_union([from_none, lambda x: from_str(x)], self.title)`); + out.push(` if self.uri is not None:`); + out.push(` result["uri"] = from_union([from_none, lambda x: from_str(x)], self.uri)`); + out.push(` if self.resource is not None:`); + out.push(` result["resource"] = from_union([from_none, lambda x: to_class(Resource, x)], self.resource)`); + out.push(` return result`); + out.push(``); + out.push(``); + out.push(`@dataclass`); + out.push(`class Result:`); + out.push(` """Backward-compatible shim for generic result payloads."""`); + out.push(` content: str | None = None`); + out.push(` contents: list[ContentElement] | None = None`); + out.push(` detailed_content: str | None = None`); + out.push(` kind: ResultKind | None = None`); + out.push(``); + out.push(` @staticmethod`); + out.push(` def from_dict(obj: Any) -> "Result":`); + out.push(` assert isinstance(obj, dict)`); + out.push(` content = from_union([from_none, lambda x: from_str(x)], obj.get("content"))`); + out.push(` contents = from_union([from_none, lambda x: from_list(ContentElement.from_dict, x)], obj.get("contents"))`); + out.push(` detailed_content = from_union([from_none, lambda x: from_str(x)], obj.get("detailedContent"))`); + out.push(` kind = from_union([from_none, lambda x: parse_enum(ResultKind, x)], obj.get("kind"))`); + out.push(` return Result(`); + out.push(` content=content,`); + out.push(` contents=contents,`); + out.push(` detailed_content=detailed_content,`); + out.push(` kind=kind,`); + out.push(` )`); + out.push(``); + out.push(` def to_dict(self) -> dict:`); + out.push(` result: dict = {}`); + out.push(` if self.content is not None:`); + out.push(` result["content"] = from_union([from_none, lambda x: from_str(x)], self.content)`); + out.push(` if self.contents is not None:`); + out.push(` result["contents"] = from_union([from_none, lambda x: from_list(lambda x: to_class(ContentElement, x), x)], self.contents)`); + out.push(` if self.detailed_content is not None:`); + out.push(` result["detailedContent"] = from_union([from_none, lambda x: from_str(x)], self.detailed_content)`); + out.push(` if self.kind is not None:`); + out.push(` result["kind"] = from_union([from_none, lambda x: to_enum(ResultKind, x)], self.kind)`); + out.push(` return result`); + out.push(``); + out.push(``); + out.push(`# Convenience aliases for commonly used nested event types.`); + out.push(`Action = ElicitationCompletedDataAction`); + out.push(`Agent = SessionCustomAgentsUpdatedDataAgentsItem`); + out.push(`AgentMode = UserMessageDataAgentMode`); + out.push(`Attachment = UserMessageDataAttachmentsItem`); + out.push(`AttachmentType = UserMessageDataAttachmentsItemType`); + out.push(`CodeChanges = SessionShutdownDataCodeChanges`); + out.push(`CompactionTokensUsed = SessionCompactionCompleteDataCompactionTokensUsed`); + out.push(`ContextClass = SessionStartDataContext`); + out.push(`CopilotUsage = AssistantUsageDataCopilotUsage`); + out.push(`DataCommand = CommandsChangedDataCommandsItem`); + out.push(`End = UserMessageDataAttachmentsItemSelectionEnd`); + out.push(`Extension = SessionExtensionsLoadedDataExtensionsItem`); + out.push(`ExtensionStatus = SessionExtensionsLoadedDataExtensionsItemStatus`); + out.push(`HostType = SessionStartDataContextHostType`); + out.push(`KindClass = SystemNotificationDataKind`); + out.push(`KindStatus = SystemNotificationDataKindStatus`); + out.push(`KindType = SystemNotificationDataKindType`); + out.push(`LineRange = UserMessageDataAttachmentsItemLineRange`); + out.push(`Metadata = SystemMessageDataMetadata`); + out.push(`Mode = ElicitationRequestedDataMode`); + out.push(`ModelMetric = SessionShutdownDataModelMetricsValue`); + out.push(`Operation = SessionPlanChangedDataOperation`); + out.push(`PermissionRequest = PermissionRequestedDataPermissionRequest`); + out.push(`PermissionRequestKind = PermissionRequestedDataPermissionRequestKind`); + out.push(`PermissionRequestCommand = PermissionRequestedDataPermissionRequestCommandsItem`); + out.push(`PossibleURL = PermissionRequestedDataPermissionRequestPossibleUrlsItem`); + out.push(`QuotaSnapshot = AssistantUsageDataQuotaSnapshotsValue`); + out.push(`ReferenceType = UserMessageDataAttachmentsItemReferenceType`); + out.push(`RepositoryClass = SessionHandoffDataRepository`); + out.push(`RequestedSchema = ElicitationRequestedDataRequestedSchema`); + out.push(`Requests = SessionShutdownDataModelMetricsValueRequests`); + out.push(`Role = SystemMessageDataRole`); + out.push(`Selection = UserMessageDataAttachmentsItemSelection`); + out.push(`Server = SessionMcpServersLoadedDataServersItem`); + out.push(`ServerStatus = SessionMcpServersLoadedDataServersItemStatus`); + out.push(`ShutdownType = SessionShutdownDataShutdownType`); + out.push(`Skill = SessionSkillsLoadedDataSkillsItem`); + out.push(`Source = SessionExtensionsLoadedDataExtensionsItemSource`); + out.push(`SourceType = SessionHandoffDataSourceType`); + out.push(`Start = UserMessageDataAttachmentsItemSelectionStart`); + out.push(`StaticClientConfig = McpOauthRequiredDataStaticClientConfig`); + out.push(`TokenDetail = AssistantUsageDataCopilotUsageTokenDetailsItem`); + out.push(`ToolRequest = AssistantMessageDataToolRequestsItem`); + out.push(`ToolRequestType = AssistantMessageDataToolRequestsItemType`); + out.push(`UI = CapabilitiesChangedDataUi`); + out.push(`Usage = SessionShutdownDataModelMetricsValueUsage`); + + return out.join("\n"); +} + +async function generateSessionEvents(schemaPath?: string): Promise { + console.log("Python: generating session-events..."); + + const resolvedPath = schemaPath ?? (await getSessionEventsSchemaPath()); + const schema = JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as JSONSchema7; + const processed = postProcessSchema(schema); + const code = generatePythonSessionEventsCode(processed); - const outPath = await writeGeneratedFile("python/copilot/generated/session_events.py", banner + code); + const outPath = await writeGeneratedFile("python/copilot/generated/session_events.py", code); console.log(` ✓ ${outPath}`); }