Kimi Code CLI Web Sessions API 全解析:从 REST 接口到源码实现
【免费下载链接】kimi-cliKimi Code CLI is your next CLI agent.项目地址: https://gitcode.com/GitHub_Trending/ki/kimi-cli
Kimi Code CLI(本仓库即其核心代码)内置了一个基于 FastAPI 的 Web 界面,而SessionsApi正是该 Web 界面用于管理会话(Session)的完整 REST API 集合。本文以web/src/lib/api/docs/SessionsApi.md为骨架,结合后端路由实现 src/kimi_cli/web/api/sessions.py、数据模型 src/kimi_cli/web/models.py 与前端 TypeScript 客户端 web/src/lib/api/apis/SessionsApi.ts,逐接口讲解会话的创建、查询、更新、删除、文件读写、git 变更统计与 AI 标题生成,帮助你在自己的应用或脚本中直接对接这套会话管理能力。
一、API 总览:10 个端点覆盖会话全生命周期
SessionsApi 的所有请求都相对http://localhost发起,统一挂载在/api/sessions/前缀之下。后端由 src/kimi_cli/web/api/sessions.py 中的APIRouter(prefix="/api/sessions", tags=["sessions"])提供路由,完整端点如下:
| Method | HTTP request | Description |
|---|---|---|
| createSessionApiSessionsPost | POST/api/sessions/ | Create a new session |
| deleteSessionApiSessionsSessionIdDelete | DELETE/api/sessions/{session_id} | Delete a session |
| generateSessionTitleApiSessionsSessionIdGenerateTitlePost | POST/api/sessions/{session_id}/generate-title | Generate session title using AI |
| getSessionApiSessionsSessionIdGet | GET/api/sessions/{session_id} | Get session |
| getSessionFileApiSessionsSessionIdFilesPathGet | GET/api/sessions/{session_id}/files/{path} | Get file or list directory from session work_dir |
| getSessionGitDiffApiSessionsSessionIdGitDiffGet | GET/api/sessions/{session_id}/git-diff | Get git diff stats |
| getSessionUploadFileApiSessionsSessionIdUploadsPathGet | GET/api/sessions/{session_id}/uploads/{path} | Get uploaded file from session uploads |
| listSessionsApiSessionsGet | GET/api/sessions/ | List all sessions |
| updateSessionApiSessionsSessionIdPatch | PATCH/api/sessions/{session_id} | Update session |
| uploadSessionFileApiSessionsSessionIdFilesPost | POST/api/sessions/{session_id}/files | Upload file to session |
从功能上可以划分为四组:
- 会话生命周期:创建(POST
/)、查询单个(GET/{session_id})、列表(GET/)、更新(PATCH/{session_id})、删除(DELETE/{session_id}); - 工作目录文件访问:读取/列目录(GET
/{session_id}/files/{path})、上传文件(POST/{session_id}/files)、读取上传文件(GET/{session_id}/uploads/{path}); - git 仓库状态:获取工作目录 diff 统计(GET
/{session_id}/git-diff); - AI 辅助:基于首轮对话生成会话标题(POST
/{session_id}/generate-title)。
所有接口的Accept均为application/json;其中创建、生成标题、更新接口的Content-Type为application/json,上传文件接口为multipart/form-data。除 200(Successful Response)外,所有接口都可能返回422 Validation Error,对应 FastAPI/Pydantic 的请求体校验失败。
二、环境与前置:如何启动 Web 服务
SessionsApi 运行在 kimi-cli 自带的 Web 服务进程内,服务入口为 src/kimi_cli/web/app.py,路由注册在 src/kimi_cli/web/api/sessions.py。启动后通过kimi web类命令拉起 FastAPI 应用,默认监听本地端口(Base URL 即http://localhost)。
从 src/kimi_cli/web/store/sessions.py 的设计说明可以看出,该存储层采用cache-aside 模式:
- 读时缓存:首次读取填充缓存,后续读直接命中;
- 写时失效:所有 API 变更操作都会调用
invalidate_sessions_cache()清空缓存; - TTL 兜底:缓存默认
CACHE_TTL = 5.0秒过期,作为外部变更的安全网。
该设计适用于单 worker 进程部署(例如不带-w参数的 uvicorn),所有变更都经由同一套 API 完成,偶发的陈旧数据(最多 5 秒)是可接受的。
三、会话生命周期:创建、查询、列表、更新与删除
3.1 创建会话:POST /api/sessions/
import { Configuration, SessionsApi, } from ''; import type { CreateSessionApiSessionsPostRequest } from ''; async function example() { const api = new SessionsApi(); const body = { // CreateSessionRequest (optional) createSessionRequest: { workDir: "/path/to/project", createDir: false, }, } satisfies CreateSessionApiSessionsPostRequest; try { const data = await api.createSessionApiSessionsPost(body); console.log(data); } catch (error) { console.error(error); } }请求体 CreateSessionRequest(可选,对应后端src/kimi_cli/web/api/sessions.py中CreateSessionRequest模型,见 sessions.py#L358-L362):
| 字段 | 类型 | 说明 | 默认值 |
|---|---|---|---|
| workDir | string | 会话工作目录,支持~展开 | 用户主目录(Path.home()) |
| createDir | boolean | 目录不存在时是否自动创建 | false |
后端行为细节(sessions.py#L299-L355):
- 若
work_dir不存在且create_dir=False,返回404Directory does not exist;若create_dir=True则自动mkdir(parents=True),权限不足返回 403,其他OSError返回 400; work_dir不是目录时返回 400Path is not a directory;- 会话底层由
KimiCLISession.create(work_dir=...)创建,session_dir落在该工作目录的会话目录中; - 返回的 Session 包含
session_id、title、lastUpdated、isRunning、status、workDir、sessionDir、archived等字段(定义见 models.py#L64-L74)。
3.2 查询单个会话:GET /api/sessions/{session_id}
const body = { sessionId: "38400000-8cf0-11bd-b23e-10b96e4ef00d", } satisfies GetSessionApiSessionsSessionIdGetRequest; const data = await api.getSessionApiSessionsSessionIdGet(body);后端(sessions.py#L285-L296)按 UUID 加载会话,并用 runner 中的进程状态实时回填is_running与status(SessionStatus 的state取值见 models.py#L11,为"stopped" | "idle" | "busy" | "restarting" | "error")。会话不存在时返回null。
3.3 列表查询:GET /api/sessions/
const body = { limit: 100, // number, optional, default 100, max 500 offset: 0, // number, optional, default 0 q: "kimi", // string, optional, 按 title 或 work_dir 过滤 archived: true, // boolean, optional } satisfies ListSessionsApiSessionsGetRequest; const data = await api.listSessionsApiSessionsGet(body);参数语义(后端见 sessions.py#L249-L282):
| 参数 | 类型 | 说明 | 默认值/约束 |
|---|---|---|---|
| limit | number | 返回的最大会话数 | 默认 100,最大 500,<=0时回退到 100 |
| offset | number | 跳过的会话数 | 默认 0,负数钳制为 0 |
| q | string | 按标题或工作目录搜索 | 可选 |
| archived | boolean | 归档过滤:None(不传)只返回未归档;true只返回归档 | 可选 |
列表接口还有两个值得注意的后端行为:
- 每次调用都会在后台触发
run_auto_archive()(内部节流,最多每 5 分钟执行一次),将超过AUTO_ARCHIVE_DAYS = 15天的会话自动归档(见 store/sessions.py#L39); - 返回前会遍历每个会话,用 runner 的运行态填充
is_running和status,因此列表天然携带实时状态。
3.4 更新会话:PATCH /api/sessions/{session_id}
const body = { sessionId: "38400000-8cf0-11bd-b23e-10b96e4ef00d", updateSessionRequest: { title: "重构认证模块", archived: true, }, } satisfies UpdateSessionApiSessionsSessionIdPatchRequest; const data = await api.updateSessionApiSessionsSessionIdPatch(body);请求体 UpdateSessionRequest(后端模型见 models.py#L77-L81):
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| title | string | 可选,min_length=1, max_length=200 | 重命名会话标题 |
| archived | boolean | 可选 | 归档/取消归档 |
后端实现(sessions.py#L586-L626)通过load_session_state/save_session_state持久化:设置title时同时标记title_generated=True(避免后续 AI 标题覆盖手动命名);归档时记录archived_at并清除auto_archive_exempt,取消归档则重置并标记为免除自动归档。更新成功返回刷新后的Session。
注意:
updateSessionRequest在文档参数表中标记为必填,而sessionId为路径参数。另外该接口会先校验会话是否 busy(见 3.5 的get_editable_session)。
3.5 删除会话:DELETE /api/sessions/{session_id}
const body = { sessionId: "38400000-8cf0-11bd-b23e-10b96e4ef00d", } satisfies DeleteSessionApiSessionsSessionIdDeleteRequest; const data = await api.deleteSessionApiSessionsSessionIdDelete(body); // any删除是物理删除(后端见 sessions.py#L565-L583):先停止关联的 runner 进程,若该会话是该工作目录的last_session_id则清空元数据引用,最后shutil.rmtree删除整个session_dir,并失效会话缓存。删除与更新共用get_editable_session(sessions.py#L110-L128)——当会话正在运行(busy)时会返回 400Session is busy,避免在任务执行中修改或删除会话。
四、文件与上传:工作目录访问的完整闭环
4.1 读取文件或列出目录:GET /api/sessions/{session_id}/files/{path}
const body = { sessionId: "38400000-8cf0-11bd-b23e-10b96e4ef00d", path: "src/main.py", // 相对 work_dir 的路径 } satisfies GetSessionFileApiSessionsSessionIdFilesPathGetRequest; const data = await api.getSessionFileApiSessionsSessionIdFilesPathGet(body);行为(后端见 sessions.py#L463-L547):
- 若
path指向文件:返回文件内容,Content-Type由mimetypes.guess_type推断,并通过Content-Disposition: attachment下载; - 若
path指向目录:返回 JSON 数组,每项为{"name", "type": "directory"|"file", "size"},按「目录优先、名称升序」排序(size仅文件有); - 路径解析经过
resolve()后必须仍在 work_dir 内,否则返回 400Invalid path: path traversal not allowed(防目录穿越)。
4.2 上传文件:POST /api/sessions/{session_id}/files
const body = { sessionId: "38400000-8cf0-11bd-b23e-10b96e4ef00d", file: BINARY_DATA_HERE, // Blob, multipart/form-data } satisfies UploadSessionFileApiSessionsSessionIdFilesPostRequest; const data = await api.uploadSessionFileApiSessionsSessionIdFilesPost(body);返回 UploadSessionFileResponse(path、filename、size)。后端关键实现(sessions.py#L379-L413):
- 文件保存到会话目录下的
uploads/子目录; - 单文件上限 100MB(
MAX_UPLOAD_SIZE = 100 * 1024 * 1024),超限返回413File too large (max 100MB); - 文件名经过
sanitize_filename清洗(仅保留字母数字与._-字符),并拼接 UUID 前缀(如report_3f2a9b.pdf)避免冲突与恶意文件名; - 上传前同样经过 busy 校验。
4.3 读取上传文件:GET /api/sessions/{session_id}/uploads/{path}
const body = { sessionId: "38400000-8cf0-11bd-b23e-10b96e4ef00d", path: "report_3f2a9b.pdf", } satisfies GetSessionUploadFileApiSessionsSessionIdUploadsPathGetRequest; const data = await api.getSessionUploadFileApiSessionsSessionIdUploadsPathGet(body);后端(sessions.py#L416-L460)将path解析后限制在uploads/目录内(同样防目录穿越),以inline方式内联返回文件,便于前端直接预览图片、PDF 等。
4.4 公开访问安全策略(重要实现细节)
getSessionFile在restrict_sensitive_apis开启时会叠加多层防护(sessions.py#L159-L191):
- 路径深度限制:相对路径深度超过
DEFAULT_MAX_PUBLIC_PATH_DEPTH = 6返回 403; - 敏感路径拦截:路径中出现
.ssh、.aws、.kube、id_rsa、credentials等关键词,或.pem、.key、.p12等敏感扩展名时返回 403; - 符号链接检查:路径任一组件是 symlink 则拒绝(防止绕过目录限制);
- 敏感家目录检测:解析后若落入
~/.ssh、~/.gnupg、~/.aws、~/.kube等位置返回 403。
列目录时也会过滤掉上述敏感条目,避免泄露凭证类文件。
五、Git 变更统计:GET /api/sessions/{session_id}/git-diff
const body = { sessionId: "38400000-8cf0-11bd-b23e-10b96e4ef00d", } satisfies GetSessionGitDiffApiSessionsSessionIdGitDiffGetRequest; const data = await api.getSessionGitDiffApiSessionsSessionIdGitDiffGet(body);返回 GitDiffStats(isGitRepo、hasChanges、totalAdditions、totalDeletions、files: GitFileDiff[]、error),其中 GitFileDiff 包含path、additions、deletions、status(added|modified|deleted|renamed,见 models.py#L42-L50)。
后端实现(sessions.py#L1131-L1245)本质上是把git命令行封装成 API:
- 工作目录没有
.git时返回is_git_repo=False; - 通过
git rev-parse --verify HEAD判断仓库是否有提交; - 有 HEAD 时执行
git diff --numstat HEAD(同时覆盖已暂存与未暂存变更),解析+/-行数并推断文件状态(只有增量为added、只有删除为deleted、否则modified); - 再执行
git ls-files --others --exclude-standard收集未跟踪的新文件,以status="added"追加(行数记为 0); - 每个 git 子进程都设置了5 秒超时,超时返回
error="Git command timed out";无 HEAD 时只汇报未跟踪文件。
该接口非常适合在 Web 界面里渲染「AI 改动概览」:无需本地安装额外依赖,前端拿到聚合后的增减行数与文件级明细即可直接展示。
六、AI 会话标题生成:POST /api/sessions/{session_id}/generate-title
const body = { sessionId: "38400000-8cf0-11bd-b23e-10b96e4ef00d", // GenerateTitleRequest (optional) - 不传时后端自动从 wire.jsonl 读取首轮对话 generateTitleRequest: { userMessage: "帮我重构认证模块", assistantResponse: "好的,我将从以下几个方面开始重构……", }, } satisfies GenerateSessionTitleApiSessionsSessionIdGenerateTitlePostRequest; const data = await api.generateSessionTitleApiSessionsSessionIdGenerateTitlePost(body);请求体 GenerateTitleRequest(userMessage、assistantResponse,均可选)与响应 GenerateTitleResponse(title)的后端模型见 models.py#L84-L98。核心逻辑在 sessions.py#L749-L901:
- 去重保护:若会话状态中
title_generated已为真,直接返回现有标题,避免重复消耗 token; - 内容兜底:请求体缺参时,通过
extract_first_turn_from_wire(sessions.py#L629-L681)解析会话目录下的wire.jsonl,提取首轮TurnBegin的用户输入与ContentPart文本作为候选;拿不到用户消息时返回"Untitled"; - 文本截断兜底:将用户消息压缩为单行并用
shorten(text, width=50)截断到 50 字符作为 fallback 标题; - AI 生成:使用配置的默认模型(
config.default_model),走kosong.generate+create_llm,系统提示要求输出不超过 50 字符、无引号无解释的纯标题;prompt 截取首轮对话各 300 字符;对 Kimi 提供商还会用SESSION_TITLE_MAX_COMPLETION_TOKENS = 512限制生成 token 数(并与配置的max_completion_tokens取较小值); - 失败重试:AI 调用失败不抛错,
title_generate_attempts递增;连续失败 3 次后直接采用截断的 fallback 标题并标记title_generated=True; - 并发安全:LLM 调用期间若其他请求已定稿标题,则以最新状态为准(read-modify-write 重新加载 state),避免覆盖手动重命名。
AI 成功生成时返回 AI 标题(超 50 字符会被截断);失败但未达 3 次时返回 fallback 标题(不标记 generated,允许下次重试)。
七、数据模型一览
以下模型文档均位于 web/src/lib/api/docs,与后端 Pydantic 定义(models.py)一一对应:
| 模型 | 关键字段 | 说明 |
|---|---|---|
| Session | sessionId, title, lastUpdated, isRunning, status, workDir, sessionDir, archived | Web UI 会话元数据 |
| SessionStatus | sessionId, state, seq, workerId, reason, detail, updatedAt | 运行时状态,state ∈ {stopped, idle, busy, restarting, error} |
| CreateSessionRequest | workDir, createDir | 创建会话请求体 |
| UpdateSessionRequest | title, archived | 更新会话请求体 |
| GenerateTitleRequest | userMessage, assistantResponse | AI 标题生成请求体(可空) |
| GenerateTitleResponse | title | AI 标题生成响应 |
| GitDiffStats | isGitRepo, hasChanges, totalAdditions, totalDeletions, files, error | 工作目录 git diff 统计 |
| GitFileDiff | path, additions, deletions, status | 单文件 diff 统计 |
| UploadSessionFileResponse | path, filename, size | 上传响应 |
八、前端集成方式:SessionsApi TypeScript 客户端
web/src/lib/api/apis/SessionsApi.ts是 OpenAPI Generator 自动生成的 TypeScript 客户端(561 行),每个端点对应一个强类型方法(如createSessionApiSessionsPost、listSessionsApiSessionsGet),并预定义了形如CreateSessionApiSessionsPostRequest的请求接口(SessionsApi.ts#L46-L80)。前端可直接:
import { SessionsApi } from '../apis'; import { Configuration } from '../runtime'; const api = new SessionsApi(new Configuration({ basePath: 'http://localhost' })); const sessions = await api.listSessionsApiSessionsGet({ limit: 50, q: 'kimi' });结合本文第四、五节的接口,一个典型的「会话浏览器」页面可以这样组织数据流:
listSessionsApiSessionsGet渲染会话列表(标题、更新时间、运行状态、归档过滤);- 选中会话后
getSessionApiSessionsSessionIdGet拉取详情; getSessionGitDiffApiSessionsSessionIdGitDiffGet展示该会话工作目录的代码改动统计;getSessionFileApiSessionsSessionIdFilesPathGet浏览/预览工作目录文件;generateSessionTitleApiSessionsSessionIdGenerateTitlePost为新会话自动生成标题;updateSessionApiSessionsSessionIdPatch/deleteSessionApiSessionsSessionIdDelete完成重命名、归档与清理。
九、错误码速查与注意事项
所有接口统一的响应码:
| 状态码 | 说明 |
|---|---|
| 200 | Successful Response(含Session、any、数组等) |
| 400 | 参数/状态非法:路径穿越、会话 busy、目录不是文件夹等 |
| 403 | 敏感文件/符号链接/目录深度超限(公开模式) |
| 404 | 会话不存在、文件不存在、目录不存在 |
| 413 | 上传文件超过 100MB |
| 422 | Pydantic 校验失败(参数类型/范围错误,如limit>500会被后端钳制而非报错) |
使用时的关键约束:
- 路径参数
session_id为 UUID 格式(文档示例为38400000-8cf0-11bd-b23e-10b96e4ef00d),非法 UUID 会触发 422; - busy 会话只读:更新、删除、上传、生成标题前都会检查会话是否正在运行,忙碌时返回 400;
- 目录穿越防护:
files/{path}与uploads/{path}均经过resolve()归一化校验,越界即 400; - 多进程部署注意:会话存储的 cache-aside 设计面向单 worker 进程,多进程部署时缓存一致性需自行评估(见 store/sessions.py 头部注释)。
十、小结
SessionsApi 是 kimi-cli Web 界面的会话中枢:它以 10 个 REST 端点完整覆盖「创建—运行—读取—更新—删除」的会话生命周期,并额外提供工作目录文件浏览、上传下载、git diff 统计与 AI 标题生成等增强能力。配合后端的 FastAPI 路由(sessions.py)、Pydantic 模型(models.py)与 TypeScript 客户端(SessionsApi.ts),无论是构建自定义前端、编写自动化脚本,还是深度定制会话管理,都能找到直接可用的接口与清晰的实现参考。
【免费下载链接】kimi-cliKimi Code CLI is your next CLI agent.项目地址: https://gitcode.com/GitHub_Trending/ki/kimi-cli
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考