1use std::path::Path;
2
3use crate::JSONRPCNotification;
4use crate::JSONRPCRequest;
5use crate::RequestId;
6use crate::export::GeneratedSchema;
7use crate::export::write_json_schema;
8use crate::protocol::v1;
9use crate::protocol::v2;
10use schemars::JsonSchema;
11use serde::Deserialize;
12use serde::Serialize;
13use strum_macros::Display;
14use ts_rs::TS;
15
16#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, TS)]
17#[ts(type = "string")]
18pub struct GitSha(pub String);
19
20impl GitSha {
21 pub fn new(sha: &str) -> Self {
22 Self(sha.to_string())
23 }
24}
25
26#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS)]
27#[serde(rename_all = "lowercase")]
28pub enum AuthMode {
29 ApiKey,
30 ChatGPT,
31}
32
33macro_rules! client_request_definitions {
38 (
39 $(
40 $(#[$variant_meta:meta])*
41 $variant:ident $(=> $wire:literal)? {
42 params: $(#[$params_meta:meta])* $params:ty,
43 response: $response:ty,
44 }
45 ),* $(,)?
46 ) => {
47 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
49 #[serde(tag = "method", rename_all = "camelCase")]
50 pub enum ClientRequest {
51 $(
52 $(#[$variant_meta])*
53 $(#[serde(rename = $wire)] #[ts(rename = $wire)])?
54 $variant {
55 #[serde(rename = "id")]
56 request_id: RequestId,
57 $(#[$params_meta])*
58 params: $params,
59 },
60 )*
61 }
62
63 pub fn export_client_responses(
64 out_dir: &::std::path::Path,
65 ) -> ::std::result::Result<(), ::ts_rs::ExportError> {
66 $(
67 <$response as ::ts_rs::TS>::export_all_to(out_dir)?;
68 )*
69 Ok(())
70 }
71
72 #[allow(clippy::vec_init_then_push)]
73 pub fn export_client_response_schemas(
74 out_dir: &::std::path::Path,
75 ) -> ::anyhow::Result<Vec<GeneratedSchema>> {
76 let mut schemas = Vec::new();
77 $(
78 schemas.push(write_json_schema::<$response>(out_dir, stringify!($response))?);
79 )*
80 Ok(schemas)
81 }
82
83 #[allow(clippy::vec_init_then_push)]
84 pub fn export_client_param_schemas(
85 out_dir: &::std::path::Path,
86 ) -> ::anyhow::Result<Vec<GeneratedSchema>> {
87 let mut schemas = Vec::new();
88 $(
89 schemas.push(write_json_schema::<$params>(out_dir, stringify!($params))?);
90 )*
91 Ok(schemas)
92 }
93 };
94}
95
96client_request_definitions! {
97 Initialize {
98 params: v1::InitializeParams,
99 response: v1::InitializeResponse,
100 },
101
102 ThreadStart => "thread/start" {
105 params: v2::ThreadStartParams,
106 response: v2::ThreadStartResponse,
107 },
108 ThreadResume => "thread/resume" {
109 params: v2::ThreadResumeParams,
110 response: v2::ThreadResumeResponse,
111 },
112 ThreadArchive => "thread/archive" {
113 params: v2::ThreadArchiveParams,
114 response: v2::ThreadArchiveResponse,
115 },
116 ThreadList => "thread/list" {
117 params: v2::ThreadListParams,
118 response: v2::ThreadListResponse,
119 },
120 ThreadCompact => "thread/compact" {
121 params: v2::ThreadCompactParams,
122 response: v2::ThreadCompactResponse,
123 },
124 TurnStart => "turn/start" {
125 params: v2::TurnStartParams,
126 response: v2::TurnStartResponse,
127 },
128 TurnInterrupt => "turn/interrupt" {
129 params: v2::TurnInterruptParams,
130 response: v2::TurnInterruptResponse,
131 },
132 ReviewStart => "review/start" {
133 params: v2::ReviewStartParams,
134 response: v2::TurnStartResponse,
135 },
136
137 ModelList => "model/list" {
138 params: v2::ModelListParams,
139 response: v2::ModelListResponse,
140 },
141
142 LoginAccount => "account/login/start" {
143 params: v2::LoginAccountParams,
144 response: v2::LoginAccountResponse,
145 },
146
147 CancelLoginAccount => "account/login/cancel" {
148 params: v2::CancelLoginAccountParams,
149 response: v2::CancelLoginAccountResponse,
150 },
151
152 LogoutAccount => "account/logout" {
153 params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
154 response: v2::LogoutAccountResponse,
155 },
156
157 GetAccountRateLimits => "account/rateLimits/read" {
158 params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
159 response: v2::GetAccountRateLimitsResponse,
160 },
161
162 FeedbackUpload => "feedback/upload" {
163 params: v2::FeedbackUploadParams,
164 response: v2::FeedbackUploadResponse,
165 },
166
167 GetAccount => "account/read" {
168 params: v2::GetAccountParams,
169 response: v2::GetAccountResponse,
170 },
171
172 NewConversation {
174 params: v1::NewConversationParams,
175 response: v1::NewConversationResponse,
176 },
177 GetConversationSummary {
178 params: v1::GetConversationSummaryParams,
179 response: v1::GetConversationSummaryResponse,
180 },
181 ListConversations {
183 params: v1::ListConversationsParams,
184 response: v1::ListConversationsResponse,
185 },
186 ResumeConversation {
188 params: v1::ResumeConversationParams,
189 response: v1::ResumeConversationResponse,
190 },
191 ArchiveConversation {
192 params: v1::ArchiveConversationParams,
193 response: v1::ArchiveConversationResponse,
194 },
195 SendUserMessage {
196 params: v1::SendUserMessageParams,
197 response: v1::SendUserMessageResponse,
198 },
199 SendUserTurn {
200 params: v1::SendUserTurnParams,
201 response: v1::SendUserTurnResponse,
202 },
203 InterruptConversation {
204 params: v1::InterruptConversationParams,
205 response: v1::InterruptConversationResponse,
206 },
207 AddConversationListener {
208 params: v1::AddConversationListenerParams,
209 response: v1::AddConversationSubscriptionResponse,
210 },
211 RemoveConversationListener {
212 params: v1::RemoveConversationListenerParams,
213 response: v1::RemoveConversationSubscriptionResponse,
214 },
215 GitDiffToRemote {
216 params: v1::GitDiffToRemoteParams,
217 response: v1::GitDiffToRemoteResponse,
218 },
219 LoginApiKey {
220 params: v1::LoginApiKeyParams,
221 response: v1::LoginApiKeyResponse,
222 },
223 LoginChatGpt {
224 params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
225 response: v1::LoginChatGptResponse,
226 },
227 CancelLoginChatGpt {
229 params: v1::CancelLoginChatGptParams,
230 response: v1::CancelLoginChatGptResponse,
231 },
232 LogoutChatGpt {
233 params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
234 response: v1::LogoutChatGptResponse,
235 },
236 GetAuthStatus {
238 params: v1::GetAuthStatusParams,
239 response: v1::GetAuthStatusResponse,
240 },
241 GetUserSavedConfig {
242 params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
243 response: v1::GetUserSavedConfigResponse,
244 },
245 SetDefaultModel {
246 params: v1::SetDefaultModelParams,
247 response: v1::SetDefaultModelResponse,
248 },
249 GetUserAgent {
250 params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
251 response: v1::GetUserAgentResponse,
252 },
253 UserInfo {
254 params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
255 response: v1::UserInfoResponse,
256 },
257 FuzzyFileSearch {
258 params: FuzzyFileSearchParams,
259 response: FuzzyFileSearchResponse,
260 },
261 ExecOneOffCommand {
263 params: v1::ExecOneOffCommandParams,
264 response: v1::ExecOneOffCommandResponse,
265 },
266}
267
268macro_rules! server_request_definitions {
273 (
274 $(
275 $(#[$variant_meta:meta])*
276 $variant:ident $(=> $wire:literal)? {
277 params: $params:ty,
278 response: $response:ty,
279 }
280 ),* $(,)?
281 ) => {
282 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
284 #[serde(tag = "method", rename_all = "camelCase")]
285 pub enum ServerRequest {
286 $(
287 $(#[$variant_meta])*
288 $(#[serde(rename = $wire)] #[ts(rename = $wire)])?
289 $variant {
290 #[serde(rename = "id")]
291 request_id: RequestId,
292 params: $params,
293 },
294 )*
295 }
296
297 #[derive(Debug, Clone, PartialEq, JsonSchema)]
298 pub enum ServerRequestPayload {
299 $( $variant($params), )*
300 }
301
302 impl ServerRequestPayload {
303 pub fn request_with_id(self, request_id: RequestId) -> ServerRequest {
304 match self {
305 $(Self::$variant(params) => ServerRequest::$variant { request_id, params },)*
306 }
307 }
308 }
309
310 pub fn export_server_responses(
311 out_dir: &::std::path::Path,
312 ) -> ::std::result::Result<(), ::ts_rs::ExportError> {
313 $(
314 <$response as ::ts_rs::TS>::export_all_to(out_dir)?;
315 )*
316 Ok(())
317 }
318
319 #[allow(clippy::vec_init_then_push)]
320 pub fn export_server_response_schemas(
321 out_dir: &Path,
322 ) -> ::anyhow::Result<Vec<GeneratedSchema>> {
323 let mut schemas = Vec::new();
324 $(
325 schemas.push(crate::export::write_json_schema::<$response>(
326 out_dir,
327 concat!(stringify!($variant), "Response"),
328 )?);
329 )*
330 Ok(schemas)
331 }
332
333 #[allow(clippy::vec_init_then_push)]
334 pub fn export_server_param_schemas(
335 out_dir: &Path,
336 ) -> ::anyhow::Result<Vec<GeneratedSchema>> {
337 let mut schemas = Vec::new();
338 $(
339 schemas.push(crate::export::write_json_schema::<$params>(
340 out_dir,
341 concat!(stringify!($variant), "Params"),
342 )?);
343 )*
344 Ok(schemas)
345 }
346 };
347}
348
349macro_rules! server_notification_definitions {
352 (
353 $(
354 $(#[$variant_meta:meta])*
355 $variant:ident $(=> $wire:literal)? ( $payload:ty )
356 ),* $(,)?
357 ) => {
358 #[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS, Display)]
360 #[serde(tag = "method", content = "params", rename_all = "camelCase")]
361 #[strum(serialize_all = "camelCase")]
362 pub enum ServerNotification {
363 $(
364 $(#[$variant_meta])*
365 $(#[serde(rename = $wire)] #[ts(rename = $wire)] #[strum(serialize = $wire)])?
366 $variant($payload),
367 )*
368 }
369
370 impl ServerNotification {
371 pub fn to_params(self) -> Result<serde_json::Value, serde_json::Error> {
372 match self {
373 $(Self::$variant(params) => serde_json::to_value(params),)*
374 }
375 }
376 }
377
378 impl TryFrom<JSONRPCNotification> for ServerNotification {
379 type Error = serde_json::Error;
380
381 fn try_from(value: JSONRPCNotification) -> Result<Self, serde_json::Error> {
382 serde_json::from_value(serde_json::to_value(value)?)
383 }
384 }
385
386 #[allow(clippy::vec_init_then_push)]
387 pub fn export_server_notification_schemas(
388 out_dir: &::std::path::Path,
389 ) -> ::anyhow::Result<Vec<GeneratedSchema>> {
390 let mut schemas = Vec::new();
391 $(schemas.push(crate::export::write_json_schema::<$payload>(out_dir, stringify!($payload))?);)*
392 Ok(schemas)
393 }
394 };
395}
396macro_rules! client_notification_definitions {
398 (
399 $(
400 $(#[$variant_meta:meta])*
401 $variant:ident $( ( $payload:ty ) )?
402 ),* $(,)?
403 ) => {
404 #[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS, Display)]
405 #[serde(tag = "method", content = "params", rename_all = "camelCase")]
406 #[strum(serialize_all = "camelCase")]
407 pub enum ClientNotification {
408 $(
409 $(#[$variant_meta])*
410 $variant $( ( $payload ) )?,
411 )*
412 }
413
414 pub fn export_client_notification_schemas(
415 _out_dir: &::std::path::Path,
416 ) -> ::anyhow::Result<Vec<GeneratedSchema>> {
417 let schemas = Vec::new();
418 $( $(schemas.push(crate::export::write_json_schema::<$payload>(_out_dir, stringify!($payload))?);)? )*
419 Ok(schemas)
420 }
421 };
422}
423
424impl TryFrom<JSONRPCRequest> for ServerRequest {
425 type Error = serde_json::Error;
426
427 fn try_from(value: JSONRPCRequest) -> Result<Self, Self::Error> {
428 serde_json::from_value(serde_json::to_value(value)?)
429 }
430}
431
432server_request_definitions! {
433 CommandExecutionRequestApproval => "item/commandExecution/requestApproval" {
437 params: v2::CommandExecutionRequestApprovalParams,
438 response: v2::CommandExecutionRequestApprovalResponse,
439 },
440
441 FileChangeRequestApproval => "item/fileChange/requestApproval" {
444 params: v2::FileChangeRequestApprovalParams,
445 response: v2::FileChangeRequestApprovalResponse,
446 },
447
448 ApplyPatchApproval {
452 params: v1::ApplyPatchApprovalParams,
453 response: v1::ApplyPatchApprovalResponse,
454 },
455 ExecCommandApproval {
458 params: v1::ExecCommandApprovalParams,
459 response: v1::ExecCommandApprovalResponse,
460 },
461}
462
463#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
464#[serde(rename_all = "camelCase")]
465#[ts(rename_all = "camelCase")]
466pub struct FuzzyFileSearchParams {
467 pub query: String,
468 pub roots: Vec<String>,
469 pub cancellation_token: Option<String>,
471}
472
473#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
475pub struct FuzzyFileSearchResult {
476 pub root: String,
477 pub path: String,
478 pub file_name: String,
479 pub score: u32,
480 pub indices: Option<Vec<u32>>,
481}
482
483#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
484pub struct FuzzyFileSearchResponse {
485 pub files: Vec<FuzzyFileSearchResult>,
486}
487
488server_notification_definitions! {
489 Error => "error" (v2::ErrorNotification),
491 ThreadStarted => "thread/started" (v2::ThreadStartedNotification),
492 TurnStarted => "turn/started" (v2::TurnStartedNotification),
493 TurnCompleted => "turn/completed" (v2::TurnCompletedNotification),
494 ItemStarted => "item/started" (v2::ItemStartedNotification),
495 ItemCompleted => "item/completed" (v2::ItemCompletedNotification),
496 AgentMessageDelta => "item/agentMessage/delta" (v2::AgentMessageDeltaNotification),
497 CommandExecutionOutputDelta => "item/commandExecution/outputDelta" (v2::CommandExecutionOutputDeltaNotification),
498 McpToolCallProgress => "item/mcpToolCall/progress" (v2::McpToolCallProgressNotification),
499 AccountUpdated => "account/updated" (v2::AccountUpdatedNotification),
500 AccountRateLimitsUpdated => "account/rateLimits/updated" (v2::AccountRateLimitsUpdatedNotification),
501 ReasoningSummaryTextDelta => "item/reasoning/summaryTextDelta" (v2::ReasoningSummaryTextDeltaNotification),
502 ReasoningSummaryPartAdded => "item/reasoning/summaryPartAdded" (v2::ReasoningSummaryPartAddedNotification),
503 ReasoningTextDelta => "item/reasoning/textDelta" (v2::ReasoningTextDeltaNotification),
504
505 WindowsWorldWritableWarning => "windows/worldWritableWarning" (v2::WindowsWorldWritableWarningNotification),
507
508 #[serde(rename = "account/login/completed")]
509 #[ts(rename = "account/login/completed")]
510 #[strum(serialize = "account/login/completed")]
511 AccountLoginCompleted(v2::AccountLoginCompletedNotification),
512
513 AuthStatusChange(v1::AuthStatusChangeNotification),
515
516 LoginChatGptComplete(v1::LoginChatGptCompleteNotification),
518 SessionConfigured(v1::SessionConfiguredNotification),
519}
520
521client_notification_definitions! {
522 Initialized,
523}
524
525#[cfg(test)]
526mod tests {
527 use super::*;
528 use anyhow::Result;
529 use codex_protocol::ConversationId;
530 use codex_protocol::account::PlanType;
531 use codex_protocol::parse_command::ParsedCommand;
532 use codex_protocol::protocol::AskForApproval;
533 use pretty_assertions::assert_eq;
534 use serde_json::json;
535 use std::path::PathBuf;
536
537 #[test]
538 fn serialize_new_conversation() -> Result<()> {
539 let request = ClientRequest::NewConversation {
540 request_id: RequestId::Integer(42),
541 params: v1::NewConversationParams {
542 model: Some("gpt-5.1-codex-max".to_string()),
543 model_provider: None,
544 profile: None,
545 cwd: None,
546 approval_policy: Some(AskForApproval::OnRequest),
547 sandbox: None,
548 config: None,
549 base_instructions: None,
550 developer_instructions: None,
551 compact_prompt: None,
552 include_apply_patch_tool: None,
553 },
554 };
555 assert_eq!(
556 json!({
557 "method": "newConversation",
558 "id": 42,
559 "params": {
560 "model": "gpt-5.1-codex-max",
561 "modelProvider": null,
562 "profile": null,
563 "cwd": null,
564 "approvalPolicy": "on-request",
565 "sandbox": null,
566 "config": null,
567 "baseInstructions": null,
568 "includeApplyPatchTool": null
569 }
570 }),
571 serde_json::to_value(&request)?,
572 );
573 Ok(())
574 }
575
576 #[test]
577 fn conversation_id_serializes_as_plain_string() -> Result<()> {
578 let id = ConversationId::from_string("67e55044-10b1-426f-9247-bb680e5fe0c8")?;
579
580 assert_eq!(
581 json!("67e55044-10b1-426f-9247-bb680e5fe0c8"),
582 serde_json::to_value(id)?
583 );
584 Ok(())
585 }
586
587 #[test]
588 fn conversation_id_deserializes_from_plain_string() -> Result<()> {
589 let id: ConversationId =
590 serde_json::from_value(json!("67e55044-10b1-426f-9247-bb680e5fe0c8"))?;
591
592 assert_eq!(
593 ConversationId::from_string("67e55044-10b1-426f-9247-bb680e5fe0c8")?,
594 id,
595 );
596 Ok(())
597 }
598
599 #[test]
600 fn serialize_client_notification() -> Result<()> {
601 let notification = ClientNotification::Initialized;
602 assert_eq!(
604 json!({
605 "method": "initialized",
606 }),
607 serde_json::to_value(¬ification)?,
608 );
609 Ok(())
610 }
611
612 #[test]
613 fn serialize_server_request() -> Result<()> {
614 let conversation_id = ConversationId::from_string("67e55044-10b1-426f-9247-bb680e5fe0c8")?;
615 let params = v1::ExecCommandApprovalParams {
616 conversation_id,
617 call_id: "call-42".to_string(),
618 command: vec!["echo".to_string(), "hello".to_string()],
619 cwd: PathBuf::from("/tmp"),
620 reason: Some("because tests".to_string()),
621 risk: None,
622 parsed_cmd: vec![ParsedCommand::Unknown {
623 cmd: "echo hello".to_string(),
624 }],
625 };
626 let request = ServerRequest::ExecCommandApproval {
627 request_id: RequestId::Integer(7),
628 params: params.clone(),
629 };
630
631 assert_eq!(
632 json!({
633 "method": "execCommandApproval",
634 "id": 7,
635 "params": {
636 "conversationId": "67e55044-10b1-426f-9247-bb680e5fe0c8",
637 "callId": "call-42",
638 "command": ["echo", "hello"],
639 "cwd": "/tmp",
640 "reason": "because tests",
641 "risk": null,
642 "parsedCmd": [
643 {
644 "type": "unknown",
645 "cmd": "echo hello"
646 }
647 ]
648 }
649 }),
650 serde_json::to_value(&request)?,
651 );
652
653 let payload = ServerRequestPayload::ExecCommandApproval(params);
654 assert_eq!(payload.request_with_id(RequestId::Integer(7)), request);
655 Ok(())
656 }
657
658 #[test]
659 fn serialize_get_account_rate_limits() -> Result<()> {
660 let request = ClientRequest::GetAccountRateLimits {
661 request_id: RequestId::Integer(1),
662 params: None,
663 };
664 assert_eq!(
665 json!({
666 "method": "account/rateLimits/read",
667 "id": 1,
668 }),
669 serde_json::to_value(&request)?,
670 );
671 Ok(())
672 }
673
674 #[test]
675 fn serialize_account_login_api_key() -> Result<()> {
676 let request = ClientRequest::LoginAccount {
677 request_id: RequestId::Integer(2),
678 params: v2::LoginAccountParams::ApiKey {
679 api_key: "secret".to_string(),
680 },
681 };
682 assert_eq!(
683 json!({
684 "method": "account/login/start",
685 "id": 2,
686 "params": {
687 "type": "apiKey",
688 "apiKey": "secret"
689 }
690 }),
691 serde_json::to_value(&request)?,
692 );
693 Ok(())
694 }
695
696 #[test]
697 fn serialize_account_login_chatgpt() -> Result<()> {
698 let request = ClientRequest::LoginAccount {
699 request_id: RequestId::Integer(3),
700 params: v2::LoginAccountParams::Chatgpt,
701 };
702 assert_eq!(
703 json!({
704 "method": "account/login/start",
705 "id": 3,
706 "params": {
707 "type": "chatgpt"
708 }
709 }),
710 serde_json::to_value(&request)?,
711 );
712 Ok(())
713 }
714
715 #[test]
716 fn serialize_account_logout() -> Result<()> {
717 let request = ClientRequest::LogoutAccount {
718 request_id: RequestId::Integer(4),
719 params: None,
720 };
721 assert_eq!(
722 json!({
723 "method": "account/logout",
724 "id": 4,
725 }),
726 serde_json::to_value(&request)?,
727 );
728 Ok(())
729 }
730
731 #[test]
732 fn serialize_get_account() -> Result<()> {
733 let request = ClientRequest::GetAccount {
734 request_id: RequestId::Integer(5),
735 params: v2::GetAccountParams {
736 refresh_token: false,
737 },
738 };
739 assert_eq!(
740 json!({
741 "method": "account/read",
742 "id": 5,
743 "params": {
744 "refreshToken": false
745 }
746 }),
747 serde_json::to_value(&request)?,
748 );
749 Ok(())
750 }
751
752 #[test]
753 fn account_serializes_fields_in_camel_case() -> Result<()> {
754 let api_key = v2::Account::ApiKey {};
755 assert_eq!(
756 json!({
757 "type": "apiKey",
758 }),
759 serde_json::to_value(&api_key)?,
760 );
761
762 let chatgpt = v2::Account::Chatgpt {
763 email: "user@example.com".to_string(),
764 plan_type: PlanType::Plus,
765 };
766 assert_eq!(
767 json!({
768 "type": "chatgpt",
769 "email": "user@example.com",
770 "planType": "plus",
771 }),
772 serde_json::to_value(&chatgpt)?,
773 );
774
775 Ok(())
776 }
777
778 #[test]
779 fn serialize_list_models() -> Result<()> {
780 let request = ClientRequest::ModelList {
781 request_id: RequestId::Integer(6),
782 params: v2::ModelListParams::default(),
783 };
784 assert_eq!(
785 json!({
786 "method": "model/list",
787 "id": 6,
788 "params": {
789 "limit": null,
790 "cursor": null
791 }
792 }),
793 serde_json::to_value(&request)?,
794 );
795 Ok(())
796 }
797}