1use std::rc::Rc;
7use std::{fmt, path::PathBuf, sync::Arc};
8
9use anyhow::Result;
10use schemars::JsonSchema;
11use serde::{Deserialize, Serialize};
12use serde_json::value::RawValue;
13
14use crate::ext::ExtRequest;
15use crate::{ContentBlock, Error, ExtNotification, Plan, SessionId, ToolCall, ToolCallUpdate};
16use crate::{ExtResponse, SessionModeId};
17
18#[async_trait::async_trait(?Send)]
24pub trait Client {
25 async fn request_permission(
36 &self,
37 args: RequestPermissionRequest,
38 ) -> Result<RequestPermissionResponse, Error>;
39
40 async fn session_notification(&self, args: SessionNotification) -> Result<(), Error>;
52
53 async fn write_text_file(
60 &self,
61 _args: WriteTextFileRequest,
62 ) -> Result<WriteTextFileResponse, Error> {
63 Err(Error::method_not_found())
64 }
65
66 async fn read_text_file(
73 &self,
74 _args: ReadTextFileRequest,
75 ) -> Result<ReadTextFileResponse, Error> {
76 Err(Error::method_not_found())
77 }
78
79 async fn create_terminal(
94 &self,
95 _args: CreateTerminalRequest,
96 ) -> Result<CreateTerminalResponse, Error> {
97 Err(Error::method_not_found())
98 }
99
100 async fn terminal_output(
107 &self,
108 _args: TerminalOutputRequest,
109 ) -> Result<TerminalOutputResponse, Error> {
110 Err(Error::method_not_found())
111 }
112
113 async fn release_terminal(
126 &self,
127 _args: ReleaseTerminalRequest,
128 ) -> Result<ReleaseTerminalResponse, Error> {
129 Err(Error::method_not_found())
130 }
131
132 async fn wait_for_terminal_exit(
136 &self,
137 _args: WaitForTerminalExitRequest,
138 ) -> Result<WaitForTerminalExitResponse, Error> {
139 Err(Error::method_not_found())
140 }
141
142 async fn kill_terminal_command(
155 &self,
156 _args: KillTerminalCommandRequest,
157 ) -> Result<KillTerminalCommandResponse, Error> {
158 Err(Error::method_not_found())
159 }
160
161 async fn ext_method(&self, _args: ExtRequest) -> Result<ExtResponse, Error> {
169 Ok(RawValue::NULL.to_owned().into())
170 }
171
172 async fn ext_notification(&self, _args: ExtNotification) -> Result<(), Error> {
180 Ok(())
181 }
182}
183
184#[async_trait::async_trait(?Send)]
185impl<T: Client> Client for Rc<T> {
186 async fn request_permission(
187 &self,
188 args: RequestPermissionRequest,
189 ) -> Result<RequestPermissionResponse, Error> {
190 self.as_ref().request_permission(args).await
191 }
192 async fn write_text_file(
193 &self,
194 args: WriteTextFileRequest,
195 ) -> Result<WriteTextFileResponse, Error> {
196 self.as_ref().write_text_file(args).await
197 }
198 async fn read_text_file(
199 &self,
200 args: ReadTextFileRequest,
201 ) -> Result<ReadTextFileResponse, Error> {
202 self.as_ref().read_text_file(args).await
203 }
204 async fn session_notification(&self, args: SessionNotification) -> Result<(), Error> {
205 self.as_ref().session_notification(args).await
206 }
207 async fn create_terminal(
208 &self,
209 args: CreateTerminalRequest,
210 ) -> Result<CreateTerminalResponse, Error> {
211 self.as_ref().create_terminal(args).await
212 }
213 async fn terminal_output(
214 &self,
215 args: TerminalOutputRequest,
216 ) -> Result<TerminalOutputResponse, Error> {
217 self.as_ref().terminal_output(args).await
218 }
219 async fn release_terminal(
220 &self,
221 args: ReleaseTerminalRequest,
222 ) -> Result<ReleaseTerminalResponse, Error> {
223 self.as_ref().release_terminal(args).await
224 }
225 async fn wait_for_terminal_exit(
226 &self,
227 args: WaitForTerminalExitRequest,
228 ) -> Result<WaitForTerminalExitResponse, Error> {
229 self.as_ref().wait_for_terminal_exit(args).await
230 }
231 async fn kill_terminal_command(
232 &self,
233 args: KillTerminalCommandRequest,
234 ) -> Result<KillTerminalCommandResponse, Error> {
235 self.as_ref().kill_terminal_command(args).await
236 }
237 async fn ext_method(&self, args: ExtRequest) -> Result<ExtResponse, Error> {
238 self.as_ref().ext_method(args).await
239 }
240 async fn ext_notification(&self, args: ExtNotification) -> Result<(), Error> {
241 self.as_ref().ext_notification(args).await
242 }
243}
244
245#[async_trait::async_trait(?Send)]
246impl<T: Client> Client for Arc<T> {
247 async fn request_permission(
248 &self,
249 args: RequestPermissionRequest,
250 ) -> Result<RequestPermissionResponse, Error> {
251 self.as_ref().request_permission(args).await
252 }
253 async fn write_text_file(
254 &self,
255 args: WriteTextFileRequest,
256 ) -> Result<WriteTextFileResponse, Error> {
257 self.as_ref().write_text_file(args).await
258 }
259 async fn read_text_file(
260 &self,
261 args: ReadTextFileRequest,
262 ) -> Result<ReadTextFileResponse, Error> {
263 self.as_ref().read_text_file(args).await
264 }
265 async fn session_notification(&self, args: SessionNotification) -> Result<(), Error> {
266 self.as_ref().session_notification(args).await
267 }
268 async fn create_terminal(
269 &self,
270 args: CreateTerminalRequest,
271 ) -> Result<CreateTerminalResponse, Error> {
272 self.as_ref().create_terminal(args).await
273 }
274 async fn terminal_output(
275 &self,
276 args: TerminalOutputRequest,
277 ) -> Result<TerminalOutputResponse, Error> {
278 self.as_ref().terminal_output(args).await
279 }
280 async fn release_terminal(
281 &self,
282 args: ReleaseTerminalRequest,
283 ) -> Result<ReleaseTerminalResponse, Error> {
284 self.as_ref().release_terminal(args).await
285 }
286 async fn wait_for_terminal_exit(
287 &self,
288 args: WaitForTerminalExitRequest,
289 ) -> Result<WaitForTerminalExitResponse, Error> {
290 self.as_ref().wait_for_terminal_exit(args).await
291 }
292 async fn kill_terminal_command(
293 &self,
294 args: KillTerminalCommandRequest,
295 ) -> Result<KillTerminalCommandResponse, Error> {
296 self.as_ref().kill_terminal_command(args).await
297 }
298 async fn ext_method(&self, args: ExtRequest) -> Result<ExtResponse, Error> {
299 self.as_ref().ext_method(args).await
300 }
301 async fn ext_notification(&self, args: ExtNotification) -> Result<(), Error> {
302 self.as_ref().ext_notification(args).await
303 }
304}
305
306#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
314#[schemars(extend("x-side" = "client", "x-method" = SESSION_UPDATE_NOTIFICATION))]
315#[serde(rename_all = "camelCase")]
316pub struct SessionNotification {
317 pub session_id: SessionId,
319 pub update: SessionUpdate,
321 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
323 pub meta: Option<serde_json::Value>,
324}
325
326#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
332#[serde(rename_all = "snake_case", tag = "sessionUpdate")]
333pub enum SessionUpdate {
334 UserMessageChunk { content: ContentBlock },
336 AgentMessageChunk { content: ContentBlock },
338 AgentThoughtChunk { content: ContentBlock },
340 ToolCall(ToolCall),
342 ToolCallUpdate(ToolCallUpdate),
344 Plan(Plan),
347 #[serde(rename_all = "camelCase")]
349 AvailableCommandsUpdate {
350 available_commands: Vec<AvailableCommand>,
351 },
352 #[serde(rename_all = "camelCase")]
356 CurrentModeUpdate { current_mode_id: SessionModeId },
357}
358
359#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
361#[serde(rename_all = "camelCase")]
362pub struct AvailableCommand {
363 pub name: String,
365 pub description: String,
367 pub input: Option<AvailableCommandInput>,
369 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
371 pub meta: Option<serde_json::Value>,
372}
373
374#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
376#[serde(untagged, rename_all = "camelCase")]
377pub enum AvailableCommandInput {
378 #[schemars(rename = "UnstructuredCommandInput")]
380 Unstructured {
381 hint: String,
383 },
384}
385
386#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
394#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
395#[serde(rename_all = "camelCase")]
396pub struct RequestPermissionRequest {
397 pub session_id: SessionId,
399 pub tool_call: ToolCallUpdate,
401 pub options: Vec<PermissionOption>,
403 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
405 pub meta: Option<serde_json::Value>,
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
410pub struct PermissionOption {
411 #[serde(rename = "optionId")]
413 pub id: PermissionOptionId,
414 pub name: String,
416 pub kind: PermissionOptionKind,
418 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
420 pub meta: Option<serde_json::Value>,
421}
422
423#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
425#[serde(transparent)]
426pub struct PermissionOptionId(pub Arc<str>);
427
428impl fmt::Display for PermissionOptionId {
429 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430 self.0.fmt(f)
431 }
432}
433
434#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
438#[serde(rename_all = "snake_case")]
439pub enum PermissionOptionKind {
440 AllowOnce,
442 AllowAlways,
444 RejectOnce,
446 RejectAlways,
448}
449
450#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
452#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
453#[serde(rename_all = "camelCase")]
454pub struct RequestPermissionResponse {
455 pub outcome: RequestPermissionOutcome,
458 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
460 pub meta: Option<serde_json::Value>,
461}
462
463#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
465#[serde(tag = "outcome", rename_all = "snake_case")]
466pub enum RequestPermissionOutcome {
467 Cancelled,
475 #[serde(rename_all = "camelCase")]
477 Selected {
478 option_id: PermissionOptionId,
480 },
481}
482
483#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
489#[schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME))]
490#[serde(rename_all = "camelCase")]
491pub struct WriteTextFileRequest {
492 pub session_id: SessionId,
494 pub path: PathBuf,
496 pub content: String,
498 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
500 pub meta: Option<serde_json::Value>,
501}
502
503#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
505#[serde(rename_all = "camelCase")]
506#[schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME))]
507#[serde(default)]
508pub struct WriteTextFileResponse {
509 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
511 pub meta: Option<serde_json::Value>,
512}
513
514#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
520#[schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME))]
521#[serde(rename_all = "camelCase")]
522pub struct ReadTextFileRequest {
523 pub session_id: SessionId,
525 pub path: PathBuf,
527 #[serde(default, skip_serializing_if = "Option::is_none")]
529 pub line: Option<u32>,
530 #[serde(default, skip_serializing_if = "Option::is_none")]
532 pub limit: Option<u32>,
533 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
535 pub meta: Option<serde_json::Value>,
536}
537
538#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
540#[schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME))]
541#[serde(rename_all = "camelCase")]
542pub struct ReadTextFileResponse {
543 pub content: String,
544 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
546 pub meta: Option<serde_json::Value>,
547}
548
549#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
552#[serde(transparent)]
553pub struct TerminalId(pub Arc<str>);
554
555impl std::fmt::Display for TerminalId {
556 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
557 write!(f, "{}", self.0)
558 }
559}
560
561#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
563#[serde(rename_all = "camelCase")]
564#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME))]
565pub struct CreateTerminalRequest {
566 pub session_id: SessionId,
568 pub command: String,
570 #[serde(default, skip_serializing_if = "Vec::is_empty")]
572 pub args: Vec<String>,
573 #[serde(default, skip_serializing_if = "Vec::is_empty")]
575 pub env: Vec<crate::EnvVariable>,
576 #[serde(default, skip_serializing_if = "Option::is_none")]
578 pub cwd: Option<PathBuf>,
579 #[serde(default, skip_serializing_if = "Option::is_none")]
588 pub output_byte_limit: Option<u64>,
589 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
591 pub meta: Option<serde_json::Value>,
592}
593
594#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
596#[serde(rename_all = "camelCase")]
597#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME))]
598pub struct CreateTerminalResponse {
599 pub terminal_id: TerminalId,
601 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
603 pub meta: Option<serde_json::Value>,
604}
605
606#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
608#[serde(rename_all = "camelCase")]
609#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME))]
610pub struct TerminalOutputRequest {
611 pub session_id: SessionId,
613 pub terminal_id: TerminalId,
615 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
617 pub meta: Option<serde_json::Value>,
618}
619
620#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
622#[serde(rename_all = "camelCase")]
623#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME))]
624pub struct TerminalOutputResponse {
625 pub output: String,
627 pub truncated: bool,
629 pub exit_status: Option<TerminalExitStatus>,
631 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
633 pub meta: Option<serde_json::Value>,
634}
635
636#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
638#[serde(rename_all = "camelCase")]
639#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME))]
640pub struct ReleaseTerminalRequest {
641 pub session_id: SessionId,
643 pub terminal_id: TerminalId,
645 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
647 pub meta: Option<serde_json::Value>,
648}
649
650#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
652#[serde(rename_all = "camelCase")]
653#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME))]
654pub struct ReleaseTerminalResponse {
655 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
657 pub meta: Option<serde_json::Value>,
658}
659
660#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
662#[serde(rename_all = "camelCase")]
663#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME))]
664pub struct KillTerminalCommandRequest {
665 pub session_id: SessionId,
667 pub terminal_id: TerminalId,
669 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
671 pub meta: Option<serde_json::Value>,
672}
673
674#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
676#[serde(rename_all = "camelCase")]
677#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME))]
678pub struct KillTerminalCommandResponse {
679 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
681 pub meta: Option<serde_json::Value>,
682}
683
684#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
686#[serde(rename_all = "camelCase")]
687#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME))]
688pub struct WaitForTerminalExitRequest {
689 pub session_id: SessionId,
691 pub terminal_id: TerminalId,
693 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
695 pub meta: Option<serde_json::Value>,
696}
697
698#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
700#[serde(rename_all = "camelCase")]
701#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME))]
702pub struct WaitForTerminalExitResponse {
703 #[serde(flatten)]
705 pub exit_status: TerminalExitStatus,
706 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
708 pub meta: Option<serde_json::Value>,
709}
710
711#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
713#[serde(rename_all = "camelCase")]
714pub struct TerminalExitStatus {
715 pub exit_code: Option<u32>,
717 pub signal: Option<String>,
719 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
721 pub meta: Option<serde_json::Value>,
722}
723
724#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
733#[serde(rename_all = "camelCase")]
734pub struct ClientCapabilities {
735 #[serde(default)]
738 pub fs: FileSystemCapability,
739 #[serde(default)]
741 pub terminal: bool,
742 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
744 pub meta: Option<serde_json::Value>,
745}
746
747#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
751#[serde(rename_all = "camelCase")]
752pub struct FileSystemCapability {
753 #[serde(default)]
755 pub read_text_file: bool,
756 #[serde(default)]
758 pub write_text_file: bool,
759 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
761 pub meta: Option<serde_json::Value>,
762}
763
764#[derive(Debug, Clone, Serialize, Deserialize)]
770pub struct ClientMethodNames {
771 pub session_request_permission: &'static str,
773 pub session_update: &'static str,
775 pub fs_write_text_file: &'static str,
777 pub fs_read_text_file: &'static str,
779 pub terminal_create: &'static str,
781 pub terminal_output: &'static str,
783 pub terminal_release: &'static str,
785 pub terminal_wait_for_exit: &'static str,
787 pub terminal_kill: &'static str,
789}
790
791pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
793 session_update: SESSION_UPDATE_NOTIFICATION,
794 session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
795 fs_write_text_file: FS_WRITE_TEXT_FILE_METHOD_NAME,
796 fs_read_text_file: FS_READ_TEXT_FILE_METHOD_NAME,
797 terminal_create: TERMINAL_CREATE_METHOD_NAME,
798 terminal_output: TERMINAL_OUTPUT_METHOD_NAME,
799 terminal_release: TERMINAL_RELEASE_METHOD_NAME,
800 terminal_wait_for_exit: TERMINAL_WAIT_FOR_EXIT_METHOD_NAME,
801 terminal_kill: TERMINAL_KILL_METHOD_NAME,
802};
803
804pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
806pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
808pub(crate) const FS_WRITE_TEXT_FILE_METHOD_NAME: &str = "fs/write_text_file";
810pub(crate) const FS_READ_TEXT_FILE_METHOD_NAME: &str = "fs/read_text_file";
812pub(crate) const TERMINAL_CREATE_METHOD_NAME: &str = "terminal/create";
814pub(crate) const TERMINAL_OUTPUT_METHOD_NAME: &str = "terminal/output";
816pub(crate) const TERMINAL_RELEASE_METHOD_NAME: &str = "terminal/release";
818pub(crate) const TERMINAL_WAIT_FOR_EXIT_METHOD_NAME: &str = "terminal/wait_for_exit";
820pub(crate) const TERMINAL_KILL_METHOD_NAME: &str = "terminal/kill";
822
823#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
830#[serde(untagged)]
831#[schemars(extend("x-docs-ignore" = true))]
832pub enum AgentRequest {
833 WriteTextFileRequest(WriteTextFileRequest),
834 ReadTextFileRequest(ReadTextFileRequest),
835 RequestPermissionRequest(RequestPermissionRequest),
836 CreateTerminalRequest(CreateTerminalRequest),
837 TerminalOutputRequest(TerminalOutputRequest),
838 ReleaseTerminalRequest(ReleaseTerminalRequest),
839 WaitForTerminalExitRequest(WaitForTerminalExitRequest),
840 KillTerminalCommandRequest(KillTerminalCommandRequest),
841 ExtMethodRequest(ExtRequest),
842}
843
844#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
851#[serde(untagged)]
852#[schemars(extend("x-docs-ignore" = true))]
853pub enum ClientResponse {
854 WriteTextFileResponse(#[serde(default)] WriteTextFileResponse),
855 ReadTextFileResponse(ReadTextFileResponse),
856 RequestPermissionResponse(RequestPermissionResponse),
857 CreateTerminalResponse(CreateTerminalResponse),
858 TerminalOutputResponse(TerminalOutputResponse),
859 ReleaseTerminalResponse(#[serde(default)] ReleaseTerminalResponse),
860 WaitForTerminalExitResponse(WaitForTerminalExitResponse),
861 KillTerminalResponse(#[serde(default)] KillTerminalCommandResponse),
862 ExtMethodResponse(#[schemars(with = "serde_json::Value")] Arc<RawValue>),
863}
864
865#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
872#[serde(untagged)]
873#[allow(clippy::large_enum_variant)]
874#[schemars(extend("x-docs-ignore" = true))]
875pub enum AgentNotification {
876 SessionNotification(SessionNotification),
877 ExtNotification(ExtNotification),
878}