Files
zclaw_openfang/wiki/routing.md
iven ed77095a37
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
docs(wiki): 系统性更新 — L0速览+L1模块标准化+L2功能链路映射(33条)
三层架构增强:
- L0 index.md: 用户功能清单+跨模块数据流全景图+导航树增强 (92→143行)
- L1 8个模块页标准化: 功能清单/API接口/测试链路/已知问题
  routing(252→326) chat(101→157) saas(153→230) memory(182→333)
  butler(137→179) middleware(121→159) hands-skills(218→257) pipeline(111→156)
- L1 新增2页: security.md(157行) data-model.md(180行)
- L2 feature-map.md: 33条端到端功能链路映射(408行)

维护机制: CLAUDE.md §8.3 wiki触发规则 5→9条
设计文档: docs/superpowers/specs/2026-04-21-wiki-systematic-overhaul-design.md
2026-04-21 23:48:19 +08:00

13 KiB
Raw Blame History

title, updated, status, tags
title updated status tags
客户端路由 2026-04-21 active
module
routing
connection

客户端路由

index 导航。关联模块: chat saas

设计思想

核心决策: Tauri 桌面端通过 SaaS Token Pool 中转访问 LLM不直连。

为什么?

  1. 集中密钥管理 — 用户不需要自己的 API KeySaaS 维护共享 Key 池
  2. 用量追踪 + 计费 — 每次调用经过 SaaSrecord_usage worker 记录 token 消耗
  3. 模型白名单 — Admin 配置哪些模型可用,listModels() 返回白名单
  4. 降级保障 — SaaS 挂了自动切本地 Kernel桌面端不变砖

功能清单

功能 描述 入口文件 状态
连接管理 5 分支路由决策 + 自动降级 connectionStore.ts
SaaS Relay 中转 Tauri 通过 SaaS Token Pool 中转 LLM connectionStore.ts
浏览器模式 SSE 连接 SaaS relay saas-relay-client.ts
本地 Kernel Tauri 内置 Kernel 直连 LLM kernel-client.ts
外部 Gateway WebSocket 独立进程 gateway-client.ts
Gateway 进程管理 启动/停止/重启/状态/诊断 gateway/commands.rs
健康检查 端口检测 + 完整诊断 health_check.rs
设备配对 设备审批 + 公钥交换 gateway/commands.rs
模型路由 白名单验证 + fallback + 别名解析 connectionStore.ts

代码逻辑

5 分支 + 降级决策树

入口: connectionStore.tsconnect(url?, token?)

connect()
  │
  ├── [1] Admin 强制本地: adminRouting === 'local' && isTauri()
  │     → 直接走 Kernel 模式,跳过 SaaS
  │
  ├── [2] SaaS Relay (Tauri 路径): savedMode === 'saas' && isTauri()
  │     → KernelClient + baseUrl = saasUrl/api/v1/relay
  │     → apiKey = SaaS JWT (不是 LLM Key!)
  │     → SaaS 不可达 → 降级到本地 Kernel
  │
  ├── [3] SaaS Relay (Browser 路径): savedMode === 'saas' && !isTauri()
  │     → SaaSRelayGatewayClient (SSE)
  │     → SaaS 不可达 → 降级到本地 Kernel
  │
  ├── [4] 本地 Kernel: isTauriRuntime() && 非 SaaS 模式
  │     → KernelClient + 用户自定义模型配置
  │     → 用户需要自己的 API Key
  │
  └── [5] External Gateway (fallback): !isTauri()
        → GatewayClient via WebSocket/REST

SaaS Relay 主路径 (Tauri 桌面端)

关键代码: connectionStore.ts:482-535

kernelClient.setConfig({
  provider: 'custom',
  model: modelToUse,                          // 从 SaaS listModels() 获取
  apiKey: session.token,                      // SaaS JWT不是 LLM Key
  baseUrl: `${session.saasUrl}/api/v1/relay`, // 指向 SaaS relay
  apiProtocol: 'openai',
});

注意: Kernel 仍然执行 LLM 调用逻辑,但请求发往 SaaS relay 而非直连 LLM。 SaaS relay 接到请求后,从 Token Pool 中取一个可用 Key转发给真实 LLM。

SaaS 降级流程

关键代码: connectionStore.ts:446-468

listModels() 失败
  → 401 → session 过期 → logout → 要求重新登录
  → 其他错误 → saasDegraded = true
    → saasStore.saasReachable = false
    → 降级到本地 Kernel 模式

客户端类型

客户端 传输 文件 用途
GatewayClient WebSocket + REST lib/gateway-client.ts 外部 Gateway 进程
KernelClient Tauri invoke() lib/kernel-chat.ts 内置 Kernel (桌面端)
SaaSRelayGatewayClient HTTP SSE lib/saas-relay-client.ts 浏览器端 SaaS 中继

