Files
iAs/src/wechat/types.rs
T
wunianxiao a04447c7bc docs: 为全部 38 个源文件补充详细模块级注释
为 iAs 项目的所有 Rust 源文件添加了系统化的模块级文档注释(//!),
覆盖全部 src/ 下的文件。每个文件包含模块职责、架构设计、数据流
和关键设计决策的说明。

主要变更:
- 入口/CLI: main.rs, cli.rs — 系统架构概览、子命令说明
- Daemon/Worker: daemon.rs, worker.rs — 三消费者架构、旧架构说明
- 微信通道: mod.rs, client.rs, types.rs — API 端点、登录流程
- LLM 系统: mod.rs, types.rs, provider.rs, deepseek.rs, conversation.rs
  — 架构分层、流式处理、工具循环、摘要机制
- 上下文管理: mod.rs, types.rs, builder.rs, tools.rs
  — Checkpoint 机制、Token Budget、双重摘要
- 工具系统: mod.rs, types.rs, builtin.rs, approval.rs
  — 两层元工具架构、审批流程
- 内置工具: mod.rs + 6 个工具 — API 端点、认证方式、参数说明
- 消息队列: mod.rs, message_queue.rs, runner.rs
  — 公平轮转算法、三消费者路由
- 数据库/状态/调度/日志: db/mod.rs, state.rs, scheduler.rs, logger.rs
  — 表结构、回退策略、定时任务、日志初始化
2026-06-10 09:46:42 +08:00

370 lines
12 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! ## 微信 iLink Bot API 协议类型
//!
//! 定义了与微信 iLink Bot API 通信的全部数据结构:
//!
//! - `BaseInfo` — 公共请求元数据
//! - `WeixinMessage` — 微信消息结构(支持 text/image/voice/file/video
//! - `MessageItem` — 消息条目(5 种类型)
//! - `GetUpdatesReq/Resp` — 长轮询收消息的请求/响应
//! - `SendMessageReq` — 发送消息请求
//! - `QRCodeResponse` / `StatusResponse` — 扫码登录相关
//! - `LoginResult` / `MessageEvent` — 业务层结果类型
use serde::{Deserialize, Serialize};
// ─── 基础类型 ───
/// 公共请求元数据
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct BaseInfo {
#[serde(skip_serializing_if = "Option::is_none")]
pub channel_version: Option<String>,
}
impl BaseInfo {
pub fn new() -> Self {
Self {
channel_version: Some(env!("CARGO_PKG_VERSION").to_string()),
}
}
}
// ─── 消息类型 ───
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TextItem {
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CDNMedia {
#[serde(skip_serializing_if = "Option::is_none")]
pub encrypt_query_param: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub aes_key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub encrypt_type: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub full_url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImageItem {
#[serde(skip_serializing_if = "Option::is_none")]
pub media: Option<CDNMedia>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb_media: Option<CDNMedia>,
#[serde(skip_serializing_if = "Option::is_none")]
pub aeskey: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mid_size: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb_size: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb_height: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb_width: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hd_size: Option<i64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VoiceItem {
#[serde(skip_serializing_if = "Option::is_none")]
pub media: Option<CDNMedia>,
#[serde(skip_serializing_if = "Option::is_none")]
pub encode_type: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bits_per_sample: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sample_rate: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub playtime: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileItem {
#[serde(skip_serializing_if = "Option::is_none")]
pub media: Option<CDNMedia>,
#[serde(skip_serializing_if = "Option::is_none")]
pub file_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub md5: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub len: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VideoItem {
#[serde(skip_serializing_if = "Option::is_none")]
pub media: Option<CDNMedia>,
#[serde(skip_serializing_if = "Option::is_none")]
pub video_size: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub play_length: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub video_md5: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb_media: Option<CDNMedia>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb_size: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb_height: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb_width: Option<i32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RefMessage {
#[serde(skip_serializing_if = "Option::is_none")]
pub message_item: Option<Box<MessageItem>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
}
/// ## 消息条目 —— 消息的具体内容
///
/// 支持 5 种消息类型:text(1), image(2), voice(3), file(4), video(5)。
/// 每种类型有对应的 `*_item` 字段,其他字段保持 None。
///
/// `text()` 便捷方法用于快速构造文本消息(发送消息时使用)。
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MessageItem {
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub item_type: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub create_time_ms: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub update_time_ms: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_completed: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub msg_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ref_msg: Option<Box<RefMessage>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text_item: Option<TextItem>,
#[serde(skip_serializing_if = "Option::is_none")]
pub image_item: Option<ImageItem>,
#[serde(skip_serializing_if = "Option::is_none")]
pub voice_item: Option<VoiceItem>,
#[serde(skip_serializing_if = "Option::is_none")]
pub file_item: Option<FileItem>,
#[serde(skip_serializing_if = "Option::is_none")]
pub video_item: Option<VideoItem>,
}
impl MessageItem {
pub const TYPE_TEXT: i32 = 1;
#[allow(dead_code)]
pub const TYPE_IMAGE: i32 = 2;
#[allow(dead_code)]
pub const TYPE_VOICE: i32 = 3;
#[allow(dead_code)]
pub const TYPE_FILE: i32 = 4;
#[allow(dead_code)]
pub const TYPE_VIDEO: i32 = 5;
pub fn text(text: &str) -> Self {
Self {
item_type: Some(Self::TYPE_TEXT),
text_item: Some(TextItem {
text: Some(text.to_string()),
}),
..Default::default()
}
}
}
impl Default for MessageItem {
fn default() -> Self {
Self {
item_type: None,
create_time_ms: None,
update_time_ms: None,
is_completed: None,
msg_id: None,
ref_msg: None,
text_item: None,
image_item: None,
voice_item: None,
file_item: None,
video_item: None,
}
}
}
/// ## 微信消息结构
///
/// 对应 iLink API 返回的单个消息。所有字段都是 Option 的,
/// 因为不同消息类型携带不同字段。
///
/// ### 关键字段
/// * `msg_type` — 消息类型:1=用户消息(TYPE_USER), 2=机器人消息(TYPE_BOT)
/// * `from_user_id` — 发送者微信 ID
/// * `to_user_id` — 接收者微信 ID
/// * `item_list` — 消息内容列表(支持 text/image/voice/file/video
/// * `context_token` — 微信上下文令牌(用于多轮对话的上下文恢复)
/// * `group_id` — 群聊 ID(群消息时非空)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WeixinMessage {
#[serde(skip_serializing_if = "Option::is_none")]
pub seq: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message_id: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub from_user_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub to_user_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub client_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub create_time_ms: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub update_time_ms: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delete_time_ms: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub group_id: Option<String>,
#[serde(rename = "message_type", skip_serializing_if = "Option::is_none")]
pub msg_type: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message_state: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub item_list: Option<Vec<MessageItem>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context_token: Option<String>,
}
impl Default for WeixinMessage {
fn default() -> Self {
Self {
seq: None,
message_id: None,
from_user_id: None,
to_user_id: None,
client_id: None,
create_time_ms: None,
update_time_ms: None,
delete_time_ms: None,
session_id: None,
group_id: None,
msg_type: None,
message_state: None,
item_list: None,
context_token: None,
}
}
}
impl WeixinMessage {
#[allow(dead_code)]
pub const TYPE_NONE: i32 = 0;
pub const TYPE_USER: i32 = 1;
pub const TYPE_BOT: i32 = 2;
/// 获取文本内容(如果有纯文本 item)
pub fn text_content(&self) -> Option<&str> {
self.item_list
.as_ref()?
.iter()
.find(|item| item.item_type == Some(MessageItem::TYPE_TEXT))
.and_then(|item| item.text_item.as_ref())
.and_then(|t| t.text.as_deref())
}
}
// ─── API 请求/响应 ───
/// 获取二维码响应
#[derive(Debug, Deserialize)]
pub struct QRCodeResponse {
pub qrcode: String,
pub qrcode_img_content: String,
}
/// 扫码状态响应
#[derive(Debug, Deserialize)]
pub struct StatusResponse {
pub status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub bot_token: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ilink_bot_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub baseurl: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ilink_user_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub redirect_host: Option<String>,
}
/// GetUpdates 请求
#[derive(Debug, Serialize)]
pub struct GetUpdatesReq {
pub get_updates_buf: String,
pub base_info: BaseInfo,
}
/// GetUpdates 响应
#[derive(Debug, Deserialize)]
pub struct GetUpdatesResp {
#[serde(default)]
pub ret: i32,
#[serde(skip_serializing_if = "Option::is_none")]
pub errcode: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub errmsg: Option<String>,
#[serde(default)]
pub msgs: Vec<WeixinMessage>,
#[serde(default)]
pub get_updates_buf: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[allow(dead_code)]
pub longpolling_timeout_ms: Option<i64>,
}
/// SendMessage 请求
#[derive(Debug, Serialize)]
pub struct SendMessageReq {
pub msg: WeixinMessage,
}
/// NotifyStart/NotifyStop 响应
#[derive(Debug, Deserialize)]
pub struct NotifyResp {
#[serde(default)]
pub ret: i32,
#[serde(skip_serializing_if = "Option::is_none")]
pub errmsg: Option<String>,
}
/// 登录结果
#[derive(Debug, Clone)]
pub struct LoginResult {
pub token: String,
pub account_id: String,
pub base_url: String,
#[allow(dead_code)]
pub user_id: String,
}
/// 收到的消息事件
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct MessageEvent {
pub from_user_id: String,
pub text: String,
pub context_token: Option<String>,
pub message_id: i64,
}