use crate::jsonrpc::RequestId;
use crate::protocol::{
methods, AccountLoginCompletedNotification, AccountRateLimitsUpdatedNotification,
AccountUpdatedNotification, AgentMessageDeltaNotification, AppListUpdatedNotification,
CmdOutputDeltaNotification, CommandExecOutputDeltaNotification, CommandExecutionApprovalParams,
ConfigWarningNotification, ContextCompactedNotification, DeprecationNoticeNotification,
ErrorNotification, ExternalAgentConfigImportCompletedNotification, FileChangeApprovalParams,
FileChangeOutputDeltaNotification, FileChangePatchUpdatedNotification, FsChangedNotification,
FuzzyFileSearchSessionCompletedNotification, FuzzyFileSearchSessionUpdatedNotification,
GuardianWarningNotification, HookCompletedNotification, HookStartedNotification,
ItemCompletedNotification, ItemGuardianApprovalReviewCompletedNotification,
ItemGuardianApprovalReviewStartedNotification, ItemStartedNotification,
McpServerOauthLoginCompletedNotification, McpServerStartupStatusUpdatedNotification,
McpToolCallProgressNotification, ModelReroutedNotification, ModelVerificationNotification,
PlanDeltaNotification, ProcessExitedNotification, ProcessOutputDeltaNotification,
ReasoningDeltaNotification, ReasoningSummaryPartAddedNotification,
ReasoningTextDeltaNotification, RemoteControlStatusChangedNotification,
ServerRequestResolvedNotification, SkillsChangedNotification, TerminalInteractionNotification,
ThreadArchivedNotification, ThreadClosedNotification, ThreadGoalClearedNotification,
ThreadGoalUpdatedNotification, ThreadNameUpdatedNotification, ThreadRealtimeClosedNotification,
ThreadRealtimeErrorNotification, ThreadRealtimeItemAddedNotification,
ThreadRealtimeOutputAudioDeltaNotification, ThreadRealtimeSdpNotification,
ThreadRealtimeStartedNotification, ThreadRealtimeTranscriptDeltaNotification,
ThreadRealtimeTranscriptDoneNotification, ThreadStartedNotification,
ThreadStatusChangedNotification, ThreadTokenUsageUpdatedNotification,
ThreadUnarchivedNotification, TurnCompletedNotification, TurnDiffUpdatedNotification,
TurnPlanUpdatedNotification, TurnStartedNotification, WarningNotification,
WindowsSandboxSetupCompletedNotification, WindowsWorldWritableWarningNotification,
};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
#[derive(Debug, Clone)]
pub enum Notification {
ThreadStarted(ThreadStartedNotification),
ThreadStatusChanged(ThreadStatusChangedNotification),
ThreadTokenUsageUpdated(ThreadTokenUsageUpdatedNotification),
TurnStarted(TurnStartedNotification),
TurnCompleted(TurnCompletedNotification),
ItemStarted(ItemStartedNotification),
ItemCompleted(ItemCompletedNotification),
AgentMessageDelta(AgentMessageDeltaNotification),
CmdOutputDelta(CmdOutputDeltaNotification),
FileChangeOutputDelta(FileChangeOutputDeltaNotification),
ReasoningDelta(ReasoningDeltaNotification),
Error(ErrorNotification),
AccountRateLimitsUpdated(AccountRateLimitsUpdatedNotification),
McpServerStartupStatusUpdated(McpServerStartupStatusUpdatedNotification),
RemoteControlStatusChanged(RemoteControlStatusChangedNotification),
McpServerOauthLoginCompleted(McpServerOauthLoginCompletedNotification),
FileChangePatchUpdated(FileChangePatchUpdatedNotification),
PlanDelta(PlanDeltaNotification),
TurnPlanUpdated(TurnPlanUpdatedNotification),
TurnDiffUpdated(TurnDiffUpdatedNotification),
ReasoningSummaryPartAdded(ReasoningSummaryPartAddedNotification),
ReasoningTextDelta(ReasoningTextDeltaNotification),
AccountLoginCompleted(AccountLoginCompletedNotification),
DeprecationNotice(DeprecationNoticeNotification),
GuardianWarning(GuardianWarningNotification),
Warning(WarningNotification),
ThreadArchived(ThreadArchivedNotification),
ThreadClosed(ThreadClosedNotification),
ThreadUnarchived(ThreadUnarchivedNotification),
ThreadGoalCleared(ThreadGoalClearedNotification),
ThreadNameUpdated(ThreadNameUpdatedNotification),
SkillsChanged(SkillsChangedNotification),
FsChanged(FsChangedNotification),
ConfigWarning(ConfigWarningNotification),
AccountUpdated(AccountUpdatedNotification),
AppListUpdated(AppListUpdatedNotification),
CommandExecOutputDelta(CommandExecOutputDeltaNotification),
ExternalAgentConfigImportCompleted(ExternalAgentConfigImportCompletedNotification),
FuzzyFileSearchSessionCompleted(FuzzyFileSearchSessionCompletedNotification),
FuzzyFileSearchSessionUpdated(FuzzyFileSearchSessionUpdatedNotification),
HookCompleted(HookCompletedNotification),
HookStarted(HookStartedNotification),
ItemGuardianApprovalReviewCompleted(ItemGuardianApprovalReviewCompletedNotification),
ItemGuardianApprovalReviewStarted(ItemGuardianApprovalReviewStartedNotification),
TerminalInteraction(TerminalInteractionNotification),
McpToolCallProgress(McpToolCallProgressNotification),
ModelRerouted(ModelReroutedNotification),
ModelVerification(ModelVerificationNotification),
ProcessExited(ProcessExitedNotification),
ProcessOutputDelta(ProcessOutputDeltaNotification),
ServerRequestResolved(ServerRequestResolvedNotification),
ContextCompacted(ContextCompactedNotification),
ThreadGoalUpdated(ThreadGoalUpdatedNotification),
ThreadRealtimeClosed(ThreadRealtimeClosedNotification),
ThreadRealtimeError(ThreadRealtimeErrorNotification),
ThreadRealtimeItemAdded(ThreadRealtimeItemAddedNotification),
ThreadRealtimeOutputAudioDelta(ThreadRealtimeOutputAudioDeltaNotification),
ThreadRealtimeSdp(ThreadRealtimeSdpNotification),
ThreadRealtimeStarted(ThreadRealtimeStartedNotification),
ThreadRealtimeTranscriptDelta(ThreadRealtimeTranscriptDeltaNotification),
ThreadRealtimeTranscriptDone(ThreadRealtimeTranscriptDoneNotification),
WindowsWorldWritableWarning(WindowsWorldWritableWarningNotification),
WindowsSandboxSetupCompleted(WindowsSandboxSetupCompletedNotification),
Unknown {
method: String,
params: Option<Value>,
},
}
impl Notification {
pub fn method(&self) -> &str {
match self {
Self::ThreadStarted(_) => methods::THREAD_STARTED,
Self::ThreadStatusChanged(_) => methods::THREAD_STATUS_CHANGED,
Self::ThreadTokenUsageUpdated(_) => methods::THREAD_TOKEN_USAGE_UPDATED,
Self::TurnStarted(_) => methods::TURN_STARTED,
Self::TurnCompleted(_) => methods::TURN_COMPLETED,
Self::ItemStarted(_) => methods::ITEM_STARTED,
Self::ItemCompleted(_) => methods::ITEM_COMPLETED,
Self::AgentMessageDelta(_) => methods::AGENT_MESSAGE_DELTA,
Self::CmdOutputDelta(_) => methods::CMD_OUTPUT_DELTA,
Self::FileChangeOutputDelta(_) => methods::FILE_CHANGE_OUTPUT_DELTA,
Self::ReasoningDelta(_) => methods::REASONING_DELTA,
Self::Error(_) => methods::ERROR,
Self::AccountRateLimitsUpdated(_) => methods::ACCOUNT_RATE_LIMITS_UPDATED,
Self::McpServerStartupStatusUpdated(_) => methods::MCP_SERVER_STARTUP_STATUS_UPDATED,
Self::RemoteControlStatusChanged(_) => methods::REMOTE_CONTROL_STATUS_CHANGED,
Self::McpServerOauthLoginCompleted(_) => methods::MCP_SERVER_OAUTH_LOGIN_COMPLETED,
Self::FileChangePatchUpdated(_) => methods::FILE_CHANGE_PATCH_UPDATED,
Self::PlanDelta(_) => methods::PLAN_DELTA,
Self::TurnPlanUpdated(_) => methods::TURN_PLAN_UPDATED,
Self::TurnDiffUpdated(_) => methods::TURN_DIFF_UPDATED,
Self::ReasoningSummaryPartAdded(_) => methods::REASONING_SUMMARY_PART_ADDED,
Self::ReasoningTextDelta(_) => methods::REASONING_TEXT_DELTA,
Self::AccountLoginCompleted(_) => methods::ACCOUNT_LOGIN_COMPLETED,
Self::DeprecationNotice(_) => methods::DEPRECATION_NOTICE,
Self::GuardianWarning(_) => methods::GUARDIAN_WARNING,
Self::Warning(_) => methods::WARNING,
Self::ThreadArchived(_) => methods::THREAD_ARCHIVED,
Self::ThreadClosed(_) => methods::THREAD_CLOSED,
Self::ThreadUnarchived(_) => methods::THREAD_UNARCHIVED,
Self::ThreadGoalCleared(_) => methods::THREAD_GOAL_CLEARED,
Self::ThreadNameUpdated(_) => methods::THREAD_NAME_UPDATED,
Self::SkillsChanged(_) => methods::SKILLS_CHANGED,
Self::FsChanged(_) => methods::FS_CHANGED,
Self::ConfigWarning(_) => methods::CONFIG_WARNING,
Self::AccountUpdated(_) => methods::ACCOUNT_UPDATED,
Self::AppListUpdated(_) => methods::APP_LIST_UPDATED,
Self::CommandExecOutputDelta(_) => methods::COMMAND_EXEC_OUTPUT_DELTA,
Self::ExternalAgentConfigImportCompleted(_) => {
methods::EXTERNAL_AGENT_CONFIG_IMPORT_COMPLETED
}
Self::FuzzyFileSearchSessionCompleted(_) => {
methods::FUZZY_FILE_SEARCH_SESSION_COMPLETED
}
Self::FuzzyFileSearchSessionUpdated(_) => methods::FUZZY_FILE_SEARCH_SESSION_UPDATED,
Self::HookCompleted(_) => methods::HOOK_COMPLETED,
Self::HookStarted(_) => methods::HOOK_STARTED,
Self::ItemGuardianApprovalReviewCompleted(_) => {
methods::ITEM_AUTO_APPROVAL_REVIEW_COMPLETED
}
Self::ItemGuardianApprovalReviewStarted(_) => {
methods::ITEM_AUTO_APPROVAL_REVIEW_STARTED
}
Self::TerminalInteraction(_) => methods::ITEM_COMMAND_EXEC_TERMINAL_INTERACTION,
Self::McpToolCallProgress(_) => methods::ITEM_MCP_TOOL_CALL_PROGRESS,
Self::ModelRerouted(_) => methods::MODEL_REROUTED,
Self::ModelVerification(_) => methods::MODEL_VERIFICATION,
Self::ProcessExited(_) => methods::PROCESS_EXITED,
Self::ProcessOutputDelta(_) => methods::PROCESS_OUTPUT_DELTA,
Self::ServerRequestResolved(_) => methods::SERVER_REQUEST_RESOLVED,
Self::ContextCompacted(_) => methods::THREAD_COMPACTED,
Self::ThreadGoalUpdated(_) => methods::THREAD_GOAL_UPDATED,
Self::ThreadRealtimeClosed(_) => methods::THREAD_REALTIME_CLOSED,
Self::ThreadRealtimeError(_) => methods::THREAD_REALTIME_ERROR,
Self::ThreadRealtimeItemAdded(_) => methods::THREAD_REALTIME_ITEM_ADDED,
Self::ThreadRealtimeOutputAudioDelta(_) => methods::THREAD_REALTIME_OUTPUT_AUDIO_DELTA,
Self::ThreadRealtimeSdp(_) => methods::THREAD_REALTIME_SDP,
Self::ThreadRealtimeStarted(_) => methods::THREAD_REALTIME_STARTED,
Self::ThreadRealtimeTranscriptDelta(_) => methods::THREAD_REALTIME_TRANSCRIPT_DELTA,
Self::ThreadRealtimeTranscriptDone(_) => methods::THREAD_REALTIME_TRANSCRIPT_DONE,
Self::WindowsWorldWritableWarning(_) => methods::WINDOWS_WORLD_WRITABLE_WARNING,
Self::WindowsSandboxSetupCompleted(_) => methods::WINDOWS_SANDBOX_SETUP_COMPLETED,
Self::Unknown { method, .. } => method,
}
}
pub fn is_unknown(&self) -> bool {
matches!(self, Self::Unknown { .. })
}
pub fn from_envelope(method: &str, params: Option<Value>) -> Result<Self, serde_json::Error> {
let params_value = params.clone().unwrap_or(Value::Null);
match method {
methods::THREAD_STARTED => {
serde_json::from_value(params_value).map(Self::ThreadStarted)
}
methods::THREAD_STATUS_CHANGED => {
serde_json::from_value(params_value).map(Self::ThreadStatusChanged)
}
methods::THREAD_TOKEN_USAGE_UPDATED => {
serde_json::from_value(params_value).map(Self::ThreadTokenUsageUpdated)
}
methods::TURN_STARTED => serde_json::from_value(params_value).map(Self::TurnStarted),
methods::TURN_COMPLETED => {
serde_json::from_value(params_value).map(Self::TurnCompleted)
}
methods::ITEM_STARTED => serde_json::from_value(params_value).map(Self::ItemStarted),
methods::ITEM_COMPLETED => {
serde_json::from_value(params_value).map(Self::ItemCompleted)
}
methods::AGENT_MESSAGE_DELTA => {
serde_json::from_value(params_value).map(Self::AgentMessageDelta)
}
methods::CMD_OUTPUT_DELTA => {
serde_json::from_value(params_value).map(Self::CmdOutputDelta)
}
methods::FILE_CHANGE_OUTPUT_DELTA => {
serde_json::from_value(params_value).map(Self::FileChangeOutputDelta)
}
methods::REASONING_DELTA => {
serde_json::from_value(params_value).map(Self::ReasoningDelta)
}
methods::ERROR => serde_json::from_value(params_value).map(Self::Error),
methods::ACCOUNT_RATE_LIMITS_UPDATED => {
serde_json::from_value(params_value).map(Self::AccountRateLimitsUpdated)
}
methods::MCP_SERVER_STARTUP_STATUS_UPDATED => {
serde_json::from_value(params_value).map(Self::McpServerStartupStatusUpdated)
}
methods::REMOTE_CONTROL_STATUS_CHANGED => {
serde_json::from_value(params_value).map(Self::RemoteControlStatusChanged)
}
methods::MCP_SERVER_OAUTH_LOGIN_COMPLETED => {
serde_json::from_value(params_value).map(Self::McpServerOauthLoginCompleted)
}
methods::FILE_CHANGE_PATCH_UPDATED => {
serde_json::from_value(params_value).map(Self::FileChangePatchUpdated)
}
methods::PLAN_DELTA => serde_json::from_value(params_value).map(Self::PlanDelta),
methods::TURN_PLAN_UPDATED => {
serde_json::from_value(params_value).map(Self::TurnPlanUpdated)
}
methods::TURN_DIFF_UPDATED => {
serde_json::from_value(params_value).map(Self::TurnDiffUpdated)
}
methods::REASONING_SUMMARY_PART_ADDED => {
serde_json::from_value(params_value).map(Self::ReasoningSummaryPartAdded)
}
methods::REASONING_TEXT_DELTA => {
serde_json::from_value(params_value).map(Self::ReasoningTextDelta)
}
methods::ACCOUNT_LOGIN_COMPLETED => {
serde_json::from_value(params_value).map(Self::AccountLoginCompleted)
}
methods::DEPRECATION_NOTICE => {
serde_json::from_value(params_value).map(Self::DeprecationNotice)
}
methods::GUARDIAN_WARNING => {
serde_json::from_value(params_value).map(Self::GuardianWarning)
}
methods::WARNING => serde_json::from_value(params_value).map(Self::Warning),
methods::THREAD_ARCHIVED => {
serde_json::from_value(params_value).map(Self::ThreadArchived)
}
methods::THREAD_CLOSED => serde_json::from_value(params_value).map(Self::ThreadClosed),
methods::THREAD_UNARCHIVED => {
serde_json::from_value(params_value).map(Self::ThreadUnarchived)
}
methods::THREAD_GOAL_CLEARED => {
serde_json::from_value(params_value).map(Self::ThreadGoalCleared)
}
methods::THREAD_NAME_UPDATED => {
serde_json::from_value(params_value).map(Self::ThreadNameUpdated)
}
methods::SKILLS_CHANGED => {
serde_json::from_value(params_value).map(Self::SkillsChanged)
}
methods::FS_CHANGED => serde_json::from_value(params_value).map(Self::FsChanged),
methods::CONFIG_WARNING => {
serde_json::from_value(params_value).map(Self::ConfigWarning)
}
methods::ACCOUNT_UPDATED => {
serde_json::from_value(params_value).map(Self::AccountUpdated)
}
methods::APP_LIST_UPDATED => {
serde_json::from_value(params_value).map(Self::AppListUpdated)
}
methods::COMMAND_EXEC_OUTPUT_DELTA => {
serde_json::from_value(params_value).map(Self::CommandExecOutputDelta)
}
methods::EXTERNAL_AGENT_CONFIG_IMPORT_COMPLETED => {
serde_json::from_value(params_value).map(Self::ExternalAgentConfigImportCompleted)
}
methods::FUZZY_FILE_SEARCH_SESSION_COMPLETED => {
serde_json::from_value(params_value).map(Self::FuzzyFileSearchSessionCompleted)
}
methods::FUZZY_FILE_SEARCH_SESSION_UPDATED => {
serde_json::from_value(params_value).map(Self::FuzzyFileSearchSessionUpdated)
}
methods::HOOK_COMPLETED => {
serde_json::from_value(params_value).map(Self::HookCompleted)
}
methods::HOOK_STARTED => serde_json::from_value(params_value).map(Self::HookStarted),
methods::ITEM_AUTO_APPROVAL_REVIEW_COMPLETED => {
serde_json::from_value(params_value).map(Self::ItemGuardianApprovalReviewCompleted)
}
methods::ITEM_AUTO_APPROVAL_REVIEW_STARTED => {
serde_json::from_value(params_value).map(Self::ItemGuardianApprovalReviewStarted)
}
methods::ITEM_COMMAND_EXEC_TERMINAL_INTERACTION => {
serde_json::from_value(params_value).map(Self::TerminalInteraction)
}
methods::ITEM_MCP_TOOL_CALL_PROGRESS => {
serde_json::from_value(params_value).map(Self::McpToolCallProgress)
}
methods::MODEL_REROUTED => {
serde_json::from_value(params_value).map(Self::ModelRerouted)
}
methods::MODEL_VERIFICATION => {
serde_json::from_value(params_value).map(Self::ModelVerification)
}
methods::PROCESS_EXITED => {
serde_json::from_value(params_value).map(Self::ProcessExited)
}
methods::PROCESS_OUTPUT_DELTA => {
serde_json::from_value(params_value).map(Self::ProcessOutputDelta)
}
methods::SERVER_REQUEST_RESOLVED => {
serde_json::from_value(params_value).map(Self::ServerRequestResolved)
}
methods::THREAD_COMPACTED => {
serde_json::from_value(params_value).map(Self::ContextCompacted)
}
methods::THREAD_GOAL_UPDATED => {
serde_json::from_value(params_value).map(Self::ThreadGoalUpdated)
}
methods::THREAD_REALTIME_CLOSED => {
serde_json::from_value(params_value).map(Self::ThreadRealtimeClosed)
}
methods::THREAD_REALTIME_ERROR => {
serde_json::from_value(params_value).map(Self::ThreadRealtimeError)
}
methods::THREAD_REALTIME_ITEM_ADDED => {
serde_json::from_value(params_value).map(Self::ThreadRealtimeItemAdded)
}
methods::THREAD_REALTIME_OUTPUT_AUDIO_DELTA => {
serde_json::from_value(params_value).map(Self::ThreadRealtimeOutputAudioDelta)
}
methods::THREAD_REALTIME_SDP => {
serde_json::from_value(params_value).map(Self::ThreadRealtimeSdp)
}
methods::THREAD_REALTIME_STARTED => {
serde_json::from_value(params_value).map(Self::ThreadRealtimeStarted)
}
methods::THREAD_REALTIME_TRANSCRIPT_DELTA => {
serde_json::from_value(params_value).map(Self::ThreadRealtimeTranscriptDelta)
}
methods::THREAD_REALTIME_TRANSCRIPT_DONE => {
serde_json::from_value(params_value).map(Self::ThreadRealtimeTranscriptDone)
}
methods::WINDOWS_WORLD_WRITABLE_WARNING => {
serde_json::from_value(params_value).map(Self::WindowsWorldWritableWarning)
}
methods::WINDOWS_SANDBOX_SETUP_COMPLETED => {
serde_json::from_value(params_value).map(Self::WindowsSandboxSetupCompleted)
}
_ => Ok(Self::Unknown {
method: method.to_string(),
params,
}),
}
}
pub fn into_envelope(self) -> Result<(String, Option<Value>), serde_json::Error> {
fn pack<T: Serialize>(
method: &str,
v: &T,
) -> Result<(String, Option<Value>), serde_json::Error> {
Ok((method.to_string(), Some(serde_json::to_value(v)?)))
}
match &self {
Self::ThreadStarted(v) => pack(methods::THREAD_STARTED, v),
Self::ThreadStatusChanged(v) => pack(methods::THREAD_STATUS_CHANGED, v),
Self::ThreadTokenUsageUpdated(v) => pack(methods::THREAD_TOKEN_USAGE_UPDATED, v),
Self::TurnStarted(v) => pack(methods::TURN_STARTED, v),
Self::TurnCompleted(v) => pack(methods::TURN_COMPLETED, v),
Self::ItemStarted(v) => pack(methods::ITEM_STARTED, v),
Self::ItemCompleted(v) => pack(methods::ITEM_COMPLETED, v),
Self::AgentMessageDelta(v) => pack(methods::AGENT_MESSAGE_DELTA, v),
Self::CmdOutputDelta(v) => pack(methods::CMD_OUTPUT_DELTA, v),
Self::FileChangeOutputDelta(v) => pack(methods::FILE_CHANGE_OUTPUT_DELTA, v),
Self::ReasoningDelta(v) => pack(methods::REASONING_DELTA, v),
Self::Error(v) => pack(methods::ERROR, v),
Self::AccountRateLimitsUpdated(v) => pack(methods::ACCOUNT_RATE_LIMITS_UPDATED, v),
Self::McpServerStartupStatusUpdated(v) => {
pack(methods::MCP_SERVER_STARTUP_STATUS_UPDATED, v)
}
Self::RemoteControlStatusChanged(v) => pack(methods::REMOTE_CONTROL_STATUS_CHANGED, v),
Self::McpServerOauthLoginCompleted(v) => {
pack(methods::MCP_SERVER_OAUTH_LOGIN_COMPLETED, v)
}
Self::FileChangePatchUpdated(v) => pack(methods::FILE_CHANGE_PATCH_UPDATED, v),
Self::PlanDelta(v) => pack(methods::PLAN_DELTA, v),
Self::TurnPlanUpdated(v) => pack(methods::TURN_PLAN_UPDATED, v),
Self::TurnDiffUpdated(v) => pack(methods::TURN_DIFF_UPDATED, v),
Self::ReasoningSummaryPartAdded(v) => pack(methods::REASONING_SUMMARY_PART_ADDED, v),
Self::ReasoningTextDelta(v) => pack(methods::REASONING_TEXT_DELTA, v),
Self::AccountLoginCompleted(v) => pack(methods::ACCOUNT_LOGIN_COMPLETED, v),
Self::DeprecationNotice(v) => pack(methods::DEPRECATION_NOTICE, v),
Self::GuardianWarning(v) => pack(methods::GUARDIAN_WARNING, v),
Self::Warning(v) => pack(methods::WARNING, v),
Self::ThreadArchived(v) => pack(methods::THREAD_ARCHIVED, v),
Self::ThreadClosed(v) => pack(methods::THREAD_CLOSED, v),
Self::ThreadUnarchived(v) => pack(methods::THREAD_UNARCHIVED, v),
Self::ThreadGoalCleared(v) => pack(methods::THREAD_GOAL_CLEARED, v),
Self::ThreadNameUpdated(v) => pack(methods::THREAD_NAME_UPDATED, v),
Self::SkillsChanged(v) => pack(methods::SKILLS_CHANGED, v),
Self::FsChanged(v) => pack(methods::FS_CHANGED, v),
Self::ConfigWarning(v) => pack(methods::CONFIG_WARNING, v),
Self::AccountUpdated(v) => pack(methods::ACCOUNT_UPDATED, v),
Self::AppListUpdated(v) => pack(methods::APP_LIST_UPDATED, v),
Self::CommandExecOutputDelta(v) => pack(methods::COMMAND_EXEC_OUTPUT_DELTA, v),
Self::ExternalAgentConfigImportCompleted(v) => {
pack(methods::EXTERNAL_AGENT_CONFIG_IMPORT_COMPLETED, v)
}
Self::FuzzyFileSearchSessionCompleted(v) => {
pack(methods::FUZZY_FILE_SEARCH_SESSION_COMPLETED, v)
}
Self::FuzzyFileSearchSessionUpdated(v) => {
pack(methods::FUZZY_FILE_SEARCH_SESSION_UPDATED, v)
}
Self::HookCompleted(v) => pack(methods::HOOK_COMPLETED, v),
Self::HookStarted(v) => pack(methods::HOOK_STARTED, v),
Self::ItemGuardianApprovalReviewCompleted(v) => {
pack(methods::ITEM_AUTO_APPROVAL_REVIEW_COMPLETED, v)
}
Self::ItemGuardianApprovalReviewStarted(v) => {
pack(methods::ITEM_AUTO_APPROVAL_REVIEW_STARTED, v)
}
Self::TerminalInteraction(v) => {
pack(methods::ITEM_COMMAND_EXEC_TERMINAL_INTERACTION, v)
}
Self::McpToolCallProgress(v) => pack(methods::ITEM_MCP_TOOL_CALL_PROGRESS, v),
Self::ModelRerouted(v) => pack(methods::MODEL_REROUTED, v),
Self::ModelVerification(v) => pack(methods::MODEL_VERIFICATION, v),
Self::ProcessExited(v) => pack(methods::PROCESS_EXITED, v),
Self::ProcessOutputDelta(v) => pack(methods::PROCESS_OUTPUT_DELTA, v),
Self::ServerRequestResolved(v) => pack(methods::SERVER_REQUEST_RESOLVED, v),
Self::ContextCompacted(v) => pack(methods::THREAD_COMPACTED, v),
Self::ThreadGoalUpdated(v) => pack(methods::THREAD_GOAL_UPDATED, v),
Self::ThreadRealtimeClosed(v) => pack(methods::THREAD_REALTIME_CLOSED, v),
Self::ThreadRealtimeError(v) => pack(methods::THREAD_REALTIME_ERROR, v),
Self::ThreadRealtimeItemAdded(v) => pack(methods::THREAD_REALTIME_ITEM_ADDED, v),
Self::ThreadRealtimeOutputAudioDelta(v) => {
pack(methods::THREAD_REALTIME_OUTPUT_AUDIO_DELTA, v)
}
Self::ThreadRealtimeSdp(v) => pack(methods::THREAD_REALTIME_SDP, v),
Self::ThreadRealtimeStarted(v) => pack(methods::THREAD_REALTIME_STARTED, v),
Self::ThreadRealtimeTranscriptDelta(v) => {
pack(methods::THREAD_REALTIME_TRANSCRIPT_DELTA, v)
}
Self::ThreadRealtimeTranscriptDone(v) => {
pack(methods::THREAD_REALTIME_TRANSCRIPT_DONE, v)
}
Self::WindowsWorldWritableWarning(v) => {
pack(methods::WINDOWS_WORLD_WRITABLE_WARNING, v)
}
Self::WindowsSandboxSetupCompleted(v) => {
pack(methods::WINDOWS_SANDBOX_SETUP_COMPLETED, v)
}
Self::Unknown { method, params } => Ok((method.clone(), params.clone())),
}
}
}
impl Serialize for Notification {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let (method, params) = self
.clone()
.into_envelope()
.map_err(serde::ser::Error::custom)?;
let mut env = serde_json::Map::new();
env.insert("method".to_string(), Value::String(method));
if let Some(p) = params {
env.insert("params".to_string(), p);
}
Value::Object(env).serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Notification {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value = Value::deserialize(deserializer)?;
let method = value
.get("method")
.and_then(|v| v.as_str())
.ok_or_else(|| serde::de::Error::missing_field("method"))?
.to_string();
let params = value.get("params").cloned();
Self::from_envelope(&method, params).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone)]
pub enum ServerRequest {
CmdExecApproval(CommandExecutionApprovalParams),
FileChangeApproval(FileChangeApprovalParams),
Unknown {
method: String,
params: Option<Value>,
},
}
impl ServerRequest {
pub fn method(&self) -> &str {
match self {
Self::CmdExecApproval(_) => methods::CMD_EXEC_APPROVAL,
Self::FileChangeApproval(_) => methods::FILE_CHANGE_APPROVAL,
Self::Unknown { method, .. } => method,
}
}
pub fn is_unknown(&self) -> bool {
matches!(self, Self::Unknown { .. })
}
pub fn from_envelope(method: &str, params: Option<Value>) -> Result<Self, serde_json::Error> {
let params_value = params.clone().unwrap_or(Value::Null);
match method {
methods::CMD_EXEC_APPROVAL => {
serde_json::from_value(params_value).map(Self::CmdExecApproval)
}
methods::FILE_CHANGE_APPROVAL => {
serde_json::from_value(params_value).map(Self::FileChangeApproval)
}
_ => Ok(Self::Unknown {
method: method.to_string(),
params,
}),
}
}
}
#[derive(Debug, Clone)]
pub enum ServerMessage {
Notification(Notification),
Request {
id: RequestId,
request: ServerRequest,
},
}
impl ServerMessage {
pub fn is_unknown(&self) -> bool {
match self {
Self::Notification(n) => n.is_unknown(),
Self::Request { request, .. } => request.is_unknown(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_notification_unknown_method_routes_to_unknown_variant() {
let n = Notification::from_envelope("foo/bar", Some(serde_json::json!({"x": 1})))
.expect("unknown methods do not error");
match n {
Notification::Unknown { method, params } => {
assert_eq!(method, "foo/bar");
assert_eq!(params, Some(serde_json::json!({"x": 1})));
}
other => panic!("expected Unknown, got {:?}", other),
}
}
#[test]
fn test_notification_known_method_with_bad_params_errors() {
let err = Notification::from_envelope("thread/started", Some(serde_json::json!({})));
assert!(err.is_err());
}
#[test]
fn test_notification_round_trip_envelope() {
let wire = serde_json::json!({
"method": "item/agentMessage/delta",
"params": {"threadId": "t1", "itemId": "i1", "delta": "hi"},
});
let n: Notification = serde_json::from_value(wire.clone()).unwrap();
assert!(matches!(n, Notification::AgentMessageDelta(_)));
let back = serde_json::to_value(&n).unwrap();
assert_eq!(back, wire);
}
}