getClient() 定义: connectionStore.ts:844 所有 Store 通过 initializeStores() (store/index.ts:94) 获取共享 client。

Store 层 (16 根文件 + chat/4 + saas/5 = 25)

desktop/src/store/
├── index.ts              Store 协调器 + client 注入
├── agentStore.ts         Agent 分身管理
├── browserHandStore.ts   浏览器 Hand 状态
├── chatStore.ts          聊天通用状态
├── classroomStore.ts     课堂模式
├── configStore.ts        配置读写
├── connectionStore.ts    路由决策核心
├── handStore.ts          Hand 状态管理
├── industryStore.ts      行业配置 (已接通 ButlerPanel)
├── memoryGraphStore.ts   记忆图谱
├── offlineStore.ts       离线队列
├── saasStore.ts          SaaS 认证 (re-export barrel)
├── securityStore.ts      安全状态
├── sessionStore.ts       会话管理
├── uiModeStore.ts        双模式 UI
├── workflowStore.ts      工作流状态
├── chat/
│   ├── artifactStore.ts  聊天产物
│   ├── conversationStore.ts  会话管理
│   ├── messageStore.ts   消息持久化
│   └── streamStore.ts    流式编排
└── saas/                 (拆分子模块, 04-17 refactor)
    ├── index.ts          子模块入口
    ├── auth.ts           认证逻辑
    ├── billing.ts        计费逻辑
    ├── shared.ts         共享状态/工具
    └── types.ts          类型定义

lib/ 工具层 (75 个 .ts 文件)

关键分类:

类别 文件 数量
Kernel 通信 kernel-client/kernel-chat/kernel-agent/kernel-skills/kernel-triggers/kernel-hands/... 8
SaaS 通信 saas-client/saas-auth/saas-billing/saas-relay/saas-industry/saas-knowledge/... 12
Gateway gateway-client/gateway-api/gateway-auth/gateway-config/... 9
Intelligence intelligence-backend/intelligence-client/embedding-client/memory-extractor 4
Viking viking-client 1
Pipeline pipeline-client/pipeline-recommender 2
Security crypto-utils/secure-storage/security-audit/security-index/api-key-storage 5
工具 config-parser/logger/utils/error-types/error-utils/json-utils/... 10+
Tauri 集成 safe-tauri/tauri-gateway 2
工作流 workflow-builder/ (index + types + yaml-converter) 3

模型路由

完整链路 (Tauri SaaS Relay 主路径)

前端模型选择
  │
  ├─ conversationStore.currentModel (用户上次选择的模型)
  │     持久化到 IndexedDB跨会话保留
  │
  ├─ connectionStore 连接时获取 SaaS 可用模型
  │     saasClient.listModels() → [{id: "deepseek-chat"}, {id: "GLM-4.7"}, ...]
  │     relayModels[0]?.id 作为 fallback
  │
  └─ 最终: preferredModel || fallbackId
        │
        ▼
kernelClient.setConfig({ model: modelToUse })
  │
  ▼
kernel_init (Tauri Command)
  │  KernelConfigRequest { model, api_key, base_url }
  │  base_url = "https://saas-host/api/v1/relay"
  │  api_key = SaaS JWT (不是 LLM Key!)
  │
  ▼
Kernel::boot(config)
  │  config.llm.model = modelToUse
  │  config.llm.base_url = SaaS relay URL
  │
  ▼
loop_runner → LLM Driver (OpenAI compatible)
  │  POST {base_url}/chat/completions
  │  body: { model: modelToUse, messages: [...] }
  │  header: Authorization: Bearer {SaaS JWT}
  │
  ▼
SaaS Relay Handler (handlers.rs)
  │  cache.get_model(model_name) → 精确匹配 model_id
  │  ⚠️ 无别名解析! "glm-4-flash" ≠ "deepseek-chat"
  │  找不到 → 400 "模型 xxx 不存在或未启用"
  │
  ▼
Key Pool 轮换
  │  priority ASC → last_used_at ASC → cooldown 检查 → RPM/TPM 滑动窗口
  │
  ▼
真实 LLM API
  │  429 → mark cooldown → 切换 key
  │  5xx → exponential backoff
  │  model_group → 跨 Provider 故障转移
  │
  ▼
响应 → SSE 流式返回 → 前端

辅助 LLM 调用 (非聊天主路径)

这些 Rust 端组件也通过同一个 relay 发起 LLM 请求:

组件 文件 模型来源 触发时机
记忆摘要 summarizer_adapter.rs kernel_init 传入的 model 定期 L0/L1 摘要生成
记忆提取 extraction_adapter.rs kernel_init 传入的 model 中间件触发提取
管家路由 ButlerRouter via loop_runner 同聊天模型 聊天中间件链

