Skip to content

Commit 403a471

Browse files
ThreeFish-AIclaude
andauthored
v0.4.0 (#233)
* fix(proxy): 修复 tool_use.input 非法 JSON 序列化导致的级联故障转移失败; (#213) - 防御性 tool_use.input 序列化: 非 dict 类型(None/字符串等)降级为空字典, 避免向 copilot 发送非 JSON 对象的 arguments 字段 - 识别 zhipu 500 'ClaudeContentBlockToolResult' 错误为格式不兼容, 不计入熔断器, 确保干净的故障转移到 copilot - 扩展 _is_likely_request_format_error 覆盖结构化 400 响应中 invalid_tool_call_format 错误码的检测 - 新增对应单元测试: input 类型降级、zhipu 格式错误检测、结构化 400 检测 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) * fix(usage-parser): 补充 OpenAI/Gemini SSE 流式分支的 model_served 提取; (#214) OpenAI 和 Gemini 的 SSE 流式响应解析中,model_served 仅写入证据记录而未赋值 到主 usage 字典,导致 Native API 流式路径下模型名称回退为 "unknown"。 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) * build(version): 升级版本号 0.3.1a3 → 0.3.1a4; 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * fix(vendor-channels): 为 tool_result 块注入 id 字段,修复 zhipu GLM-5 后端 500 错误; (#215) 新增 _inject_tool_result_id_for_zhipu 辅助函数,在所有 targeting zhipu 的转换通道 (copilot→zhipu、anthropic→zhipu、zhipu self-cleanup)末尾统一注入 tool_result.id, 解决 zhipu 后端错误访问 .id 属性导致的 'ClaudeContentBlockToolResult' object has no attribute 'id' 级联故障转移问题。 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) * build(version): 升级版本号 0.3.1a4 → 0.3.1a5; 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * fix(vendor-channels): 移除 zhipu 目标转换中有害步骤,修复 500 级联故障及遗留测试; (#216) * fix(vendor-channels): 移除 zhipu 目标转换中的有害步骤,修复 500 级联故障; 从 prepare_copilot_to_zhipu、prepare_anthropic_to_zhipu、prepare_zhipu_self_cleanup 三个通道中移除 enforce_anthropic_tool_pairing(tool_result 搬迁触发 zhipu 500)、 _inject_tool_result_id_for_zhipu(zhipu 类不读取 JSON id,注入无效)、 _strip_cache_control(GLM-5 原生支持 cache_control)。 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * fix(vendor-channels): 修复 zhipu 移除 id 注入后遗留测试断言错误; 6 处测试的断言仍检查旧行为(id 被注入 / cache_control 被移除 / tool_result 被搬迁), 导致 KeyError 或 AssertionError。统一更新为验证新语义:id 不注入、cache_control 保留、 inline tool_result 保留在 assistant 原位。 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * docs(agents): 新增"In-depth and close to the facts"协作准则; 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * build(version): 升级版本号 0.3.1a5 → 0.3.1a6; 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * build(version): 升级版本号 0.3.1a6 → 0.3.1a7; 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * fix(vendor-channels): 修复 zhipu tool_result 500 级联故障 (#217) * fix(vendor-channels): 移除 zhipu 目标转换中的有害步骤,修复 500 级联故障; 从 prepare_copilot_to_zhipu、prepare_anthropic_to_zhipu、prepare_zhipu_self_cleanup 三个通道中移除 enforce_anthropic_tool_pairing(tool_result 搬迁触发 zhipu 500)、 _inject_tool_result_id_for_zhipu(zhipu 类不读取 JSON id,注入无效)、 _strip_cache_control(GLM-5 原生支持 cache_control)。 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * fix(vendor-channels): 修复 zhipu 移除 id 注入后遗留测试断言错误; 6 处测试的断言仍检查旧行为(id 被注入 / cache_control 被移除 / tool_result 被搬迁), 导致 KeyError 或 AssertionError。统一更新为验证新语义:id 不注入、cache_control 保留、 inline tool_result 保留在 assistant 原位。 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * fix(vendor-channels): 在 tier 门控层主动跳过含 tool_result 的 zhipu 请求; 修正前两次诊断:zhipu 500 发生于所有含 tool_result 块的请求(非仅 tool pairing 后)。 根因是 zhipu 后端 ClaudeContentBlockToolResult 类缺少 id 属性且 JSON 注入无效。 通过 RequestCapabilities.has_tool_results 门控,在发送请求前跳过 zhipu tier, 消除「尝试 → 500 → failover」的无效延迟。 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * fix(vendor-channels): 将 tool_use/tool_result 展平为 text 块,彻底修复 zhipu 500 错误 zhipu 后端的 ClaudeContentBlockToolResult 类缺少 id 属性, 无法通过 JSON 注入修复。改用在转换通道中将所有 tool_use 和 tool_result 块转为 text 块的策略,让 zhipu 仅看到纯文本对话。 - 新增 _flatten_tool_blocks 辅助函数 - 三个 zhipu 目标通道均调用展平逻辑 - 移除 ZhipuVendor.supports_request 中的 tool_result 门控 - 更新全部相关测试用例 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> * revert(vendor-channels): 还原至 v0.3.0 基线,修复 failover 级联 server_tool_use.id 400 错误 (#218) * revert(vendor-channels): 还原至 v0.3.0 基线,修复 failover 级联 server_tool_use.id 400 错误; - 还原 vendor_channels.py 至 v0.3.0(移除 _flatten_tool_blocks、_inject_tool_result_id_for_zhipu、 _enforce_pairing_sanity_pass、zhipu 自清理通道、anthropic→zhipu 通道等 v0.3.0 后引入的特殊处理) - 修复 _determine_source_vendor Priority 1 逻辑:仅当 failed_tier 有已注册转换通道时才返回, 否则降级到 session history / body inference,确保 copilot→anthropic failover 时能通过 session 找到 zhipu 源并正确清理 server_tool_use 块 - 还原 model/vendor.py(移除 TOOL_RESULTS capability) - 部分还原 error_classifier.py(保留 CN 语义拒绝标记,移除 has_tool_results) - 还原 executor.py(移除 zhipu 500 特殊检测,保留 invalid_tool_call_format 检测) - 同步更新测试用例,全部 1399 测试通过 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * docs(issue): 恢复与 vendor-channels 回退无关的 Issue #1 文档; 回退 vendor-channels 至 v0.3.0 基线时误删了 docs/issue.md 全部内容, 其中 Issue #1(streaming usage parse failed)描述的 null 防护修复 仍保留在 usage_parser.py 中,属于无关文档,不应被连带删除。 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * feat(session-policy): 新增 Session 级别路由策略与会话活动面板 (#219) * feat(session-policy): 新增 Session 级别路由策略与会话活动面板; - 新增 SessionPolicy 配置模型(Pydantic),支持按 session_key / client_category 匹配并覆盖 vendor 优先级 - 新增 SessionPolicyResolver 解析引擎,启动时构建索引实现 O(1) 查找 - 为 usage_log 表新增 session_key 列及索引,含幂等迁移逻辑 - 新增 query_recent_sessions / query_session_profile 聚合查询方法 - executor 路由流式/非流式请求时按 Session Policy 动态重排 tier 顺序 - Dashboard 新增 Recent Active Sessions 面板,展示近期会话聚合数据 - 新增 /api/dashboard/sessions API 端点 - 新增完整测试覆盖(schema、迁移、策略解析、tier 重排、聚合查询) 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * fix(session-policy): 修复 Dashboard XSS 风险并标注 client_category 为预留字段; - 新增 escapeHtml() 工具函数,对所有用户可控数据(session_key、models、vendors、categories)插入 innerHTML 前进行 HTML 实体转义 - title 属性中的 session_key 同样使用转义后的值 - 在 SessionPolicyMatch.client_category 字段描述和 config.default.yaml 中明确标注该条件为预留功能,当前版本暂未生效 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * build(version): 升级版本号 0.3.1a7 → 0.3.1a8; 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * feat(dashboard): 拆分首页为 Overview / Recent Active Sessions 双页签; (#220) - 顶部新增 Tab 导航(Overview / Recent Active Sessions),用 `?tab=` 同步 URL 状态,F5 与书签可保留页签; - 将 refresh() 拆为 refreshOverview() + refreshSessions() + 分发器,⟳ 按钮与 10 分钟 setInterval 均仅刷新当前页签; - 页签切换采用懒加载(首次切到才请求,再切回不重复请求),降低无关 API 调用; - refresh() 加入循环补刷快照机制,防止 await 期间用户切页签导致 tabLoaded 标记错位; - 沿用既有 CSS 变量与暗色主题,新增 .tabs / .tab-btn / .tab-pane 样式。 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) * feat(session-vendor): 新增 Session 级别运行时 vendor 绑定能力 (#221) * feat(session-vendor): 新增 Session 级别运行时 vendor 绑定能力 - SessionPolicyResolver 支持运行时 upsert/remove/list 操作,无需重启服务 - 新增 PUT/DELETE/GET /api/session-vendor API 端点 - Dashboard Sessions 表新增 "Vendor Bind" 下拉选择器,即时绑定 vendor - CLI 新增 session 子命令组(bind/unbind/list) 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * fix(session-vendor): 修复 CLI unbind URL 编码与 list_runtime_bindings 类型标注; - session_unbind 中 key 参数使用 urllib.parse.quote 进行 URL 编码,防止特殊字符导致路由匹配失败 - 修正 list_runtime_bindings 返回类型为 dict[str, str | list[str]],准确反映 session_key 为 str、vendors 为 list[str] 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * fix(session-vendor): 修复 Dashboard bind 变更响应检查与 XSS 防护; - handleBindChange 增加 resp.ok 检查,失败时 revert select 并输出错误日志 - buildBindSelect 改用 data-session-key 属性 + 事件委托替代 inline onchange,消除字符串拼接注入风险 - resolve() docstring 补充 SessionPolicy 不可变约束说明 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * fix(session-vendor): 修复运行时绑定覆盖配置策略与 Dashboard 容错; - upsert 时备份被覆盖的配置策略,remove 时自动恢复,避免配置策略永久丢失 - Dashboard updateSessions 改用 Promise.allSettled,bind/status 请求失败时降级而不阻塞表格渲染 - 新增 test_runtime_remove_restores_config_policy 测试用例覆盖恢复路径 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * fix(session-vendor): 修复 API JSON 解析异常处理与 Dashboard 多绑定显示; - routes: request.json() 添加 try/except,malformed JSON 返回 400 而非 500 - routes: 区分 "非 JSON" 与 "非 JSON Object" 两种错误消息 - dashboard: buildBindSelect 多 vendor 绑定时显示 (+N) 后缀提示 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * build(version): 升级版本号 0.3.1a8 → 0.3.1a9; 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * fix(dashboard): 优化 Recent Active Sessions 表格 UI,消除滚动条并新增分页 (#222) * fix(dashboard): 优化 Recent Active Sessions 表格 UI,消除滚动条并新增分页; - 解析 session_key JSON,分别展示 session_id(首列完整显示)、device_id 与 account_uuid(紧凑副文本) - 采用 table-layout: fixed + colgroup 固定列宽比例,消除底部水平滚动条 - 新增客户端分页(默认 30 行/页,显示总行数与页码),消除右侧垂直滚动条 - 后端 API limit 上限从 100 调整到 200,支持前端拉取更多数据 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * fix(dashboard): 提升 session-key 样式选择器优先级,确保覆盖通用 td 规则; 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * build(version): 升级版本号 0.3.1a9 → 0.3.1a10; 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * fix(dashboard): 优化 Recent Active Sessions 表格列显示,消除内容截断 (#223) - 重分配列宽:Session ID 22%→16%,Client 6%→10%,Vendor Bind 8%→10%, Models 14%→16%,Vendors 12%→14%,解决 Client/Vendor Bind 每行溢出问题 - 供应商标签去文本前缀,改用颜色区分 API(蓝)/CC(绿) 类型,节省约 40% 列宽 - Models/Vendors 列允许标签换行,解除全局 nowrap 约束 - Models 显示上限 2→3,"+N" 标签添加 title tooltip 显示完整列表 - Vendors 增加上限 4,超出部分 "+N" + tooltip 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) * fix(dashboard): 优化 Recent Active Sessions 表格显示与交互体验 (#224) * fix(dashboard): 优化 Recent Active Sessions 表格列显示,消除内容截断 - 重分配列宽:Session ID 22%→16%,Client 6%→10%,Vendor Bind 8%→10%, Models 14%→16%,Vendors 12%→14%,解决 Client/Vendor Bind 每行溢出问题 - 供应商标签去文本前缀,改用颜色区分 API(蓝)/CC(绿) 类型,节省约 40% 列宽 - Models/Vendors 列允许标签换行,解除全局 nowrap 约束 - Models 显示上限 2→3,"+N" 标签添加 title tooltip 显示完整列表 - Vendors 增加上限 4,超出部分 "+N" + tooltip 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * fix(dashboard): 优化 Session 表格显示 — 禁止换行、添加 Tooltip 与 Copy 按钮 - 移除 cell-tags 换行样式,所有单元格统一 nowrap + ellipsis,行高统一 48px - Session ID 列缩窄至 12%,添加 ⧉ Copy 按钮一键拷贝 Session Key - Models/Vendors 单元格添加 title tooltip,鼠标悬停显示完整标签列表 - 修复 Success 列 "..." 问题:添加 cell-success 类禁用 ellipsis(进度条复合内容) - 进一步优化列宽:Vendors 14%→12%、Client/Vendor Bind 10%→12%、Models 16%→17% 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * fix(dashboard): Avg Latency 列使用可读时长单位(ms/s/min)自动换算 - <1s 显示毫秒(如 234ms) - 1s~60s 显示秒(如 2.3s、15s) - >=60s 显示分钟+秒(如 2min 30s、5min) 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * feat(dashboard): 支持点击行展开详情卡片,显示完整字段内容 - 点击任意数据行展开/收起 detail-card(手风琴模式,仅展开一行) - 卡片以 grid 布局展示全部字段完整值:Session ID、Device、Account、 Models(全量标签)、Vendors(全量标签)、Avg Latency、Success Rate、Client - Session ID 列和 Vendor Bind 列点击事件隔离(stopPropagation), 避免误触展开 - 新增 toggleRow 函数,切换 .row-detail.open 类控制显隐 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * chore(gitignore): 添加 .playwright-mcp/ 到忽略规则 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * fix(dashboard): 优化 Session 表格显示与交互体验 (#225) * fix(dashboard): 优化 Recent Active Sessions 表格列显示,消除内容截断 - 重分配列宽:Session ID 22%→16%,Client 6%→10%,Vendor Bind 8%→10%, Models 14%→16%,Vendors 12%→14%,解决 Client/Vendor Bind 每行溢出问题 - 供应商标签去文本前缀,改用颜色区分 API(蓝)/CC(绿) 类型,节省约 40% 列宽 - Models/Vendors 列允许标签换行,解除全局 nowrap 约束 - Models 显示上限 2→3,"+N" 标签添加 title tooltip 显示完整列表 - Vendors 增加上限 4,超出部分 "+N" + tooltip 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * fix(dashboard): 优化 Session 表格显示 — 禁止换行、添加 Tooltip 与 Copy 按钮 - 移除 cell-tags 换行样式,所有单元格统一 nowrap + ellipsis,行高统一 48px - Session ID 列缩窄至 12%,添加 ⧉ Copy 按钮一键拷贝 Session Key - Models/Vendors 单元格添加 title tooltip,鼠标悬停显示完整标签列表 - 修复 Success 列 "..." 问题:添加 cell-success 类禁用 ellipsis(进度条复合内容) - 进一步优化列宽:Vendors 14%→12%、Client/Vendor Bind 10%→12%、Models 16%→17% 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * fix(dashboard): Avg Latency 列使用可读时长单位(ms/s/min)自动换算 - <1s 显示毫秒(如 234ms) - 1s~60s 显示秒(如 2.3s、15s) - >=60s 显示分钟+秒(如 2min 30s、5min) 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * feat(dashboard): 支持点击行展开详情卡片,显示完整字段内容 - 点击任意数据行展开/收起 detail-card(手风琴模式,仅展开一行) - 卡片以 grid 布局展示全部字段完整值:Session ID、Device、Account、 Models(全量标签)、Vendors(全量标签)、Avg Latency、Success Rate、Client - Session ID 列和 Vendor Bind 列点击事件隔离(stopPropagation), 避免误触展开 - 新增 toggleRow 函数,切换 .row-detail.open 类控制显隐 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * chore(gitignore): 添加 .playwright-mcp/ 到忽略规则 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * fix(dashboard): 修复 Copy 按钮导致 JS 报错、展开卡片样式不可读及版本号显示异常 - 将 copyText 内联值改为 copyFromParent 从 data-key 属性读取,避免 JSON 双引号在 onclick 中导致 JS 解析失败 - 优化展开卡片样式:渐变背景、蓝色边框、阴影、增大间距与圆角 - 页面初始化时立即加载版本号,修复直接进入 Sessions tab 时版本显示为 v-.-.- 的问题 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * style(dashboard): 重命名页签「Recent Active Sessions」为「Sessions」 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * style(dashboard): 增强 Success 列进度条底色对比度 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * style(dashboard): 将页签导航移至头部栏,优化为胶囊样式; (#226) 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) * fix(dashboard): 重构 Session 详情卡片布局,解决内容重叠问题; (#227) 将 Session 详情卡片的扁平网格布局重构为身份信息行 + 指标网格两层结构, 分离 Session ID/Device/Account 等身份字段与指标字段的展示区域, 新增 title 属性提供完整值悬浮提示,消除窄屏下的内容重叠与截断。 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) * style(dashboard): 优化 Session 详情卡片布局与信息展示; (#228) - 调整 identity-row flex 比例,首项 2 其余 3,改善空间分配 - 将 Success Rate 与 Client 合并为 inline-pair 横向排列,减少纵向高度 - 移除 Sessions 卡片冗余标题(Tab 已提供上下文) - 修复 success-bar-fill display 属性确保正确渲染 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) * fix(dashboard): 切换页签时自动刷新当前页数据; (#229) 移除 switchTab() 中的 tabLoaded 条件守卫,使每次在 Overview 与 Sessions 页签间切换时都触发 refresh(),而非仅首次访问时加载数据。 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) * fix(dashboard): 修复供应商状态卡片进度条显示异常,使用 quota-group 包裹配额元素确保折行一致性; (#230) 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) * style(dashboard): 收窄 KPI 卡片间隙至原来的 1/3; (#231) 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) * fix(dashboard): 修复 Session 详情卡片内容折行问题,强制单行显示; (#232) - 身份行(Session ID/Device/Account)长值不再折行,超长部分以省略号截断 - 指标网格改为固定 8 列布局,确保所有指标在一行中均匀排列 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) * chore(release): 发布 v0.4.0 版本; 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 20f5201 commit 403a471

30 files changed

Lines changed: 2065 additions & 1710 deletions

‎.gitignore‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,8 @@ config.yaml
2323
.claude/.prompts.md
2424
.python-version
2525

26+
# Playwright MCP
27+
.playwright-mcp/
28+
2629
# Log files (dual-write logging)
2730
coding-proxy.log*

‎AGENTS.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
1. **Python**: 严禁使用 pip/poetry,**必须**统一使用 `uv` 进行包管理与脚本执行(如 `uv run`);
5353
2. **JavaScript/TypeScript**: 严禁使用 npm/yarn,**必须**统一使用 `pnpm` 进行包管理与脚本执行。
5454
- **Database Management**: 谨慎操作,数据迁移、测试等操作严禁将现有数据删除,谨慎操作数据迁移的回滚,防止数据被清理。
55+
- **In-depth and close to the facts**:系统且全面地进行问题的分析,深入贴近事实,如有疑问,需先发问,不要乱做决定。
5556

5657
## Documentation Standards (文档规范)
5758

‎CHANGELOG.md‎

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,27 @@
44

55
## [Unreleased]
66

7-
- fix(vendor-channels): 新增 zhipu 同 vendor 自清理通道,修复 GLM-5 自循环 400 + tool_results 偶发降级;
8-
- fix(vendor-channels): 修复 `_rewrite_srvtoolu_ids` 块顺序敏感性导致 inline tool_result 漏改名,进而 enforce 阶段 dict key 与 tool_use_ids 错位、anthropic 报 `tool_use ids without tool_result blocks immediately after` 的 cascade failover 问题(改为两遍扫描:先收集 id_map,再统一改写所有 tool_result.tool_use_id 引用);
9-
- fix(vendor-channels): `enforce_anthropic_tool_pairing` 增加全局 sanity check pass,主循环边角错位让 dangling tool_use 漏过校验时兜底合成 is_error 占位并打 `pairing_sanity_repaired` 标签,避免 anthropic 二次报错;
7+
## [v0.4.0](https://github.com/ThreeFish-AI/coding-proxy/releases/tag/v0.4.0) — 2026-05-01
108

11-
### Bug Fixes
9+
> [!IMPORTANT]
10+
>
11+
> **🚀 Session 级专属路由策略!**
12+
>
13+
> 给每个 Session 指定专属的 vendor,动态调节不同 vendors 间的 LLM 流量。
14+
15+
![session](assets/session-v0.4.0.png)
16+
17+
### ✨ 核心亮点
18+
19+
- feat(session-policy): 新增 Session 级专属路由策略 (#219)
20+
- feat(dashboard): 新增会话活动面板 (#222)
21+
22+
### 🔧 更多特性
1223

13-
- fix(vendor-channels): 新增 `anthropic → zhipu` 跨供应商转换通道,修复 Anthropic beta 功能(web search, computer use)产生的 `server_tool_use` 块导致 zhipu 400 错误的问题;
14-
- fix(error-classifier): 增强语义拒绝检测,识别 zhipu 等供应商返回的中文错误消息(如「API 调用参数有误」code=1210),确保正确触发故障转移;
15-
- fix(vendor-channels): `_remove_vendor_blocks` 增加空内容占位保护,防止内容块全部剥离后消息结构不合法。
24+
- refactor(logging): 移除已被 ModelCall 汇总行覆盖的冗余 DEBUG 日志 (#203)
25+
- style(dashboard): 加宽图表 tooltip 令模型名称与用量值单行显示 (#211)
26+
- fix(usage-parser): 补充 OpenAI/Gemini SSE 流式分支的 model_served 提取 (#214)
27+
- fix(usage-parser): 兼容 SSE chunk 中 usage 字段为 null 的极端格式 (#212)
1628

1729
## [v0.3.0](https://github.com/ThreeFish-AI/coding-proxy/releases/tag/v0.3.0) — 2026-04-20
1830

‎assets/session-v0.4.0.png‎

377 KB
Loading

‎docs/issue.md‎

Lines changed: 0 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -45,99 +45,3 @@ if "usage" in data: # 仅判断 key 存在
4545

4646
- 本仓库内 `parse_usage_from_chunk` 的 Gemini `usageMetadata` 分支 (line ~219) 已经使用 `isinstance(um, dict)` 防御, 不受影响, 可作为参考实现。
4747
- 检查其他解析器 (如 routing / vendor adapter 层) 是否还有 `if "key" in data: v = data["key"]; v.get(...)` 这种模式, 必要时同步加固。
48-
49-
---
50-
51-
## zhipu 自循环 400 + tool_results 偶发降级
52-
53-
**问题描述**
54-
55-
生产日志反复出现下述链路: 请求一开始命中 zhipu 主 tier, 但在含 `tool_results` 的多轮工具调用场景下偶发返回 400, 触发到 copilot 二级 tier。具体日志特征:
56-
57-
```
58-
WARNING Tier zhipu likely format incompatibility (400 + tool_results), trying next tier without recording failure
59-
WARNING Tier zhipu semantic rejection (400), trying next tier without recording failure
60-
DEBUG Applied transition channel zhipu → copilot: rewritten_38_srvtoolu_ids, stripped_16_thinking_blocks, removed_3_cache_control_fields, misplaced_tool_result_relocated
61-
```
62-
63-
zhipu → copilot 通道的 adaptations 列表暴露了上一轮 zhipu 响应中存在的非标准产物 (`srvtoolu_*` ID、自签 thinking、错位的 `tool_result`)。
64-
65-
**表因**
66-
67-
zhipu 自身偶发返回 400, 但错误体非 JSON 结构, 由 `_is_likely_request_format_error()` 判定为「格式不兼容」并跳过当前 tier。
68-
69-
**根因**
70-
71-
1. zhipu 是 `NativeAnthropicVendor` 薄透传供应商, **不做任何请求体预处理**。
72-
2. `executor._determine_source_vendor` 三条优先级路径均以 `source != target_name` 过滤掉了同 vendor 自转换。
73-
3. `VENDOR_TRANSITIONS` 注册表中无 `("zhipu", "zhipu")` 条目。
74-
75-
后果: GLM-5 偶发产出非标准产物 (assistant 内联 `tool_result`、`server_tool_use_delta` 流式残块) 后, Claude Code 把这些产物原样回送下一轮请求时, **没有任何清洗发生**, 直接被转发到 zhipu 自身, 命中 zhipu 端的输入校验返回 400。
76-
77-
**处理方式**
78-
79-
- 在 `vendor_channels.py` 新增 `prepare_zhipu_self_cleanup` 函数, 仅修复 zhipu 自身拒绝的两类产物:
80-
1. 剥离 `server_tool_use_delta` 流式残块
81-
2. `enforce_anthropic_tool_pairing` 把 assistant 内联 `tool_result` 重定位到紧随 user 消息
82-
- 显式 **保留** zhipu 原生支持的特性: `srvtoolu_*` ID、`server_tool_use` 类型、自签 thinking signature、`cache_control` (cache_read 已在生产实证)、顶层 `thinking` 参数。
83-
- 在 `VENDOR_TRANSITIONS` 注册 `("zhipu", "zhipu") = prepare_zhipu_self_cleanup`。
84-
- 在 `executor._determine_source_vendor` 三条优先级路径中, 把「`source != target`」过滤替换为「通道已注册」门控 (`get_transition_channel(...) is not None`), 让自转换通道在显式注册时启用, 未注册时退化为原行为。
85-
86-
**后续防范**
87-
88-
- 新增 `NativeAnthropicVendor` 子类 (minimax / kimi / doubao / xiaomi / alibaba 等) 时, 若上游 vendor 偶发产出违反 Anthropic 规范的产物, 可按需注册同名自清理通道, executor 无需任何额外改动。
89-
- 同 vendor 自转换通道应**精确剪裁**: 仅修复 vendor 自身拒绝的产物, 不要套用跨 vendor 通道的全量清理 (会误伤 vendor 原生支持的特性, 如 cache_control 损失带来 cache_read miss)。
90-
91-
**同类问题影响与处理注意事项**
92-
93-
- `enforce_anthropic_tool_pairing` 仅识别 `tool_use` 类型 (不含 `server_tool_use`), 因为 `server_tool_use` 由 vendor 自身执行, 不需要客户端 `tool_result`。构造测试或类似清洗逻辑时需注意此差别。
94-
- `_is_likely_request_format_error()` 把「400 + tool_results + 非结构化错误体」一律标记为格式不兼容并跳过 tier 不计熔断器, 这层兜底虽能维持可用性但会**掩盖** vendor 自身的间歇性问题, 让根因更难发现。处理类似 400 偶发时, 应优先看 `Applied transition channel` 日志中的 adaptations 列表, 它能精确暴露上游响应中的非标准产物。
95-
96-
---
97-
98-
## anthropic 报 messages.X tool_use 缺 tool_result (zhipu→anthropic 故障转移失败)
99-
100-
**问题描述**
101-
102-
zhipu 完成响应后, executor 故障转移至 anthropic 时反复失败 (HTTP 400):
103-
104-
```
105-
DEBUG Applied transition channel zhipu → anthropic: rewritten_86_srvtoolu_ids, misplaced_tool_result_relocated, stripped_18_thinking_blocks
106-
WARNING anthropic stream error: status=400 ... messages.3: `tool_use` ids were found without `tool_result` blocks immediately after: toolu_normalized_3.
107-
INFO Failover: anthropic → zhipu (reason: HTTP 400)
108-
```
109-
110-
adaptations 列表显示 `misplaced_tool_result_relocated` 但**没有** `orphaned_tool_use_repaired`, 即 enforce 单遍扫描视角下认为所有 tool_use 都已配对; 但 anthropic 仍报 messages.X 缺 tool_result, 导致请求级 cascade failover 反复回到 zhipu。
111-
112-
**表因**
113-
114-
`prepare_zhipu_to_anthropic` 链路输出的请求体中, 某个 assistant 的 `tool_use` 在紧邻的 user 消息中没有匹配的 `tool_result` 块。
115-
116-
**根因**
117-
118-
`_rewrite_srvtoolu_ids` 采用单遍正向扫描: 在同一次循环中一边收集 srvtoolu_* → toolu_normalized_* 的 id_map, 一边改写遇到的 `tool_result.tool_use_id`。GLM-5 流式偶发将 inline tool_result 输出在本消息 `server_tool_use` 之前 (block 顺序异常), 导致:
119-
120-
1. 处理 inline tool_result 时, id_map 尚未填入对应 srvtoolu_* → 漏改名, inline 仍保留 `srvtoolu_X`
121-
2. 处理本消息 server_tool_use 时, 填入 id_map 并把 tool_use 改名为 `toolu_normalized_X`
122-
3. 进入 `enforce_anthropic_tool_pairing` 时:
123-
- A 步 extracted dict key = `srvtoolu_X` (inline 保留的旧 ID)
124-
- B 步 tool_use_ids = `[toolu_normalized_X]` (已改名)
125-
- F 步 `uid in extracted` 检查失败 (key 错位), 但若 next user 已含其他 stale tool_result 让 existing_result_ids "巧合" 命中, F 步会跳过 synth → 不触发 orphan 标签
126-
- 最终 anthropic 看到 messages.X 真的缺 toolu_normalized_X 的 tool_result → 400
127-
128-
**处理方式**
129-
130-
- `_rewrite_srvtoolu_ids` 改为**两遍扫描**: Pass 1 仅遍历 assistant 消息, 收集 id_map 并改写 tool_use 自身的 id 与 type; Pass 2 全量遍历所有消息 (含 user / 异常 assistant 内联), 统一改写所有 `tool_result.tool_use_id` 引用。彻底消除 block 顺序敏感性。
131-
- `enforce_anthropic_tool_pairing` 主循环结束后追加**全局 sanity check pass**: 重新遍历每条 assistant, 验证其 tool_use_ids 全部在 next user 的 tool_result 中存在; 发现遗漏直接合成 is_error 占位并打 `pairing_sanity_repaired` 标签。作为防御深度抵御未来其他主循环边角错位。
132-
- A 步对 `tool_use_id` 缺失的破损 inline tool_result 也计入 relocated_count (避免 silent drop 影响 adaptations 标签可观测性)。
133-
134-
**后续防范**
135-
136-
- 任何"按出现顺序填充字典 + 同遍引用查询"的两阶段操作都应警惕**顺序耦合**问题。两遍扫描 (collect → apply) 是消除此类 bug 的标准 pattern。
137-
- 关键校验函数应有**主循环 + 全局 sanity check** 的双层结构, 单层校验在边角场景下容易被绕过。
138-
- 处理 anthropic `tool_use ids without tool_result blocks immediately after` 类 cascade failover 时, **adaptations 标签能否复现日志**是定位 root cause 的强信号: 若 enforce 视角与 anthropic 视角不一致 (有 misplaced 但无 orphan, anthropic 仍报错), 必有上游 _rewrite / id 改写阶段的隐藏漏洞。
139-
140-
**同类问题影响与处理注意事项**
141-
142-
- 任何对 messages 进行 ID 重写的转换链 (如 `_rewrite_srvtoolu_ids`、`anthropic_to_openai`、`anthropic_to_gemini`) 都应使用两遍扫描或一次性收集后再批量改写, 以保证 block 顺序无关性。
143-
- enforce 类校验函数若依赖 dict key 与 list 元素的**等同性**, 必须先确保两者在同一参考系下 (改名前 vs 改名后); 否则错位会以 "看起来 OK 实际有漏" 的方式静默泄漏到下游。

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "coding-proxy"
3-
version = "0.3.1a3"
3+
version = "0.4.0"
44
description = "A High-Availability, Transparent, and Smart Multi-Vendor Proxy for Claude Code. Support Claude Plans, GitHub Copilot, Google Antigravity, ZAI/GLM, MiniMax, Qwen, Xiaomi, Kimi, Doubao..."
55
readme = "README.md"
66
requires-python = ">=3.12"

‎src/coding/proxy/cli/__init__.py‎

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
# 注册 Auth 子应用
3131
app.add_typer(auth_app, name="auth")
3232

33+
# 注册 Session 子应用
34+
session_app = typer.Typer(name="session", help="管理 Session-Vendor 运行时绑定")
35+
app.add_typer(session_app, name="session")
36+
3337

3438
def _build_token_store(cfg_path: Path | None = None):
3539
"""按配置解析 Token Store 路径并完成加载."""
@@ -264,6 +268,97 @@ def reset(
264268
console.print("[red]代理服务未运行[/red]")
265269

266270

271+
# ── Session 子命令 ───────────────────────────────────────────────
272+
273+
274+
@session_app.command("bind")
275+
def session_bind(
276+
key: str = typer.Option(..., "--key", "-k", help="Session key"),
277+
vendor: str = typer.Option(
278+
..., "--vendor", "-v", help="绑定 vendor(逗号分隔多个)"
279+
),
280+
port: int = typer.Option(3392, "--port", "-p", help="代理服务端口"),
281+
) -> None:
282+
"""为指定 Session 绑定 vendor 优先级."""
283+
import httpx as _httpx
284+
285+
vendors = [v.strip() for v in vendor.split(",") if v.strip()]
286+
try:
287+
resp = _httpx.put(
288+
f"http://127.0.0.1:{port}/api/session-vendor",
289+
json={"session_key": key, "vendors": vendors},
290+
timeout=5,
291+
)
292+
if resp.status_code == 200:
293+
data = resp.json()
294+
console.print(
295+
f"[green]绑定成功:[/] session [cyan]{key[:16]}…[/cyan] → "
296+
+ " → ".join(data.get("vendors", vendors))
297+
)
298+
else:
299+
try:
300+
err = resp.json()
301+
msg = err.get("error", {}).get("message", resp.text)
302+
except Exception:
303+
msg = resp.text
304+
console.print(f"[red]绑定失败: {msg}[/red]")
305+
except _httpx.ConnectError:
306+
console.print("[red]代理服务未运行[/red]")
307+
308+
309+
@session_app.command("unbind")
310+
def session_unbind(
311+
key: str = typer.Option(..., "--key", "-k", help="Session key"),
312+
port: int = typer.Option(3392, "--port", "-p", help="代理服务端口"),
313+
) -> None:
314+
"""解除指定 Session 的 vendor 绑定."""
315+
from urllib.parse import quote
316+
317+
import httpx as _httpx
318+
319+
try:
320+
resp = _httpx.delete(
321+
f"http://127.0.0.1:{port}/api/session-vendor/{quote(key, safe='')}",
322+
timeout=5,
323+
)
324+
if resp.status_code == 200:
325+
console.print(f"[green]已解除绑定:[/] session [cyan]{key[:16]}…[/cyan]")
326+
elif resp.status_code == 404:
327+
console.print(f"[yellow]未找到绑定:[/] session [cyan]{key[:16]}…[/cyan]")
328+
else:
329+
console.print(f"[red]解除失败: {resp.status_code} {resp.text}[/red]")
330+
except _httpx.ConnectError:
331+
console.print("[red]代理服务未运行[/red]")
332+
333+
334+
@session_app.command("list")
335+
def session_list(
336+
port: int = typer.Option(3392, "--port", "-p", help="代理服务端口"),
337+
) -> None:
338+
"""列出所有运行时 Session-Vendor 绑定."""
339+
import httpx as _httpx
340+
341+
try:
342+
resp = _httpx.get(
343+
f"http://127.0.0.1:{port}/api/session-vendor",
344+
timeout=5,
345+
)
346+
if resp.status_code == 200:
347+
data = resp.json()
348+
bindings = data.get("bindings", [])
349+
if not bindings:
350+
console.print("[dim]当前无运行时绑定[/dim]")
351+
return
352+
for b in bindings:
353+
key = b.get("session_key", "?")
354+
vendors = b.get("vendors", [])
355+
console.print(f" [cyan]{key[:24]}…[/cyan] → " + " → ".join(vendors))
356+
else:
357+
console.print(f"[red]查询失败: {resp.status_code} {resp.text}[/red]")
358+
except _httpx.ConnectError:
359+
console.print("[red]代理服务未运行[/red]")
360+
361+
267362
def _resolve_config_path(config: str | Path | None = None) -> Path | None:
268363
"""标准化配置路径输入."""
269364
if config is None:

‎src/coding/proxy/config/config.default.yaml‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,3 +644,31 @@ native_api:
644644
base_url: "https://api.anthropic.com"
645645
timeout_ms: 300000
646646
connect_timeout_ms: 15000
647+
648+
# === Session 级别路由策略(可选)===
649+
#
650+
# 为特定 Session 或客户端类别定制 vendor 优先级顺序。
651+
# 匹配策略按定义顺序求值,首次匹配生效。
652+
#
653+
# 支持的匹配条件(OR 语义,满足任一即匹配):
654+
# session_keys: 精确匹配的 session key 列表
655+
# client_category: 按客户端类别匹配(⚠️ 预留字段,当前版本暂未生效,后续版本支持)
656+
#
657+
# tiers: 覆盖全局 tier 顺序的供应商优先级列表(未提及的 vendor 保持在末尾)
658+
#
659+
# 示例 1:为特定 session 绑定专属 vendor 组合
660+
# session_policies:
661+
# - name: "vip-session"
662+
# match:
663+
# session_keys: ["my-specific-session-id"]
664+
# tiers: ["anthropic", "copilot", "zhipu"]
665+
#
666+
# 示例 2:Claude Code 请求优先走 Copilot
667+
# session_policies:
668+
# - name: "claude-code-preferred"
669+
# match:
670+
# client_category: "cc"
671+
# tiers: ["copilot", "anthropic", "zhipu"]
672+
#
673+
# 未配置时(默认),所有 Session 使用全局 tiers 顺序。
674+
session_policies: []

‎src/coding/proxy/config/schema.py‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444

4545
# ── 子模块 re-export ────────────────────────────────────────────
4646
from .server import DatabaseConfig, LoggingConfig, ServerConfig # noqa: F401
47+
from .session_policy import SessionPoliciesConfig # noqa: F401
4748
from .vendors import ( # noqa: F401
4849
AlibabaConfig,
4950
AnthropicConfig,
@@ -152,11 +153,19 @@ class ProxyConfig(BaseModel):
152153
"三个 provider 默认 enabled=False,显式启用才暴露 /api/{provider}/* 端点。"
153154
),
154155
)
156+
# Session 级别路由策略
157+
session_policies: SessionPoliciesConfig = Field(
158+
default_factory=SessionPoliciesConfig,
159+
description=(
160+
"Session 级别的路由策略配置。"
161+
"可为特定 Session 或客户端类别定制 vendor 优先级顺序。"
162+
),
163+
)
155164

156165
@model_validator(mode="before")
157166
@classmethod
158167
def _migrate_legacy_fields(cls, data: Any) -> Any:
159-
"""向后兼容迁移(legacy flat 格式 → vendors 列表格式).
168+
"""向后兼容迁移(legacy flat 格式 → vendors 列表格式)+ session_policies 规范化.
160169
161170
迁移规则:
162171
1. ``anthropic`` / ``zhipu`` 字段名自动映射为 ``primary`` / ``fallback``
@@ -165,6 +174,12 @@ def _migrate_legacy_fields(cls, data: Any) -> Any:
165174
if not isinstance(data, dict):
166175
return data
167176

177+
# session_policies 规范化:YAML 中 session_policies: [] 解析为 list,
178+
# 需转为 dict 包装以匹配 SessionPoliciesConfig 模型
179+
sp = data.get("session_policies")
180+
if isinstance(sp, list):
181+
data["session_policies"] = {"policies": sp}
182+
168183
# 1. 字段别名迁移
169184
if "anthropic" in data and "primary" not in data:
170185
data["primary"] = data.pop("anthropic")
@@ -331,4 +346,6 @@ def compat_state_path(self) -> Path:
331346
"AlibabaConfig",
332347
# native api passthrough
333348
"NativeApiConfig",
349+
# session policy
350+
"SessionPoliciesConfig",
334351
]

0 commit comments

Comments
 (0)