Scan frontend and Rust sources for noncanonical host detection, platform category shortcuts and incomplete platform matches. Point each violation to the documented rule and its intended replacement. Run the guard through pnpm check and replace the legacy mobile exclusion in generation-background setup with explicit desktop and OHOS branches.
22 KiB
TauriTavern 后端结构
本文档说明 TauriTavern Rust 后端的 Clean Architecture 原则、workspace crate 边界、依赖方向和代码落点。
它不是 API 命令清单,也不是专题实现百科。具体 API 参考 docs/API/,当前实现快照参考 docs/CurrentState/,前端宿主契约参考 docs/FrontendHostContract.md。
1. 最高约束:Clean Architecture
Clean Architecture 是 TauriTavern 后端最重要的长期约束。它不是一组漂亮的目录名,而是一条依赖规则:
越稳定、越靠近业务语义的代码越靠内;越依赖框架、文件系统、网络、平台 API 的代码越靠外;源码依赖只能从外层指向内层。
在本项目里,这条规则有几个直接含义:
- 领域模型和纯规则不能知道 Tauri、文件系统、HTTP、async runtime 或 repository。
- 用例可以表达“我需要某种外部能力”,但不能依赖具体实现。
- 具体 IO、文件格式、网络请求、ZIP、PNG、tokenizer、sync runtime 都是外层细节。
- host 可以知道所有实现,因为它负责启动和装配;但这种知识必须停留在 composition root 附近。
- 浏览器可观察的 SillyTavern 兼容语义是外部契约,不能被 Rust 内部分层命名替代。
因此,本项目的 Clean Architecture 不是靠约定俗成维护,而是被 Cargo workspace 和 scripts/check-rust-crate-boundaries.mjs 物理化、自动化地守住。
2. 从原则到 crate
当前 workspace crate 拆分,就是 Clean Architecture 的物理边界。理解这些 crate 时,不要先问“这个文件技术上用什么实现”,而要先问“这段代码属于哪一类变化原因”。
| Clean Architecture 概念 | 当前落点 | 稳定职责 | 不应承担 |
|---|---|---|---|
| Entities / 领域核心 | tt-domain |
领域模型、值对象、领域错误、纯规则、纯文本/JSON 工具 | repository trait、async runtime、文件系统、网络、Tauri |
| Cross-crate contracts | tt-contracts |
跨 crate DTO、事件、payload、observability、通用值契约 | concrete IO、application service、Tauri |
| Application ports | tt-ports |
repository trait、gateway trait、event sink、runtime port、Host Resource opened-source port | concrete adapter、reqwest/axum/tauri/image/zip 实现 |
| Use cases | tt-application |
service、use case、job coordinator、策略编排 | Tauri、adapter、reqwest、具体 tokenizer、文件格式实现 |
| Interface adapters | tt-adapter-* |
Tauri-free 的具体 IO、运行时、持久化、文件格式、外部系统访问 | application service、Tauri host glue |
| Frameworks / Drivers | tauritavern |
Tauri builder、plugins、setup、window、commands、composition root、platform glue | 领域规则、用例实现、Tauri-free repository |
这里有两个容易混淆但很关键的点:
tt-domain不定义 repository trait。repository 是用例对外部世界的需求,属于 port,不属于领域实体本身。tt-adapter-*不是“比 application 更核心”的代码。它们可能很复杂,但复杂不等于内层。只要依赖文件系统、网络、第三方格式或运行时细节,就属于外层。
3. Workspace 总览
workspace 根目录是 src-tauri/Cargo.toml,默认 member 是 crates/tauritavern。
src-tauri/
├── Cargo.toml
├── Cargo.lock
├── resources/
└── crates/
├── tauritavern
├── tt-domain
├── tt-contracts
├── tt-ports
├── tt-application
├── tt-adapter-http
├── tt-adapter-mcp
├── tt-adapter-provider-http
├── tt-adapter-vector
├── tt-adapter-tokenization
├── tt-adapter-storage-core
├── tt-adapter-storage-userdata
├── tt-adapter-media
├── tt-adapter-extension
├── tt-adapter-workspace-shell
├── tt-adapter-triviumdb
├── tt-adapter-sync
└── tt-adapter-archive
当前 host 不再承载所有后端实现;用例、端口、契约、领域和具体 adapter 已经被拆成独立 Cargo crate。后续维护重点不是继续追求更多 crate,而是避免边界回流,确保每个新增能力都落在正确变化轴上。
| crate | 当前职责 |
|---|---|
tauritavern |
Tauri host、presentation command、composition root、host-bound infrastructure、platform glue |
tt-domain |
领域模型、值对象、领域错误、纯规则 |
tt-contracts |
DTO、事件、payload、通用值契约、observability contract |
tt-ports |
repository / gateway / runtime trait、Host Resource opened-source port |
tt-application |
用例服务、业务编排、任务协调、policy 执行 |
tt-adapter-http |
共享 HTTP client pool/profile/helper |
tt-adapter-mcp |
RMCP client、协议版本策略与 Streamable HTTP lifecycle、tools/list pagination 与 tool validation |
tt-adapter-provider-http |
LLM、SearXNG、SD、Translate、TTS、embedding、provider metadata 的 HTTP repository |
tt-adapter-triviumdb |
按命名空间管理 TriviumDB、阻塞执行、原生查询与持久化 |
tt-adapter-vector |
Vector 的 ACID 本地索引与本地 embedding runtime;不承载 provider HTTP |
tt-adapter-tokenization |
tokenizer concrete repository |
tt-adapter-storage-core |
DataDirectory、基础文件系统与格式 helper、chat/settings/user/theme/secret/quick reply/prompt cache/asset/llm connection/extension-store |
tt-adapter-storage-userdata |
character、world info、agent workspace、agent profile、skill local package store、PNG card metadata |
tt-adapter-media |
persona/avatar/background/user media/image metadata、browser-visible host resource file store |
tt-adapter-extension |
third-party extension 发现、安装、版本检查、更新、分支查询/切换、删除与移动;Gitoxide smart HTTP 与 embedded worktree |
tt-adapter-workspace-shell |
Bashkit(含 jq/Python)与 QuickJS 执行;受限 WorkspaceFs 适配 |
tt-adapter-sync |
LAN Sync、TT-Sync v2 runtime、stores、client/server、sync jobs |
tt-adapter-archive |
data archive import/export executor、archive path safety |
4. 依赖方向
箭头表示 Rust crate 依赖方向。它们应当始终指向更内层、更稳定的策略或契约。
flowchart TB
host["tauritavern<br/>Tauri host / commands / composition root"]
adapters["tt-adapter-*<br/>concrete IO / runtime / file formats"]
application["tt-application<br/>use cases / services / policies"]
ports["tt-ports<br/>ports required by use cases"]
contracts["tt-contracts<br/>DTO / events / payloads"]
domain["tt-domain<br/>models / errors / pure rules"]
host --> application
host --> adapters
application --> ports
application -. may use .-> contracts
application -. may use .-> domain
adapters --> ports
adapters -. may use .-> contracts
adapters -. may use .-> domain
ports --> contracts
contracts --> domain
当前允许少量 adapter helper 关系:
tt-adapter-provider-http -> tt-adapter-http
tt-adapter-mcp -> tt-adapter-http
tt-adapter-tokenization -> tt-adapter-http
tt-adapter-extension -> tt-adapter-http + tt-adapter-storage-core
tt-adapter-storage-userdata -> tt-adapter-storage-core
tt-adapter-media -> tt-adapter-storage-core
禁止方向:
tt-domain不依赖tt-ports、tt-contracts、adapter、Tauri、网络或文件系统。tt-contracts不依赖tt-ports、tt-application、adapter 或 Tauri。tt-ports不依赖 adapter、Tauri、网络/文件系统具体实现。tt-application不依赖任何tt-adapter-*、tauritavern、reqwest、axum、tauri、zip、image。- 任何
tt-adapter-*不依赖tt-application或tauritavern。 - host 的
infrastructure与platform不下沉为 application 或 adapter 的隐式依赖;具体服务装配集中在app/composition。
如果新增代码需要违反这些方向,通常不是边界脚本太严格,而是抽象落点还没有想清楚。
5. 上游兼容契约
TauriTavern 不复刻 SillyTavern 的 Node/Express 实现细节。它复刻的是上游前端、扩展、脚本和角色卡能观察到的契约:
- 同源 URL、HTTP method、状态码、请求体解析、JSON/text/stream 响应形状。
- 上传字段名和资源路径,例如
/characters/*、/User Avatars/*、/backgrounds/*、/assets/*、/user/images/*、/user/files/*、/scripts/extensions/third-party/*。 - 聊天 JSONL、角色卡 PNG metadata、世界书、预设、主题、用户目录等文件布局。
- 上游
eventSource事件语义,例如生成、World Info、stream token、tool call 相关事件。
Rust command、Tauri Channel、repository/service 分层是内部实现,不能当成上游兼容 API 暴露给扩展作者。
兼容流量的真实链路是:
SillyTavern frontend / extension / script
-> same-origin fetch / jQuery.ajax
-> src/tauri/main/interceptors.js
-> src/tauri/main/routes/*
-> context.safeInvoke(...)
-> tauritavern presentation command
-> tt-application service
-> tt-ports trait
-> tt-adapter-* concrete implementation
新的 TauriTavern 能力优先通过 window.__TAURITAVERN__.api.* 暴露。只有行为确实对齐上游 SillyTavern 路由时,才进入 /api/* 兼容层。
6. Host 边界
tauritavern 是最外层 host crate。它可以知道所有 concrete implementation,但只应在明确边界处使用它们。
主要目录:
src-tauri/crates/tauritavern/src/
├── lib.rs # mobile entry / host lib entry,只转入 app::host::run()
├── main.rs # desktop entry
├── app/
│ ├── host/ # Tauri builder、plugin、setup、window、resources、shutdown
│ ├── composition.rs # composition root 入口
│ ├── composition/ # repositories、services、host adapters 装配
│ ├── state.rs # AppState / AppServices / lifecycle handles
│ └── startup_profile.rs # 启动期 settings / policy snapshot
├── presentation/ # Tauri commands、CommandError、web resource adapter
├── infrastructure/ # Tauri-bound infrastructure
└── platform/ # 宿主身份、iOS/UIKit、macOS/WebView、native UI glue
6.1 Host shell
app/host/* 是 Tauri shell:
- 安装 plugin。
- 决议 runtime paths。
- 初始化 logging、HTTP pool、bundled templates、host resource service。
- 创建窗口并挂载 web resource request hook。
- 启动异步 AppState 初始化。
这里可以触碰 tauri::AppHandle、window、plugin、platform API。这里不写业务用例。
6.2 Composition root
app/composition/* 是唯一可以同时知道 application service 与 concrete adapter 的地方。
维护规则:
- command 不直接构造 repository。
- service 不直接构造 concrete adapter。
- adapter 不调用 application service。
AppState只持有长期运行的 service 包、lifecycle handle 和启动期策略快照,不作为全局 service locator 下沉到 application 或 adapter。
6.3 Host-bound infrastructure
只有确实依赖 AppHandle、WebView、Tauri plugin、bundled resources、runtime path、平台 API 或 host lifecycle 的代码才留在 tauritavern/src/infrastructure 或 platform。
当前合理留在 host 的例子:
- runtime path 和数据目录选择引导配置。
- bundled resource 读取。
- logging / Dev observability 中需要 Tauri event 或 AppHandle 的部分。
- Data Archive 的 Tauri file picker/share glue。
file_content_repository与file_preset_repository,因为它们仍依赖 packaged default content / bundled resources。- 小型 host-bound API glue,例如更新检查和外部导入下载器。
新的 Tauri-free repository 不应再放回 host infrastructure。
6.4 平台事实
编译目标只在 host crate 读取:platform/identity.rs 的 HOST_IDENTITY(HostPlatform + HostKind,类型在 tt-contracts::host)是唯一来源,同时注入前端(见 docs/FrontendHostContract.md §2.2)。
HostKind取 Tauri 的desktop/mobile编译别名,与命令和插件的门控一致。OpenHarmony 的target_os是linux,以target_env = "ohos"区分。- host 内外壳类别用
cfg(desktop)/cfg(mobile),系统 API 用target_os/target_env。Cargo.toml的 target 依赖表看不到这两个别名,是唯一需要写完整平台组合的地方。 - 非 host crate 不读平台(
desktop/mobile在那里恒为假),cfg 只用于依赖或系统 API 能否编译;行为差异由 composition root 注入事实(如HostKind)或数值(如 IPC 帧预算)。 - 为每个平台选择实现时,
match列出全部平台、不写通配;某个平台独有的处理用正向条件。
pnpm run check:platform 检查各 crate 源码是否遵守以上约束,报错指向本节。
7. Adapter 边界
adapter 是外层细节,但不是可以任意堆放的 common bucket。一个 adapter crate 应当对应一个清晰的 bounded context 或一组稳定变化原因。
当前实践:
tt-adapter-storage-core只承载基础 data root 能力和较底层的文件系统 helper。它不是“所有文件仓储”的新大桶。tt-adapter-storage-userdata承载长期用户数据仓储,例如角色卡、世界书、Agent workspace/profile、Skill package。它关心 data root / user data 语义,而不是泛泛的“文件”。tt-adapter-extension承载 third-party extension 发现、安装、版本检查、更新、分支查询/切换、删除与移动,以及 Git transport、repository 与 worktree 物化边界。扩展仓库不是普通 user data JSON。tt-adapter-media承载浏览器可见的 avatar/background/user media 资源契约。tt-adapter-workspace-shell通过WorkspaceShell提供执行,经WorkspaceFs访问文件;权限、Skill 绑定、冻结宏投影与发布策略由 application 拥有。tt-adapter-provider-http、tt-adapter-mcp和tt-adapter-tokenization可以复用tt-adapter-http,但协议/provider 规则不能下沉到通用 HTTP helper。tt-adapter-sync与tt-adapter-archive是独立运行时/执行器边界,Tauri UI glue 仍留在 host。
如果一个新 adapter 只是为了两个调用点提前抽象,先不要建 crate。等它有明确 bounded context、独立依赖成本或稳定变化原因时再拆。
8. Presentation 边界
presentation 是前端可调用的 Tauri command 与 WebView resource 边界。
规则:
#[tauri::command]只放在presentation/commands/*。- command 做参数解析、policy gate、错误映射和 service 调用,不写复杂业务流程。
- command registry 集中在
presentation/commands/registry.rs。 - 对上游兼容的
/api/*行为主要由前端 route shim 维持,Rust command 名不是第三方稳定 API。 - 浏览器子资源必须通过真实可加载的 URL/Response 语义提供,不用 IPC/base64 假装资源加载。
9. 代码落点决策表
| 需求 | 放置位置 | 注意 |
|---|---|---|
| 新领域模型、值对象、纯验证 | tt-domain |
不引入 async、IO、repository trait |
| 新跨 crate payload/event/DTO | tt-contracts |
只放契约,不放 service |
| 新 repository/gateway/runtime trait | tt-ports |
trait 面向 application;具体实现不在这里 |
| 新用例、业务编排、job coordinator | tt-application |
依赖 port,不依赖 adapter |
| 新 Tauri command | tauritavern/src/presentation/commands |
调 service,不直接操作仓储细节 |
| 新 service/adapter 装配 | tauritavern/src/app/composition |
显式构造,避免 DI 容器或自动注册魔法 |
| 需要 AppHandle/WebView/plugin/platform API | tauritavern/src/app、infrastructure 或 platform |
不下沉到 adapter |
| 按平台区分的行为 | tauritavern/src/app/composition 注入 |
非 host crate 不读平台,见 §6.4 |
| data root/default-user 的基础文件仓储 | tt-adapter-storage-core |
chat/settings/user/theme/secret 等基础存储 |
| 角色卡、世界书、Agent workspace/profile、Skill package | tt-adapter-storage-userdata |
skill 是 local package store,不是普通 JSON repo |
| 第三方扩展安装、更新、发现 | tt-adapter-extension |
不归入 storage-userdata |
| avatar/background/user media/host resource 文件读取 | tt-adapter-media |
保持浏览器资源契约 |
| LLM/SearXNG/SD/Translate/TTS/provider metadata/embedding HTTP | tt-adapter-provider-http |
复用 tt-adapter-http |
| Vector 索引与本地 embedding runtime | tt-adapter-vector |
独立持久化/推理依赖,不下沉到 storage-core |
| 通用 HTTP pool/profile/helper | tt-adapter-http |
不放 provider 业务规则 |
| LAN/TT Sync runtime | tt-adapter-sync |
Tauri event/UI adapter 留 host composition |
| Data Archive import/export executor | tt-adapter-archive |
Tauri picker/share 留 host infrastructure |
| 工作区 Shell、jq、Python 与 JavaScript 执行 | tt-adapter-workspace-shell |
依赖 WorkspaceShell / WorkspaceFs port,复用调用方的文件范围;不接触物理路径 |
10. 添加后端能力的最小流程
新增能力时,先从契约和变化原因出发,再决定文件位置:
- 判断这是上游兼容行为,还是 TauriTavern 新能力。
- 上游兼容行为保持
/api/*、资源路径、stream 和事件语义。 - 新能力优先走
window.__TAURITAVERN__.api.*。 - 如有新业务概念,先放
tt-domain。 - 如有外部 IO 或持久化边界,在
tt-ports定义最小 trait。 - 在
tt-application写 service/use case。 - 在合适的
tt-adapter-*写 concrete implementation。 - 在
tauritavern/src/app/composition装配。 - 在
tauritavern/src/presentation增加 command。 - 在前端 route 或 Host ABI 层接入。
- 更新相关文档和 guard/test。
如果一个新抽象只有一个实现,先不要加。Rust 项目里显式构造通常比通用 DI、factory、inventory/linkme 更容易维护。
11. 数据目录与文件布局
数据目录布局是 SillyTavern 兼容契约的一部分。目录名大小写和空格不能随意改,例如:
default-usercharacterschatsgroup chatsUser AvatarsQuickRepliesOpenAI SettingsTextGen Settings
TauriTavern 用户数据中的私有状态放在 _tauritavern 下,例如 agent workspace、agent profiles、skills、prompt cache、legacy extension source metadata、LLM connections。能够授予宿主权限的安全状态不属于用户数据:自定义端点 grant 存在 app_root/security,不会随可切换的 data_root、备份或导入迁移。
权威代码入口:
src-tauri/crates/tt-adapter-storage-core/src/file_system.rssrc-tauri/crates/tt-domain/src/models/user_directory.rs- 桌面数据目录选择现状见
docs/CurrentState/DataDirectorySelection.md
设置分区与迁移归 tt-adapter-storage-core 的设置仓储,前端 API 保持完整设置对象。同步约定见 Sync。
设置保存复用共享字节提交;领域层定义修复与 revision 规则,应用层协调保存、恢复及 Persona 独立确认。仓储与归档共用文件清单和修复规则,读取及归档投影不回写源数据。
文件写入以单文件原子发布为边界,持久化写入在发布前同步文件;失败不回退为覆盖复制。具体保证见文件系统实现。跨 adapter 不为复用文件 helper 引入仓储依赖。
12. 专题文档导航
BackendStructure.md 只保留总边界。细节进入专题文档:
| 主题 | 文档 |
|---|---|
| 前端 Host ABI、请求拦截、资源契约 | docs/FrontendHostContract.md |
| 前端集成结构 | docs/FrontendGuide.md |
| 扩展作者 API | docs/API/README.md |
| Chat payload(完整历史、原子提交、只读分页) | docs/CurrentState/ChatPayload.md |
| Third-party extension 资源和发现 | docs/CurrentState/ThirdPartyExtensions.md |
| 媒体 Range / browser resource contract | docs/CurrentState/MediaAssetContract.md |
| Logging / Dev observability | docs/CurrentState/LoggingObservability.md |
| Native provider API formats | docs/CurrentState/NativeApiFormats.md |
| Custom endpoint SSRF 与用户授权 | docs/CurrentState/UserEndpointAccess.md |
| Vector 兼容层 | docs/CurrentState/VectorApi.md |
| Sync | docs/CurrentState/Sync.md |
| Agent 总览 | Agent |
| Agent 细节 | docs/Agent/README.md |
| iOS policy | docs/CurrentState/iOSPolicy.md |
13. 验证
架构边界变更至少运行:
pnpm run check:rust-boundaries
按影响面追加:
pnpm run test:rust
pnpm run check:rust:dev
cargo test --manifest-path src-tauri/Cargo.toml -p <affected-crate> <focused-test>
涉及前端 route shim、Host ABI 或资源端点时,还要运行前端 contract/guard:
pnpm run check:frontend
pnpm run check:types
pnpm run test:contracts
14. 常见误区
- 不要把 Clean Architecture 理解成“多建几层目录”。真正的边界是依赖方向和变化原因。
- 不要把 repository trait 放回
tt-domain。 - 不要让
tt-application依赖 concrete adapter。 - 不要让 adapter 依赖
tauritavern、AppState或 presentation command。 - 不要把 Tauri-free 文件仓储塞回 host infrastructure。
- 不要把
tt-adapter-storage-core变成新的 common 大桶。 - 不要为了两个 ZIP 调用提前抽一个泛化
common-archivecrate;等第三个真实 bounded context 出现再说。 - 不要把 Tauri command 名当作扩展公共 API。
- 不要用 IPC/base64 替代浏览器原生子资源语义。
- 不要静默降级上游契约。字段无法保真、provider metadata 无法表达、目录状态不可恢复时,应显式失败。
移动窗口背景边界
tt-contracts::window_layout 定义窗口快照与背景请求。主 WebView 布局、系统栏政策、条带显示与过期结果的丢弃属于 tauritavern::platform 的 shell glue。tt-adapter-media::window_backdrop 负责 Tauri-free 的资源读取、解码与条带裁切,复用 Host Resource Store。流程见 CurrentState/MobileStyleAdaptation.md。