{
  "metadata": {
    "id": "ch42",
    "title": "第42章：跨平台部署",
    "volume": "vol11",
    "volume_title": "生态与跨平台",
    "word_count": 6055,
    "difficulty": "intermediate",
    "prerequisites": [
      "ch36"
    ],
    "key_concepts": [
      "概述：为什么需要跨平台",
      "Agent 产品跨平台的必要性",
      "Agent 跨平台的特殊挑战",
      "跨平台架构总览",
      "Web 端部署",
      "Web 端的技术选型",
      "WebSocket 实时通信",
      "SSE（Server-Sent Events）作为替代方案",
      "移动端部署",
      "移动端 Agent 的技术选型",
      "React Native Agent 客户端实现",
      "移动端特有的 Agent 能力",
      "离线与弱网策略",
      "桌面端部署",
      "Electron vs Tauri"
    ],
    "learning_objectives": [],
    "estimated_tokens": 3633,
    "source_file": "vol11/ch42_跨平台部署.md"
  },
  "overview": "",
  "sections": [
    {
      "id": "42.1",
      "title": "42.1 概述：为什么需要跨平台",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "42.1.1",
          "title": "42.1.1 Agent 产品跨平台的必要性",
          "content": "Agent 产品与传统的 Web 应用或移动应用有着本质区别——它需要与用户进行持续的、多轮次的、上下文感知的交互。这种交互模式决定了 Agent 产品必须在用户最方便的场景中出现，无论用户是在浏览器中工作、在手机上查收消息、在 IDE 中编码，还是在微信中沟通。\n\nAgent 跨平台部署的核心驱动力来自三个方面：\n\n**1. 用户场景的碎片化**\n\n现代用户的信息触达场景高度碎片化。一个企业用户可能在工作时间使用桌面端 IDE 进行编码，在通勤途中通过手机查阅 Agent 生成的报告，在会议中通过大屏展示 Agent 分析的结果。如果 Agent 只存在于单一平台，就会错过大量用户交互机会。\n\n**2. 交互形式的多样化**\n\n不同平台有着天然适合的交互形式：\n- **Web 端**：适合复杂的数据展示、长文档编辑、多面板工作台\n- **移动端**：适合语音交互、拍照识别、推送通知、快速查阅\n- **桌面端**：适合与本地工具深度集成、文件系统访问、后台常驻\n- **CLI 端**：适合开发者工作流、CI/CD 集成、脚本自动化\n- **IDE 端**：适合编码辅助、代码审查、调试辅助\n- **微信生态**：适合中国企业用户、低门槛触达、社交传播\n\n**3. 商业覆盖的最大化**\n\n从商业视角看，每多覆盖一个平台，就意味着多一个用户触达渠道、多一种变现方式。特别是 ToB 市场，客户往往要求私有化部署 + 多终端适配，跨平台能力直接决定了产品的竞争力。"
        },
        {
          "id": "42.1.2",
          "title": "42.1.2 Agent 跨平台的特殊挑战",
          "content": "与普通应用不同，Agent 跨平台面临以下特殊挑战：\n\n| 挑战维度 | 具体问题 | 影响 |\n|---------|---------|------|\n| **模型推理** | 边缘设备的算力限制，大模型无法在移动端运行 | 需要云-端协同架构 |\n| **实时通信** | Agent 的流式响应需要低延迟、可靠的双向通信 | WebSocket/SSE 的跨平台实现 |\n| **上下文同步** | 用户在不同设备间的会话状态需要无缝衔接 | 需要统一的会话管理后端 |\n| **工具调用** | 不同平台的系统能力差异（如文件系统访问权限） | 需要平台感知的工具适配层 |\n| **UI 一致性** | Agent 的交互体验需要在不同平台上保持连贯 | 设计系统的跨平台适配 |\n| **安全合规** | 不同平台有不同的安全模型和合规要求 | 如 iOS 的沙箱限制 vs 桌面端的完整权限 |"
        },
        {
          "id": "42.1.3",
          "title": "42.1.3 跨平台架构总览",
          "content": "一个完整的 Agent 跨平台架构通常包含以下层次：\n\n\n**核心设计原则：**\n\n- **后端统一**：所有平台共享同一套 Agent Core 后端服务，确保行为一致性\n- **前端适配**：每个平台使用最合适的技术栈实现前端，不强制跨平台 UI 框架\n- **通信标准化**：使用 REST API + WebSocket/SSE 的标准通信协议，便于各端接入\n- **数据模型统一**：定义跨平台的统一数据模型，确保会话、消息等核心数据的一致性\n\n---"
        }
      ]
    },
    {
      "id": "42.2",
      "title": "42.2 Web 端部署",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "42.2.1",
          "title": "42.2.1 Web 端的技术选型",
          "content": "Web 端是 Agent 产品最基础的部署平台，也是大多数 Agent 产品的第一个版本的目标平台。以下是主流的技术选型方案：\n\n**前端框架对比：**\n\n| 特性 | React | Vue 3 | Next.js | Nuxt 3 |\n|------|-------|-------|---------|--------|\n| **生态成熟度** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |\n| **Agent 组件库** | 丰富 | 适中 | 丰富 | 适中 |\n| **SSR 支持** | 需配合 Next.js | 需配合 Nuxt | 内置 | 内置 |\n| **学习曲线** | 中等 | 较低 | 中高 | 中等 |\n| **适合场景** | 复杂交互型 | 快速原型 | SEO/SSR | 全栈型 |\n\n对于 Agent 产品而言，**React + TypeScript** 是当前最主流的选择，原因在于：\n1. Agent 产品通常有复杂的交互状态管理需求，React 的状态管理生态最为丰富\n2. TypeScript 的类型系统能有效管理 Agent 交互中的复杂类型（消息、工具调用结果等）\n3. 大量的 AI/Agent 开源组件和示例都基于 React"
        },
        {
          "id": "42.2.2",
          "title": "42.2.2 React + TypeScript 的 Agent Web 客户端架构",
          "content": "下面是一个典型的 Agent Web 客户端架构示例："
        },
        {
          "id": "42.2.3",
          "title": "42.2.3 WebSocket 实时通信",
          "content": "Agent 产品的核心交互特性——流式输出、工具调用状态更新、实时协作——都依赖于高效的实时通信。WebSocket 是实现这些功能的基础协议。"
        },
        {
          "id": "42.2.4",
          "title": "42.2.4 SSE（Server-Sent Events）作为替代方案",
          "content": "在某些场景下，SSE 是比 WebSocket 更简单的单向推送方案：\n\n| 特性 | SSE | WebSocket |\n|------|-----|-----------|\n| **通信方向** | 单向（服务端→客户端） | 双向 |\n| **协议** | HTTP | WS/WSS |\n| **自动重连** | 浏览器内置 | 需手动实现 |\n| **复杂度** | 简单 | 较高 |\n| **适用场景** | 流式输出、通知推送 | 实时聊天、协作编辑 |\n| **代理兼容** | 好（标准HTTP） | 可能被拦截 |\n\n对于 Agent 产品，**推荐组合使用**：SSE 处理流式响应（单向推送），WebSocket 处理实时协作（双向通信）。\n\n\n---"
        }
      ]
    },
    {
      "id": "42.3",
      "title": "42.3 移动端部署",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "42.3.1",
          "title": "42.3.1 移动端 Agent 的技术选型",
          "content": "移动端 Agent 部署的核心挑战在于：移动设备的算力有限，无法在本地运行大模型推理，因此必须采用**云端推理 + 轻量客户端**的架构。\n\n**技术方案对比：**\n\n| 方案 | 优势 | 劣势 | 适合场景 |\n|------|------|------|---------|\n| **React Native** | 与 Web 端共享业务逻辑 | 原生体验略差 | 已有 React 技术栈的团队 |\n| **Flutter** | 性能好、UI 一致性高 | Dart 生态较小 | 新项目、追求极致 UI |\n| **原生 iOS + Android** | 最佳性能和体验 | 开发成本翻倍 | 高端产品、深度原生集成 |\n| **Capacitor/Ionic** | 快速从 Web 转化 | 性能有上限 | 简单 Agent 展示类应用 |\n| **小程序** | 零安装、低门槛 | 能力受限 | 中国市场、轻量级工具 |"
        },
        {
          "id": "42.3.2",
          "title": "42.3.2 React Native Agent 客户端实现",
          "content": ""
        },
        {
          "id": "42.3.3",
          "title": "42.3.3 移动端特有的 Agent 能力",
          "content": "移动端相比 Web 端有一些独特的能力可以利用：\n\n**1. 语音交互**\n- 语音输入：录音 → 语音转文字 → 发送给 Agent\n- 语音输出：Agent 文本 → 文字转语音 → 播放\n- 语音唤醒：\"嘿，助手\" 唤醒 Agent\n\n**2. 相机能力**\n- 拍照识别：OCR、物体识别、文档扫描\n- 实时取景：AR 场景下的 Agent 辅助\n\n**3. 位置服务**\n- 基于位置的 Agent 建议\n- 地理围栏触发的自动化\n\n**4. 推送通知**\n- Agent 任务完成通知\n- 定时提醒、关键信息告警"
        },
        {
          "id": "42.3.4",
          "title": "42.3.4 离线与弱网策略",
          "content": "移动端网络环境不稳定，需要专门的离线/弱网策略：\n\n\n---"
        }
      ]
    },
    {
      "id": "42.4",
      "title": "42.4 桌面端部署",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "42.4.1",
          "title": "42.4.1 Electron vs Tauri",
          "content": "桌面端 Agent 应用适合需要**深度系统集成**的场景，如文件管理、IDE 集成、后台常驻服务等。\n\n| 特性 | Electron | Tauri |\n|------|----------|-------|\n| **包大小** | ~150MB | ~5-10MB |\n| **内存占用** | 较高（Chromium 内核） | 低（系统 WebView） |\n| **启动速度** | 较慢 | 快 |\n| **技术栈** | HTML/CSS/JS | 前端任意 + Rust 后端 |\n| **系统集成** | 通过 Node.js | 通过 Rust |\n| **安全模型** | Node.js 完整权限 | 最小权限原则 |\n| **生态成熟度** | 非常成熟 | 快速增长 |"
        },
        {
          "id": "42.4.2",
          "title": "42.4.2 Tauri Agent 客户端实现",
          "content": ""
        },
        {
          "id": "42.4.3",
          "title": "42.4.3 桌面端特有的 Agent 能力",
          "content": "桌面端相比移动端和 Web 端，有以下独特优势：\n\n1. **文件系统完整访问**：读写任意文件、目录浏览和管理、文件监听\n2. **终端命令执行**：运行 Shell 命令、获取命令输出、管理子进程\n3. **系统级集成**：系统托盘常驻、全局快捷键、剪贴板访问、窗口管理\n4. **本地存储**：大容量本地数据库、本地文件缓存、本地向量索引\n\n---"
        }
      ]
    },
    {
      "id": "42.5",
      "title": "42.5 CLI 工具",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "42.5.1",
          "title": "42.5.1 Agent CLI 的价值",
          "content": "CLI 是开发者最熟悉的工作界面。一个设计精良的 Agent CLI 可以：\n\n1. **融入开发者工作流**：通过管道与其他工具组合\n2. **自动化脚本集成**：在 CI/CD 中使用 Agent\n3. **快速原型验证**：无需启动 GUI 即可测试 Agent 行为\n4. **低资源开销**：终端应用资源占用极低"
        },
        {
          "id": "42.5.2",
          "title": "42.5.2 Rich + Click 构建 Agent CLI",
          "content": ""
        },
        {
          "id": "42.5.3",
          "title": "42.5.3 CLI 使用示例",
          "content": "---"
        }
      ]
    },
    {
      "id": "42.6",
      "title": "42.6 IDE 插件",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "42.6.1",
          "title": "42.6.1 IDE Agent 插件的价值",
          "content": "IDE 是开发者每天工作时间最长的环境。将 Agent 集成到 IDE 中，可以实现：\n\n1. **上下文感知**：自动获取当前文件、选区、项目结构作为 Agent 输入\n2. **无缝集成**：不需要切换窗口，直接在编码环境中使用 Agent\n3. **工作流增强**：代码生成、重构、调试、测试等编码活动都能被 Agent 增强"
        },
        {
          "id": "42.6.2",
          "title": "42.6.2 VS Code Extension API 开发",
          "content": ""
        },
        {
          "id": "42.6.3",
          "title": "42.6.3 JetBrains 插件开发",
          "content": "JetBrains IDE（IntelliJ、PyCharm、WebStorm 等）也可以使用 Kotlin + IntelliJ Platform SDK 开发插件：\n\n\n---"
        }
      ]
    },
    {
      "id": "42.7",
      "title": "42.7 微信生态",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "42.7.1",
          "title": "42.7.1 微信生态的特殊性",
          "content": "微信生态在中国市场具有不可替代的地位。将 Agent 接入微信生态，可以：\n\n1. **零门槛触达**：用户无需安装新应用\n2. **社交传播**：通过群聊、朋友圈实现病毒式传播\n3. **企业场景**：企业微信已成为企业标配\n4. **支付闭环**：微信支付实现商业闭环"
        },
        {
          "id": "42.7.2",
          "title": "42.7.2 微信小程序 Agent 集成",
          "content": ""
        },
        {
          "id": "42.7.3",
          "title": "42.7.3 企业微信机器人",
          "content": "企业微信机器人是将 Agent 接入企业工作群的有效方式：\n\n\n---"
        }
      ]
    },
    {
      "id": "42.8",
      "title": "42.8 跨平台架构设计",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "42.8.1",
          "title": "42.8.1 统一数据模型",
          "content": "跨平台部署的关键是定义一套**统一的数据模型**，确保各端对核心数据结构的理解一致："
        },
        {
          "id": "42.8.2",
          "title": "42.8.2 统一 API 网关",
          "content": "所有平台通过统一的 API 网关与后端通信："
        },
        {
          "id": "42.8.3",
          "title": "42.8.3 跨平台状态同步",
          "content": "用户在不同设备间的会话状态需要无缝同步。核心同步策略包括：\n\n**1. 增量同步**\n- 每条新消息作为增量事件推送到所有在线设备\n- 离线设备重新上线后，拉取增量消息\n\n**2. 冲突解决**\n- 基于时间戳的 Last-Write-Wins 策略\n- 编辑类操作使用 Operational Transformation (OT)\n\n**3. 会话一致性**\n- 统一的 Session ID 在所有平台间共享\n- 通过 QR 码或链接实现跨设备会话迁移"
        },
        {
          "id": "42.8.4",
          "title": "42.8.4 跨平台测试策略",
          "content": ""
        },
        {
          "id": "42.8.5",
          "title": "42.8.5 跨平台最佳实践总结",
          "content": "| 实践 | 说明 |\n|------|------|\n| **共享类型定义** | 使用 monorepo 管理跨平台共享的类型定义和工具函数 |\n| **平台适配层** | 为每个平台的特有能力建立适配层（Capability Bridge） |\n| **渐进式增强** | 核心功能全平台一致，高级功能按平台增强 |\n| **统一认证** | 使用 OAuth 2.0 + JWT 实现跨平台统一认证 |\n| **API 版本管理** | 使用 URL 版本前缀（/v1/、/v2/）确保向后兼容 |\n| **统一监控** | 所有平台的请求都携带平台标识，便于分平台监控和分析 |\n| **灰度发布** | 按平台维度进行功能灰度，降低跨平台发布风险 |\n| **性能预算** | 为每个平台设定性能预算（包大小、启动时间、内存占用） |"
        },
        {
          "id": "42.8.6",
          "title": "42.8.6 架构演进路线",
          "content": "一个 Agent 产品的跨平台部署通常经历以下演进阶段：\n\n**阶段一：Web 单平台（MVP）**\n- 专注 Web 端，快速验证产品价值\n- REST API + SSE 实现流式响应\n- 简单的会话管理和用户系统\n\n**阶段二：+ 移动端**\n- 使用 React Native 复用业务逻辑\n- 添加推送通知、语音输入等移动端能力\n- 实现基础的跨设备会话同步\n\n**阶段三：+ 桌面端 + CLI**\n- 使用 Tauri 构建桌面应用，提供文件系统和终端访问能力\n- CLI 工具融入开发者工作流\n- 完善跨平台状态同步机制\n\n**阶段四：+ IDE 插件 + 微信生态**\n- VS Code/JetBrains 插件实现编码场景的 Agent 集成\n- 微信小程序/企业微信接入，覆盖中国市场\n- 全平台统一监控和运营体系\n\n**阶段五：平台深度融合**\n- 每个平台都有平台原生的深度优化体验\n- 智能平台选择：根据用户场景自动推荐最合适的平台\n- 跨平台工作流：不同平台间无缝协作\n\n\n---"
        }
      ]
    },
    {
      "id": "本章小结",
      "title": "本章小结",
      "level": 2,
      "content": "跨平台部署是 Agent 产品从\"技术验证\"走向\"规模化应用\"的关键一步。本章从 Web、移动端、桌面端、CLI、IDE 插件、微信生态六个维度，详细探讨了 Agent 跨平台部署的技术方案和最佳实践。\n\n**核心要点回顾：**\n\n1. **后端统一、前端适配**是跨平台部署的基本架构原则\n2. **WebSocket + SSE** 的组合可以满足 Agent 产品对实时通信的多样化需求\n3. **移动端**需要特别关注离线策略和平台特有能力（语音、相机、位置）的利用\n4. **桌面端**（Tauri/Electron）提供了文件系统和终端命令的深度集成能力\n5. **CLI** 是融入开发者工作流的重要入口，Rich + Click 可以快速构建专业级 CLI\n6. **IDE 插件**能实现上下文感知的 Agent 辅助，显著提升编码效率\n7. **微信生态**是中国市场的必选项，小程序和企业微信各有适用场景\n8. **统一数据模型 + API 网关**是跨平台一致性的技术保障\n\n**下一步思考：**\n\n- 如何在保持跨平台一致性的同时，充分利用各平台的独特能力？\n- 边缘计算的发展将如何改变 Agent 的跨平台架构？\n- 如何设计一套可扩展的平台适配层，使新平台的接入成本最低化？\n\n---\n\n*「一个好的 Agent 产品应该像水一样，倒入任何容器（平台）都能自然地适应其形状。」*",
      "subsections": []
    }
  ],
  "code_blocks": [
    {
      "id": "code-1",
      "language": "text",
      "description": "一个完整的 Agent 跨平台架构通常包含以下层次：",
      "code": "┌──────────────────────────────────────────────────────┐\n│                   客户端展示层                         │\n│  ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐       │\n│  │ Web  │ │ Mobile│ │Desktop│ │ CLI │ │ IDE │       │\n│  └──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘       │\n├─────┼────────┼────────┼────────┼────────┼───────────┤\n│     │     跨平台通信层（REST API + WebSocket + SSE）    │\n├─────┼────────┼────────┼────────┼────────┼───────────┤\n│     │     业务逻辑层（Agent Core - 语言无关）            │\n├─────┼────────┼────────┼────────┼────────┼───────────┤\n│     │     数据持久层（统一数据模型 + 多端同步）           │\n├─────┼────────┼────────┼────────┼────────┼───────────┤\n│     │     基础设施层（模型推理 · 向量库 · 消息队列）      │\n└─────┴────────┴────────┴────────┴────────┴───────────┘",
      "section_ref": "42.1.3",
      "runnable": false,
      "dependencies": []
    },
    {
      "id": "code-2",
      "language": "typescript",
      "description": "下面是一个典型的 Agent Web 客户端架构示例：",
      "code": "// src/types/agent.ts - 统一类型定义\nexport interface AgentMessage {\n  id: string;\n  role: 'user' | 'assistant' | 'system' | 'tool';\n  content: string;\n  timestamp: number;\n  toolCalls?: ToolCall[];\n  toolResults?: ToolResult[];\n  metadata?: Record<string, unknown>;\n}\n\nexport interface ToolCall {\n  id: string;\n  name: string;\n  arguments: Record<string, unknown>;\n  status: 'pending' | 'running' | 'completed' | 'failed';\n}\n\nexport interface ToolResult {\n  callId: string;\n  content: string;\n  isError?: boolean;\n}\n\nexport interface Conversation {\n  id: string;\n  title: string;\n  messages: AgentMessage[];\n  model: string;\n  createdAt: number;\n  updatedAt: number;\n}\n\n// src/hooks/useAgent.ts - Agent 交互核心 Hook\nimport { useState, useCallback, useRef } from 'react';\n\ninterface UseAgentOptions {\n  apiUrl: string;\n  model?: string;\n  onToolCall?: (call: ToolCall) => Promise<string>;\n}\n\nexport function useAgent(options: UseAgentOptions) {\n  const [messages, setMessages] = useState<AgentMessage[]>([]);\n  const [isLoading, setIsLoading] = useState(false);\n  const [error, setError] = useState<string | null>(null);\n  const abortControllerRef = useRef<AbortController | null>(null);\n\n  const sendMessage = useCallback(async (content: string) => {\n    const userMessage: AgentMessage = {\n      id: crypto.randomUUID(),\n      role: 'user',\n      content,\n      timestamp: Date.now(),\n    };\n    setMessages(prev => [...prev, userMessage]);\n    setIsLoading(true);\n    setError(null);\n\n    const controller = new AbortController();\n    abortControllerRef.current = controller;\n\n    try {\n      const response = await fetch(`${options.apiUrl}/chat`, {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({\n          messages: [...messages, userMessage].map(m => ({\n            role: m.role, content: m.content,\n          })),\n          model: options.model,\n          stream: true,\n        }),\n        signal: controller.signal,\n      });\n\n      if (!response.ok) {\n        throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n      }\n\n      // 流式读取响应\n      const reader = response.body!.getReader();\n      const decoder = new TextDecoder();\n      let assistantContent = '';\n\n      const assistantMessage: AgentMessage = {\n        id: crypto.randomUUID(),\n        role: 'assistant',\n        content: '',\n        timestamp: Date.now(),\n      };\n      setMessages(prev => [...prev, assistantMessage]);\n\n      while (true) {\n        const { done, value } = await reader.read();\n        if (done) break;\n        const chunk = decoder.decode(value, { stream: true });\n        const lines = chunk.split('\\n').filter(Boolean);\n        for (const line of lines) {\n          if (line.startsWith('data: ')) {\n            const data = line.slice(6);\n            if (data === '[DONE]') break;\n            const parsed = JSON.parse(data);\n            if (parsed.choices?.[0]?.delta?.content) {\n              assistantContent += parsed.choices[0].delta.content;\n              setMessages(prev =>\n                prev.map(m =>\n                  m.id === assistantMessage.id\n                    ? { ...m, content: assistantContent }\n                    : m\n                )\n              );\n            }\n          }\n        }\n      }\n    } catch (err: any) {\n      if (err.name !== 'AbortError') setError(err.message);\n    } finally {\n      setIsLoading(false);\n      abortControllerRef.current = null;\n    }\n  }, [messages, options]);\n\n  const stopGeneration = useCallback(() => {\n    abortControllerRef.current?.abort();\n  }, []);\n\n  return { messages, isLoading, error, sendMessage, stopGeneration };\n}\n\n// src/components/ChatPanel.tsx - 聊天面板组件\nimport { useRef, useEffect } from 'react';\nimport { useAgent } from '../hooks/useAgent';\n\ninterface ChatPanelProps {\n  apiUrl: string;\n  model?: string;\n}\n\nexport function ChatPanel({ apiUrl, model }: ChatPanelProps) {\n  const { messages, isLoading, error, sendMessage, stopGeneration } = useAgent({\n    apiUrl, model,\n  });\n  const messagesEndRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });\n  }, [messages]);\n\n  return (\n    <div className=\"flex flex-col h-full\">\n      <div className=\"flex-1 overflow-y-auto p-4 space-y-4\">\n        {messages.map((msg) => (\n          <MessageBubble key={msg.id} message={msg} />\n        ))}\n        {isLoading && (\n          <div className=\"flex items-center gap-2 text-gray-500\">\n            <div className=\"animate-spin w-4 h-4 border-2 border-gray-300 border-t-blue-500 rounded-full\" />\n            <span>Agent 思考中...</span>\n          </div>\n        )}\n        {error && (\n          <div className=\"text-red-500 bg-red-50 p-3 rounded-lg\">{error}</div>\n        )}\n        <div ref={messagesEndRef} />\n      </div>\n      <div className=\"border-t p-4\">\n        <div className=\"flex gap-2\">\n          <textarea\n            className=\"flex-1 resize-none border rounded-lg p-3 focus:outline-none focus:ring-2 focus:ring-blue-500\"\n            placeholder=\"输入消息...\"\n            rows={1}\n            onKeyDown={(e) => {\n              if (e.key === 'Enter' && !e.shiftKey) {\n                e.preventDefault();\n                const target = e.target as HTMLTextAreaElement;\n                if (target.value.trim()) {\n                  sendMessage(target.value.trim());\n                  target.value = '';\n                }\n              }\n            }}\n          />\n          {isLoading ? (\n            <button onClick={stopGeneration}\n              className=\"px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600\">停止</button>\n          ) : (\n            <button onClick={() => {\n              const textarea = document.querySelector('textarea');\n              if (textarea?.value.trim()) {\n                sendMessage(textarea.value.trim());\n                textarea.value = '';\n              }\n            }} className=\"px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600\">发送</button>\n          )}\n        </div>\n      </div>\n    </div>\n  );\n}\n\n// 消息气泡组件 - 处理工具调用展示\nfunction MessageBubble({ message }: { message: AgentMessage }) {\n  const isUser = message.role === 'user';\n  return (\n    <div className={`flex ${isUser ? 'justify-end' : 'justify-start'}`}>\n      <div className={`max-w-[80%] rounded-lg p-4 ${isUser ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-900'}`}>\n        {message.toolCalls?.map((call) => (\n          <div key={call.id} className=\"mb-2 p-2 bg-white/10 rounded border border-white/20\">\n            <div className=\"flex items-center gap-2 text-sm\">\n              <span className=\"font-mono\">{call.name}</span>\n              <ToolCallStatus status={call.status} />\n            </div>\n            <pre className=\"mt-1 text-xs overflow-x-auto\">\n              {JSON.stringify(call.arguments, null, 2)}\n            </pre>\n          </div>\n        ))}\n        <div className=\"whitespace-pre-wrap\">{message.content}</div>\n      </div>\n    </div>\n  );\n}\n\nfunction ToolCallStatus({ status }: { status: ToolCall['status'] }) {\n  const config = {\n    pending: { icon: '⏳', text: '等待中' },\n    running: { icon: '⚡', text: '执行中' },\n    completed: { icon: '✅', text: '已完成' },\n    failed: { icon: '❌', text: '失败' },\n  };\n  const { icon, text } = config[status];\n  return <span>{icon} {text}</span>;\n}",
      "section_ref": "42.2.2",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-3",
      "language": "python",
      "description": "Agent 产品的核心交互特性——流式输出、工具调用状态更新、实时协作——都依赖于高效的实时通信。WebSocket 是实现这些功能的基础协议。",
      "code": "# backend/websocket_manager.py - WebSocket 连接管理\nimport asyncio\nimport json\nimport uuid\nfrom typing import Dict, Set\nfrom dataclasses import dataclass, field\nfrom datetime import datetime\n\n@dataclass\nclass WSClient:\n    \"\"\"WebSocket 客户端连接\"\"\"\n    client_id: str\n    user_id: str\n    session_id: str\n    websocket: object\n    connected_at: datetime = field(default_factory=datetime.now)\n    last_heartbeat: datetime = field(default_factory=datetime.now)\n\nclass WebSocketManager:\n    \"\"\"WebSocket 连接管理器\"\"\"\n\n    def __init__(self):\n        self._clients: Dict[str, WSClient] = {}\n        self._session_clients: Dict[str, Set[str]] = {}\n        self._user_clients: Dict[str, Set[str]] = {}\n        self._heartbeat_interval = 30\n\n    async def connect(self, websocket, user_id: str, session_id: str) -> str:\n        client_id = str(uuid.uuid4())\n        client = WSClient(client_id=client_id, user_id=user_id,\n                          session_id=session_id, websocket=websocket)\n        self._clients[client_id] = client\n        if session_id not in self._session_clients:\n            self._session_clients[session_id] = set()\n        self._session_clients[session_id].add(client_id)\n        if user_id not in self._user_clients:\n            self._user_clients[user_id] = set()\n        self._user_clients[user_id].add(client_id)\n        return client_id\n\n    async def disconnect(self, client_id: str):\n        client = self._clients.pop(client_id, None)\n        if not client:\n            return\n        if client.session_id in self._session_clients:\n            self._session_clients[client.session_id].discard(client_id)\n            if not self._session_clients[client.session_id]:\n                del self._session_clients[client.session_id]\n        if client.user_id in self._user_clients:\n            self._user_clients[client.user_id].discard(client_id)\n            if not self._user_clients[client.user_id]:\n                del self._user_clients[client.user_id]\n\n    async def send_to_session(self, session_id: str, message: dict):\n        if session_id not in self._session_clients:\n            return\n        disconnected = []\n        for client_id in self._session_clients[session_id]:\n            client = self._clients.get(client_id)\n            if client:\n                try:\n                    await client.websocket.send_json(message)\n                except Exception:\n                    disconnected.append(client_id)\n        for cid in disconnected:\n            await self.disconnect(cid)\n\n    async def send_to_user(self, user_id: str, message: dict):\n        if user_id not in self._user_clients:\n            return\n        disconnected = []\n        for client_id in self._user_clients[user_id]:\n            client = self._clients.get(client_id)\n            if client:\n                try:\n                    await client.websocket.send_json(message)\n                except Exception:\n                    disconnected.append(client_id)\n        for cid in disconnected:\n            await self.disconnect(cid)\n\n    async def broadcast(self, message: dict, exclude_client: str = None):\n        disconnected = []\n        for client_id, client in self._clients.items():\n            if client_id == exclude_client:\n                continue\n            try:\n                await client.websocket.send_json(message)\n            except Exception:\n                disconnected.append(client_id)\n        for cid in disconnected:\n            await self.disconnect(cid)\n\n\n# 消息类型定义\nclass MessageType:\n    AGENT_CHUNK = \"agent.chunk\"\n    AGENT_COMPLETE = \"agent.complete\"\n    AGENT_ERROR = \"agent.error\"\n    TOOL_CALL_START = \"tool.call.start\"\n    TOOL_CALL_RESULT = \"tool.call.result\"\n    TOOL_CALL_ERROR = \"tool.call.error\"\n    SESSION_CREATED = \"session.created\"\n    SESSION_UPDATED = \"session.updated\"\n    TYPING_START = \"status.typing.start\"\n    TYPING_STOP = \"status.typing.stop\"\n    HEARTBEAT = \"system.heartbeat\"\n    HEARTBEAT_ACK = \"system.heartbeat.ack\"",
      "section_ref": "42.2.3",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-4",
      "language": "python",
      "description": "HEARTBEATACK = \"system.heartbeat.ack\"",
      "code": "# backend/routes/chat_ws.py - WebSocket 聊天路由\nfrom fastapi import APIRouter, WebSocket, WebSocketDisconnect\nimport json\n\nrouter = APIRouter()\n\n@router.websocket(\"/ws/chat/{session_id}\")\nasync def chat_websocket(websocket: WebSocket, session_id: str,\n                         token: str = None):\n    user = await verify_ws_token(token)\n    if not user:\n        await websocket.close(code=4001, reason=\"Unauthorized\")\n        return\n    await websocket.accept()\n    client_id = await ws_manager.connect(websocket, user.id, session_id)\n\n    try:\n        while True:\n            data = await websocket.receive_text()\n            message = json.loads(data)\n            msg_type = message.get(\"type\")\n\n            if msg_type == MessageType.HEARTBEAT:\n                await websocket.send_json({\n                    \"type\": MessageType.HEARTBEAT_ACK,\n                    \"timestamp\": datetime.now().isoformat(),\n                })\n                ws_manager._clients[client_id].last_heartbeat = datetime.now()\n\n            elif msg_type == \"chat.message\":\n                content = message.get(\"content\", \"\")\n                model = message.get(\"model\", \"default\")\n                asyncio.create_task(\n                    process_agent_stream(session_id, user.id, content,\n                                         model, ws_manager)\n                )\n\n            elif msg_type == MessageType.STOP_GENERATION:\n                await cancel_stream(session_id)\n\n    except WebSocketDisconnect:\n        await ws_manager.disconnect(client_id)\n    except Exception as e:\n        await ws_manager.disconnect(client_id)\n\n\nasync def process_agent_stream(session_id, user_id, content, model, ws_manager):\n    try:\n        await ws_manager.send_to_session(session_id, {\n            \"type\": MessageType.TYPING_START,\n            \"data\": {\"session_id\": session_id},\n        })\n        async for event in agent_engine.stream_chat(\n            session_id=session_id, user_id=user_id,\n            content=content, model=model,\n        ):\n            event_type = event.get(\"type\")\n            if event_type == \"text_delta\":\n                await ws_manager.send_to_session(session_id, {\n                    \"type\": MessageType.AGENT_CHUNK,\n                    \"data\": {\"content\": event[\"content\"], \"message_id\": event[\"message_id\"]},\n                })\n            elif event_type == \"tool_call\":\n                await ws_manager.send_to_session(session_id, {\n                    \"type\": MessageType.TOOL_CALL_START,\n                    \"data\": {\"call_id\": event[\"call_id\"], \"name\": event[\"name\"],\n                             \"arguments\": event[\"arguments\"]},\n                })\n            elif event_type == \"tool_result\":\n                await ws_manager.send_to_session(session_id, {\n                    \"type\": MessageType.TOOL_CALL_RESULT,\n                    \"data\": {\"call_id\": event[\"call_id\"], \"content\": event[\"content\"]},\n                })\n\n        await ws_manager.send_to_session(session_id, {\n            \"type\": MessageType.AGENT_COMPLETE,\n            \"data\": {\"session_id\": session_id},\n        })\n        await ws_manager.send_to_session(session_id, {\n            \"type\": MessageType.TYPING_STOP,\n            \"data\": {\"session_id\": session_id},\n        })\n    except Exception as e:\n        await ws_manager.send_to_session(session_id, {\n            \"type\": MessageType.AGENT_ERROR,\n            \"data\": {\"message\": str(e)},\n        })",
      "section_ref": "42.2.3",
      "runnable": true,
      "dependencies": [
        "fastapi"
      ]
    },
    {
      "id": "code-5",
      "language": "typescript",
      "description": "对于 Agent 产品，推荐组合使用：SSE 处理流式响应（单向推送），WebSocket 处理实时协作（双向通信）。",
      "code": "// SSE 客户端实现\nexport function useSSEAgent(apiUrl: string) {\n  const [messages, setMessages] = useState<AgentMessage[]>([]);\n  const [isLoading, setIsLoading] = useState(false);\n\n  const sendMessage = async (content: string) => {\n    const userMsg: AgentMessage = {\n      id: crypto.randomUUID(), role: 'user',\n      content, timestamp: Date.now(),\n    };\n    setMessages(prev => [...prev, userMsg]);\n    setIsLoading(true);\n    const assistantMsg: AgentMessage = {\n      id: crypto.randomUUID(), role: 'assistant',\n      content: '', timestamp: Date.now(),\n    };\n    setMessages(prev => [...prev, assistantMsg]);\n\n    try {\n      const response = await fetch(`${apiUrl}/chat/stream`, {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({ messages: [...messages, userMsg] }),\n      });\n      const reader = response.body!.getReader();\n      const decoder = new TextDecoder();\n      let accumulated = '';\n      while (true) {\n        const { done, value } = await reader.read();\n        if (done) break;\n        const text = decoder.decode(value, { stream: true });\n        const events = text.split('\\n\\n');\n        for (const event of events) {\n          const dataLine = event.split('\\n').find(l => l.startsWith('data: '));\n          if (!dataLine) continue;\n          const jsonStr = dataLine.slice(6);\n          if (jsonStr === '[DONE]') break;\n          const parsed = JSON.parse(jsonStr);\n          if (parsed.content) {\n            accumulated += parsed.content;\n            setMessages(prev =>\n              prev.map(m => m.id === assistantMsg.id ? { ...m, content: accumulated } : m)\n            );\n          }\n        }\n      }\n    } finally {\n      setIsLoading(false);\n    }\n  };\n  return { messages, isLoading, sendMessage };\n}",
      "section_ref": "42.2.4",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-6",
      "language": "typescript",
      "description": "| 小程序 | 零安装、低门槛 | 能力受限 | 中国市场、轻量级工具 |",
      "code": "// mobile/src/services/AgentService.ts - Agent 服务层\nimport { Platform } from 'react-native';\n\ninterface AgentConfig {\n  baseUrl: string;\n  model: string;\n  apiKey: string;\n}\n\nclass AgentService {\n  private config: AgentConfig;\n  private abortController: AbortController | null = null;\n\n  constructor(config: AgentConfig) { this.config = config; }\n\n  async *sendMessageStream(\n    messages: Array<{ role: string; content: string }>,\n  ): AsyncGenerator<{\n    type: 'text_delta' | 'tool_call' | 'tool_result' | 'done' | 'error';\n    content?: string; data?: any;\n  }> {\n    const controller = new AbortController();\n    this.abortController = controller;\n\n    try {\n      const response = await fetch(`${this.config.baseUrl}/chat/stream`, {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n          'Authorization': `Bearer ${this.config.apiKey}`,\n        },\n        body: JSON.stringify({\n          messages, model: this.config.model,\n          platform: Platform.OS,\n        }),\n        signal: controller.signal,\n      });\n\n      if (!response.ok) throw new Error(`HTTP ${response.status}`);\n\n      const reader = response.body!.getReader();\n      const decoder = new TextDecoder();\n      let buffer = '';\n\n      while (true) {\n        const { done, value } = await reader.read();\n        if (done) break;\n        buffer += decoder.decode(value, { stream: true });\n        const lines = buffer.split('\\n');\n        buffer = lines.pop() || '';\n        for (const line of lines) {\n          if (!line.startsWith('data: ')) continue;\n          const data = line.slice(6).trim();\n          if (data === '[DONE]') { yield { type: 'done' }; return; }\n          try {\n            const parsed = JSON.parse(data);\n            yield parsed;\n          } catch { /* skip */ }\n        }\n      }\n    } catch (err: any) {\n      if (err.name !== 'AbortError') yield { type: 'error', content: err.message };\n    }\n  }\n\n  stopGeneration() { this.abortController?.abort(); }\n\n  // 语音转文字（移动端特有能力）\n  async transcribeAudio(audioUri: string): Promise<string> {\n    const formData = new FormData();\n    formData.append('audio', {\n      uri: audioUri, type: 'audio/m4a', name: 'recording.m4a',\n    } as any);\n    const response = await fetch(`${this.config.baseUrl}/audio/transcribe`, {\n      method: 'POST',\n      headers: { 'Authorization': `Bearer ${this.config.apiKey}` },\n      body: formData,\n    });\n    const result = await response.json();\n    return result.text;\n  }\n\n  // 图片识别（移动端拍照后调用）\n  async analyzeImage(imageUri: string, prompt: string): Promise<string> {\n    const formData = new FormData();\n    formData.append('image', {\n      uri: imageUri, type: 'image/jpeg', name: 'photo.jpg',\n    } as any);\n    formData.append('prompt', prompt);\n    const response = await fetch(`${this.config.baseUrl}/vision/analyze`, {\n      method: 'POST',\n      headers: { 'Authorization': `Bearer ${this.config.apiKey}` },\n      body: formData,\n    });\n    const result = await response.json();\n    return result.content;\n  }\n}",
      "section_ref": "42.3.2",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-7",
      "language": "typescript",
      "description": "- 定时提醒、关键信息告警",
      "code": "// mobile/src/services/CapabilityBridge.ts - 能力桥接层\nimport { Platform } from 'react-native';\nimport { Linking, Notifications } from 'react-native';\n\n/**\n * 能力桥接层：统一封装不同平台的特有能力\n * Agent 工具调用时通过此层访问设备能力\n */\nclass CapabilityBridge {\n  private handlers: Map<string, Function> = new Map();\n\n  registerCapability(name: string, handler: Function) {\n    this.handlers.set(name, handler);\n  }\n\n  async callCapability(name: string, params: any): Promise<any> {\n    const handler = this.handlers.get(name);\n    if (!handler) throw new Error(`Capability not supported: ${name}`);\n\n    const platformCheck = await this.checkPlatformSupport(name);\n    if (!platformCheck.supported) {\n      return { success: false, error: `Not supported on ${Platform.OS}` };\n    }\n    return handler(params);\n  }\n\n  private async checkPlatformSupport(name: string) {\n    const capabilities: Record<string, Record<string, boolean>> = {\n      'camera.capture': { ios: true, android: true, web: true },\n      'location.current': { ios: true, android: true, web: true },\n      'contacts.read': { ios: true, android: true, web: false },\n      'sms.send': { ios: false, android: true, web: false },\n      'notification.local': { ios: true, android: true, web: true },\n      'calendar.read': { ios: true, android: true, web: false },\n    };\n    const cap = capabilities[name];\n    if (!cap) return { supported: true };\n    return { supported: cap[Platform.OS] ?? false };\n  }\n\n  init() {\n    this.registerCapability('phone.call', async (params: any) => {\n      const { number } = params;\n      const supported = await Linking.canOpenURL(`tel:${number}`);\n      if (supported) { await Linking.openURL(`tel:${number}`); return { success: true }; }\n      return { success: false, error: 'Cannot make phone calls' };\n    });\n\n    this.registerCapability('notification.local', async (params: any) => {\n      await Notifications.scheduleNotificationAsync({\n        content: { title: params.title, body: params.body, data: params.data },\n        trigger: params.trigger || null,\n      });\n      return { success: true };\n    });\n  }\n}\n\nexport default new CapabilityBridge();",
      "section_ref": "42.3.3",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-8",
      "language": "python",
      "description": "移动端网络环境不稳定，需要专门的离线/弱网策略：",
      "code": "# backend/services/offline_sync.py - 离线同步策略\nfrom dataclasses import dataclass\nfrom datetime import datetime\nfrom typing import List\n\n\n@dataclass\nclass PendingMessage:\n    id: str\n    user_id: str\n    content: str\n    timestamp: datetime\n    retry_count: int = 0\n    max_retries: int = 3\n\n\nclass OfflineSyncService:\n    \"\"\"离线消息同步服务\"\"\"\n\n    def __init__(self, storage_backend):\n        self.storage = storage_backend\n\n    async def cache_message(self, user_id: str, content: str) -> str:\n        \"\"\"离线时缓存消息\"\"\"\n        import uuid\n        msg = PendingMessage(\n            id=str(uuid.uuid4()),\n            user_id=user_id,\n            content=content,\n            timestamp=datetime.now(),\n        )\n        await self.storage.save_pending(msg)\n        return msg.id\n\n    async def sync_pending(self, user_id: str,\n                           process_fn) -> dict:\n        \"\"\"网络恢复时同步缓存消息\"\"\"\n        pending = await self.storage.get_pending(user_id)\n        sent, failed = 0, 0\n\n        for msg in pending:\n            try:\n                await process_fn(msg.content)\n                await self.storage.remove_pending(msg.id)\n                sent += 1\n            except Exception:\n                msg.retry_count += 1\n                if msg.retry_count >= msg.max_retries:\n                    await self.storage.mark_failed(msg)\n                    failed += 1\n                else:\n                    await self.storage.update_pending(msg)\n\n        return {\"sent\": sent, \"failed\": failed, \"remaining\": len(pending) - sent - failed}\n\n    async def get_pending_count(self, user_id: str) -> int:\n        return await self.storage.count_pending(user_id)",
      "section_ref": "42.3.4",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-9",
      "language": "rust",
      "description": "| 生态成熟度 | 非常成熟 | 快速增长 |",
      "code": "// src-tauri/src/commands/agent.rs - Tauri Agent 命令\nuse tauri::{command, State};\nuse serde::{Deserialize, Serialize};\nuse std::sync::Mutex;\n\n#[derive(Serialize, Deserialize, Clone)]\npub struct AgentMessage {\n    pub id: String,\n    pub role: String,\n    pub content: String,\n    pub timestamp: i64,\n}\n\n#[derive(Serialize, Deserialize)]\npub struct AppConfig {\n    pub api_url: String,\n    pub api_key: String,\n    pub model: String,\n    pub theme: String,\n}\n\npub struct AppState {\n    pub config: Mutex<AppConfig>,\n}\n\n#[command]\npub async fn send_chat_message(\n    state: State<'_, AppState>,\n    messages: Vec<AgentMessage>,\n    model: String,\n) -> Result<String, String> {\n    let config = state.config.lock().map_err(|e| e.to_string())?;\n\n    let client = reqwest::Client::new();\n    let response = client\n        .post(format!(\"{}/chat\", config.api_url))\n        .header(\"Authorization\", format!(\"Bearer {}\", config.api_key))\n        .json(&serde_json::json!({\n            \"messages\": messages,\n            \"model\": model,\n            \"stream\": false,\n        }))\n        .send()\n        .await\n        .map_err(|e| format!(\"Request failed: {}\", e))?;\n\n    if !response.status().is_success() {\n        return Err(format!(\"API error: {}\", response.status()));\n    }\n\n    response.text().await.map_err(|e| e.to_string())\n}\n\n#[command]\npub async fn read_local_file(path: String) -> Result<String, String> {\n    let expanded = shellexpand::tilde(&path).to_string();\n    tokio::fs::read_to_string(&expanded)\n        .await\n        .map_err(|e| format!(\"Failed to read file: {}\", e))\n}\n\n#[command]\npub async fn execute_terminal_command(\n    command: String,\n    cwd: Option<String>,\n) -> Result<String, String> {\n    let output = if let Some(dir) = cwd {\n        tokio::process::Command::new(\"sh\")\n            .arg(\"-c\").arg(&command)\n            .current_dir(shellexpand::tilde(&dir).as_ref())\n            .output().await\n    } else {\n        tokio::process::Command::new(\"sh\")\n            .arg(\"-c\").arg(&command)\n            .output().await\n    };\n\n    match output {\n        Ok(output) => Ok(serde_json::json!({\n            \"stdout\": String::from_utf8_lossy(&output.stdout),\n            \"stderr\": String::from_utf8_lossy(&output.stderr),\n            \"exit_code\": output.status.code(),\n        }).to_string()),\n        Err(e) => Err(format!(\"Command failed: {}\", e)),\n    }\n}\n\n#[command]\npub async fn get_config(state: State<'_, AppState>) -> Result<AppConfig, String> {\n    state.config.lock().map_err(|e| e.to_string()).map(|c| c.clone())\n}\n\n#[command]\npub async fn update_config(\n    state: State<'_, AppState>,\n    new_config: AppConfig,\n) -> Result<(), String> {\n    let mut config = state.config.lock().map_err(|e| e.to_string())?;\n    *config = new_config;\n    Ok(())\n}",
      "section_ref": "42.4.2",
      "runnable": false,
      "dependencies": []
    },
    {
      "id": "code-10",
      "language": "python",
      "description": "4. 低资源开销：终端应用资源占用极低",
      "code": "# cli/main.py - Agent CLI 主入口\nimport click\nfrom rich.console import Console\nfrom rich.panel import Panel\nfrom rich.markdown import Markdown\nfrom rich.syntax import Syntax\nfrom rich.table import Table\nfrom rich.progress import Progress, SpinnerColumn, TextColumn\nfrom rich.live import Live\nfrom rich.prompt import Prompt\n\nconsole = Console()\n\n\n@click.group()\n@click.option('--api-url', default=None, help='Agent API URL')\n@click.option('--model', default=None, help='Default model')\n@click.pass_context\ndef cli(ctx, api_url, model):\n    \"\"\"🤖 Agent CLI - Your AI assistant in the terminal.\"\"\"\n    ctx.ensure_object(dict)\n    ctx.obj['api_url'] = api_url\n    ctx.obj['model'] = model\n\n\n@cli.command()\n@click.argument('message', nargs=-1, required=False)\n@click.option('--session', '-s', default=None, help='Session ID')\n@click.option('--stream/--no-stream', default=True, help='Stream response')\n@click.pass_context\ndef chat(ctx, message, session, stream):\n    \"\"\"Chat with the Agent.\"\"\"\n    from agent_client import AgentClient\n\n    api_url = ctx.obj['api_url'] or load_config().api_url\n    model = ctx.obj['model'] or load_config().model\n    client = AgentClient(api_url, model)\n\n    if message:\n        msg = ' '.join(message)\n        console.print(f\"\\n[bold cyan]You:[/] {msg}\\n\")\n        with console.status(\"[bold green]Agent thinking...\"):\n            response = client.send_message(msg, session_id=session)\n        console.print(Panel(Markdown(response), title=\"[bold magenta]Agent[/]\",\n                           border_style=\"magenta\"))\n    else:\n        _interactive_chat(client, session)\n\n\ndef _interactive_chat(client, session_id):\n    \"\"\"交互式对话循环\"\"\"\n    console.print(Panel(\n        \"[bold]Interactive Chat Mode[/]\\n\"\n        \"Type your message and press Enter.\\n\"\n        \"[dim]/quit - Exit  |  /clear - Clear  |  /model - Switch  |  /export - Save[/]\",\n        title=\"🤖 Agent CLI\", border_style=\"blue\",\n    ))\n\n    history = []\n    current_model = client.model\n\n    while True:\n        try:\n            user_input = Prompt.ask(\"\\n[bold cyan]You[/bold]\")\n        except (EOFError, KeyboardInterrupt):\n            console.print(\"\\n[dim]Goodbye! 👋[/]\")\n            break\n\n        if user_input.startswith('/'):\n            cmd = user_input.strip().lower()\n            if cmd in ('/quit', '/exit'):\n                console.print(\"[dim]Goodbye! 👋[/]\")\n                break\n            elif cmd == '/clear':\n                history.clear()\n                console.print(\"[yellow]History cleared.[/]\")\n                continue\n            elif cmd == '/model':\n                current_model = Prompt.ask(\"Model name\", default=current_model)\n                console.print(f\"[green]Switched to: {current_model}[/]\")\n                continue\n            elif cmd == '/export':\n                _export_chat(history)\n                continue\n            else:\n                console.print(f\"[red]Unknown: {cmd}[/]\")\n                continue\n\n        history.append({\"role\": \"user\", \"content\": user_input})\n\n        # 流式输出\n        console.print()\n        full_response = \"\"\n        with Live(console=console, refresh_per_second=15) as live:\n            for chunk in client.stream_message(user_input, history=history,\n                                                model=current_model):\n                if chunk.get(\"type\") == \"text_delta\":\n                    full_response += chunk[\"content\"]\n                    live.update(Panel(Markdown(full_response),\n                                     title=\"[bold magenta]Agent[/]\",\n                                     border_style=\"magenta\"))\n                elif chunk.get(\"type\") == \"tool_call\":\n                    live.update(Panel(\n                        f\"[yellow]⚡ Calling: {chunk['name']}[/]\\n\"\n                        f\"[dim]{chunk.get('arguments', '')}[/]\",\n                        title=\"[bold magenta]Agent[/]\", border_style=\"yellow\"))\n\n        history.append({\"role\": \"assistant\", \"content\": full_response})\n\n\n@cli.command()\n@click.argument('file_path', type=click.Path(exists=True))\n@click.option('--question', '-q', help='Question about the file')\n@click.pass_context\ndef analyze(ctx, file_path, question):\n    \"\"\"Analyze a file with the Agent.\"\"\"\n    import os\n    from agent_client import AgentClient\n\n    api_url = ctx.obj['api_url'] or load_config().api_url\n    model = ctx.obj['model'] or load_config().model\n    client = AgentClient(api_url, model)\n\n    with open(file_path, 'r') as f:\n        content = f.read()\n\n    console.print(Panel(\n        f\"[bold]File:[/] {file_path}\\n[bold]Size:[/] {os.path.getsize(file_path):,} bytes\",\n        title=\"📄 File Analysis\", border_style=\"blue\",\n    ))\n\n    ext = os.path.splitext(file_path)[1].lstrip('.')\n    syntax = Syntax(content[:2000], ext, theme=\"monokai\", line_numbers=True)\n    console.print(syntax)\n\n    prompt = question or f\"分析这个文件，指出关键问题和改进建议：\\n```\\n{content[:5000]}\\n```\"\n    with console.status(\"[bold green]Analyzing...\"):\n        response = client.send_message(prompt)\n    console.print(Panel(Markdown(response), title=\"[bold magenta]Analysis[/]\",\n                       border_style=\"magenta\"))\n\n\n@cli.command()\n@click.argument('command_text')\ndef exec_agent(command_text):\n    \"\"\"Execute an agent command with tool access.\"\"\"\n    from agent_client import AgentClient\n    client = AgentClient(load_config().api_url, load_config().model)\n\n    console.print(f\"[bold]Executing:[/] {command_text}\\n\")\n    with Progress(SpinnerColumn(), TextColumn(\"[progress.description]{task.description}\"),\n                  console=console) as progress:\n        task = progress.add_task(\"Agent executing...\", total=None)\n        for event in client.execute_with_tools(command_text):\n            if event[\"type\"] == \"tool_call\":\n                progress.update(task, description=f\"⚡ Tool: {event['name']}\")\n            elif event[\"type\"] == \"tool_result\":\n                console.print(Panel(event[\"content\"][:500],\n                    title=f\"[green]✅ {event.get('name', 'tool')}[/]\",\n                    border_style=\"green\"))\n            elif event[\"type\"] == \"text\":\n                console.print(Markdown(event[\"content\"]))\n    console.print(\"[bold green]✓ Done[/]\")\n\n\ndef _export_chat(history):\n    import json\n    from datetime import datetime\n    ts = datetime.now().strftime(\"%Y%m%d_%H%M%S\")\n    with open(f\"chat_{ts}.json\", 'w') as f:\n        json.dump(history, f, ensure_ascii=False, indent=2)\n    console.print(f\"[green]Exported to chat_{ts}.json[/]\")\n\n\nif __name__ == '__main__':\n    cli()",
      "section_ref": "42.5.2",
      "runnable": true,
      "dependencies": [
        "click",
        "rich"
      ]
    },
    {
      "id": "code-11",
      "language": "python",
      "description": "cli()",
      "code": "# cli/agent_client.py - CLI 的 Agent 客户端\nimport httpx\nimport json\nfrom typing import Generator, Optional, List, Dict\n\n\nclass AgentClient:\n    def __init__(self, api_url: str, model: str, api_key: str = \"\"):\n        self.api_url = api_url.rstrip('/')\n        self.model = model\n        self.api_key = api_key\n        self.client = httpx.Client(timeout=120.0)\n\n    def send_message(self, message: str, session_id: Optional[str] = None,\n                     history: Optional[List[Dict]] = None,\n                     model: Optional[str] = None) -> str:\n        messages = history or []\n        messages.append({\"role\": \"user\", \"content\": message})\n        response = self.client.post(\n            f\"{self.api_url}/v1/chat/completions\",\n            json={\"messages\": messages, \"model\": model or self.model, \"stream\": False},\n            headers=self._headers(),\n        )\n        response.raise_for_status()\n        return response.json()[\"choices\"][0][\"message\"][\"content\"]\n\n    def stream_message(self, message: str, history=None, model=None) -> Generator:\n        messages = history or []\n        messages.append({\"role\": \"user\", \"content\": message})\n        with self.client.stream(\"POST\",\n            f\"{self.api_url}/v1/chat/completions\",\n            json={\"messages\": messages, \"model\": model or self.model, \"stream\": True},\n            headers=self._headers(),\n        ) as response:\n            response.raise_for_status()\n            for line in response.iter_lines():\n                if line.startswith(\"data: \"):\n                    data = line[6:]\n                    if data == \"[DONE]\": break\n                    try:\n                        parsed = json.loads(data)\n                        delta = parsed[\"choices\"][0].get(\"delta\", {})\n                        if delta.get(\"content\"):\n                            yield {\"type\": \"text_delta\", \"content\": delta[\"content\"]}\n                    except json.JSONDecodeError:\n                        continue\n\n    def execute_with_tools(self, command: str) -> Generator:\n        messages = [\n            {\"role\": \"system\", \"content\": \"你是终端 Agent，可以执行工具完成任务。\"},\n            {\"role\": \"user\", \"content\": command},\n        ]\n        response = self.client.post(\n            f\"{self.api_url}/v1/chat/completions\",\n            json={\"messages\": messages, \"model\": self.model,\n                  \"tools\": self._default_tools(), \"tool_choice\": \"auto\"},\n            headers=self._headers(),\n        )\n        response.raise_for_status()\n        data = response.json()\n        choice = data[\"choices\"][0]\n        if choice.get(\"tool_calls\"):\n            for tc in choice[\"tool_calls\"]:\n                fn = tc[\"function\"]\n                yield {\"type\": \"tool_call\", \"name\": fn[\"name\"],\n                       \"arguments\": json.loads(fn[\"arguments\"])}\n                result = self._execute_tool(fn[\"name\"], json.loads(fn[\"arguments\"]))\n                yield {\"type\": \"tool_result\", \"name\": fn[\"name\"], \"content\": result}\n        yield {\"type\": \"text\", \"content\": choice[\"message\"][\"content\"]}\n\n    def _default_tools(self) -> list:\n        return [\n            {\"type\": \"function\", \"function\": {\n                \"name\": \"read_file\", \"description\": \"Read file contents\",\n                \"parameters\": {\"type\": \"object\", \"properties\": {\"path\": {\"type\": \"string\"}},\n                               \"required\": [\"path\"]}}},\n            {\"type\": \"function\", \"function\": {\n                \"name\": \"write_file\", \"description\": \"Write content to file\",\n                \"parameters\": {\"type\": \"object\",\n                    \"properties\": {\"path\": {\"type\": \"string\"}, \"content\": {\"type\": \"string\"}},\n                    \"required\": [\"path\", \"content\"]}}},\n            {\"type\": \"function\", \"function\": {\n                \"name\": \"run_command\", \"description\": \"Execute shell command\",\n                \"parameters\": {\"type\": \"object\",\n                    \"properties\": {\"command\": {\"type\": \"string\"}},\n                    \"required\": [\"command\"]}}},\n        ]\n\n    def _execute_tool(self, name: str, args: dict) -> str:\n        import subprocess\n        if name == \"read_file\":\n            with open(args[\"path\"], \"r\") as f: return f.read()\n        elif name == \"write_file\":\n            with open(args[\"path\"], \"w\") as f: f.write(args[\"content\"])\n            return f\"Written to {args['path']}\"\n        elif name == \"run_command\":\n            result = subprocess.run(args[\"command\"], shell=True, capture_output=True, text=True)\n            return result.stdout or result.stderr\n        return f\"Unknown tool: {name}\"\n\n    def _headers(self) -> dict:\n        h = {\"Content-Type\": \"application/json\"}\n        if self.api_key: h[\"Authorization\"] = f\"Bearer {self.api_key}\"\n        return h",
      "section_ref": "42.5.2",
      "runnable": true,
      "dependencies": [
        "httpx"
      ]
    },
    {
      "id": "code-12",
      "language": "bash",
      "description": "",
      "code": "# 单条消息\n$ agent chat \"解释 MCP 协议\"\n\n# 交互式对话\n$ agent chat\n\n# 分析文件\n$ agent analyze main.py -q \"找出潜在内存泄漏\"\n\n# 管道操作\n$ cat error.log | agent chat \"分析这些错误日志\"\n\n# Agent 执行（带工具调用）\n$ agent exec-agent \"重构项目，添加类型注解\"",
      "section_ref": "42.5.3",
      "runnable": false,
      "dependencies": []
    },
    {
      "id": "code-13",
      "language": "typescript",
      "description": "3. 工作流增强：代码生成、重构、调试、测试等编码活动都能被 Agent 增强",
      "code": "// src/extension.ts - VS Code Agent 插件入口\nimport * as vscode from 'vscode';\nimport { AgentService } from './services/agentService';\nimport { ChatViewProvider } from './views/chatViewProvider';\nimport { InlineCompletionProvider } from './providers/inlineCompletionProvider';\nimport { CodeActionProvider } from './providers/codeActionProvider';\n\nexport function activate(context: vscode.ExtensionContext) {\n    const agentService = new AgentService();\n\n    // 注册侧边栏 Chat 视图\n    const chatProvider = new ChatViewProvider(context.extensionUri, agentService);\n    context.subscriptions.push(\n        vscode.window.registerWebviewViewProvider('agentChatView', chatProvider)\n    );\n\n    // 注册内联补全 Provider\n    const inlineProvider = new InlineCompletionProvider(agentService);\n    context.subscriptions.push(\n        vscode.languages.registerInlineCompletionItemProvider({ pattern: '**' }, inlineProvider)\n    );\n\n    // 注册 Code Action Provider\n    const codeActionProvider = new CodeActionProvider(agentService);\n    context.subscriptions.push(\n        vscode.languages.registerCodeActionsProvider(\n            { pattern: '**' }, codeActionProvider,\n            { providedCodeActionKinds: [vscode.CodeActionKind.Refactor] }\n        )\n    );\n\n    // 快捷命令\n    context.subscriptions.push(\n        vscode.commands.registerTextEditorCommand('agent.explainSelection', async (editor) => {\n            const selection = editor.document.getText(editor.selection);\n            if (!selection) return;\n            const explanation = await agentService.explainCode(selection, editor.document.languageId);\n            const doc = await vscode.workspace.openTextDocument({\n                content: explanation, language: 'markdown',\n            });\n            await vscode.window.showTextDocument(doc, { preview: true });\n        }),\n\n        vscode.commands.registerTextEditorCommand('agent.refactorSelection', async (editor, edit) => {\n            const selection = editor.document.getText(editor.selection);\n            if (!selection) return;\n            const refactored = await agentService.refactorCode(selection, editor.document.languageId);\n            edit.replace(editor.selection, refactored);\n        }),\n\n        vscode.commands.registerTextEditorCommand('agent.addTests', async (editor, edit) => {\n            const selection = editor.document.getText(editor.selection);\n            if (!selection) return;\n            const tests = await agentService.generateTests(selection, editor.document.languageId);\n            const lastLine = editor.document.lineCount;\n            editor.edit((eb) => eb.insert(new vscode.Position(lastLine, 0), '\\n\\n' + tests));\n        }),\n\n        vscode.commands.registerCommand('agent.openChat', () => {\n            vscode.commands.executeCommand('agentChatView.focus');\n        }),\n\n        vscode.commands.registerCommand('agent.sendFileToChat', () => {\n            const editor = vscode.window.activeTextEditor;\n            if (editor) chatProvider.sendFile(editor.document);\n        }),\n    );\n}\n\nexport function deactivate() {}",
      "section_ref": "42.6.2",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-14",
      "language": "typescript",
      "description": "export function deactivate() {}",
      "code": "// src/providers/inlineCompletionProvider.ts - 内联补全\nimport * as vscode from 'vscode';\nimport { AgentService } from '../services/agentService';\n\nexport class InlineCompletionProvider implements vscode.InlineCompletionItemProvider {\n    constructor(private agentService: AgentService) {}\n\n    async provideInlineCompletionItems(\n        document: vscode.TextDocument,\n        position: vscode.Position,\n        context: vscode.InlineCompletionContext,\n        token: vscode.CancellationToken,\n    ): Promise<vscode.InlineCompletionItem[]> {\n        // 获取光标前 20 行上下文\n        const prefix = document.getText(\n            new vscode.Range(\n                new vscode.Position(Math.max(0, position.line - 20), 0),\n                position\n            )\n        );\n\n        try {\n            const completion = await this.agentService.getInlineCompletion(\n                prefix, document.languageId, token,\n            );\n            if (!completion || token.isCancellationRequested) return [];\n            return [new vscode.InlineCompletionItem(completion,\n                new vscode.Range(position, position))];\n        } catch {\n            return [];\n        }\n    }\n}",
      "section_ref": "42.6.2",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-15",
      "language": "kotlin",
      "description": "JetBrains IDE（IntelliJ、PyCharm、WebStorm 等）也可以使用 Kotlin + IntelliJ Platform SDK 开发插件：",
      "code": "// src/main/kotlin/com/agent/jetbrains/AgentAction.kt\npackage com.agent.jetbrains\n\nimport com.intellij.notification.NotificationGroupManager\nimport com.intellij.notification.NotificationType\nimport com.intellij.openapi.actionSystem.AnAction\nimport com.intellij.openapi.actionSystem.AnActionEvent\nimport com.intellij.openapi.actionSystem.CommonDataKeys\nimport com.intellij.openapi.progress.ProgressManager\nimport com.intellij.openapi.progress.Task\n\nclass ExplainCodeAction : AnAction() {\n    override fun actionPerformed(e: AnActionEvent) {\n        val editor = e.getData(CommonDataKeys.EDITOR) ?: return\n        val project = e.project ?: return\n        val selectedText = editor.selectionModel.selectedText ?: return\n        val language = editor.document.fileType.name\n\n        ProgressManager.getInstance().run(object : Task.Backgroundable(\n            project, \"Agent Analyzing\", true\n        ) {\n            override fun run(indicator: com.intellij.openapi.progress.ProgressIndicator) {\n                indicator.text = \"Agent analyzing...\"\n                indicator.fraction = 0.3\n                val service = AgentService.getInstance(project)\n                val explanation = service.explainCode(selectedText, language)\n                indicator.fraction = 1.0\n                NotificationGroupManager.getInstance()\n                    .getNotificationGroup(\"Agent\")\n                    .createNotification(\"Code Explanation\", explanation, NotificationType.INFORMATION)\n                    .notify(project)\n            }\n        })\n    }\n}",
      "section_ref": "42.6.3",
      "runnable": false,
      "dependencies": []
    },
    {
      "id": "code-16",
      "language": "javascript",
      "description": "4. 支付闭环：微信支付实现商业闭环",
      "code": "// miniprogram/pages/chat/chat.js - 小程序聊天页面\nconst app = getApp();\n\nPage({\n  data: {\n    messages: [],\n    inputValue: '',\n    isLoading: false,\n    scrollToBottom: false,\n  },\n\n  onLoad() { this.loadHistory(); },\n\n  loadHistory() {\n    const history = wx.getStorageSync('chat_history') || [];\n    this.setData({ messages: history });\n  },\n\n  onInputChange(e) { this.setData({ inputValue: e.detail.value }); },\n\n  async sendMessage() {\n    const { inputValue, messages } = this.data;\n    if (!inputValue.trim() || this.data.isLoading) return;\n\n    const userMsg = {\n      id: Date.now().toString(),\n      role: 'user',\n      content: inputValue.trim(),\n      time: new Date().toLocaleTimeString(),\n    };\n\n    const newMessages = [...messages, userMsg];\n    this.setData({ messages: newMessages, inputValue: '', isLoading: true, scrollToBottom: true });\n\n    try {\n      const response = await this.callAgentAPI(newMessages);\n      const assistantMsg = {\n        id: (Date.now() + 1).toString(),\n        role: 'assistant',\n        content: response,\n        time: new Date().toLocaleTimeString(),\n      };\n      const allMessages = [...newMessages, assistantMsg];\n      this.setData({ messages: allMessages, isLoading: false, scrollToBottom: true });\n      wx.setStorageSync('chat_history', allMessages.slice(-50));\n    } catch (err) {\n      wx.showToast({ title: '请求失败', icon: 'none' });\n      this.setData({ isLoading: false });\n    }\n  },\n\n  callAgentAPI(messages) {\n    return new Promise((resolve, reject) => {\n      wx.request({\n        url: `${app.globalData.apiBaseUrl}/chat`,\n        method: 'POST',\n        header: {\n          'Content-Type': 'application/json',\n          'Authorization': `Bearer ${wx.getStorageSync('token')}`,\n        },\n        data: {\n          messages: messages.map(m => ({ role: m.role, content: m.content })),\n          platform: 'miniprogram',\n        },\n        success: (res) => {\n          if (res.statusCode === 200) resolve(res.data.content);\n          else reject(new Error(res.data.message));\n        },\n        fail: reject,\n      });\n    });\n  },\n\n  clearChat() {\n    wx.showModal({\n      title: '确认清空',\n      content: '确定清空聊天记录？',\n      success: (res) => {\n        if (res.confirm) {\n          this.setData({ messages: [] });\n          wx.removeStorageSync('chat_history');\n        }\n      },\n    });\n  },\n});",
      "section_ref": "42.7.2",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-17",
      "language": "xml",
      "description": "});",
      "code": "<!-- miniprogram/pages/chat/chat.wxml -->\n<view class=\"chat-container\">\n  <scroll-view class=\"message-list\" scroll-y\n    scroll-into-view=\"{{scrollToBottom ? 'msg-bottom' : ''}}\" scroll-with-animation>\n    <view wx:for=\"{{messages}}\" wx:key=\"id\" class=\"message-wrapper {{item.role}}\">\n      <view class=\"avatar\">\n        <image wx:if=\"{{item.role === 'user'}}\" src=\"/images/user.png\" mode=\"aspectFill\" />\n        <view wx:else class=\"agent-avatar\">🤖</view>\n      </view>\n      <view class=\"message-content\">\n        <view class=\"message-bubble {{item.role}}\">{{item.content}}</view>\n        <text class=\"message-time\">{{item.time}}</text>\n      </view>\n    </view>\n    <view wx:if=\"{{isLoading}}\" class=\"loading-indicator\">\n      <view class=\"typing-dots\"><view class=\"dot\"></view><view class=\"dot\"></view><view class=\"dot\"></view></view>\n    </view>\n    <view id=\"msg-bottom\"></view>\n  </scroll-view>\n  <view class=\"input-area\">\n    <input class=\"message-input\" value=\"{{inputValue}}\" bindinput=\"onInputChange\"\n      placeholder=\"输入消息...\" bindconfirm=\"sendMessage\" confirm-type=\"send\" />\n    <button class=\"send-btn\" bindtap=\"sendMessage\"\n      disabled=\"{{!inputValue || isLoading}}\">发送</button>\n  </view>\n</view>",
      "section_ref": "42.7.2",
      "runnable": false,
      "dependencies": []
    },
    {
      "id": "code-18",
      "language": "python",
      "description": "企业微信机器人是将 Agent 接入企业工作群的有效方式：",
      "code": "# backend/integrations/wecom_bot.py - 企业微信机器人集成\nimport hmac\nimport hashlib\nimport base64\nimport time\nimport urllib.parse\nimport httpx\nfrom typing import Optional\nfrom dataclasses import dataclass\n\n\n@dataclass\nclass WecomBotConfig:\n    webhook_url: str\n    secret: Optional[str] = None\n\n\nclass WecomBotClient:\n    \"\"\"企业微信机器人客户端\"\"\"\n\n    def __init__(self, config: WecomBotConfig):\n        self.config = config\n        self.client = httpx.Client(timeout=30.0)\n\n    def _sign(self) -> Optional[str]:\n        if not self.config.secret:\n            return None\n        timestamp = str(int(time.time()))\n        string_to_sign = f\"{timestamp}\\n{self.config.secret}\"\n        hmac_code = hmac.new(\n            self.config.secret.encode('utf-8'),\n            string_to_sign.encode('utf-8'),\n            digestmod=hashlib.sha256,\n        ).digest()\n        sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))\n        return f\"&timestamp={timestamp}&sign={sign}\"\n\n    def send_text(self, content: str, mentioned_list: list = None):\n        payload = {\n            \"msgtype\": \"text\",\n            \"text\": {\"content\": content, \"mentioned_list\": mentioned_list or []},\n        }\n        return self._send(payload)\n\n    def send_markdown(self, content: str):\n        payload = {\"msgtype\": \"markdown\", \"markdown\": {\"content\": content}}\n        return self._send(payload)\n\n    def send_agent_response(self, question: str, answer: str):\n        content = f\"**🤖 Agent 响应**\\n\\n> {question}\\n\\n{answer}\\n\\n---\\n*由 Agent 自动生成*\"\n        return self.send_markdown(content)\n\n    def _send(self, payload: dict):\n        url = self.config.webhook_url\n        sign = self._sign()\n        if sign:\n            url += sign\n        response = self.client.post(url, json=payload)\n        response.raise_for_status()\n        return response.json()\n\n\n# FastAPI 回调集成\nfrom fastapi import FastAPI, Request\nimport asyncio\n\napp = FastAPI()\nwecom_bot = WecomBotClient(WecomBotConfig(\n    webhook_url=\"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx\",\n))\n\n\n@app.post(\"/wecom/callback\")\nasync def wecom_callback(request: Request):\n    body = await request.json()\n    if body.get(\"MsgType\") != \"text\":\n        return {\"errcode\": 0, \"errmsg\": \"ok\"}\n\n    content = body.get(\"Content\", \"\").strip()\n    if not content.startswith(\"@bot\"):\n        return {\"errcode\": 0, \"errmsg\": \"ok\"}\n\n    question = content.replace(\"@bot\", \"\").strip()\n    if not question:\n        return {\"errcode\": 0, \"errmsg\": \"ok\"}\n\n    asyncio.create_task(_handle_wecom_question(question))\n    return {\"errcode\": 0, \"errmsg\": \"ok\"}\n\n\nasync def _handle_wecom_question(question: str):\n    try:\n        response = await agent_engine.chat(question)\n        wecom_bot.send_agent_response(question, response)\n    except Exception as e:\n        wecom_bot.send_text(f\"Agent 处理失败：{str(e)}\")",
      "section_ref": "42.7.3",
      "runnable": true,
      "dependencies": [
        "hmac",
        "base64",
        "urllib",
        "httpx",
        "fastapi"
      ]
    },
    {
      "id": "code-19",
      "language": "typescript",
      "description": "跨平台部署的关键是定义一套统一的数据模型，确保各端对核心数据结构的理解一致：",
      "code": "// packages/shared/src/models.ts - 跨平台共享模型\n\nexport interface Message {\n  id: string;\n  sessionId: string;\n  role: 'user' | 'assistant' | 'system' | 'tool';\n  content: string;\n  createdAt: string; // ISO 8601\n  metadata: MessageMetadata;\n}\n\nexport interface MessageMetadata {\n  model?: string;\n  tokens?: TokenUsage;\n  duration?: number;\n  toolCalls?: ToolCallInfo[];\n  platform?: string;\n  parentId?: string;\n}\n\nexport interface TokenUsage {\n  prompt: number;\n  completion: number;\n  total: number;\n}\n\nexport interface ToolCallInfo {\n  id: string;\n  name: string;\n  arguments: Record<string, unknown>;\n  result?: string;\n  duration?: number;\n  isError?: boolean;\n}\n\nexport interface Session {\n  id: string;\n  title: string;\n  messages: Message[];\n  model: string;\n  systemPrompt?: string;\n  createdAt: string;\n  updatedAt: string;\n  settings: SessionSettings;\n}\n\nexport interface SessionSettings {\n  temperature: number;\n  maxTokens: number;\n  topP: number;\n  toolsEnabled: boolean;\n  contextWindow: 'auto' | number;\n}\n\nexport interface ChatRequest {\n  messages: Array<{ role: string; content: string }>;\n  model?: string;\n  stream?: boolean;\n  sessionId?: string;\n  platform?: string;\n  tools?: Tool[];\n  temperature?: number;\n  maxTokens?: number;\n}\n\nexport interface StreamEvent {\n  type: 'text_delta' | 'tool_call' | 'tool_result' | 'done' | 'error';\n  id?: string;\n  content?: string;\n  data?: any;\n  timestamp: string;\n}\n\nexport interface PlatformCapabilities {\n  id: string;\n  name: string;\n  supportsVoiceInput: boolean;\n  supportsVoiceOutput: boolean;\n  supportsFileAccess: boolean;\n  supportsCamera: boolean;\n  supportsLocation: boolean;\n  supportsNotifications: boolean;\n  supportsClipboard: boolean;\n  maxUploadSize: number;\n  supportedFileTypes: string[];\n}",
      "section_ref": "42.8.1",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-20",
      "language": "python",
      "description": "所有平台通过统一的 API 网关与后端通信：",
      "code": "# backend/gateway/router.py - 统一 API 网关\nfrom fastapi import FastAPI, Request, HTTPException\nfrom fastapi.middleware.cors import CORSMiddleware\nfrom fastapi.responses import StreamingResponse\nimport json\n\napp = FastAPI(title=\"Agent API Gateway\", version=\"1.0.0\")\n\napp.add_middleware(\n    CORSMiddleware,\n    allow_origins=[\n        \"http://localhost:3000\",\n        \"https://app.your-agent.com\",\n        \"capacitor://localhost\",\n        \"tauri://localhost\",\n    ],\n    allow_credentials=True,\n    allow_methods=[\"*\"],\n    allow_headers=[\"*\"],\n)\n\n\n@app.middleware(\"http\")\nasync def platform_aware_middleware(request: Request, call_next):\n    \"\"\"识别请求来源平台\"\"\"\n    platform = request.headers.get(\"X-Platform\", \"unknown\")\n\n    ua = request.headers.get(\"User-Agent\", \"\")\n    if \"MicroMessenger\" in ua:\n        platform = \"wechat_miniprogram\"\n    elif \"Electron\" in ua:\n        platform = \"electron_desktop\"\n    elif \"Tauri\" in ua:\n        platform = \"tauri_desktop\"\n\n    request.state.platform = platform\n    response = await call_next(request)\n    response.headers[\"X-Platform\"] = platform\n    return response\n\n\n@app.post(\"/api/v1/chat\")\nasync def chat(request: ChatRequest, req: Request):\n    \"\"\"统一聊天端点 - 支持流式和非流式\"\"\"\n    platform = getattr(req.state, 'platform', 'unknown')\n\n    # 平台感知的参数调整\n    if platform == \"wechat_miniprogram\":\n        request.stream = False\n        request.maxTokens = min(request.maxTokens or 4096, 2048)\n\n    if request.stream:\n        return StreamingResponse(\n            _stream_response(request, platform),\n            media_type=\"text/event-stream\",\n            headers={\"Cache-Control\": \"no-cache\", \"Connection\": \"keep-alive\"},\n        )\n    else:\n        return await _sync_response(request, platform)\n\n\nasync def _stream_response(request, platform):\n    async for event in agent_engine.stream_chat(\n        messages=request.messages, model=request.model,\n        session_id=request.sessionId, platform=platform,\n    ):\n        yield f\"data: {json.dumps(event, ensure_ascii=False)}\\n\\n\"\n    yield \"data: [DONE]\\n\\n\"",
      "section_ref": "42.8.2",
      "runnable": true,
      "dependencies": [
        "fastapi"
      ]
    },
    {
      "id": "code-21",
      "language": "python",
      "description": "- 通过 QR 码或链接实现跨设备会话迁移",
      "code": "# backend/services/sync_service.py - 跨平台同步服务\nfrom datetime import datetime\nfrom typing import Dict, Optional\nfrom dataclasses import dataclass\n\n\n@dataclass\nclass SyncEvent:\n    event_id: str\n    user_id: str\n    session_id: str\n    event_type: str  # 'message_new', 'session_update', 'settings_change'\n    data: dict\n    timestamp: datetime\n    sequence: int  # 单调递增序列号\n\n\nclass SyncService:\n    \"\"\"跨平台状态同步服务\"\"\"\n\n    def __init__(self, ws_manager, storage):\n        self.ws_manager = ws_manager\n        self.storage = storage\n        self._sequences: Dict[str, int] = {}  # session_id -> sequence\n\n    async def on_message_created(self, user_id: str, session_id: str, message: dict):\n        \"\"\"新消息创建时同步到所有在线设备\"\"\"\n        seq = self._next_sequence(session_id)\n\n        event = SyncEvent(\n            event_id=message[\"id\"],\n            user_id=user_id,\n            session_id=session_id,\n            event_type=\"message_new\",\n            data={\"message\": message},\n            timestamp=datetime.now(),\n            sequence=seq,\n        )\n\n        # 实时推送到在线设备\n        await self.ws_manager.send_to_user(user_id, {\n            \"type\": \"sync.event\",\n            \"event\": {\n                \"event_id\": event.event_id,\n                \"event_type\": event.event_type,\n                \"data\": event.data,\n                \"sequence\": event.sequence,\n            },\n        })\n\n        # 持久化到同步日志\n        await self.storage.save_sync_event(event)\n\n    async def get_missing_events(self, user_id: str, session_id: str,\n                                  since_sequence: int) -> list:\n        \"\"\"获取设备缺失的同步事件\"\"\"\n        events = await self.storage.get_sync_events(\n            session_id=session_id,\n            since_sequence=since_sequence,\n        )\n        return events\n\n    async def resolve_conflict(self, event_1: SyncEvent, event_2: SyncEvent) -> SyncEvent:\n        \"\"\"解决同步冲突\"\"\"\n        # Last-Write-Wins 策略\n        if event_1.timestamp > event_2.timestamp:\n            return event_1\n        return event_2\n\n    def _next_sequence(self, session_id: str) -> int:\n        current = self._sequences.get(session_id, 0)\n        next_seq = current + 1\n        self._sequences[session_id] = next_seq\n        return next_seq",
      "section_ref": "42.8.3",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-22",
      "language": "python",
      "description": "",
      "code": "# tests/test_cross_platform.py - 跨平台测试\nimport pytest\nfrom unittest.mock import AsyncMock, MagicMock\n\n\nclass TestCrossPlatformAPI:\n    \"\"\"跨平台 API 测试\"\"\"\n\n    @pytest.mark.parametrize(\"platform,expected_max_tokens\", [\n        (\"web\", 4096),\n        (\"ios\", 4096),\n        (\"android\", 4096),\n        (\"wechat_miniprogram\", 2048),  # 小程序限制更小\n        (\"tauri_desktop\", 8192),\n        (\"electron_desktop\", 8192),\n    ])\n    async def test_platform_aware_limits(self, platform, expected_max_tokens):\n        \"\"\"测试平台感知的参数限制\"\"\"\n        request = ChatRequest(maxTokens=8192)\n        adjusted = apply_platform_limits(request, platform)\n        assert adjusted.maxTokens == expected_max_tokens\n\n    @pytest.mark.parametrize(\"platform\", [\"web\", \"ios\", \"android\", \"desktop\"])\n    async def test_unified_data_model(self, platform):\n        \"\"\"测试统一数据模型在各平台的一致性\"\"\"\n        message = Message(\n            id=\"test-123\",\n            sessionId=\"sess-456\",\n            role=\"user\",\n            content=\"Hello\",\n            createdAt=datetime.now().isoformat(),\n            metadata={\"platform\": platform},\n        )\n        # 序列化和反序列化应保持一致\n        serialized = message.model_dump_json()\n        deserialized = Message.model_validate_json(serialized)\n        assert deserialized.id == message.id\n        assert deserialized.content == message.content\n\n    async def test_websocket_multi_device(self):\n        \"\"\"测试多设备 WebSocket 连接\"\"\"\n        ws_manager = WebSocketManager()\n\n        # 模拟两个设备连接\n        ws1 = AsyncMock()\n        ws2 = AsyncMock()\n\n        await ws_manager.connect(ws1, \"user-1\", \"session-1\")\n        await ws_manager.connect(ws2, \"user-1\", \"session-1\")\n\n        # 发送消息到 session\n        await ws_manager.send_to_session(\"session-1\", {\"type\": \"test\", \"data\": \"hello\"})\n\n        # 两个设备都应收到\n        ws1.send_json.assert_called_once()\n        ws2.send_json.assert_called_once()\n\n    async def test_offline_sync_recovery(self):\n        \"\"\"测试离线同步恢复\"\"\"\n        sync_service = SyncService(\n            ws_manager=AsyncMock(),\n            storage=AsyncMock(),\n        )\n\n        # 模拟设备 A 在线时产生的消息\n        await sync_service.on_message_created(\"user-1\", \"sess-1\", {\"id\": \"msg-1\"})\n        await sync_service.on_message_created(\"user-1\", \"sess-1\", {\"id\": \"msg-2\"})\n\n        # 设备 B 上线后拉取缺失事件\n        sync_service.storage.get_sync_events.return_value = [\n            {\"event_id\": \"msg-1\", \"sequence\": 1},\n            {\"event_id\": \"msg-2\", \"sequence\": 2},\n        ]\n\n        missing = await sync_service.get_missing_events(\"user-1\", \"sess-1\", since_sequence=0)\n        assert len(missing) == 2",
      "section_ref": "42.8.4",
      "runnable": true,
      "dependencies": [
        "pytest"
      ]
    },
    {
      "id": "code-23",
      "language": "text",
      "description": "- 跨平台工作流：不同平台间无缝协作",
      "code": "时间线：\nWeb MVP → +Mobile → +Desktop/CLI → +IDE/微信 → 深度融合\n  1月        3月         6月           9月          12月+",
      "section_ref": "42.8.6",
      "runnable": false,
      "dependencies": []
    }
  ],
  "tables": [
    {
      "headers": [
        "挑战维度",
        "具体问题",
        "影响"
      ],
      "data": [
        [
          "**模型推理**",
          "边缘设备的算力限制，大模型无法在移动端运行",
          "需要云-端协同架构"
        ],
        [
          "**实时通信**",
          "Agent 的流式响应需要低延迟、可靠的双向通信",
          "WebSocket/SSE 的跨平台实现"
        ],
        [
          "**上下文同步**",
          "用户在不同设备间的会话状态需要无缝衔接",
          "需要统一的会话管理后端"
        ],
        [
          "**工具调用**",
          "不同平台的系统能力差异（如文件系统访问权限）",
          "需要平台感知的工具适配层"
        ],
        [
          "**UI 一致性**",
          "Agent 的交互体验需要在不同平台上保持连贯",
          "设计系统的跨平台适配"
        ],
        [
          "**安全合规**",
          "不同平台有不同的安全模型和合规要求",
          "如 iOS 的沙箱限制 vs 桌面端的完整权限"
        ]
      ]
    },
    {
      "headers": [
        "特性",
        "React",
        "Vue 3",
        "Next.js",
        "Nuxt 3"
      ],
      "data": [
        [
          "**生态成熟度**",
          "⭐⭐⭐⭐⭐",
          "⭐⭐⭐⭐",
          "⭐⭐⭐⭐⭐",
          "⭐⭐⭐⭐"
        ],
        [
          "**Agent 组件库**",
          "丰富",
          "适中",
          "丰富",
          "适中"
        ],
        [
          "**SSR 支持**",
          "需配合 Next.js",
          "需配合 Nuxt",
          "内置",
          "内置"
        ],
        [
          "**学习曲线**",
          "中等",
          "较低",
          "中高",
          "中等"
        ],
        [
          "**适合场景**",
          "复杂交互型",
          "快速原型",
          "SEO/SSR",
          "全栈型"
        ]
      ]
    },
    {
      "headers": [
        "特性",
        "SSE",
        "WebSocket"
      ],
      "data": [
        [
          "**通信方向**",
          "单向（服务端→客户端）",
          "双向"
        ],
        [
          "**协议**",
          "HTTP",
          "WS/WSS"
        ],
        [
          "**自动重连**",
          "浏览器内置",
          "需手动实现"
        ],
        [
          "**复杂度**",
          "简单",
          "较高"
        ],
        [
          "**适用场景**",
          "流式输出、通知推送",
          "实时聊天、协作编辑"
        ],
        [
          "**代理兼容**",
          "好（标准HTTP）",
          "可能被拦截"
        ]
      ]
    },
    {
      "headers": [
        "方案",
        "优势",
        "劣势",
        "适合场景"
      ],
      "data": [
        [
          "**React Native**",
          "与 Web 端共享业务逻辑",
          "原生体验略差",
          "已有 React 技术栈的团队"
        ],
        [
          "**Flutter**",
          "性能好、UI 一致性高",
          "Dart 生态较小",
          "新项目、追求极致 UI"
        ],
        [
          "**原生 iOS + Android**",
          "最佳性能和体验",
          "开发成本翻倍",
          "高端产品、深度原生集成"
        ],
        [
          "**Capacitor/Ionic**",
          "快速从 Web 转化",
          "性能有上限",
          "简单 Agent 展示类应用"
        ],
        [
          "**小程序**",
          "零安装、低门槛",
          "能力受限",
          "中国市场、轻量级工具"
        ]
      ]
    },
    {
      "headers": [
        "特性",
        "Electron",
        "Tauri"
      ],
      "data": [
        [
          "**包大小**",
          "~150MB",
          "~5-10MB"
        ],
        [
          "**内存占用**",
          "较高（Chromium 内核）",
          "低（系统 WebView）"
        ],
        [
          "**启动速度**",
          "较慢",
          "快"
        ],
        [
          "**技术栈**",
          "HTML/CSS/JS",
          "前端任意 + Rust 后端"
        ],
        [
          "**系统集成**",
          "通过 Node.js",
          "通过 Rust"
        ],
        [
          "**安全模型**",
          "Node.js 完整权限",
          "最小权限原则"
        ],
        [
          "**生态成熟度**",
          "非常成熟",
          "快速增长"
        ]
      ]
    },
    {
      "headers": [
        "实践",
        "说明"
      ],
      "data": [
        [
          "**共享类型定义**",
          "使用 monorepo 管理跨平台共享的类型定义和工具函数"
        ],
        [
          "**平台适配层**",
          "为每个平台的特有能力建立适配层（Capability Bridge）"
        ],
        [
          "**渐进式增强**",
          "核心功能全平台一致，高级功能按平台增强"
        ],
        [
          "**统一认证**",
          "使用 OAuth 2.0 + JWT 实现跨平台统一认证"
        ],
        [
          "**API 版本管理**",
          "使用 URL 版本前缀（/v1/、/v2/）确保向后兼容"
        ],
        [
          "**统一监控**",
          "所有平台的请求都携带平台标识，便于分平台监控和分析"
        ],
        [
          "**灰度发布**",
          "按平台维度进行功能灰度，降低跨平台发布风险"
        ],
        [
          "**性能预算**",
          "为每个平台设定性能预算（包大小、启动时间、内存占用）"
        ]
      ]
    }
  ],
  "key_takeaways": [
    "如何在保持跨平台一致性的同时，充分利用各平台的独特能力？",
    "边缘计算的发展将如何改变 Agent 的跨平台架构？",
    "如何设计一套可扩展的平台适配层，使新平台的接入成本最低化？"
  ],
  "common_pitfalls": [],
  "related_chapters": [
    "ch36"
  ]
}