关键: summarizer_adapter.rsextraction_adapter.rskernel_init 时配置, 使用与聊天相同的 modelbase_url。未配置时会明确报错,不会静默 fallback 到错误模型。

SaaS Relay 模型匹配规则

前端发送 model: "deepseek-chat"
  → SaaS cache 按 model_id 精确匹配
  → 匹配: cache.models["deepseek-chat"] → 命中
  → 不匹配: cache.models["glm-4-flash"] → null → 400 错误

⚠️ config.toml 中的 [llm.aliases] 仅用于本地 KernelSaaS relay 不解析别名!

Browser 模式模型路由

createSaaSRelayGatewayClient(saasUrl, getModel)
  │  getModel() 回调 → conversationStore.currentModel || relayModels[0]?.id
  │
  ▼
chatStream() → saasClient.chatCompletion({ model: getModel() })
  │  未获取到模型时 → onError 报错,不发请求
  │
  ▼
POST /api/v1/relay/chat/completions → SSE 流

API 接口

Tauri 命令

Gateway 管理 (desktop/src-tauri/src/gateway/commands.rs):

命令 参数 返回值 说明
zclaw_status LocalGatewayStatus Kernel 运行状态
zclaw_start LocalGatewayStatus 启动 Kernel
zclaw_stop LocalGatewayStatus 停止 Kernel
zclaw_restart LocalGatewayStatus 重启 Kernel
zclaw_local_auth LocalGatewayAuth 获取本地认证 token
zclaw_prepare_for_tauri LocalGatewayPrepareResult 更新 Tauri allowed origins
zclaw_approve_device_pairing device_id, public_key_base64, url? PairingApprovalResult 设备配对审批
zclaw_doctor String 诊断报告
zclaw_process_list ProcessListResponse 进程列表
zclaw_process_logs pid?, lines? ProcessLogsResponse 进程日志
zclaw_version VersionResponse 版本信息

健康检查 (desktop/src-tauri/src/health_check.rs):

命令 参数 返回值 说明
zclaw_health_check port?, timeout_ms? HealthCheckResponse 完整健康检查
zclaw_ping bool 快速端口检测

SaaS Relay 路由 (crates/zclaw-saas/src/relay/)

方法 路径 权限 说明
POST /api/v1/relay/chat/completions 认证+配额 主聊天中转
GET /api/v1/relay/models 认证 可用模型列表
GET /api/v1/relay/tasks 认证 任务列表
GET /api/v1/relay/tasks/:id 认证 任务详情
POST /api/v1/relay/tasks/:id/retry admin 重试失败任务

Provider Key 管理 (crates/zclaw-saas/src/relay/handlers.rs)

方法 路径 权限 说明
GET /api/v1/providers/:id/keys admin 列出 Provider Key
POST /api/v1/providers/:id/keys admin 添加加密 Key
PUT /api/v1/providers/:id/keys/:kid/toggle admin 启停 Key
DELETE /api/v1/providers/:id/keys/:kid admin 删除 Key

测试链路

功能 测试文件 测试数 覆盖状态
Admin 路由解析 tests/desktop/connectionStore.adminRouting.test.ts parseAdminRouting() 纯函数
连接流程 tests/desktop/gatewayStore.test.ts connect() + 数据加载
GatewayClient tests/gateway/ws-client.test.ts WS 连接/事件/断连
Mock Server tests/fixtures/zclaw-mock-server.ts HTTP+WS 模拟

关联模块

  • chat — 路由决定使用哪种 ChatStream
  • saas — Token Pool、认证、模型管理
  • middleware — 请求经过中间件链处理
  • butler — 管家模式通过 ButlerRouter 中间件介入

关键文件

文件 职责
desktop/src/store/connectionStore.ts 路由决策核心
desktop/src/lib/gateway-client.ts WebSocket 客户端
desktop/src/lib/kernel-chat.ts Tauri 内核聊天
desktop/src/lib/kernel-client.ts Kernel 客户端配置
desktop/src/lib/saas-relay-client.ts SaaS SSE 中继
desktop/src/lib/saas-client.ts SaaS API 客户端
desktop/src/store/index.ts Store 协调器 + client 注入

已知问题

  • Tauri invoke 参数名 snake_case vs camelCase — P1 已修复 (commit f6c5dd2)。Tauri 2.x 默认 rename_all = "camelCase",所有 invoke 调用必须用 camelCase
  • Provider Key 解密失败导致 relay 500 — P1 已修复 (commit b69dc61)。key_pool.rs 现在 decrypt 失败时 warn+skip 到下一个 key启动时 heal_provider_keys() 自动重新加密有效 key