Repository navigation
支持会话附件删除与图片输入模型兼容降级 #711
Copy link
Copy link
Closed
Description
目标问题
- 附件无法删除导致资源泄漏:用户上传附件后无法删除,切换会话或取消上传时服务端文件残留,缺乏生命周期管理。
- 切换到不支持图片的模型后历史图片导致请求失败:用户上传图片后切换模型(如从 Claude 切到纯文本模型),历史消息中的图片内容会导致上游 API 返回 400 错误。
- 新会话创建期间被自动切换:Web 端创建新会话时,fetchSessions 回调会自动切换到第一个会话,导致用户被意外跳转。
设计方案
附件删除
- 从 RuntimePort 拆出 SessionAssetPort 独立接口(接口隔离原则),包含 SaveSessionAsset、OpenSessionAsset、DeleteSessionAsset 三个方法。
- Gateway 注册 DELETE /api/session-assets/{sessionID}/{assetID} 端点,支持多工作区路由和认证。
- CLI Bridge 实现 DeleteSessionAsset:校验身份 → 查找会话 → 通过 sessionAssetDeleter 接口删除文件。
- Web 端取消上传/移除附件时调用 deleteSessionAsset 释放服务端资源。
- 修复 SaveSessionAsset 缺少会话存在性校验的问题(不存在的 sessionID 之前静默创建文件,现在返回 ErrRuntimeResourceNotFound)。
- 修复 OpenSessionAsset 缺少 os.ErrNotExist 映射的问题。
图片输入模型兼容降级
- Runtime 新增 currentInputParts 字段记录当前用户输入的原始 Parts。
- 在 session_start 阶段调用 rejectUnsupportedCurrentImageInput:若当前模型不支持图片但用户上传了图片,直接拒绝并返回明确错误。
- 在 prepareTurnBudgetSnapshot 中调用 projectImagesForModelRequest:对历史消息中的图片做投影降级——不支持图片的模型将历史图片替换为占位文本 [历史图片已省略:当前模型不支持图片输入],不修改持久化消息,仅影响发送给模型的内容。
新会话防自动切换
- Web 端 useSessionStore 新增 _pendingNewSession 内部状态标记。
- 用户创建/切换新会话时置 true,阻止 fetchSessions 回调自动切换到第一个会话。
- 会话绑定成功后置 false。
落地清单
- Gateway:
- 从 RuntimePort 拆出 SessionAssetPort 独立接口
- 新增 DeleteSessionAsset 方法和 DeleteSessionAssetInput 结构体
- 注册 DELETE /api/session-assets/{sessionID}/{assetID} HTTP 端点
- 多工作区运行时适配 SessionAssetPort,检测下游是否实现该接口
- 新增 sessionAssetDeleteMethod 到 HTTP ACL 白名单
- 新增 ErrRuntimeSessionAssetDeleteNotSupported 错误码
- CLI Bridge:
- 实现 DeleteSessionAsset 全流程
- 修复 SaveSessionAsset 缺少会话存在性校验
- 修复 OpenSessionAsset 缺少 os.ErrNotExist 映射
- Runtime:
- 新增 currentInputParts 字段
- 实现 projectImagesForModelRequest 图片投影降级
- 实现 rejectUnsupportedCurrentImageInput 拒绝不支持的图片输入
- 新增 hasImageCapability / partIsImage 辅助函数
- Web:
- 新增 deleteSessionAsset() API 方法
- 新增 session_asset_delete 方法名常量
- ChatInput 取消上传时调用 deleteSessionAsset
- useSessionStore 新增 _pendingNewSession 防止自动切换
验收标准
- 附件删除全链路可用:Web 取消上传 → Gateway 路由 → CLI Bridge 执行删除
- 删除后 OpenSessionAsset 返回 not found
- SaveSessionAsset 对不存在的会话返回 ErrRuntimeResourceNotFound
- 图片投影不污染持久化消息,仅影响模型请求
- 不支持图片的模型收到占位文本而非图片数据
- 当前输入含不支持的图片时拒绝并返回明确错误
- 新会话创建期间不被 fetchSessions 自动切换
- go build ./... 通过
- go test ./internal/gateway/... ./internal/runtime/... ./internal/cli/... 通过
测试场景
- 正常删除附件后再次 Open 返回 not found
- 对不存在的 sessionID 执行 Save 返回 not found
- 模型支持图片时历史图片正常传递
- 模型不支持图片时历史图片被替换为占位文本
- 当前输入含图片但模型不支持时拒绝
- 新会话创建期间 fetchSessions 不触发自动切换
破坏性变更说明
- RuntimePort 移除了 SaveSessionAsset / OpenSessionAsset 方法,拆入独立的 SessionAssetPort 接口。所有 RuntimePort 实现方需适配:不再需要实现附件方法,改为实现 SessionAssetPort。
关联
- 部分实现 Web 会话区支持通用文件附件、文档解析与附件管理 #702 落地清单中的「增加删除附件能力」
- 补充 支持 Web 多模态图片上传并修正图片 token 预算估算 #705 的图片输入模型兼容性
风险与回滚
- 风险:SessionAssetPort 拆分是破坏性接口变更,所有 RuntimePort 实现方需适配
- 风险:SaveSessionAsset 行为变更(不存在的 sessionID 从静默创建变为返回错误)
- 回滚:恢复 RuntimePort 中的附件方法,移除 SessionAssetPort,移除图片投影逻辑
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
wynxing commentedon Jun 7, 2026
更新:PR Review 修复
基于 PR #712 的 review 反馈,已修复以下问题(commit ca378b0):
1. ACL 方法名互换 bug(安全相关)
network_server.go 中 handleSessionAssetRead(GET)和 handleSessionAssetDelete(DELETE)的 ACL 检查方法名写反,导致 ACL 配置与实际行为不一致。已互换修正,并新增 TestNetworkServerSessionAssetACLIndependent 测试验证 ACL 独立生效。
2. NewConfiguredBuilder nil source 保护
builder.go 中 newStablePromptSources 对 nil SectionSource 缺少过滤,传入 nil 会导致 collectPromptSections panic。已添加 nil 过滤,并补充测试。