数据截至 (上游 commit 3dcf4cad0124)
Jan — 架构与原理
30 秒导读: Jan 是一个装在你电脑上的开源聊天客户端。它既能把模型跑在本地(GGUF 走 llama.cpp、macOS 上还有 MLX),也能接 OpenAI / Anthropic 这类云端 provider;对话里可以调 MCP 工具去干真事,而每次工具执行前都要过一道用户审批;它还反过来在
localhost:1337开一个 OpenAI 兼容接口,让别的程序把它当模型服务用(依据:README.md的 Features 段)。
本页是多文件文档的入口。读完这一页,你应该能判断:Jan 大概怎么转、要不要继续往下读、该跳哪一章。 具体机制一律不在这里展开,全部下放到六个章节。
1. 这是什么(零基础也能懂)
一句话定义。 Jan 是一个本地优先(local-first)的桌面聊天应用,壳是 Tauri(Rust 主进程 + 系统 webview 前端),把"下载模型、跑模型、聊天、调工具"四件事装进一个可安装的 App。
给谁用、解决什么。 假设你想用大模型,但 不愿意把对话交给别人的服务器;或者你已经在用 Claude、GPT,但想让它读你本地的文件、调你本地的工具。那你就得自己搞定"去哪下模型、下完怎么启动推理进程、显存不够怎么办、聊天记录存哪、工具怎么接"这一堆脏活。Jan 就是把这些脏活包起来,给你一个装完就能用的窗口。
它能做什么。 依据 README.md 的 Features 段:
| 能力 | 说明 |
|---|---|
| 本地模型 | 从 HuggingFace 下载 GGUF(Llama / Gemma / Qwen / GPT-oss 等)并在本机运行 |
| 云端 provider | 同一个界面里切到 OpenAI、Anthropic、Google、Mistral、Groq、xAI 等,以及任意 OpenAI 兼容端点 |
| 自定义 assistant | 给不同任务预设不同的 system 指令与采样参数 |
| MCP 工具 | 接 Model Context Protocol 服务器(本地子进程 / SSE / Streamable HTTP),让模型能真的动手 |
| 附件与 RAG | 给对话挂文档,自动切块、向量化,再由模型自己调内置检索工具取回 |
| 本地 API 服务 | 在 localhost:1337 暴露 OpenAI / Anthropic 兼容接口,给其他应用调用 |
用起来什么样(开发者视角)。 从源码跑起来只有三步(依据:README.md 的 Build from Source、package.json 的 scripts):
git clone https://github.com/janhq/jan
cd jan
make dev # 等价于 yarn install + yarn build + yarn dev:tauri
yarn dev 最终落到 tauri dev(package.json:13,27),也就是"编译 Rust 宿主 + 起 Vite 前端 + 开一个原生窗口"。
用起来什么样(外部程序视角)。 打开设置里的 Local API Server 之后,别的程序可以直接把它当 OpenAI 端点:
curl http://127.0.0.1:1337/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "qwen3-4b",
"messages": [{"role": "user", "content": "列出我桌面上的文件"}],
"max_turns": 5
}'
打开"服务端工具执行"开关后,这一个请求内部会自动跑完「模型要调工具 → Rust 侧调 MCP → 把结果喂回模型」的多轮循环,最后只返回一条最终答案(src-tauri/src/core/server/proxy.rs:1136,run_server_side_openai_orchestration)。
一句话直觉。 把 Jan 当成一台本地装配好的 AI 工作站:webview 是操作台(界面和一轮对话的编排),Rust 主进程是机房(长连接、子进程、端口、文件全归它管),MCP 服务器和 llama-server 是插在机房上的外设。
2. 顶层全景(它大概怎么转)
2.1 三层进程,外加横跨的扩展层
怎么读这张图: 从上往下是调用方向。前端只发命令名,不碰进程和端口;所有有状态的东西(MCP 连接、模型进程、HTTP 服务)都活在 Rust 主进程里。
┌────────────────────────────────────────┐
你 ───────▶│ ① 前端 webview (React + Vercel AI SDK) │
│ 对话编排 · 工具审批弹窗 · 扩展宿主 │
└────────────────────┬───────────────────┘
│ invoke("call_tool" / "list_threads" …)
▼
┌────────────────────────────────────────┐
│ ② Rust 主进程 (Tauri) │
│ AppState:所有长连接与句柄的唯一持有者 │
└───┬───────────────┬──────────────┬─────┘
│ │ │
▼ ▼ ▼
③ MCP 服务器 ③ llama-server ③ 本地兼容 API
子进程 / SSE router 模式 :1337 /v1
/ HTTP (或 MLX) (可反向调用 MCP)
还有横跨的第四块:扩展(extensions/,7 个包)。它是 TypeScript,跑在第 ① 层里,只是被单独打包、单独安装,像浏览器插件一样可插拔。三层 + 扩展的完整拆解见 01 章。
三个 TypeScript 包的分工:
| 包 | 干什么 | 是不是根 workspace 成员 |
|---|---|---|
web-app/ | React 界面 + 一轮对话的编排 | 是 |
core/(@janhq/core) | 扩展 SDK:抽象基类 + 类型枚举 + 事件总线 | 是 |
extensions/(7 个) | llamacpp · mlx · rag · vector-db · assistant · conversational · download | 否,独立 workspace |
根 package.json 的 workspaces.packages 只列了 core 和 web-app(package.json:5-9);extensions/ 和 Rust 侧的插件 API 包各有自己的 workspace,构建时单独 yarn install(package.json:50 的 build:extensions、package.json:43 的 build:tauri:plugin:api)。
2.2 五个 Tauri 插件
Rust 宿主本身只是个 tauri::Builder,重活拆成插件挂上去。插件链在 src-tauri/src/lib.rs:231-254(run):
| 插件 | 干什么 | 何时挂载 |
|---|---|---|
tauri_plugin_llamacpp | 起 / 停 llama.cpp router 进程、加载卸载模型 | 总是(lib.rs:222) |
tauri_plugin_vector_db | 本地向量库读写 | 总是(lib.rs:223) |
tauri_plugin_rag | 文档切块、嵌入、检索工具 | 总是(lib.rs:224) |
tauri_plugin_mlx | Apple Silicon 上的 MLX 推理后端 | 仅 macOS(lib.rs:233,依赖声明在 Cargo.toml:164-165 的 macOS target 块) |
tauri_plugin_hardware | 读 CPU / 内存 / GPU 信息 | 非移动端(lib.rs:238) |
五个插件的源码都在仓库内:src-tauri/plugins/tauri-plugin-{llamacpp,mlx,rag,vector-db,hardware},路径依赖见 src-tauri/Cargo.toml:98-102。
2.3 部件一句话职责
| 部件 | 干什么 | 在哪个文件 |
|---|---|---|
CustomChatTransport | 一轮对话的编排中枢:选模型、装工具、裁上下文、调 streamText | web-app/src/lib/custom-chat-transport.ts:725 |
useChat | 包一层 AI SDK 的 useChat,让换模型不用重建 transport | web-app/src/hooks/use-chat.ts:30 |
| 线程页面 | 挂 onToolCall 收集工具、跑审批与执行、决定是否自动续轮 | web-app/src/routes/threads/$threadId.tsx |
useToolApprovalRequests | 审批待批队列(不落盘);白名单持久化拆在 useToolApproval | web-app/src/hooks/useToolApprovalRequests.ts:40 |
MCPOrchestrator | 服务器多了以后,挑出跟这句话相关的那几台再取工具 | web-app/src/lib/mcp-orchestrator/mcp-orchestrator.ts:79 |
ServiceHub | 前端统一的服务门面(mcp / rag / threads / models …),按平台换实现 | web-app/src/services/index.ts:56,81 |
ExtensionManager | 运行时 import() 扩展 JS、注册、按 ExtensionTypeEnum 取用 | web-app/src/lib/extension.ts:62 |
BaseExtension | 扩展的抽象基类,规定 onLoad / onUnload / type() | core/src/browser/extension.ts:32 |
AppState | Rust 侧全局状态:MCP 连接表、下载管理器、服务句柄、取消令牌 | src-tauri/src/core/state.rs:59 |
| MCP 命令层 | 连服务器、列工具、call_tool、按服务器筛工具、取消 | src-tauri/src/core/mcp/commands.rs:298,308,329,389 |
| llamacpp 扩展 | 管 router 进程、模型加载顺序与容量上限 | extensions/llamacpp-extension/src/index.ts:3425 |
| router 进程封装 | 拼 llama-server argv、拉起、健康检查、优雅停 | src-tauri/plugins/tauri-plugin-llamacpp/src/router.rs:89,120 |
| threads 存储 | 线程 / 消息 / 线程级 assistant 的持久化 | src-tauri/src/core/threads/commands.rs:24 |
| 本地 API 服务 | localhost:1337 的反向代理 + 服务端工具循环 | src-tauri/src/core/server/proxy.rs:1285,1135 |
2.4 主线走一遍(高层,不进代码)
怎么读这张图: 从上往下是一轮对话;第 ⑥ 步产出的工具结果回灌到第 ② 步,开始下一轮。
你敲下回车
│
▼
① UI 收到消息 ──▶ ② CustomChatTransport 组装这一轮
· 选哪个模型 / provider
· 该带哪些工具(MCP + RAG)
· 上下文超了就裁 / 压
│
▼
③ streamText 流式产出
文本 / 推理过程 / 工具调用
│
┌──────────────┴───────────────┐
▼ ▼
文本直接上屏 ④ onToolCall 只「收集」不执行
│
▼
⑤ 审批闸门:逐个问你
允许一次 / 一直允许 / 拒绝
│
▼
⑥ 服务层执行(RAG 直放 / MCP 落到 Rust)
│
└──▶ 结果回灌,回到 ②
六步各自的落点:
| 步 | 落在哪 |
|---|---|
| ② 组装 | custom-chat-transport.ts:1102 sendMessages |
| ③ 流式 | custom-chat-transport.ts:1369 streamText |
| ④ 收集 | $threadId.tsx:546-548 onToolCall |
| ⑤ 审批 | useToolApprovalRequests.ts:43 requestApproval;UI 四个按钮在 tool.tsx:348-370 |
| ⑥ 执行 | $threadId.tsx:422,430 serviceHub.rag()/mcp().callTool → Rust mcp/commands.rs:405 call_tool |
| 续轮 | $threadId.tsx:549 sendAutomaticallyWhen: followUpMessage |
一个要点先说在前面: AI SDK 本身支持工具自动执行,Jan 偏偏不用——onToolCall 只把工具调用推进一个数组,真正的执行放在应用层的循环里,好在中间插进"问用户"这一步。这是整个项目最值得看的设计,详见 03 章。
3. 阅读地图
建议按顺序读:先建立"进程和扩展"的地形感,再跟着一次对话走完全程,然后分别下钻工具、推理、记忆三条支线,最后看反向暴露的 API。
| 顺序 | 章节 | 这章讲什么 | 适合谁 |
|---|---|---|---|
| 1 | 骨架:三层进程与可插拔扩展系统 | webview / Rust 主进程 / 外部进程如何分工,window.core.api 桥怎么把驼峰路由变成 Tauri 命令,扩展如何被动态 import 与按类型解析 | 先看这个再看别的 |
| 2 | 一次对话怎么跑完:CustomChatTransport 与 AI SDK | sendMessages 的完整流水线:建模型、装工具、九个消息清洗器、streamText、流元数据与 token 速率回填 | 想改对话行为的人 |
| 3 | 手脚:MCP 客户端、审批闸门与工具路由 | rmcp 三种传输、健康监控与指数退避重连、审批三态状态机、服务器超过阈值时的关键词 + LLM 双层路由 | 关心 agent 安全与工具选择 |
| 4 | 本地推理运行时:llama.cpp router 与后端分发 | router 模式的 argv 与就绪探测、多模型 FIFO 驱逐、后端下载与依赖校验、OOM 识别与优雅退出 | 关心本地推理工程 |
| 5 | 记性:线程持久化、分支、上下文压缩与 RAG | 线程落盘(桌面 JSONL / 移动 SQLite)、父指针消息树与版本切换、trim 与 auto-compact、RAG 三件套工具与向量库 | 关心记忆与检索 |
| 6 | 反向暴露:本地兼容 API 与服务端工具循环 | /v1 代理如何解析上游、Anthropic ↔ OpenAI 双向翻译、max_turns 有界循环与 schema 归一化 | 想把 Jan 当后端用 |
只想抓重点的话: 读 01 建立地图,再读 03 —— 这两章覆盖了 Jan 作为 "agent 宿主"最有工程含量的部分。
4. 巧妙之处速览
这一节只点破"妙在哪 + 在哪能看到",细节在对应章节。
① 审批闸门放在"流结束之后",而不是塞进模型的执行回调。
AI SDK 允许在工具定义里直接写 execute。Jan 偏不:给工具只声明 description + inputSchema(custom-chat-transport.ts:950)。于是模型只能"提出"调用,SDK 无处 执行,调用被 onToolCall 攒进一个数组($threadId.tsx:546-548),由 UI 层串行地过审批再执行——被拒就写回一条 output-error: 'Tool execution denied by user' 继续下一个($threadId.tsx:409),内置 RAG 工具显式豁免审批($threadId.tsx:401)。好处很实在:审批要等人点按钮,塞进流式回调会阻塞整条流;拆开之后流可以干净地结束,待办队列再慢慢消化,还能整队 abort。→ 03 章
② 服务器 ≤ 5 台时,根本不启动智能路由。
ROUTING_THRESHOLD = 5(intent-classifier.ts:4)。getRelevantTools 开头就判断 summaries.length <= ROUTING_THRESHOLD 则直接取全量工具返回(mcp-orchestrator.ts:96-113)。妙在:小规模场景下,多一次 LLM 路由调用带来的延迟远大于它省下的 token,于是干脆不做。超过阈值才走"关键词打分(能力名精确命中 +4、部分命中 +2、描述命中 +1,intent-classifier.ts:33;要求 score ≥ 2,:83)→ 可选的轻量 LLM router → 拿不到就回退全量"这条链(mcp-orchestrator.ts:116-165)。→ 03 章
③ 一个 router 进程托管多模型,超额时按 FIFO 驱逐。
Jan 不给每个模型开一个 llama-server,而是用 --models-preset + --models-max 起一个 router 进程(router.rs:452 router_args)。前端维护一份 loadedChatOrder 数组当队列,加载新模型前若已到上限就 shift() 出最老的那个卸掉(extensions/llamacpp-extension/src/index.ts:3495 evictChatIfAtCapacity)。细节讲究:嵌入模型不参与驱逐,免得后台跑一次嵌入就把你的聊天模型顶下去(index.ts:2727-2728);拿不到 router 的真实已加载列表时,宁可拿本地队列过度驱逐也不违反上限(index.ts:2755-2758)。退出时还有一道礼貌门:主窗口关闭前先问 router "还有模型在忙吗",忙就先不退、发事件让 UI 提示(src-tauri/src/lib.rs:170 handle_graceful_exit)。→ 04 章
④ 消息不是数组,是一棵父指针树。
编辑一条旧消息、或对同一个问题重新生成,在 Jan 里都不是覆盖,而是新建一个兄弟版本。每条消息 metadata.parentId 指向前驱,metadata.activeChildId 指定当前展示哪个分支;可见对话 = 从活跃根沿着活跃子一路走下来的那条路径(message-branching.ts:95 computeActivePath)。老线程没有这些字段,照样当线性对话渲染,直到第一次分叉时才回填父指针(message-branching.ts:142 backfillParentIds)。这是"新数据模型兼容旧数据"的一个干净范例——而且没动任何存储 schema。→ 05 章
⑤ 上下文超预算时,先总结再截断,总结完还要再截一次。
compactMessages 会把将被丢弃的旧消息喂给同一个模型做摘要,摘要以 role: 'system' 注入(而不是伪装成用户发言,以免打乱轮次交替)。关键的一步是:摘要本身也占 token,所以合并之后再跑一次 trimMessages 才能保证真的装得下;摘要失败则静默退回纯截断(context-manager.ts:124 compactMessages、:69 trimMessages)。→ 05 章
⑥ 给 Anthropic 单独把串行 tool-use 拆成 wave。
Claude 的 API 要求 tool_use / tool_result 严格配对,而一条 assistant 消息里"工具 → 文本 → 又工具"的交错会转出非法结构。Jan 只对 apiType === 'anthropic' 的 provider 做一次 flatMap:扫 parts,遇到"工具段之后出现非工具段"就切一刀,把一条消息切成 id_w0、id_w1……多条(custom-chat-transport.ts:530-565 splitAssistantToolWaves)。妙在这是纯前置改写,不污染其它 provider 的路径——同一段流水线上还收着"Qwen 模板要求窗口里有真实用户提问""模型不支持视觉时先剥图片"等一串同类补丁。→ 02 章
⑦ 前端到 Rust 的桥是"路由名自动转命令名"。
window.core.api 不是手写几十个包装函数,而是把一张路由名数组批量映射成代理:驼峰名 listThreads 用一条正则转成 list_threads 再 invoke(web-app/src/lib/service.ts:40,47,名单在同文件 :6-27)。这让扩展代码写 window.core.api.listThreads() 就够了,新增命令只要往数组里加一个名字。→ 01 章
⑧ 本地 API 服务在服务端自己跑工具循环。
外部程序调 localhost:1337 时可以带一个 max_turns(默认 8,夹在 1..20),Rust 侧就在服务端跑"请求 → 抽 tool_calls → 执行 MCP → 把 tool 结果 append 回 messages → 再请求"的循环,直到模型不再要工具(proxy.rs:1199-1203、:1135 run_server_side_openai_orchestration)。跑满仍在要工具就报错并把最后一次响应原样带回(proxy.rs:1277-1282)。 妙在:调用方只发一个普通的 chat completions 请求,agent 循环整个藏在服务端。→ 06 章
5. 边界(先说清楚,免得读错)
- 不是服务端产品。 整套设计围绕"一台个人电脑上的一个 App":单实例(
lib.rs:210的single_instance插件)、数据放本地目录、localhost:1337默认只绑本机。 - MLX 只有 macOS。 插件依赖写在 macOS target 块里(
Cargo.toml:164-165),构建脚本也把@janhq/mlx-extension从 win32 / linux 的构建里排除(package.json:52-53)。 - 审批是"人在回路"的,不是沙箱。 闸门管的是"要不要执行",执行本身直接跑 MCP 服务器,没有额外隔离;而且服务端那条循环完全没有这道闸门(见 06 章 §9),它靠"默认关闭 + 端口访问控制"防守。
- 移动端是裁剪版。
lib.rs:249-254显示 iOS / Android 分支不挂 updater、走另一套 remote provider 命令;扩展也在编译期就定死,目前只有 conversational 一个。
6. 代码地图(全项目导航索引)
按主题分组,给出文件路径和可 grep 的真实符号名;grep 符号名比按行号更抗上游漂移。
入口与宿主
| 主题 | 文件 | 符号 | 锚点 |
|---|---|---|---|
| Rust 宿主启动、插件链 | src-tauri/src/lib.rs | run | lib.rs:206,216-238 |
| 前端可调用的全部 Tauri 命令清单 | src-tauri/src/lib.rs | invoke_commands_with_extras! | lib.rs:24 |
| 优雅退出:等 router 空闲再关 | src-tauri/src/lib.rs | handle_graceful_exit | lib.rs:155 |
| 全局可变状态(MCP 连接、下载、服务器句柄) | src-tauri/src/core/state.rs | AppState、RunningServiceEnum | state.rs:50,44 |
| 首次启动 / 升级时的扩展安装与 MCP 迁移 | src-tauri/src/core/setup.rs | install_extensions、migrate_mcp_servers | setup.rs:27,169 |
扩展系统
| 主题 | 文件 | 符号 | 锚点 |
|---|---|---|---|
| 扩展抽象基类与设置合并 | core/src/browser/extension.ts | BaseExtension、registerSettings | extension.ts:31,114 |
| 扩展类型枚举(当接口名用) | core/src/browser/extension.ts | ExtensionTypeEnum | extension.ts:4 |
| 运行时注册与按类型 / provider 检索 | web-app/src/lib/extension.ts | ExtensionManager、activateExtension、registerActive | extension.ts:62,190,227 |
| 扩展装载的 React 入口 | web-app/src/providers/ExtensionProvider.tsx | ExtensionProvider、setupExtensions | ExtensionProvider.tsx:24,28 |
| 前端 → Rust 命令桥 | web-app/src/lib/service.ts | APIs、AppRoutes | service.ts:40,6-27 |
| 前端服务门面(按平台换实现) | web-app/src/services/index.ts | ServiceHub、PlatformServiceHub、initializeServiceHub | index.ts:56,81 |
对话主线
| 主题 | 文件 | 符号 | 锚点 |
|---|---|---|---|
| 一轮对话的组装与流式 | web-app/src/lib/custom-chat-transport.ts | CustomChatTransport、sendMessages | custom-chat-transport.ts:559,850 |
| 工具清单装配(RAG + MCP + 禁用过滤 + 重名告警) | web-app/src/lib/custom-chat-transport.ts | refreshTools、resolveRouterModel | custom-chat-transport.ts:643,788 |
| Anthropic 串行 tool-use 拆 wave | web-app/src/lib/custom-chat-transport.ts | splitAssistantToolWaves | custom-chat-transport.ts:530 |
| 消息清洗器与早失败守卫 | web-app/src/lib/custom-chat-transport.ts | stripUnsupportedImageParts、resolveOrphanToolCalls、hasGenuineUserQuery | custom-chat-transport.ts:353,405,465 |
| 模型工厂(各 provider → AI SDK 模型) | web-app/src/lib/model-factory.ts | ModelFactory.createModel、createLlamaCppModel | model-factory.ts:705,760 |
| useChat 包装(换模型不重建 transport) | web-app/src/hooks/use-chat.ts | useChat | use-chat.ts:30 |
| 线程页面:工具收集、审批、执行、续轮 | web-app/src/routes/threads/$threadId.tsx | onToolCall、followUpMessage | $threadId.tsx:546,158 |
工具与 MCP
| 主题 | 文件 | 符号 | 锚点 |
|---|---|---|---|
| 审批待批队列(不落盘) | web-app/src/hooks/useToolApprovalRequests.ts | useToolApprovalRequests、requestApproval、resolveApproval | useToolApprovalRequests.ts:27,43,69 |
| 审批持久化白名单 | web-app/src/hooks/useToolApproval.ts | useToolApproval、approveToolForThread、approveServer、isToolApproved | useToolApproval.ts:26,62 |
| 审批 UI(拒绝 / 允许一次 / 一直允许) | web-app/src/components/ai-elements/tool.tsx | ToolApprovalActions | tool.tsx:225,245-262 |
| 工具路由(阈值 + 关键词 + LLM) | web-app/src/lib/mcp-orchestrator/mcp-orchestrator.ts | MCPOrchestrator、getRelevantTools | mcp-orchestrator.ts:79,85 |
| 路由阈值与关键词打分 | web-app/src/lib/mcp-orchestrator/intent-classifier.ts | ROUTING_THRESHOLD、MAX_ROUTED_SERVERS、classifyIntent、scoreServer | intent-classifier.ts:4,6,62,33 |
| LLM router 调用 | web-app/src/lib/mcp-orchestrator/mcp-router-llm.ts | selectServersWithLlm | mcp-router-llm.ts:35 |
| MCP 命令层(列工具 / 调工具 / 取消) | src-tauri/src/core/mcp/commands.rs | get_tools、get_tools_for_servers、get_server_summaries、call_tool、cancel_tool_call | commands.rs:298,308,329,389,519 |
| MCP 连接、三种传输、健康监控 | src-tauri/src/core/mcp/helpers.rs | start_mcp_server、schedule_mcp_start_task、monitor_mcp_server_handle | helpers.rs:324,385,163 |
| MCP 运行时设置(超时、退避、路由开关) | src-tauri/src/core/mcp/models.rs | McpSettings、McpServerConfig、ServerSummary | models.rs:53,8,106 |
本地推理运行时
| 主题 | 文件 | 符号 | 锚点 |
|---|---|---|---|
| router 启动 / 停止 / 强杀 | src-tauri/plugins/tauri-plugin-llamacpp/src/router.rs | router_args、start_router、try_graceful_stop_router、force_kill_router_tree | router.rs:89,120 |
| load / unload 的 HTTP 翻译与轮询 | src-tauri/plugins/tauri-plugin-llamacpp/src/commands.rs | post_load、wait_until_loaded、find_session_by_model | commands.rs:42,75,402 |
| 模型加载与 FIFO 驱逐 | extensions/llamacpp-extension/src/index.ts | load、performLoad、ensureRouterReady、evictChatIfAtCapacity | index.ts:2680,2715,2706,2749 |
| router preset 文件生成 | extensions/llamacpp-extension/src/preset.ts | generatePreset、MTP_MIN_BUILD | preset.ts:75 |
| 后端二进制选型与依赖校验 | extensions/llamacpp-extension/src/backend.ts · .../src/backend.rs | listSupportedBackends · prioritize_backends、verify_backend_installation | — |
| MLX 后端(macOS) | src-tauri/plugins/tauri-plugin-mlx、extensions/mlx-extension | MlxState、mlx_extension.load | — |
| 硬件信息 | src-tauri/plugins/tauri-plugin-hardware | — | — |
记忆与检索
| 主题 | 文件 | 符号 | 锚点 |
|---|---|---|---|
| 线程 / 消息命令(桌面文件 / 移动 SQLite 分叉) | src-tauri/src/core/threads/commands.rs | list_threads、create_message、modify_message | commands.rs:24,159 |
| 存储后端选择与 JSONL 读写、每线程锁 | src-tauri/src/core/threads/helpers.rs | should_use_sqlite、write_messages_to_file、MESSAGE_LOCKS | helpers.rs:17,34,14 |
| 移动端 SQLite 初始化与幂等写 | src-tauri/src/core/threads/db.rs | init_database、db_create_message、db_modify_message | db.rs:27 |
| 消息版本树与活跃路径 | web-app/src/lib/message-branching.ts | computeActivePath、backfillParentIds、makeSibling | message-branching.ts:90,137,146 |
| 上下文裁剪与自动压缩 | web-app/src/lib/context-manager.ts | trimMessages、compactMessages、CHARS_PER_TOKEN | context-manager.ts:69,124,11 |
| 前端线程 API(经扩展转发) | extensions/conversational-extension/src/index.ts | JanConversationalExtension | index.ts:12 |
| RAG 内置工具定义 | extensions/rag-extension/src/tools.ts | getRAGTools | tools.ts:8 |
| 向量库封装与集合命名 | extensions/vector-db-extension/src/index.ts | VectorDBExt、ingestFile | index.ts:5 |
本地 API 服务与下载
| 主题 | 文件 | 符号 | 锚点 |
|---|---|---|---|
| 服务器启停 | src-tauri/src/core/server/commands.rs | StartServerConfig、start_server、stop_server | commands.rs:12,23 |
| 请求总入口与上游解析 | src-tauri/src/core/server/proxy.rs | proxy_request、resolve_upstream_for_model | proxy.rs:1285,888 |
| 服务端工具循环 | src-tauri/src/core/server/proxy.rs | run_server_side_openai_orchestration、collect_mcp_openai_tools、execute_mcp_tool_calls | proxy.rs:1135,958,1014 |
| 前端配置存储(端口、前缀、可信主机、工具开关) | web-app/src/hooks/useLocalApiServer.ts | useLocalApiServer、serverPort | useLocalApiServer.ts:21,60 |
| 模型下载 | src-tauri/src/core/downloads/commands.rs | download_files、cancel_download_task、pause_download_task | commands.rs:11,75 |
| 下载扩展(HF token、进度事件) | extensions/download-extension/src/index.ts | DownloadManager | index.ts:23 |