# 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`。
```text
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-quickjs
├── 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、bounded response、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-quickjs` | QuickJS Runtime/Context、内存 ESM 与 JavaScript binding;只接触逻辑模块、JSON、内存 workspace snapshot/delta |
| `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 依赖方向。它们应当始终指向更内层、更稳定的策略或契约。
```mermaid
flowchart TB
host["tauritavern
Tauri host / commands / composition root"]
adapters["tt-adapter-*
concrete IO / runtime / file formats"]
application["tt-application
use cases / services / policies"]
ports["tt-ports
ports required by use cases"]
contracts["tt-contracts
DTO / events / payloads"]
domain["tt-domain
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 关系:
```text
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 1.18.0 的 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 暴露给扩展作者。
兼容流量的真实链路是:
```text
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,但只应在明确边界处使用它们。
主要目录:
```text
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。
## 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-quickjs` 只承载 JavaScript 执行语义与内存 binding;Skill、Workspace policy、持久化和 journal 仍由 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 |
| 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 |
| Skill JavaScript 内存执行 | `tt-adapter-quickjs` | 不接触物理路径、Workspace repository 或平台领域模型 |
## 10. 添加后端能力的最小流程
新增能力时,先从契约和变化原因出发,再决定文件位置:
1. 判断这是上游兼容行为,还是 TauriTavern 新能力。
2. 上游兼容行为保持 `/api/*`、资源路径、stream 和事件语义。
3. 新能力优先走 `window.__TAURITAVERN__.api.*`。
4. 如有新业务概念,先放 `tt-domain`。
5. 如有外部 IO 或持久化边界,在 `tt-ports` 定义最小 trait。
6. 在 `tt-application` 写 service/use case。
7. 在合适的 `tt-adapter-*` 写 concrete implementation。
8. 在 `tauritavern/src/app/composition` 装配。
9. 在 `tauritavern/src/presentation` 增加 command。
10. 在前端 route 或 Host ABI 层接入。
11. 更新相关文档和 guard/test。
如果一个新抽象只有一个实现,先不要加。Rust 项目里显式构造通常比通用 DI、factory、inventory/linkme 更容易维护。
## 11. 数据目录与文件布局
数据目录布局是 SillyTavern 兼容契约的一部分。目录名大小写和空格不能随意改,例如:
- `default-user`
- `characters`
- `chats`
- `group chats`
- `User Avatars`
- `QuickReplies`
- `OpenAI Settings`
- `TextGen 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.rs`
- `src-tauri/crates/tt-domain/src/models/user_directory.rs`
- 桌面数据目录选择现状见 `docs/CurrentState/DataDirectorySelection.md`
设置分区与迁移归 `tt-adapter-storage-core` 的设置仓储,前端 API 保持完整设置对象。同步约定见 [Sync](CurrentState/Sync.md#独立设置范围)。
文件写入以单文件原子发布为边界,持久化写入在发布前同步文件;失败不回退为覆盖复制。具体保证见[文件系统实现](../src-tauri/crates/tt-adapter-storage-core/src/file_system.rs)。跨 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/README.md) |
| Agent 细节 | `docs/Agent/README.md` |
| iOS policy | `docs/CurrentState/iOSPolicy.md` |
## 13. 验证
架构边界变更至少运行:
```bash
pnpm run check:rust-boundaries
```
按影响面追加:
```bash
pnpm run test:rust
pnpm run check:rust:dev
cargo test --manifest-path src-tauri/Cargo.toml -p
```
涉及前端 route shim、Host ABI 或资源端点时,还要运行前端 contract/guard:
```bash
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-archive` crate;等第三个真实 bounded context 出现再说。
- 不要把 Tauri command 名当作扩展公共 API。
- 不要用 IPC/base64 替代浏览器原生子资源语义。
- 不要静默降级上游契约。字段无法保真、provider metadata 无法表达、目录状态不可恢复时,应显式失败。