Fix Issue #104 thinking prepender wiring - #107
Open
Python382clang11 wants to merge 115 commits into
Open
Python382clang11 wants to merge 115 commits into
Python382clang11 wants to merge 115 commits into
Conversation
…th.json
- GenerateConfigToml 不再写 env_key 或 experimental_bearer_token
- 启用 requires_openai_auth 时同时写入 codex_home/auth.json
- 新增 writeAuthJSON 辅助函数写入 {"openai_api_key": "..."}
- config.example.yml 新增 server.auth_token 文档
- 清理脚本和文档中的 MOONBRIDGE_CLIENT_API_KEY 环境变量
- 修复 TestAuthAcceptsValidBearerToken 将 AuthToken 设在错误对象的问题
---
refactor(codex): migrate Codex auth from env_key to requires_openai_auth + auth.json
- GenerateConfigToml no longer writes env_key or experimental_bearer_token
- When requires_openai_auth is set, also writes codex_home/auth.json
- Add writeAuthJSON helper to write {"openai_api_key": "..."}
- Document server.auth_token in config.example.yml
- Remove MOONBRIDGE_CLIENT_API_KEY env var from scripts and docs
- Fix TestAuthAcceptsValidBearerToken setting AuthToken on wrong config object
- 删除 ConfigTypeProvider(被 ExtensionConfigSpec.Factory 取代) - 删除 loadPluginConfigFiles / mergePluginConfig / decodePluginConfig / isYAMLFile / pluginsFromFileConfig / PluginConfig 方法和字段 - Registry.InitAll 不再要求 PluginConfig 接口 - schema.go 移除 plugins/ 目录循环,4 层级扩展类型注入主 schema - plugins/deepseek_v4.example.yml 已删除(改为内联 extensions:) - docker-compose 移除 plugins 只读挂载 - 清理关联测试
- ModelMeta/RouteEntry 新增 InputModalities 和 SupportsImageDetailOriginal 字段 - 新增 input_modalities / supports_image_detail_original YAML 配置 - 开启 visual 插件的模型自动注入 "image" 模态 - 新增 catalog_test.go 多模态传播与默认值测试 - config.example.yml 添加 Claude Sonnet 4 多模态示例 --- ✨ feat(codex): add multimodal model support to models_catalog - Add InputModalities and SupportsImageDetailOriginal to ModelMeta/RouteEntry - Add input_modalities / supports_image_detail_original YAML fields - Auto-inject "image" modality for visual-extension-enabled models - Add multimodal propagation and default-value tests - Add Claude Sonnet 4 multimodal example to config.example.yml
…, gofmt - Registry.InitAll: appCfg any → InitConfigProvider 接口,移除死代码 - injectVisualModalities: 追加 "image" 而非覆盖已有模态 - schema: extractTypeDef 按 $ref 名称精确查找;先构建 defs 再注入 properties - gofmt 对齐 5 个文件 --- 🦄 refactor: GPT-5.5 review fixes — InitConfigProvider, extractTypeDef, gofmt - Registry.InitAll: replace any with InitConfigProvider interface, remove dead code - injectVisualModalities: append "image" instead of overwriting existing modalities - schema: extractTypeDef by $ref name; build defs before injecting properties - gofmt alignment across 5 files
…ties + SQLite metrics persistence - Add RequestCompletionHook interface for post-request observability hooks - Add RouteRegistrar interface for plugin-mounted HTTP endpoints - Implement SQLite-backed metrics store (internal/service/metrics/) - Implement metrics extension plugin with OnRequestCompleted + GET /v1/admin/metrics - Wire completion hooks into handleResponses, handleStream, handleOpenAIResponse with defer-based error coverage - Register metrics extension in builtin catalog - Update config.example.yml with metrics configuration (opt-in) --- ✨ feat(plugin): 新增 RequestCompletionHook 和 RouteRegistrar 能力 + SQLite metrics 持久化 - 新增 RequestCompletionHook 接口用于请求后 observability 钩子 - 新增 RouteRegistrar 接口用于插件挂载 HTTP 端点 - 实现 SQLite 指标存储层 (internal/service/metrics/) - 实现 metrics 扩展插件,支持 OnRequestCompleted + GET /v1/admin/metrics - 在 handleResponses/handleStream/handleOpenAIResponse 三条路径接入 completion hook,OpenAI 直通使用 defer 统一收口错误路径 - 将 metrics 扩展注册到内置插件目录 - 更新 config.example.yml 添加 metrics 配置(默认关闭)
…te/D1 providers
- Add internal/foundation/db/ abstraction: Provider/Consumer/Store/Registry with table-name isolation via {consumer}_{table} prefix
- Add DBProvider/DBConsumer capabilities to plugin system
- Implement SQLite provider plugin (extension/db/sqlite/, modernc.org/sqlite, WASM-excluded via build tag)
- Implement D1 provider plugin (extension/db/d1/, injectable *sql.DB, no direct cloudflare SDK import)
- Migrate metrics extension to DBConsumer, remove old internal/service/metrics/ store
- Wire db.Registry into app.go and cloudflare/main.go with active_provider config
- Add persistence.active_provider config, update config.example.yml
- Full test coverage: 30 registry tests, provider tests, all 28 packages pass
---
🦄 refactor(foundation): 实现通用持久化抽象层,支持 SQLite/D1 Provider
- 新增 internal/foundation/db/ 抽象层:Provider/Consumer/Store/Registry,表名通过 {consumer}_{table} 前缀隔离
- 插件系统新增 DBProvider/DBConsumer 能力接口
- 实现 SQLite Provider 插件(extension/db/sqlite/,modernc.org/sqlite,WASM 通过 build tag 排除)
- 实现 D1 Provider 插件(extension/db/d1/,注入式 *sql.DB,不直接 import cloudflare SDK)
- 迁移 metrics 扩展为 DBConsumer,删除旧 internal/service/metrics/ 存储层
- 在 app.go 和 cloudflare/main.go 中接入 db.Registry,支持 active_provider 配置
- 新增 persistence.active_provider 配置,更新 config.example.yml
- 完整测试覆盖:30 个 registry 测试、provider 测试,28 个 package 全部通过
- 在 handleResponses 中新增前置校验:模型不在 Routes 且不是 provider/model 直写时,立即返回 404 model_not_found - 清理 handleOpenAIResponse 中不再可达的 providerKey 空值回退代码 - 修复 3 个因新校验而失败的测试用例 --- 🐞 fix(server): return 404 for models without routes instead of silent fallthrough - Add early guard in handleResponses: return 404 model_not_found when model is not in Routes and not a direct provider/model ref - Remove dead fallback code in handleOpenAIResponse that was previously handling empty providerKey - Fix 3 test cases that relied on the old fallthrough behavior
- handleResponses 非流式 Provider 调用失败路径补 logger.Flush() - handleOpenAIResponse 上游请求失败路径补 logger.Flush() - 与 handleStream 中已有的 flush 模式保持一致 --- 🐞 fix(server): add missing logger.Flush() in error paths to prevent log buffer growth - Add logger.Flush() in handleResponses non-stream provider failure path - Add logger.Flush() in handleOpenAIResponse upstream request failure path - Consistent with existing flush pattern in handleStream
- Stop appending (provider) suffix to route DisplayName - Auto-format route DisplayName from slug: gpt-x-suffix -> GPT x Suffix - Add DisplayNameFromSlug helper with ASCII-safe GPT prefix detection - Generate both model(provider) and provider/model slugs in catalog - Remove provider/model -> model(provider) conversion in GenerateConfigToml - Stop copying upstream model DisplayName/Description into route config
兼容 Anthropic-compatible Provider 在 message_start 或 message_delta 返回 cache usage 的差异,确保 OpenAI cached_tokens 和缓存命中率正确。 Support Anthropic-compatible providers that report cache usage in either message_start or message_delta so OpenAI cached_tokens and cache hit rate stay accurate.
Track provider raw telemetry separately from normalized response usage in metrics, and move session stats billing to explicit provider billing dimensions. Tests cover Anthropic stream/non-stream, OpenAI Responses body/SSE usage, metrics persistence, and cache-only stream delta handling.
- Remove dual-slug emission (model(provider) + provider/model) in BuildModelInfosFromConfig that caused every model to appear twice in models_catalog.json - Normalize direct provider/model references to model(provider) format in GenerateConfigToml so Codex can match catalog metadata - Add test coverage for slug normalization edge cases - Update stale docs referencing provider/upstream_model format
- Move trace output from trace/ to data/trace/ via DefaultRoot - Add /data/ to .gitignore, keep /trace/ as legacy ignore - Update docker-compose volume mount to ./data:/app/data - Sync trace paths in README, architecture docs, and tests --- 🦄 refactor: 将运行时产物统一收敛到 data/ 目录 - trace 输出从 trace/ 迁移至 data/trace/(DefaultRoot) - .gitignore 新增 /data/,保留 /trace/ 作为遗留忽略 - docker-compose 卷挂载更新为 ./data:/app/data - 同步更新 README、architecture 文档和测试中的 trace 路径
- 重写 collectToolUsesFromEvents,按 event.Index 从 content_block_delta 的 input_json_delta.partial_json 分片组装完整 tool_use.Input - 新增 collectContentFromEvents 重建所有 content block(text/thinking/signature/tool_use) - 修复 StreamMessage 下一轮 assistant content 丢失非工具 block 的问题 - 新增 streamContentBlock 辅助类型 - 补充 9 个单元测试:分片 JSON、多工具、thinking+signature、staticStream 遍历 --- 🐞 fix(websearch): fix streaming tavily_search injection failure — assemble partial JSON and preserve non-tool blocks - Rewrite collectToolUsesFromEvents to assemble complete tool_use.Input from content_block_delta input_json_delta.partial_json fragments by event.Index - Add collectContentFromEvents to reconstruct all content blocks (text/thinking/signature/tool_use) - Fix StreamMessage next-round assistant content dropping non-tool blocks - Add streamContentBlock helper type - Add 9 unit tests covering partial JSON, multi-tool, thinking+signature, staticStream drain
Add a developer-facing script that fetches request-level metrics from MoonBridge /v1/admin/metrics API and renders three Rich tables: Usage (token volume & cost), Cache Analysis (hit/read/write rates), and SLA (unit cost, latency, error rate). Groups by actual_model (upstream model ID) rather than request slug. --- ✨ feat(metrics): 新增指标分析脚本 新增开发者工具脚本,从 MoonBridge /v1/admin/metrics API 拉取请求级 指标数据,渲染三张 Rich 表格:Usage(Token 用量与费用)、Cache Analysis(命中/读写率)、SLA(单位成本、延迟、错误率)。 按 actual_model(上游真实模型 ID)而非请求 slug 聚合。
…yBuffer Migrate all request-level bypass logs (fmt.Fprintln → MemoryBuffer) to structured slog attrs. Remove buffer.go, Output(), Flush(), Buffer(), SetConsumeFunc(). Plugin LogConsumer interface retained (SUSPENDED). Breaking: per-request session summary no longer printed on each request; logger.Output()/Flush()/Buffer()/SetConsumeFunc() removed. --- 🦄 refactor(logging): 统一请求日志到 slog 结构化路径,移除 MemoryBuffer 将所有请求级 bypass 日志(fmt.Fprintln → MemoryBuffer)迁移到 slog 结构化 attrs。移除 buffer.go、Output()、Flush()、Buffer()、 SetConsumeFunc()。插件 LogConsumer 接口保留类型(SUSPENDED)。 Breaking: 每请求不再输出会话 summary;logger.Output()/Flush()/ Buffer()/SetConsumeFunc() 不再可用。
Add consumeHandler that wraps slog handlers and dispatches every log record through a ConsumeFunc pipeline, restoring plugin LogConsumer data flow without re-introducing MemoryBuffer. Key design: consumeFuncHolder shared via pointer ensures WithAttrs/ WithGroup derived loggers see later SetConsumeFunc calls. Handler- level attrs (from With) and groups (from WithGroup) are merged into LogEntry before dispatching to consumers. --- ✨ feat(logger): 通过 slog.Handler 适配层恢复 LogConsumer 管线 新增 consumeHandler 包装层,将每条 slog 日志通过 ConsumeFunc 分发 给插件 LogConsumer,恢复数据流的同时不引入 MemoryBuffer。 核心设计:consumeFuncHolder 通过指针共享,确保 WithAttrs/WithGroup 派生 logger 能看到后续的 SetConsumeFunc 调用。Handler 级 attrs 和 groups 在分发前合并到 LogEntry。
- 更新 logger 组件说明,补充 consumeHandler 包装层描述 --- 📃 docs(architecture): sync logger description with consumeHandler impl - Update logger component description to mention consumeHandler wrapper layer
解决 5 处冲突:config.example.yml, docker-compose.example.yml, docs/api.md, catalog_test.go, config_loader.go - config.example.yml: 更新 Kimi base_url 为 api.kimi.com 并保留注释 - docker-compose.example.yml: 合并 data/logs/trace volumes - docs/api.md: 采用 dev 完整 metrics 字段定义,保留 main 的开发中标记 - catalog_test.go: 保留 dev 的 DisplayNameFromSlug 测试 - config_loader.go: 保留 dev 的 Persistence 配置结构 --- 🔀 merge(main): merge main branch into dev Resolved 5 conflicts: config.example.yml, docker-compose.example.yml, docs/api.md, catalog_test.go, config_loader.go
- 将 staticStream.Next() 的 fmt.Errorf("EOF") 改为标准 io.EOF,
消除非 sentinel EOF 的源头
- 增大 bufio.Scanner buffer 至 4MB,防止 DeepSeek 巨形 SSE 行
触发 token too long 断流
- 在 4xx 上游错误日志中增加 req_messages/req_tools/req_max_tokens
诊断字段
- 在 sseStream.Next() 和 handleStream() 中增设 "EOF" 字符串
fallback 检查
Dynamic model routing (plan 202604302200): - ProviderManager: reverse index, ResolveModel() 3-tier priority routing, ProviderCandidate/ResolvedRoute structs, fallback chain - Server: replace bridge.ProviderFor() with ResolveModel, streaming & non-streaming fallback, request-feature candidate filtering - Catalog: deduplicate models across providers, pure slug output - OpenAI Responses pass-through: multi-candidate fallback - Provider priority: providers.<key>.priority field for fallback order Config schema redesign (plan 202605010000): - Separate model definitions (models.*) from provider offers (providers.*.offers), no more per-provider model metadata duplication - Remove 6 legacy single-provider fields (ProviderBaseURL/APIKey/ Version/UserAgent/Protocol) and all bridging code - Flatten developer.proxy.* → proxy.*, trace_requests → trace.enabled - Add scripts/migrate_config_v5.py for v4→v5 migration - File naming convention added to AGENTS.md workspace rules
- Remove TraceRequests from FileConfig struct, handle backward compat via UnmarshalYAML (fixes schema requiring both trace and trace_requests) - Add json tags to all FileConfig sub-structs for proper jsonschema omitempty support (fixes 24 VSCode lint errors) - Migration script: create minimal models entries for route-only models (gpt-5.4, gpt-5.3-codex, gpt-5.2) that lacked provider model defs
- OfferEntry/OfferFileConfig: add Priority field (0=highest) - ProviderDef/ProviderDefFileConfig: remove Priority field - modelProviders map type changed from []string to []modelProviderEntry carrying priority alongside providerKey - ResolveModel sorts candidates by offer.Priority instead of provider.Priority - Migration script: transfer provider-level priority to all its offers
Backend infrastructure: - ConfigStore interface + SQLite implementation (db.Consumer pattern) - Config serialization layer (Config <-> FileConfig bidirectional) - Runtime/ConfigSnapshot with atomic.Pointer for hot reload - Staged change model (StageChange -> ApplyPendingChanges -> Runtime.Reload) - ProviderManager.Reload with atomic field swap - Schema migration tracking API endpoints (34 total, prefix /api/v1): - Provider CRUD + connectivity test + per-model offer management - Model CRUD with reference integrity checks - Route CRUD - Settings (defaults, web_search, extensions) - Config (effective/export/import/validate) - Changes (list/apply/discard) with transactional staging - Status, stats, sessions, logs, version Security: - Secret masking on GET effective, export, sessions, provider detail - Export plaintext requires X-Confirm-Secrets header - Store nil -> management API not registered - Auth middleware on all management endpoints Reviewed by GPT-5.5 (2 rounds): 2 P0 + 7 P1 issues fixed - ApplyPendingChanges atomicity (tx-internal applied mark) - Setting apply format mismatch + offer delete condition - /config/import creates pending changes - PATCH pointer semantics for offer fields - maskSecrets write-back for provider web_search keys
…at types - RequestMutator uses format.CoreRequest instead of anthropic.MessageRequest - ToolInjector returns format.CoreTool instead of anthropic.Tool - MessageRewriter uses format.CoreMessage instead of anthropic.Message - ContentFilter uses format.CoreContentBlock with simplified return type - ContentRememberer uses format.CoreContentBlock instead of anthropic.ContentBlock - ThinkingPrepender uses format.CoreMessage/format.CoreContentBlock - ReasoningExtractor returns format.CoreContentBlock - All extension plugins (deepseek_v4, visual, websearchinjected) updated - Plugin registry dispatch methods updated - Tests updated for new type signatures
- FromAnthropicRequest converts anthropic.MessageRequest -> CoreRequest then delegates to FromCoreRequest - ToAnthropicResponse delegates to ToCoreResponse - anthropicToCoreRequest private helper for anthropic->Core conversion - toCoreThinkingConfig/toCoreOutputConfig/toCoreCacheControl helpers - parseTTLSeconds helper for TTL string parsing
- Remove deprecated internal/foundation/ packages (config, db, logger, modelref, openai, session) - Add new packages: internal/format/, internal/config/, internal/db/, internal/logger/, internal/modelref/, internal/openai_dto/, internal/session/ - Update all imports and references across codebase - Update server, provider, extension, and e2e layers to use new package paths - Clean up adapter dispatch and session management
- Anthropic: 修复缓存 token 双计、TTL 解析扩展至分时、buffer 上限 - Chat: tool_choice "required" 原生支持、参数双重编码修复 - Google: JSON 标签 camelCase、FunctionResponse 函数名修复 - OpenAI: 扩展字段映射、新增 file_search 等工具类型 - dispatch: 修复 Anthropic 响应的 interface 类型断言 --- 🐞 fix(protocol): fix protocol compliance issues across 4 adapters - Anthropic: fix cached token double-count, extend TTL parsing - Chat: native tool_choice "required", fix arg double-encoding - Google: camelCase JSON tags, fix FunctionResponse name - OpenAI: map extension fields, add file_search tool type - dispatch: fix interface type assertion for Anthropic responses
- cache_hit_rate 分母改用 freshInput + cachedInput,避免>100% - streaming 路径 CacheReadInputTokens 映射修复 --- 🐞 fix(metrics): fix cache hit rate calc and billing field mapping - Use effective total (fresh + cached) as hit rate denominator - Fix streaming path CacheReadInputTokens mapping
C-02: ToolCall arguments 恢复 JSON 字符串编码 OpenAI Chat API 要求 arguments 为 JSON 字符串而非 raw 对象 C-04: 删除 off-by-one 索引条件,恢复 URL /v1 剥离 条件 baseURL[len-4]=='/' 对所有 URL 均为 false,完全禁用了 /v1 剥离 --- 🐞 fix: revert C-02/C-04 erroneous fixes C-02: restore JSON string encoding for ToolCall arguments OpenAI Chat API expects arguments as JSON string, not raw object C-04: remove broken off-by-one index, restore URL /v1 stripping
- anthropic: 合并连续 tool_result 消息,满足 API 交替约束 - dispatch: 通过 AnthropicClientAccessor 绕过 StreamMessage 类型擦除 - test: 添加 4 个回归测试覆盖 URL 剥离/arguments 编码/合并/accessor --- 🐞 fix: fix tool_result merge and Stream type erasure, add regression tests - anthropic: merge consecutive tool_result messages for API alternation - dispatch: bypass StreamMessage type erasure via AnthropicClientAccessor - test: 4 regression tests for URL strip/args encoding/merge/accessor
NewPlugin 未传入 isEnabled resolver,stream interceptor 始终被跳过 导致 OnStreamComplete 无法持久化 thinking 数据到 session --- 🐞 fix: fix DeepSeek V4 EnabledForModel always returning false Missing isEnabled resolver in NewPlugin caused stream interceptor skip, preventing OnStreamComplete from persisting thinking data
层次1: NewPlugin resolver → EnabledForModel 层次2: Registry OnStreamComplete dispatch --- 🧪 test: add regression tests for DeepSeek V4 EnabledForModel Level 1: NewPlugin resolver → EnabledForModel Level 2: Registry OnStreamComplete dispatch
路由解析将别名(如 gpt-5.4-mini)映射到实际模型(deepseek-v4-flash)后, 上游请求仍使用别名,导致 API 返回 404(model not found)。 --- 🐞 fix(dispatch): override coreReq.Model with upstream model name After route resolution maps alias(gpt-5.4-mini) to actual model (deepseek-v4-flash), upstream request still used the alias, causing API 404(model not found).
CoreContentBlockDone 未将累积文本写入 Content[0].Text, CoreEventCompleted 未从 output items 重构 OutputText。 --- 🐞 fix(openai): fix empty output_text after streaming completes CoreContentBlockDone didn't write accumulated text to Content[0].Text, CoreEventCompleted didn't rebuild OutputText from output items.
- openai: inputItem.Output 改为 json.RawMessage,兼容数组格式 - dispatch: prependCachedThinking 回退分支替换为空 thinking block --- 🐞 fix: visual plugin output array compat + empty thinking boundary - openai: change inputItem.Output to json.RawMessage for array compat - dispatch: replace fallback with empty thinking block boundary
…ache) Anthropic Messages API 的 input_tokens 是 fresh-only 的,不包含 cache reads。 之前直接将其映射为 CoreUsage.InputTokens,但下游消费者(OpenAI 适配器、 stats 记录、计费计算)都预期 total(fresh + cache)语义,导致: - Codex 收到的 input_tokens 只有 fresh 部分,低估实际 token 消耗 - 缓存命中率高时,上下文窗口 exceeded 且未被正确报告 修复: - toCoreUsage: InputTokens = input_tokens + cache_read(total) - 流式 message_delta: 同上 - billingUsageFromAnthropic: FreshInputTokens = InputTokens - CachedInputTokens --- 🐞 fix(adapter): Anthropic usage normalization — input_tokens must be total (fresh + cache) Anthropic Messages API input_tokens is fresh-only (does NOT include cache reads). Previously it was passed directly as CoreUsage.InputTokens, but downstream consumers (OpenAI adapter, stats recording, billing) expect total (fresh + cache) semantics, causing: - Codex's received input_tokens under-reported actual consumption - Context window exceeded not properly reported when cache hit rate is high Fixes: - toCoreUsage: InputTokens = input_tokens + cache_read (total) - Stream message_delta: same - billingUsageFromAnthropic: FreshInputTokens = InputTokens - CachedInputTokens
修复 13 份文档中的事实错误:
CookBook (Unix/Windows):
- Go 版本 1.26+ → 1.25+
- 配置格式 v4 (provider.providers) → v5 (顶层 providers/models/routes)
- DeepSeek base_url 从 api.deepseek.com → api.deepseek.com/anthropic
- 路由格式从字符串 "deepseek/deepseek-chat" → 对象 {model, provider}
- CLI flag 从 --print-codex-model → -print-codex-model
Canonical docs (README, architecture, GETTING-STARTED, DEVELOPMENT,
TESTING, CONFIGURATION, API, DEPLOYMENT, CONTRIBUTING):
- 全部 regenerate,修复 internal/foundation/ 不存在的目录结构
- 修正为实际路径:internal/config/, internal/logger/, internal/openai_dto/
- 修正代码引用为实际文件(adapter_dispatch.go, router.go, types.go)
Hand-written docs:
- development-conventions.md: 3 处路径引用修复
- extension-system.md: 重写,移除过时 bridge.PluginHooks/pluginhooks 引用
更新为 CorePluginHooks + Registry.CorePluginHooks()
---
📃 docs: full factual verification fix — config v5 format, directory structure, extension system
视觉插件通过 ToolInjector 注入工具定义,但在 dispatch 层没有拦截 图像 base64 数据。prepareRequestForVisual() 负责从请求中剥离图像, 但视觉 orchestrator 从未被调用,因为插件未包装上游 provider。 根因:adapter_dispatch.go 直接调用 effectiveProvider.CreateMessage() 发送包含完整 base64 图像数据的 anthropic.MessageRequest 给文本模型。 修复:添加 wrapAnthropicWithVisual() 方法,在 CreateMessage 前检测 视觉扩展是否启用。若启用,通过 visual.WrapProvider() 包装 provider, 由 prepareRequestForVisual() 剥离图像 base64,替换为文本占位符, 再发给文本模型。 修复验证: - 编译通过,全量测试通过 - trace 验证:129/264 的 Anthropic trace 曾含图像 base64 数据 --- 🐞 fix(dispatch): visual plugin base64 image leak — wrapAnthropicWithVisual
server_test.go: TestAuthRejectsRequestsWithoutValidToken - 根因: 测试用 AppConfig.AuthToken,但 currentConfig() 读 serverCfg - 修复: AppConfig → ServerCfg(P2-17 重复字段问题) config_test.go: visual_provider_missing - 根因: ValidateConfig 仅检查全局 extensions.visual.enabled, 忽略模型级 models.deepseek-v4-pro.extensions.visual.enabled: true - 修复: validateModelConfig 改用 fullCfg.ExtensionEnabled() 检查 模型级状态,新增 decodeVisualConfig() 从全配置解码扩展配置
修复: - adapter_dispatch.go: 流式 StreamMessage 前调用 StripImagesFromAnthropic 直接剥离 image block(上一轮仅覆盖了非流式 CreateMessage 路径) - orchestrator.go: 新增 StripImagesFromAnthropic() 公开函数 回归测试: - TestStripImagesFromAnthropic_StripsBase64: image → placeholder - TestStripImagesFromAnthropic_TextOnlyUnchanged: 纯文本不误伤 - TestStripImagesFromAnthropic_MixedContent: 多图均剥离 - TestPrepareCoreRequestForVisual_StripsBase64: Core 层剥离 - TestPrepareCoreRequestForVisual_TextOnlyUnchanged: Core 层不误伤 - TestPrepareCoreRequestForVisual_MixedContent: Core 层多图剥离
在三个 Adapter 的 FromCoreRequest 入口处(MutateCoreRequest 之后、协议
转换之前),对所有 CoreContentBlock 的文本内容递归执行 StripImageData。
检测并替换三类 base64 图片数据:
- data:image/{fmt};base64,... 标准 data URL
- iVBORw0KGgo... 裸 PNG base64
- /9j/... 裸 JPEG base64
替换为 [Image data: {fmt}, {bytes} bytes] 占位符。
覆盖:tool_result、system、普通 text 块,所有协议和流式/非流式路径。
仅替换长度 > 500 字符的长 base64 串以避免误伤。
internal/format/types.go: StripImageData / StripContentBlocks
internal/protocol/anthropic/adapter.go:218
internal/protocol/chat/adapter.go:71
internal/protocol/google/adapter.go:96
修复 3 个 bug: 1. Anthropic 协议路径未处理 web_search "injected" 模式 - Core 层注入 tavily_search/firecrawl_fetch 工具 (injectCoreWebSearch) - Provider 包装搜索编排器 (searchProviderAdapter) 2. web_search "auto" + 配置了 API key 应回退到注入模式 - injectCoreWebSearch 放宽条件 3. executeChatSearchLoop 空 case 丢失 tavily_search 调用 - case 合并,正确路由到 searchCalls 4. Chat API arguments 外层引号导致 JSON 解析失败 - 增加 unquoteRawJSON 解一层引号再解析
根因: config_loader.go 无条件从 modelDef 复制 DisplayName 到 route entry, 导致多个指向同一 model 的 route 全部显示相同名称 (如 gpt-5.4 → "DeepSeek V4 Pro", gpt-5.5 → "DeepSeek V4 Pro")。 修复: config_loader.go 移除 route 对 modelDef.DisplayName 的继承。 下游消费者 (BuildModelInfoFromRoute / listModels / /v1/models) 在 DisplayName 为空时从 alias slug 派生唯一名称。 回归测试: - TestRouteDisplayNameNotInheritedFromModelDef: 3 route -> 同 model -> 均空 - TestRouteExplicitDisplayNameStillWorks: 显式配置 display_name 仍生效 - TestBuildModelInfoFromRouteDifferentAliasesSameModel: 5 个别名 -> 5 个不重名 - TestBuildModelInfoForProviderModelsPreserveDisplayName: provider model 不变
…_tool_result/Search results
Kimi 原生 web_search 使用 Anthropic 服务端执行模式,返回三种无法
直接转发的流内容:
- text: "Search results for query: ..." → 基础设施状态消息
- server_tool_use {web_search} → 服务端工具标记
- web_search_tool_result {...} → 搜索结果(已注入模型上下文)
修复:
1. content_block_start: server_tool_use → silent skip
2. content_block_start: web_search_tool_result → silent skip
3. content_block_delta: "Search results for query:" → suppressText 标记
4. content_block_stop: suppressText 索引 → 跳过 block done 事件
5. flow: web_search_tool_result 不转发为 text → 避免输出污染
server_tool_use 不转发为 tool_use → 避免 orphan API rejection
…eaming path - Add kimi_workaround plugin to limit Kimi model tool-call rounds - Tracks tool-call rounds and injects progress/limit prompts - Configurable max_tool_rounds and convergence_margin - Appends prompts to tool_result content to preserve tool_use/tool_result pairing - Wire RewriteMessages hook into anthropic/chat/google adapters - Fix cost reporting: move onRequestCompleted after cost computation in non-streaming path - Also adds cost reporting to streaming completion path
修复 3 个 bug + 1 个设计改进: 1. roundCount 从整个历史累计 → 仅从最后真实 user prompt 起算 2. 注入方式从独立 user 消息 → 追加到 tool_result 内容(遵守配对要求) 3. lastIdx 扫描跳过 isSystemReminder(避免被自己在历史中的残留挡住) 4. 达到 maxRounds 时:替换 tool_result 内容为 MAX_TOOL_ROUNDS 硬错误 接近时:追加 SystemReminder + 收敛提示
Resolve 30 merge conflicts across three categories: - modify/delete (5): remove stale internal/foundation/ and internal/protocol/bridge/ files - add/add (10): keep dev versions for extension modules and scripts - content (15): keep dev's new package structure (internal/format, internal/config, etc.) over main's legacy internal/foundation/ paths; take main's go.mod for jsonschema dep Key merge decisions: - All Go conflicts resolved in favor of dev (new architecture with adapter dispatch, remove old pluginhooks/bridge pattern) - Docs already synced with dev codebase before merge - go.mod takes main's version (adds jsonschema indirect dep) - ci.yml and scripts updated from main Full test suite: CGO_ENABLED=0 go test ./... — 30 packages pass, 0 failures
…use/tool_result adjacency Two protocol-level fixes for Codex ↔ DeepSeek via Moon Bridge: ## Fix 1: Suppress reasoning_summary when client didn't request reasoning DeepSeek V4 always returns thinking blocks regardless of whether the client requested reasoning. Previously, streamLoop unconditionally emitted `response.reasoning_summary_part.added` / `reasoning_summary_text.delta` events for thinking blocks, causing "ReasoningSummaryDelta without active item" errors in Codex when `reasoning effort: none`. Added `hasReasoningRequested()` guard that checks `coreReq.Output.Effort`, `coreReq.Thinking`, and `openaiExt["reasoning"]` before treating a block as reasoning. When reasoning wasn't requested, thinking blocks are converted to regular text. ## Fix 2: Preserve tool_use/tool_result adjacency in convertInput The Anthropic protocol requires that every assistant message with `tool_use` blocks MUST be immediately followed by a user message with matching `tool_result` blocks. The previous `convertInput()` could create two consecutive assistant messages — one with tool_use and one with text/reasoning — breaking this invariant and causing 502 errors from DeepSeek's API. Two changes in convertInput: - `function_call_output` handler: merge `pendingReasoning` into `pendingFCBlocks` instead of flushing as a separate message - `role == "assistant"` handler: when `pendingFCBlocks` is non-empty, merge assistant text blocks into the pending batch Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…on-standard formats DeepSeek sometimes generates *** Add File/*** Modify File markers or bare unified diffs for apply_patch, which Codex rejects. Add patch grammar normalization in the output path (both streaming and non-streaming): - normalizePatchInput: detects apply_patch tool calls and normalizes the input JSON, preserving the original if no changes needed - normalizePatchContent: strips non-standard *** markers and wraps bare diffs in Codex's expected *** Begin Patch / *** End Patch delimiters - Deterministic fix — does not depend on model prompt compliance - Both streaming (streamLoop) and non-streaming (convertResponse) paths covered Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…use/tool_result adjacency (ZhiYi-R#29) ## Summary Two protocol-level fixes for Codex CLI ↔ DeepSeek V4 integration via Moon Bridge. These fix the two most common runtime errors users hit. ## Fix 1: Suppress reasoning_summary when client didn't request reasoning **Symptom:** `ReasoningSummaryDelta without active item` errors in Codex when `reasoning effort: none`. **Root cause:** DeepSeek V4 always returns thinking blocks regardless of whether the client requested reasoning. `streamLoop` unconditionally emitted `response.reasoning_summary_part.added` / `reasoning_summary_text.delta` events for thinking blocks, but Codex with `reasoning effort: none` had no active reasoning item to receive them. **Fix:** Added `hasReasoningRequested()` guard that checks `coreReq.Output.Effort`, `coreReq.Thinking`, and `openaiExt["reasoning"]` before treating a block as reasoning. When reasoning wasn't requested, thinking blocks are converted to regular text, preserving the thinking content without breaking the protocol. ## Fix 2: Preserve tool_use/tool_result adjacency in convertInput **Symptom:** 502 errors from DeepSeek: `messages.N: tool_use ids were found without tool_result blocks immediately after`. **Root cause:** The Anthropic protocol requires every assistant message with `tool_use` blocks to be immediately followed by a user/tool message with matching `tool_result` blocks. The previous `convertInput()` could create two consecutive assistant messages — one with tool_use and one with text/reasoning — breaking this invariant. **Fix:** Two changes in `convertInput()`: - `function_call_output` handler: merge `pendingReasoning` into `pendingFCBlocks` instead of flushing as a separate assistant message - `role == "assistant"` handler: when `pendingFCBlocks` is non-empty, merge assistant text blocks into the pending batch instead of creating a separate message ## Testing - Full project build: ✅ - All 33 test packages pass: ✅ - Live tested against DeepSeek V4 + Codex CLI v0.128.0: ✅ - Simple text conversation - exec/shell command execution - File read → edit → write (apply_patch after dev merge) - Multi-retry stability (no more 502 on reconnection) ## Related - Fixes the issue reported in ZhiYi-R#28 - Compatible with DeepSeek official Codex integration guide
…pipeline The deepseek_v4 plugin implements the ThinkingPrepender capability and format.CorePluginHooks exposes a PrependThinkingToAssistant hook, but the registry never collected the capability nor wired the hook, so thinking blocks were never replayed into follow-up tool-use requests through the plugin path. - Collect ThinkingPrepender plugins at registration. - Add Registry.PrependThinking dispatch that routes tool_use assistant messages through PrependThinkingForToolUse and text-only assistant messages through PrependThinkingForAssistant, using per-session state carried in the request context. - Wire CorePluginHooks.PrependThinkingToAssistant to the new dispatch. - Invoke the hook from the anthropic/chat/google provider adapters. - Inject per-session plugin state into the request context in the adapter dispatch path via format.WithPluginSessionData. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
修复 #104:ThinkingPrepender 能力和 PrependThinkingToAssistant hook 已实现但从未接入请求管道。本 PR 在 Registry 中收集该能力、新增 PrependThinking 派发(tool_use 走 PrependThinkingForToolUse、纯文本走 PrependThinkingForAssistant),并在 anthropic/chat/google 三个 provider adapter 的 FromCoreRequest 中调用,会话状态经 format.WithPluginSessionData 注入 context 传回插件。与现有的服务层 prependCachedThinking/prependCachedReasoningForChat 幂等互补。
注意!本修复由Deepseek V4 pro在VSCode当中生成,并通过了Agent根据贡献准则给出的测试,希望开发者能够再次核实代码的可行性再merge,bug已经暂时无法复现