Skip to main content

smcp_computer/mcp_clients/
model.rs

1/**
2* 文件名: model
3* 作者: JQQ
4* 创建日期: 2025/12/15
5* 最后修改日期: 2025/12/15
6* 版权: 2023 JQQ. All rights reserved.
7* 依赖: serde, async-trait
8* 描述: MCP客户端相关的数据模型定义
9*/
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12use std::fmt;
13use thiserror::Error;
14
15// 常量定义 / Constants definition
16pub const A2C_TOOL_META: &str = "a2c_tool_meta";
17pub const A2C_VRL_TRANSFORMED: &str = "a2c_vrl_transformed";
18
19// 类型别名 / Type aliases
20pub type ServerName = String;
21pub type ToolName = String;
22
23/// MCP工具元数据 / MCP tool metadata
24#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
25pub struct ToolMeta {
26    /// 是否自动使用 / Whether to auto-apply
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub auto_apply: Option<bool>,
29    /// 工具别名 / Tool alias
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub alias: Option<String>,
32    /// 工具标签 / Tool tags
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub tags: Option<Vec<String>>,
35    /// 返回值字段映射 / Return value field mapping
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub ret_object_mapper: Option<HashMap<String, String>>,
38}
39
40impl ToolMeta {
41    /// 创建空的工具元数据 / Create empty tool metadata
42    pub fn new() -> Self {
43        Self {
44            auto_apply: None,
45            alias: None,
46            tags: None,
47            ret_object_mapper: None,
48        }
49    }
50}
51
52impl Default for ToolMeta {
53    fn default() -> Self {
54        Self::new()
55    }
56}
57
58/// MCP服务器配置基类 / Base MCP server configuration
59#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
60#[serde(tag = "type")]
61pub enum MCPServerConfig {
62    /// STDIO类型服务器 / STDIO type server
63    #[serde(alias = "stdio", alias = "STDIO")]
64    Stdio(StdioServerConfig),
65    /// SSE类型服务器 / SSE type server
66    #[serde(alias = "sse", alias = "SSE")]
67    Sse(SseServerConfig),
68    /// HTTP类型服务器 / HTTP type server
69    #[serde(alias = "http", alias = "HTTP")]
70    Http(HttpServerConfig),
71}
72
73impl MCPServerConfig {
74    /// 获取服务器名称 / Get server name
75    pub fn name(&self) -> &str {
76        match self {
77            MCPServerConfig::Stdio(config) => &config.name,
78            MCPServerConfig::Sse(config) => &config.name,
79            MCPServerConfig::Http(config) => &config.name,
80        }
81    }
82
83    /// 获取是否禁用标志 / Get disabled flag
84    pub fn disabled(&self) -> bool {
85        match self {
86            MCPServerConfig::Stdio(config) => config.disabled,
87            MCPServerConfig::Sse(config) => config.disabled,
88            MCPServerConfig::Http(config) => config.disabled,
89        }
90    }
91
92    /// 获取禁用工具列表 / Get forbidden tools list
93    pub fn forbidden_tools(&self) -> &[String] {
94        match self {
95            MCPServerConfig::Stdio(config) => &config.forbidden_tools,
96            MCPServerConfig::Sse(config) => &config.forbidden_tools,
97            MCPServerConfig::Http(config) => &config.forbidden_tools,
98        }
99    }
100
101    /// 获取工具元数据映射 / Get tool metadata mapping
102    pub fn tool_meta(&self) -> &HashMap<ToolName, ToolMeta> {
103        match self {
104            MCPServerConfig::Stdio(config) => &config.tool_meta,
105            MCPServerConfig::Sse(config) => &config.tool_meta,
106            MCPServerConfig::Http(config) => &config.tool_meta,
107        }
108    }
109
110    /// 获取默认工具元数据 / Get default tool metadata
111    pub fn default_tool_meta(&self) -> Option<&ToolMeta> {
112        match self {
113            MCPServerConfig::Stdio(config) => config.default_tool_meta.as_ref(),
114            MCPServerConfig::Sse(config) => config.default_tool_meta.as_ref(),
115            MCPServerConfig::Http(config) => config.default_tool_meta.as_ref(),
116        }
117    }
118
119    /// 获取VRL脚本 / Get VRL script
120    pub fn vrl(&self) -> Option<&str> {
121        match self {
122            MCPServerConfig::Stdio(config) => config.vrl.as_deref(),
123            MCPServerConfig::Sse(config) => config.vrl.as_deref(),
124            MCPServerConfig::Http(config) => config.vrl.as_deref(),
125        }
126    }
127}
128
129/// STDIO服务器配置 / STDIO server configuration
130#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
131pub struct StdioServerConfig {
132    /// 服务器名称 / Server name
133    pub name: ServerName,
134    /// 是否禁用 / Whether disabled
135    #[serde(default)]
136    pub disabled: bool,
137    /// 禁用工具列表 / Forbidden tools list
138    #[serde(default)]
139    pub forbidden_tools: Vec<ToolName>,
140    /// 工具元数据 / Tool metadata
141    #[serde(default)]
142    pub tool_meta: HashMap<ToolName, ToolMeta>,
143    /// 默认工具元数据 / Default tool metadata
144    pub default_tool_meta: Option<ToolMeta>,
145    /// VRL脚本 / VRL script
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub vrl: Option<String>,
148    /// STDIO服务器参数 / STDIO server parameters
149    pub server_parameters: StdioServerParameters,
150}
151
152/// SSE服务器配置 / SSE server configuration
153#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
154pub struct SseServerConfig {
155    /// 服务器名称 / Server name
156    pub name: ServerName,
157    /// 是否禁用 / Whether disabled
158    #[serde(default)]
159    pub disabled: bool,
160    /// 禁用工具列表 / Forbidden tools list
161    #[serde(default)]
162    pub forbidden_tools: Vec<ToolName>,
163    /// 工具元数据 / Tool metadata
164    #[serde(default)]
165    pub tool_meta: HashMap<ToolName, ToolMeta>,
166    /// 默认工具元数据 / Default tool metadata
167    pub default_tool_meta: Option<ToolMeta>,
168    /// VRL脚本 / VRL script
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub vrl: Option<String>,
171    /// SSE服务器参数 / SSE server parameters
172    pub server_parameters: SseServerParameters,
173}
174
175/// HTTP服务器配置 / HTTP server configuration
176#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
177pub struct HttpServerConfig {
178    /// 服务器名称 / Server name
179    pub name: ServerName,
180    /// 是否禁用 / Whether disabled
181    #[serde(default)]
182    pub disabled: bool,
183    /// 禁用工具列表 / Forbidden tools list
184    #[serde(default)]
185    pub forbidden_tools: Vec<ToolName>,
186    /// 工具元数据 / Tool metadata
187    #[serde(default)]
188    pub tool_meta: HashMap<ToolName, ToolMeta>,
189    /// 默认工具元数据 / Default tool metadata
190    pub default_tool_meta: Option<ToolMeta>,
191    /// VRL脚本 / VRL script
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub vrl: Option<String>,
194    /// HTTP服务器参数 / HTTP server parameters
195    pub server_parameters: HttpServerParameters,
196}
197
198fn null_to_empty_map<'de, D>(deserializer: D) -> Result<HashMap<String, String>, D::Error>
199where
200    D: serde::Deserializer<'de>,
201{
202    let opt = Option::<HashMap<String, String>>::deserialize(deserializer)?;
203    Ok(opt.unwrap_or_default())
204}
205
206/// STDIO服务器参数 / STDIO server parameters
207#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
208pub struct StdioServerParameters {
209    /// 命令 / Command
210    pub command: String,
211    /// 参数 / Arguments
212    #[serde(default)]
213    pub args: Vec<String>,
214    /// 环境变量 / Environment variables
215    #[serde(default, deserialize_with = "null_to_empty_map")]
216    pub env: HashMap<String, String>,
217    /// 工作目录 / Working directory
218    #[serde(skip_serializing_if = "Option::is_none")]
219    pub cwd: Option<String>,
220}
221
222/// SSE服务器参数 / SSE server parameters
223#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
224pub struct SseServerParameters {
225    /// URL / URL
226    pub url: String,
227    /// Headers / Headers
228    #[serde(default)]
229    pub headers: HashMap<String, String>,
230}
231
232/// HTTP服务器参数 / HTTP server parameters
233#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
234pub struct HttpServerParameters {
235    /// URL / URL
236    pub url: String,
237    /// Headers / Headers
238    #[serde(default)]
239    pub headers: HashMap<String, String>,
240}
241
242/// MCP服务器输入项基类 / Base MCP server input configuration
243#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
244#[serde(tag = "type")]
245pub enum MCPServerInput {
246    /// 字符串输入 / String input
247    PromptString(PromptStringInput),
248    /// 选择输入 / Pick string input
249    PickString(PickStringInput),
250    /// 命令输入 / Command input
251    Command(CommandInput),
252}
253
254impl MCPServerInput {
255    /// 获取输入ID / Get input ID
256    pub fn id(&self) -> &str {
257        match self {
258            MCPServerInput::PromptString(input) => &input.id,
259            MCPServerInput::PickString(input) => &input.id,
260            MCPServerInput::Command(input) => &input.id,
261        }
262    }
263
264    /// 获取输入描述 / Get input description
265    pub fn description(&self) -> &str {
266        match self {
267            MCPServerInput::PromptString(input) => &input.description,
268            MCPServerInput::PickString(input) => &input.description,
269            MCPServerInput::Command(input) => &input.description,
270        }
271    }
272
273    /// 获取默认值 / Get default value
274    pub fn default(&self) -> Option<serde_json::Value> {
275        match self {
276            MCPServerInput::PromptString(input) => input
277                .default
278                .as_ref()
279                .map(|s| serde_json::Value::String(s.clone())),
280            MCPServerInput::PickString(input) => input
281                .default
282                .as_ref()
283                .map(|s| serde_json::Value::String(s.clone())),
284            MCPServerInput::Command(_input) => {
285                // Command 类型不支持默认值
286                // Command type doesn't support default values
287                None
288            }
289        }
290    }
291}
292
293/// 字符串输入类型 / String input type
294#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
295pub struct PromptStringInput {
296    /// 输入ID / Input ID
297    pub id: String,
298    /// 描述 / Description
299    pub description: String,
300    /// 默认值 / Default value
301    #[serde(skip_serializing_if = "Option::is_none")]
302    pub default: Option<String>,
303    /// 是否为密码 / Whether password
304    #[serde(skip_serializing_if = "Option::is_none")]
305    pub password: Option<bool>,
306}
307
308/// 选择输入类型 / Pick string input type
309#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
310pub struct PickStringInput {
311    /// 输入ID / Input ID
312    pub id: String,
313    /// 描述 / Description
314    pub description: String,
315    /// 选项 / Options
316    #[serde(default)]
317    pub options: Vec<String>,
318    /// 默认值 / Default value
319    #[serde(skip_serializing_if = "Option::is_none")]
320    pub default: Option<String>,
321}
322
323/// 命令输入类型 / Command input type
324#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
325pub struct CommandInput {
326    /// 输入ID / Input ID
327    pub id: String,
328    /// 描述 / Description
329    pub description: String,
330    /// 命令 / Command
331    pub command: String,
332    /// 参数 / Arguments
333    #[serde(skip_serializing_if = "Option::is_none")]
334    pub args: Option<HashMap<String, String>>,
335}
336
337/// 健康检查配置 / Health check configuration
338#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
339pub struct HealthCheckConfig {
340    /// 健康检查间隔(秒)/ Health check interval in seconds
341    #[serde(default = "default_health_check_interval")]
342    pub interval_secs: u64,
343    /// 超时时间(秒)/ Timeout in seconds
344    #[serde(default = "default_health_check_timeout")]
345    pub timeout_secs: u64,
346    /// 是否启用健康检查 / Whether to enable health check
347    #[serde(default = "default_health_check_enabled")]
348    pub enabled: bool,
349}
350
351fn default_health_check_interval() -> u64 {
352    30
353}
354
355fn default_health_check_timeout() -> u64 {
356    5
357}
358
359fn default_health_check_enabled() -> bool {
360    true
361}
362
363impl Default for HealthCheckConfig {
364    fn default() -> Self {
365        Self {
366            interval_secs: 30,
367            timeout_secs: 5,
368            enabled: true,
369        }
370    }
371}
372
373/// 重连策略 / Reconnect policy
374#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
375pub struct ReconnectPolicy {
376    /// 是否启用自动重连 / Whether to enable auto reconnect
377    #[serde(default = "default_reconnect_enabled")]
378    pub enabled: bool,
379    /// 最大重试次数(0表示无限重试)/ Max retry count (0 means infinite)
380    #[serde(default = "default_max_retries")]
381    pub max_retries: u32,
382    /// 初始延迟时间(毫秒)/ Initial delay in milliseconds
383    #[serde(default = "default_initial_delay_ms")]
384    pub initial_delay_ms: u64,
385    /// 最大延迟时间(毫秒)/ Max delay in milliseconds
386    #[serde(default = "default_max_delay_ms")]
387    pub max_delay_ms: u64,
388    /// 退避因子(延迟时间乘数)/ Backoff factor (delay multiplier)
389    #[serde(default = "default_backoff_factor")]
390    pub backoff_factor: f64,
391}
392
393fn default_reconnect_enabled() -> bool {
394    true
395}
396
397fn default_max_retries() -> u32 {
398    5
399}
400
401fn default_initial_delay_ms() -> u64 {
402    1000
403}
404
405fn default_max_delay_ms() -> u64 {
406    30000
407}
408
409fn default_backoff_factor() -> f64 {
410    2.0
411}
412
413impl Default for ReconnectPolicy {
414    fn default() -> Self {
415        Self {
416            enabled: true,
417            max_retries: 5,
418            initial_delay_ms: 1000,
419            max_delay_ms: 30000,
420            backoff_factor: 2.0,
421        }
422    }
423}
424
425impl ReconnectPolicy {
426    /// 计算下次重试的延迟时间 / Calculate delay for next retry
427    pub fn calculate_delay(&self, retry_count: u32) -> std::time::Duration {
428        let delay_ms = (self.initial_delay_ms as f64 * self.backoff_factor.powi(retry_count as i32))
429            .min(self.max_delay_ms as f64) as u64;
430        std::time::Duration::from_millis(delay_ms)
431    }
432
433    /// 检查是否应该继续重试 / Check if should continue retry
434    pub fn should_retry(&self, retry_count: u32) -> bool {
435        self.enabled && (self.max_retries == 0 || retry_count < self.max_retries)
436    }
437}
438
439/// 健康检查结果 / Health check result
440#[derive(Debug, Clone)]
441pub struct HealthCheckResult {
442    /// 是否健康 / Is healthy
443    pub is_healthy: bool,
444    /// 检查时间 / Check time
445    pub checked_at: std::time::Instant,
446    /// 错误信息(如果有)/ Error message if any
447    pub error: Option<String>,
448    /// 响应时间(毫秒)/ Response time in milliseconds
449    pub response_time_ms: Option<u64>,
450}
451
452/// MCP客户端协议trait / MCP client protocol trait
453#[async_trait::async_trait]
454pub trait MCPClientProtocol: Send + Sync {
455    /// 获取客户端状态 / Get client state
456    fn state(&self) -> ClientState;
457
458    /// 连接MCP服务器 / Connect to MCP server
459    async fn connect(&self) -> Result<(), MCPClientError>;
460
461    /// 断开连接 / Disconnect
462    async fn disconnect(&self) -> Result<(), MCPClientError>;
463
464    /// 获取可用工具列表 / Get available tools list
465    async fn list_tools(&self) -> Result<Vec<Tool>, MCPClientError>;
466
467    /// 调用工具 / Call tool
468    async fn call_tool(
469        &self,
470        tool_name: &str,
471        params: serde_json::Value,
472    ) -> Result<CallToolResult, MCPClientError>;
473
474    /// 列出窗口资源 / List window resources
475    async fn list_windows(&self) -> Result<Vec<Resource>, MCPClientError>;
476
477    /// 获取窗口详情 / Get window detail
478    async fn get_window_detail(
479        &self,
480        resource: Resource,
481    ) -> Result<ReadResourceResult, MCPClientError>;
482
483    /// 订阅窗口资源更新 / Subscribe to window resource updates
484    async fn subscribe_window(&self, resource: Resource) -> Result<(), MCPClientError>;
485
486    /// 取消订阅窗口资源更新 / Unsubscribe from window resource updates
487    async fn unsubscribe_window(&self, resource: Resource) -> Result<(), MCPClientError>;
488
489    /// 执行健康检查 / Perform health check
490    /// 默认实现通过检查状态和尝试 list_tools 来验证连接
491    /// Default implementation checks state and tries list_tools to verify connection
492    async fn health_check(&self) -> HealthCheckResult {
493        let start = std::time::Instant::now();
494
495        // 首先检查状态 / First check state
496        if self.state() != ClientState::Connected {
497            return HealthCheckResult {
498                is_healthy: false,
499                checked_at: start,
500                error: Some(format!("Client state is {:?}, not Connected", self.state())),
501                response_time_ms: None,
502            };
503        }
504
505        // 尝试调用 list_tools 来验证连接 / Try calling list_tools to verify connection
506        match tokio::time::timeout(std::time::Duration::from_secs(5), self.list_tools()).await {
507            Ok(Ok(_)) => {
508                let elapsed = start.elapsed();
509                HealthCheckResult {
510                    is_healthy: true,
511                    checked_at: start,
512                    error: None,
513                    response_time_ms: Some(elapsed.as_millis() as u64),
514                }
515            }
516            Ok(Err(e)) => HealthCheckResult {
517                is_healthy: false,
518                checked_at: start,
519                error: Some(format!("Health check failed: {}", e)),
520                response_time_ms: None,
521            },
522            Err(_) => HealthCheckResult {
523                is_healthy: false,
524                checked_at: start,
525                error: Some("Health check timed out".to_string()),
526                response_time_ms: None,
527            },
528        }
529    }
530}
531
532/// 客户端状态 / Client state
533#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
534pub enum ClientState {
535    /// 已初始化 / Initialized
536    Initialized,
537    /// 已连接 / Connected
538    Connected,
539    /// 已断开 / Disconnected
540    Disconnected,
541    /// 错误状态 / Error
542    Error,
543}
544
545impl fmt::Display for ClientState {
546    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
547        match self {
548            ClientState::Initialized => write!(f, "initialized"),
549            ClientState::Connected => write!(f, "connected"),
550            ClientState::Disconnected => write!(f, "disconnected"),
551            ClientState::Error => write!(f, "error"),
552        }
553    }
554}
555
556/// MCP客户端错误 / MCP client error
557#[derive(Debug, Error)]
558pub enum MCPClientError {
559    /// 连接错误 / Connection error
560    #[error("Connection error: {0}")]
561    ConnectionError(String),
562    /// 协议错误 / Protocol error
563    #[error("Protocol error: {0}")]
564    ProtocolError(String),
565    /// IO错误 / IO error
566    #[error("IO error: {0}")]
567    IoError(#[from] std::io::Error),
568    /// JSON错误 / JSON error
569    #[error("JSON error: {0}")]
570    JsonError(#[from] serde_json::Error),
571    /// 超时错误 / Timeout error
572    #[error("Timeout error: {0}")]
573    TimeoutError(String),
574    /// 其他错误 / Other error
575    #[error("Other error: {0}")]
576    Other(String),
577}
578
579/// MCP工具定义 / MCP tool definition
580#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
581pub struct Tool {
582    /// 工具名称 / Tool name
583    pub name: String,
584    /// 工具描述 / Tool description
585    pub description: String,
586    /// 输入模式 / Input schema
587    #[serde(rename = "inputSchema")]
588    pub input_schema: serde_json::Value,
589    /// 工具注解 / Tool annotations
590    #[serde(skip_serializing_if = "Option::is_none")]
591    pub annotations: Option<ToolAnnotations>,
592    /// 工具元数据 / Tool metadata
593    #[serde(skip_serializing_if = "Option::is_none")]
594    pub meta: Option<HashMap<String, serde_json::Value>>,
595}
596
597/// 工具注解 / Tool annotations
598#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
599pub struct ToolAnnotations {
600    /// 标题 / Title
601    pub title: String,
602    /// 是否只读 / Read only hint
603    #[serde(rename = "readOnlyHint")]
604    pub read_only_hint: bool,
605    /// 是否破坏性 / Destructive hint
606    #[serde(rename = "destructiveHint")]
607    pub destructive_hint: bool,
608    /// 开放世界提示 / Open world hint
609    #[serde(rename = "openWorldHint")]
610    pub open_world_hint: bool,
611}
612
613/// 资源定义 / Resource definition
614#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
615pub struct Resource {
616    /// URI / URI
617    pub uri: String,
618    /// 名称 / Name
619    pub name: String,
620    /// 描述 / Description
621    #[serde(skip_serializing_if = "Option::is_none")]
622    pub description: Option<String>,
623    /// MIME类型 / MIME type
624    #[serde(skip_serializing_if = "Option::is_none")]
625    pub mime_type: Option<String>,
626}
627
628/// 工具调用结果 / Tool call result
629#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
630pub struct CallToolResult {
631    /// 内容 / Content
632    pub content: Vec<Content>,
633    /// 是否为错误 / Is error
634    #[serde(default)]
635    pub is_error: bool,
636    /// 元数据 / Metadata
637    #[serde(skip_serializing_if = "Option::is_none")]
638    pub meta: Option<HashMap<String, serde_json::Value>>,
639}
640
641/// 内容块 / Content block
642#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
643#[serde(tag = "type")]
644pub enum Content {
645    /// 文本内容 / Text content
646    #[serde(rename = "text")]
647    Text { text: String },
648    /// 图片内容 / Image content
649    #[serde(rename = "image")]
650    Image { data: String, mime_type: String },
651    /// 资源内容 / Resource content
652    #[serde(rename = "resource")]
653    Resource {
654        uri: String,
655        mime_type: Option<String>,
656    },
657}
658
659/// 读取资源结果 / Read resource result
660#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
661pub struct ReadResourceResult {
662    /// 内容 / Contents
663    pub contents: Vec<TextResourceContents>,
664}
665
666/// 文本资源内容 / Text resource contents
667#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
668pub struct TextResourceContents {
669    /// URI / URI
670    pub uri: String,
671    /// 文本内容 / Text content
672    pub text: String,
673    /// MIME类型 / MIME type
674    #[serde(skip_serializing_if = "Option::is_none")]
675    pub mime_type: Option<String>,
676}
677
678/// 列出资源结果 / List resources result(支持分页)
679#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
680pub struct ListResourcesResult {
681    /// 资源列表 / Resource list
682    pub resources: Vec<Resource>,
683    /// 下一页游标 / Next page cursor
684    #[serde(skip_serializing_if = "Option::is_none")]
685    pub next_cursor: Option<String>,
686}