Changelog¶
All notable changes to LLM-Rosetta are documented here. This project follows Keep a Changelog conventions.
[未发布]¶
v0.7.2 — 2026-07-20¶
新增¶
- 管理面板
custom_head注入 (#378):setup_admin()接受可选的custom_headHTML 片段,注入到</head>之前。下游项目可注入<style>/<script>标签来定制管理面板 UI,无需修改参考admin.html。按值缓存,无每次请求开销。 - 管理面板
branding品牌配置 (#378):setup_admin(..., branding={title, subtitle, version, links, attribution})可定制页头、登录页面和设置页脚。通过custom_head序列化为window.__branding;admin.html中的消费脚本负责修改 DOM。新增元素 ID:brandTitle、brandLoginTitle、brandFooterName、brandFooterLinks。未提供 branding 时,默认 llm-rosetta 标识不变。
变更¶
- 升级 vendored
httpclient0.4.4 → 0.4.5——修复 fd 泄漏问题:close()未关闭_async_writer,导致__del__无法清理泄漏的异步流式响应。 - 提取
ConfigIO协议用于管理面板配置读写 (#376):管理面板路由现在通过ConfigIO协议而非直接导入load_config/load_config_raw/write_config。默认JsoncConfigIO实现保持现有行为不变;下游项目(如 argo-proxy)可通过setup_admin(..., config_io=...)提供替代实现。内部辅助函数_get_config_path和_get_config_io在值缺失时抛出描述性RuntimeError,移除了路由处理器中 16 处冗余的空值检查。 - 内容捕获表格中将 Unicode emoji(🔍)替换为内联 SVG,确保跨平台渲染一致。
修复¶
- 修复 branding JSON 序列化中
</未转义的问题,防止 branding 值包含</script>时导致<script>标签断裂。
v0.7.1 — 2026-07-16¶
修复¶
- Anthropic 和 Google 的工具 Schema 清理 (#372):Anthropic 拒绝工具参数 schema 中的 OpenAPI
nullable扩展(例如 Pydantic 生成的 JSON Schema)。新增convert_nullable_to_type_array()helper,递归地将"nullable": true转换为标准 JSON Schema"type": [T, "null"]。Anthropic converter 现在会剥离title字段并转换nullable为 type 数组;Google GenAI converter 剥离title(保留nullable——Google 支持该字段)。同时处理了nullable: true与anyOf/oneOf共存但无type字段的边界情况。 flatten_system复选框布局和国际化 修复(网关管理面板)。
变更¶
- 升级 vendored
validate0.6.0 → 0.6.1(支持 dataclass 实例)。 - 限制 Dependabot 仅监控 LLM SDK 依赖。
新增¶
- SDK 类型覆盖扫描器和手动 CI 工作流,用于追踪 provider SDK 类型对齐情况。
v0.7.0 — 2026-07-10¶
新增¶
- Anthropic
cache_control保留 (#362):IR 部件(TextPart、ImagePart、FilePart、ReasoningPart、ToolCallPart、ToolResultPart、ToolDefinition)新增cache_hint字段,支持 Anthropic block 级cache_control在 IR 管线中的往返传递。Anthropic converter 在输入时读取cache_control→cache_hint,在输出时写回;非 Anthropic converter 静默忽略cache_hint,确保跨格式安全。 flatten_system_content()变换 (#370):新增 body 级变换工厂,将系统消息内容数组展平为纯文本字符串。OpenAI Chat converter 现在为系统消息输出结构化内容数组(保留cache_hint的 block 边界);flatten_system_content()在需要时降级为纯字符串以兼容上游。支持 per-modelflatten_system网关配置,Gemini 模型自动检测。管理面板包含开关控件。
修复¶
- OpenAI SDK 2.45+ 兼容性:在
InputTokensDetails(Responses API)和PromptTokensDetails(Chat Completions API)TypedDict 副本中添加cache_write_tokens字段,以匹配上游 SDK 变更。
Changed¶
- Transform 字段重命名 ——
from_transforms→pre_ir_transforms,to_transforms→post_ir_transforms(ProviderShim上的字段)。旧名称在构造函数参数和transforms.py导出中均作为向后兼容别名继续有效。 system_instruction统一为list[TextPart](#364):IR 中system_instruction的规范形式从str改为list[TextPart]。单个字符串"You are helpful."表示为[TextPart(type="text", text="You are helpful.")]。确保所有 converter 间结构一致,并支持 block 级元数据(如 Anthropic prompt caching 的cache_hint)在 IR 管线中流转。四个 converter 均已更新。Breaking:直接将ir_request["system_instruction"]当str读取的代码需要改为处理list[TextPart]。
v0.7.0a1 — 2026-06-27¶
新增¶
- 可观测性包 (#341):将
MetricsCollector、RequestLog、RequestLogEntry、PersistenceManager和ProfilerState从gateway/admin/提取到新的顶层llm_rosetta.observability包。这些模块与框架无关,任何 LLM 代理消费者(如 argo-proxy)均可直接使用,无需依赖网关的配置系统或 HTTP 服务器。gateway/admin/模块通过重新导出保持完全向后兼容 - 混合性能分析系统 (#339):
ConversionPipeline.profile中内置 always-on 的perf_counter阶段计时(source_to_ir_ms、ir_transforms_ms、ir_to_target_ms 等),加上按需的 per-request pyinstrument 深度分析(通过 admin API 控制)。核心库新增DeepProfiler上下文管理器(llm_rosetta.profiling)。新增[profiling]可选依赖组。Gateway admin 端点:POST /admin/api/profiling/enable、GET /admin/api/profiling/results、GET /admin/api/profiling/results/<index>、POST /admin/api/profiling/disable、DELETE /admin/api/profiling/results - 性能分析管理 UI (#339):管理面板新增 "Profiling" 区域,包含启用/停用控制、结果列表、火焰图下载(单个和批量)以及重启提示
- 错误转储功能 (#341):Fire-and-forget 错误转储系统,在上游/转换失败时捕获完整请求上下文。哈希前进行图片卸载以实现基于内容的去重,zlib 压缩,10K 条目上限并级联修剪。proxy.py/app.py 中 4 个触发点覆盖上游错误、流头部错误、流块错误和转换错误。新导出函数:
dump_error()、offload_images()、compute_body_hash()、compress_body()、decompress_body()(从llm_rosetta.observability) - 指标重建 (#340):
POST /admin/api/metrics/rebuild端点及管理面板 "Rebuild Counters" 按钮。使用fetchmany(5000)批量迭代和原子交换从请求日志历史中重建所有指标计数器,避免暴露半重建状态
修复¶
- 指标按提供商名称分组 (#340):面板 breakdown 区域现在按提供商显示名称分组,而非按 API 类型(此前将所有 Anthropic 格式的提供商合并为一行)
- 配置文件写入安全:
write_config()现使用文件锁确保跨进程安全 - Vendored httpserver 更新至 0.2.1:对格式错误的请求返回正确的 HTTP 错误响应,而非静默断开连接
- Vendored SSE 更新至 0.3.2:解析器初始化使用构造函数参数,而非初始化后修改
变更¶
- 开发工具版本锁定:在
[project.optional-dependencies]中锁定ruff==0.15.20和ty==0.0.54,防止上游工具发版导致 CI 漂移
v0.7.0a0 — 2026-06-25¶
新增¶
- ConversionPipeline 类 (#322):高层编排类,封装完整的 Phase 1→2→4 转换生命周期。提供
convert_request()、convert_response()、create_stream_processor()及on_ir_ready回调用于元数据存储集成。一次性保护防止意外复用 - 路由层 (#323):核心库中的
ResolvedRoute冻结数据类和Router协议。GatewayConfig.resolve()将模型查找、provider 类型、shim 绑定、capabilities 和 reasoning 覆盖整合为单一类型化结果 - 能力模块 (#335):
capabilities.py包含enforce_reasoning()(IR 前)和enforce_vision()(IR 后)——平台级能力约束,与 provider 特定的 shim 变换分离 - IRTransform 系统 (#330):
TransformContext数据类、IRTransform可调用类型、apply_ir_transforms()执行器和_NamedIRTransform包装器。IR 层变换现在在ProviderShim.ir_transforms上声明式配置,与 body 层Transform分离 - IR 变换工厂函数:
strip_non_vision_images()、truncate_images(max, pattern)、unwind_parallel_tool_calls(pattern)—— 产出IRTransform可调用对象的工厂函数 - 消息级变换原语 (#328):
replace_message_field()、default_message_field()、strip_fields_for_model(),用于messages[]嵌套字段操作 - Transport 层 (#321):
UpstreamTransport协议、HttpTransport实现、UpstreamResponse/UpstreamStream类型、HttpClientPool、send_passthrough()用于非转换端点 resolve_shim()公共函数:从私有_resolve_shim()提升为provider_shim.py上的公共 API
破坏性变更¶
ProviderShim字段移除:删除max_images、max_images_pattern、unwind_parallel_tool_calls、unwind_parallel_tool_calls_pattern—— 这些能力现通过ir_transforms元组使用工厂函数声明(truncate_images()、unwind_parallel_tool_calls())apply_shim_to_ir()行为变更:不再硬编码图片/unwind 操作,改为声明式读取shim.ir_transforms。重命名为apply_ir_transforms()(旧名称为弃用别名)- Gateway handler 签名变更:
handle_non_streaming和handle_streaming接收route: ResolvedRoute而非 6 个松散参数
重构¶
- Pipeline 重命名 (#330):
apply_shim_to_ir()→apply_ir_transforms()、setup_shim_context()→configure_context()。旧名称发出DeprecationWarning - Gateway proxy.py:handler 内部使用
ConversionPipeline。删除_resolve_target_transforms、process_stream_chunk - Embeddings handler:使用
transport.send_passthrough()替代直接访问HttpTransport._pool。从旧的resolve_model()迁移至统一的resolve()API,用共享的_record_telemetry()替换内联遥测代码 - 认证函数重命名:
_openai_auth→openai_auth等(去掉下划线,公共 API) - 移除
GatewayConfig.resolve_model():旧的 5-tuple API 已被返回ResolvedRoute+ProviderInfo的resolve()取代。移除重复的DEFAULT_CAPABILITIES类变量
修复¶
- 恢复旧
converters/base/导入路径 (#317):在旧路径提供向后兼容 shim 模块 sanitize_schema剥离exclusiveMinimum/exclusiveMaximum(#337):Google GenAI API 拒绝工具定义中的 JSON Schema draft 6+ 数值约束- 停止为 OpenAI Responses API 生成
reasoning.type(#337):OpenAI 和 Volcengine Responses API 拒绝reasoning.type—— reasoning 仅通过reasoning.effort控制。v0.6.8 的历史 bug
v0.6.12 — 2026-06-23¶
修复¶
- 恢复
converters/base/旧导入路径 (#310):v0.6.11 的 helpers/ 重组意外破坏了外部调用方依赖的导入路径。现在sanitize_schema、extract_part_ids、log_orphan_warnings、fix_orphaned_tool_calls_ir、strip_orphaned_tool_config重新从converters.base.tools导出,并在converters.base.schema、converters.base.tool_content、converters.base.cache提供兼容性 shim 模块,重定向到它们在helpers/下的新位置。规范导入路径仍为llm_rosetta.converters.base.helpers;按旧路径导入的现有代码(如from llm_rosetta.converters.base.tools import sanitize_schema)无需改动即可继续工作。缓存单例在新旧路径间共享
v0.6.11 — 2026-06-21¶
新增¶
- Admin 面板服务方 UX 增强 (#292):服务方标签页三项改进:
- 多密钥条目列表:API 密钥字段自动检测逗号分隔的密钥(轮转),切换为多个
<input type="password">输入框。始终显示+ 添加密钥按钮。眼睛和复制按钮统一在底部 - 服务方搜索栏:服务方数量超过 6 个时自动显示,支持按名称、类型、Base URL 过滤
- 网格/列表视图切换:两个图标按钮切换卡片网格和紧凑单列列表视图,偏好保存在 localStorage
- 多密钥条目列表:API 密钥字段自动检测逗号分隔的密钥(轮转),切换为多个
- Request ID 传播 (#296, #122):每个代理请求生成或继承
X-Request-ID头。向上游传播,包含在所有响应头中(包括错误响应),并以[request_id]前缀记录日志,实现端到端可追踪 - 增强健康检查端点 (#297, #127):
/health— 返回运行时间、请求总数、最近一小时错误数、每个服务方的健康状态快照(成功率、平均延迟、最后错误)。始终 HTTP 200;status字段显示"ok"或"degraded"/health/live— 始终 200(Kubernetes 存活探针)/health/ready— 所有服务方健康时 200,任一服务方严重不健康时 503(Kubernetes 就绪探针)
- Admin API 的 CORS 限制 (#294, #233):
/admin/api/*端点不再发送Access-Control-Allow-Origin: *。新增配置选项server.admin_cors_origins(列表,默认[])允许显式指定允许的来源。/v1/*代理端点不受影响 - Shim 层图片数量限制 (#301, #299):
ProviderShim新增max_images和max_images_pattern字段。超限时最早的图片被替换为[image omitted due to limit],保留最近的 N 张。Argo OpenAI shim 声明max_images: 50,pattern 为^(gpt|o\d)— 仅 GPT/o 模型被截断;经过同一服务方的 Gemini 和 Claude 不受影响 - 视觉能力运行时检查 (#314, #313):没有
vision能力的模型会自动将所有图片替换为[image not available],而非直接转发给上游导致不明错误(如 DeepSeek 的 "unknown variantimage_url")。Gateway 日志会记录 warning 包含图片数量和模型名 - Unix 域套接字支持 (#315):Gateway 可通过
--socket/-SCLI 参数或server.socket配置字段监听 Unix 套接字而非 TCP。适用于共享多用户主机上的安全部署(127.0.0.1仍会暴露给所有本地用户)。套接字文件权限限制为仅所有者可访问(0600),关闭时自动清理 - 并行工具调用展开 (#303, #300):
ProviderShim新增unwind_parallel_tool_calls和unwind_parallel_tool_calls_pattern字段。启用后,并行工具调用(一条 assistant 消息包含多个tool_call)会在转发前展开为顺序调用-结果对。Argo OpenAI shim 以^geminipattern 启用 — Gemini 模型获得顺序对;GPT/o 模型不受影响
变更¶
-
converters/base/重组为 helpers/ 子包 (#311, #312, #310):工具函数从converters/base/平铺目录提取到converters/base/helpers/。抽象基类(Ops 模式契约)保留在顶层;实现工具(cache、schema、tool_orphan_fix、tool_content、tool_call_unwind、image_limit、reasoning)移至helpers/。tools.py从 428 行精简到 185 行(纯 ABC)。reasoning_helpers.py从converters/根目录移入。orphan_fix.py重命名为tool_orphan_fix.py保持tool_*前缀一致。helpers/__init__.py重新导出公共函数 -
移除 Argo
_normalize_thinking废弃代码 (#304, #192):从 Argo Anthropic shim 中移除了已废弃的_normalize_thinking函数、_BUDGET_RATIO和_ADAPTIVE_THINKING_MODELS——这些已被provider.yaml中声明式的reasoning.model_overrides取代,但代码和 19 个测试仍然保留着 - 实验性扩展类型标记 (#302, #71):
SystemEvent、BatchMarker、SessionControl、ToolChainNode从types.ir.extensions移至types.ir.extensions_experimental。旧导入路径仍可用但会触发DeprecationWarning。这些类型从默认types.ir命名空间移除,可通过from llm_rosetta.types.ir import experimental访问 - Admin 面板 i18n:中文翻译从"服务商"更新为"服务方"(对混合商业和自建服务方更中性)
- 请求日志时间戳 (#298):现在显示日期和时间(如 "06/19, 20:25:29"),而非仅显示时间
修复¶
- Admin 面板认证内容闪烁 (#291):消除了配置
admin_password时登录遮罩出现前短暂显示管理内容的问题。通过 CSS(body.auth-pending)在异步认证检查完成前隐藏主界面 - Admin 密码未解析环境变量 (#293):如果
admin_password包含未解析的${...}占位符,网关现在拒绝启动,防止可预测的字面量字符串被用作密码 is_image_part类型守卫支持 OpenAI 格式 (#306):is_image_part()现在同时匹配type: "image"(IR 规范格式)和type: "image_url"(OpenAI 格式保留在 IR 中),修复了 OpenAI 格式请求的图片截断被静默跳过的问题- 工具结果中的图片纳入截断计数 (#308, #299):
truncate_images()现在扫描tool_result.result列表内的图片,而不仅是直接消息内容。修复了在 IR 层面 ≤50 张图片但 OpenAI Chat 转换器解包工具结果图片后超过 50 张的请求。同时优化了deepcopy,仅复制受影响的消息而非整个对话 - Argo Gemini 并行工具调用失败 (#303, #300):Claude Code 发起并行工具调用时,所有通过 Argo 的 Gemini 模型都报 "function response parts ≠ function call parts" 错误。根因:Argo 内部的 OpenAI→Gemini 转换不会将独立的 tool result 消息合并为单个
functionResponseContent 块。通过在转发前将并行工具调用展开为顺序对修复
v0.6.10 — 2026-06-18¶
Added¶
- Process-level conversion cache (#276, #279, #281, #283): Per-entry LRU cache with access-refreshed TTL (default 30 min) for tool conversion, schema sanitization, and IR validation. Eliminates repeated work for unchanged tool definitions and messages across conversation turns
- Hub-and-spoke architecture: conversion caches (spokes) are converter-specific; IR validation cache (hub) is converter-agnostic and shared across all converters
- Per-entry caching: individual tools and messages cached by content hash — partial tool changes only re-convert the changed entries, and cross-agent tool overlap shares cache entries
- Incremental message validation: only newly appended messages are validated; previously-seen messages are skipped via the IR validation hub
- Mutation detection:
check_integrity()on test teardown catches accidental in-place mutation of cached objects; optionalverify=Truemode for runtime self-healing - Benchmark: 4.4× warm-path speedup (3250 µs → 527 µs local); 33% TTFB reduction in production (11.4 ms → 7.6 ms)
validate_tools()(#283): New standalone IR validation function for tool definition lists, symmetric withvalidate_messages()- OpenRouter Anthropic shim (#284): OpenRouter's Anthropic-compatible Messages endpoint is now a first-class provider type. The single
openroutershim is split intoopenrouter--openai_chat(Chat Completions) andopenrouter--anthropic(Messages API), letting OpenRouter route Claude models through the native Anthropic format - Admin panel per-model reasoning override (#288): The model edit modal now displays the effective reasoning config (
thinking_type,budget_tokens_ratio,disabled_strategy) with a source badge (provider / model_override / config) and inline editing. Overrides are persisted toconfig.jsoncand resolved at runtime with priority: config override > shim model_override > shim provider default budget_tokens_default_ratioreasoning capability (#287):ReasoningCapabilitygains abudget_tokens_default_ratiofield. When a provider requiresthinking.type=enabledbut the caller omitsbudget_tokens, a default is derived asmin(max(1024, max_tokens × ratio), max_tokens - 1)instead of falling back to the unsupportedadaptivetype
Changed¶
_convert_tools_from_pno longer abstract (#281): Default implementation inBaseConverterhandles all providers (including Google's list/None return). Per-converter overrides removed — 90 lines of duplicated code eliminated- Complete Claude thinking model_overrides (#287): Added per-model thinking overrides for the Anthropic and Argo shims based on tested support matrices — Haiku 4.5 (
enabled+budget), Opus 4.7/4.8 (adaptive-only), Sonnet 4 on Argo (enabled+budget) - Model "Clone" replaces "Copy" (#290): The model row's clone action now opens a prefilled model modal (provider, capabilities, upstream model, and effective reasoning config) with a blank name, matching the provider row's "Clone" behavior — instead of copying a YAML snippet to the clipboard. The model name in the table remains click-to-copy
Fixed¶
- Haiku 4.5
adaptivethinking 400 errors (#287): Haiku 4.5 supports extended thinking but only acceptsthinking.type=enabled+budget_tokens, notadaptive. The previous fallback toadaptivewhen no budget was provided caused 400 errors on Anthropic Official, Argo, and OpenRouter. The newbudget_tokens_default_ratioderives a budget instead - Haiku 4.5
effortparameter 400 errors (#289): Theeffortparameter (output_config.effort) is only supported on Opus 4.5/4.6/4.7/4.8 and Sonnet 4.6 — not Haiku. Anthropic Official rejectedreasoning_efforton Haiku 4.5 with a 400. The Haiku model_override now setseffort_field: noneto drop the unsupported field while keeping the workingthinking.type=enabled+ budget path - OpenRouter Anthropic reasoning effort field (#284): The
openrouter--anthropicshim usesoutput_config.effort(Anthropic format) instead of the OpenAI Chatreasoning_effortfield .envsecret leakage in Docker builds: Docker build context no longer includes.envfiles, preventing API keys from being baked into image layers
v0.6.9 — 2026-06-13¶
Added¶
- API key rotate: New
POST /admin/api/keys/<id>/rotateendpoint generates a fresh key value while preserving the same id and label. The admin panel shows a "Rotate" button with inline confirmation and a one-time copy modal for the new key. Request logs are unaffected — they associate by label, not key value - Model type selector in Fetch from Provider modal: Users can now choose between LLM and Embedding when batch-adding models. LLM shows capability checkboxes (text, vision, tools, reasoning); Embedding auto-sets
['embedding'] - Model type selector in Add/Edit Model modal: Replaces the old embedding checkbox + mutual-exclusion logic with the same Model Type radio pattern
Changed¶
- API key length upgraded: Default generated keys increased from 36 characters (
rsk-+ 32 hex) to 52 characters (rsk-+ 48 hex), matching OpenAI's key length (192-bit entropy)
Fixed¶
- SSE streaming proxy compatibility (#274, #275): Vendored
httpserverv0.1.1 — SSE (text/event-stream) streaming responses now useTransfer-Encoding: chunkedinstead of raw byte flushing withConnection: close. Fixes Go-based reverse proxies (notably NPShttputil.ReverseProxy) misinterpreting SSE data as chunked encoding, producinginvalid byte in chunk lengtherrors and intermittent connection failures under concurrent load. Upstream fix: Oaklight/zerodep#101 - Admin panel active tab not loading after login:
initApp()now triggers data loading for the currently active tab after successful authentication, fixing the issue where the Request Log tab appeared empty until manually switched away and back - Uppercase model type radio labels: Added
text-transform: noneto.fetch-type-radios labelto prevent.form-group labelCSS from uppercasing "Embedding" to "EMBEDDING"
v0.6.8 — 2026-06-11¶
Added¶
- Shim-driven reasoning configuration (#244, #245): Reasoning effort mapping is now declarative. Provider shims declare a
ReasoningCapabilityinprovider.yaml— specifyingdisabledstrategy (omitorthinking_disabled),effort_field,effort_map, andmax_effortcap — instead of hardcoded converter branches. New sharedreasoning_helpers.pyprovidesnormalize_reasoning_input()andapply_reasoning_config()used by all four converters - Expanded reasoning effort ladder (#245): IR
ReasoningEffortLevelexpanded to six levels:minimal,low,medium,high,xhigh,max. Input normalization acceptsnone(maps tomode: disabled) and provider-native values (xhigh,max) as first-class efforts. Provider shims declareeffort_mapto convert IR levels to provider-specific strings andmax_effortto cap the highest level emitted block_indexon IR stream delta events (#246, #249):TextDeltaEvent,ReasoningDeltaEvent, andToolCallDeltaEventnow carry an optionalblock_indexfield, preserving the provider's content block index through IR round-tripscache_creation_tokensinUsageInfo(#252): New field on theUsageInfoTypedDict for Anthropic cache creation token counts- Model-level
thinking_typein shim reasoning config (#256):ReasoningCapabilitygains athinking_typefield to force the outboundthinking.typeto"enabled"or"adaptive".ProviderShimgainsmodel_reasoningfor per-model overrides keyed by upstream model ID (e.g. Argoclaudeopus47 → thinking_type: adaptive). The_normalize_thinkingtransform is retired — thinking type normalization is now declarative via shim YAML - Anthropic
provider_metadataon tool calls, tool results, and reasoning blocks (#257): The Anthropic converter now serializesprovider_metadataas_provider_metadataontool_use,tool_result, andthinkingblocks during IR→provider conversion, and reads it back during provider→IR. Fixes Googlethought_signaturebeing lost in cross-provider round-trips (Anthropic client → Google upstream), which caused Gemini 2.5+ to reject requests with 400 "missing thought_signature" -
Response reasoning losslessness across converters (#263): Reasoning content is now preserved through response-side IR→provider conversion in all converters that previously dropped it:
- Google GenAI:
p_reasoning_to_irnow capturesthoughtSignatureintoprovider_metadatainstead of discarding it;message_opsdelegates tocontent_ops.p_reasoning_to_ir()instead of constructing a bareReasoningPartinline - Anthropic:
ir_text_to_p/p_text_to_irnow round-trip_provider_metadataon text blocks, matching the treatment already applied to reasoning and tool blocks - OpenAI Chat:
_build_choice_to_providernow collectsReasoningPartcontent and emits it asreasoning_contenton the response message, instead of silently dropping reasoning parts
- Google GenAI:
-
Provider-specific reasoning field normalization (#264): Shim transforms and config for MiniMax, OpenRouter, and Volcengine reasoning fields:
- MiniMax:
thinking_type: adaptive(rejectsenabled);_inject_reasoning_splitto_transform auto-setsreasoning_split: truewhen thinking is requested;_parse_think_tagsfrom_transform extracts<think>tags from content as fallback - OpenRouter:
_rename_reasoning_fieldfrom_transform renamesmessage.reasoning→message.reasoning_content(OpenRouter uses non-standard field name) - Volcengine:
thinking_type: enabled(rejectsadaptive; overrides base converter'sauto → adaptivedefault)
- MiniMax:
Changed¶
_build_ir_usagereturn type tightened toUsageInfo(#253): All four converter overrides now returnUsageInfoinstead ofdict[str, Any], and_build_provider_usageacceptsMapping[str, Any]instead ofdict[str, Any]. Removes all usage-relatedty: ignorecomments- Anthropic stream usage handlers deduplicated (#253):
_handle_message_start_from_pand_handle_message_delta_from_pnow call_build_ir_usage()instead of duplicating cache field extraction inline (−21 lines)
Fixed¶
- Anthropic stream block index desync after thinking block (#246, #249): During Anthropic→IR→Anthropic streaming round-trip, text deltas after a thinking block used index 0 instead of the correct block index (e.g. 1). The Anthropic
from_ppath now copieschunk["index"]onto IR delta events, and theto_ppath prefers the explicitblock_indexover the context fallback. Fixes Claude CLI "Content block is not a text block" errors - Cross-provider stream block boundary synthesis (#250, #251): When converting IR streams from providers without content block events (OpenAI Chat, OpenAI Responses, Google GenAI) to Anthropic format, the serializer now emits synthetic
content_block_stop/content_block_startat content-type transitions (e.g. reasoning → text). Previously text deltas could land inside a synthetic thinking block. Addedcurrent_block_typetracking toStreamContext - Stream usage detail propagation (#252): Cache and detail token fields (
cache_read_tokens,cache_creation_tokens,prompt_tokens_details,completion_tokens_details,cachedContentTokenCount) are now preserved through all four converters' streaming paths. Previously these fields were dropped during stream round-trips - OpenAI Chat
thinking.type=autopassthrough (#258): IRmode: "auto"is not a valid upstream value for OpenAI Chat'sthinking.type. The OpenAI Chat converter now mapsauto→adaptivebefore emitting thethinkingobject, and applies the same shimthinking_typeoverride +enabled→adaptivesafety fallback that the Anthropic path uses thinking_type=enabledfallback whenbudget_tokensmissing: When a shim declaresthinking_type: enabledbut the request has nobudget_tokens(required by Anthropic fortype: "enabled"), the converter now automatically falls back totype: "adaptive"instead of emitting an invalid payload. Applied to both Anthropic and OpenAI Chat converter paths- Unsigned Anthropic reasoning blocks in Argo history (#268, #269):
ReasoningCapabilitynow supportsunsigned_reasoning_blocks: as_is | preserve. Theargo--anthropicshim usespreserveso prior assistantthinkingblocks without a usable signature are not forwarded to Argo, avoiding 400 errors while preserving the reasoning content inprovider_metadata.anthropic.unsigned_reasoning_blocks
v0.6.7 — 2026-06-04¶
Fixed¶
- Embedding endpoint upstream_model alias: The
/v1/embeddingspassthrough handler now substitutes theupstream_modelname into the request body before forwarding, matching the behavior of the chat completions proxy handler. Previously model aliases (e.g.bge-m3→BAAI/bge-m3) were ignored, causing upstream model-not-found errors. - Admin test timer leak: The elapsed-time counter is now tracked globally and cleared when a new test starts, preventing multiple timers from writing alternating values to the same display element.
- Admin test timeout auto-cancel: When the browser-side 120s timeout fires, the server-side task is now explicitly cancelled via the API instead of being left running.
- Server-side test task timeout: Added
asyncio.wait_for()with a 120s timeout to_run_test_task, so hung upstream calls are terminated server-side instead of lingering until the 300s cleanup window.
v0.6.6 — 2026-06-03¶
Added¶
- Admin status bar total requests: Lifetime request counter shown as the first footer segment with locale-aware thousand separators; per-segment hover tooltips (en/zh) explain each metric
- Vendor httpclient URL-encoded form data:
httpclientv0.4.2 — whendatais a dict without files, encode asapplication/x-www-form-urlencodedinstead of requiring explicit serialization
Changed¶
- Schema sanitization module split: JSON Schema sanitization extracted from
converters/base/tools.pyinto its ownconverters/base/schema.pymodule for clearer separation of concerns - Cyclomatic complexity reduction: Reduced cognitive complexity across tool ops (cross-converter
extract_part_ids/log_orphan_warningsreuse), gateway auth (check_admin_auth), proxy streaming (process_stream_chunk), config parsing, logging, and admin routes - complexipy threshold: Raised
max-complexity-allowedfrom 15 to 25; addedcomplexipy-pre-commithook definition (commented out) for future enablement
Fixed¶
- Admin footer i18n: Status bar footer now re-renders on language switch instead of requiring a page refresh
- Docker non-semver build:
make build-docker V=dev-testno longer fails — non-semverVvalues fall back to installing from local wheel instead ofpip install ==<version>
v0.6.5 — 2026-06-02¶
Added¶
- API key label filter — new dropdown on the Request Log tab to filter entries by API key name
- Client IP logging — extracts client IP from
X-Forwarded-For/X-Real-IP/ TCP peer address and displays it in a new "Client IP" column on the Request Log tab - System clock — live-updating clock in the admin header for correlating log timestamps with current time
- Dual-threshold log retention — success and error request log entries are pruned independently; errors get their own cap (
error_max) so rare failures are not evicted by a flood of successful traffic - DB sizing footer — admin panel footer shows on-disk database size, entry counts per class, and retention caps
Fixed¶
- Provider filter — filter now correctly matches entries by provider display name, with three-tier fallback (
target_provider_name→target_provider→ API type for legacy NULL rows) to handle backfill gaps and disabled providers /healthinfo leak — endpoint no longer exposes the full provider and model list to unauthenticated callers; now returns only{"status": "ok"}- i18n completeness — added missing Chinese translations for footer stats, system time label, filter options, and Client IP column header
Changed¶
- Shim directory layout — provider shims now support grouped subdirectories (e.g.
argo/anthropic/,argo/openai_chat/) - Schema migration —
_migrate_add_columns()is now generic, adding any missing nullable columns in a single pass - CI — switched to pre-commit for lint/type checks, pinned ty version
v0.6.4 — 2026-05-20¶
Added¶
- Tinyleaf-style settings popup: Replace the modal-overlay settings dialog with a lightweight centered popup — click outside or press Escape to dismiss, theme and language via
<select>dropdowns with instant apply, About section with version and project links (GitHub, PyPI, Docker Hub, Docs) - Lightweight host IP detection endpoint:
GET /admin/api/diagnostics/host-ipreads/proc/net/routeonly (microsecond-level, no network calls); proxy URL placeholders auto-update with the correct Docker host IP on page load - Admin login persistence: Login state stored in
localStoragewith 30-minute inactivity auto-logout, logout button in header, password manager compatibility (proper<form>,autocompleteattributes) - Inline delete confirmation: Two-step confirm for models, API keys, and request logs replaces native
confirm()dialogs - Test modal improvements: Cancel button with elapsed timer, chart empty state message, Clone button for providers/models
- Mobile responsiveness: Responsive header with wrapping, horizontally scrollable tabs and tables
Fixed¶
- Argo Anthropic response normalization: Detect and convert OpenAI Chat Completions format responses from Argo's
/v1/messagesendpoint to Anthropic Messages format - Model-level
thinking_typein shim reasoning config (#254, #256):ReasoningCapabilitysupportsthinking_typeto forcethinking.typeto"enabled"or"adaptive".ProviderShimgainsmodel_reasoningfor per-model overrides keyed by upstream model ID. Argoclaudeopus47 → thinking_type: adaptiveviamodel_overrides._normalize_thinkingtransform retired — thinking type normalization is now declarative - Inline confirm i18n and onclick restore: Add missing
confirm.sure/confirm.yestranslation keys; restore originalonclickhandler after confirmation reverts - Reverse proxy caching: Add
Cache-Control: no-cache, no-store, must-revalidateon all admin API responses; switch test polling to POST - Login overlay loop: Prevent login overlay from dismissing password manager autofill popups
- C901 complexity: Extract
_format_connection_errorhelper fromfetch_upstream_models
Security¶
- Admin login rate limiting: 5 failed attempts trigger a 5-minute IP lockout
Changed¶
- Settings UI simplified: Themes reduced to Light/Dark; theme and language selectors moved from header dropdowns into the settings popup
v0.6.3 — 2026-05-17¶
Added¶
- Full
custom_tool_callsupport for OpenAI Responses API: Handle thetype: "custom"tool type end-to-end — request ingestion (coerce to IRtype: "function"with_passthroughfor round-trip), response parsing (custom_tool_callitems with plain-textinput), and streaming (response.custom_tool_call_input.delta/doneevents). Cross-provider degradation synthesizes a single-string-param JSON Schema so custom tools remain usable on Anthropic/Google tool_typefield on IRToolCallStartEvent: Streaming events now carrytool_type("function", "custom", etc.) so converters can emit the correct provider-specific event types- Argo shims with
model_id_fieldandupstream_modelalias: Newargo_openai,argo_anthropic,argo_googleprovider shims that rewrite the model field name for Argo-proxied endpoints. Includes thinking normalization transform forargo_anthropic - Async server-side test tasks: Admin panel test requests now run in background tasks, preventing browser connection pool exhaustion on slow models
- Admin login rate limiting: Brute-force protection on the admin login endpoint
Fixed¶
- Stored XSS in admin UI: Escape single quotes in the
esc()helper to prevent injection via provider/model names custom_tool_callstreaming type loss in gateway:OpenAIResponsesStreamContext.from_base()now copies_tool_call_types, fixing custom tools falling back tofunction_callevent types during IR→provider streaming- Admin UI regressions: Fix infinite recursion in fetch models checkbox handler, allow API key editing regardless of
credential_visiblesetting, remove prefix real-time preview input lag, fix fetch models prefix losing selections, abort test requests on modal close - Reasoning test
max_tokenstoo small: Enforcebudget_tokens >= 1024for reasoning capability tests - httpclient AsyncClient serialization lock: Update vendored httpclient to v0.4.1, use per-task AsyncClient for test self-calls to avoid deadlock
- ty type-check errors: Resolve compatibility issues with ty 0.0.32+
Changed¶
- Admin routes split into subpackage: Refactored monolithic
routes.pyintoroutes/with dedicated modules for auth, config, keys, observability, and testing - CI switched to pre-commit: Linting now uses
pre-commit run --all-files(ruff + ty); complexipy suspended pending upstream fix
v0.6.2 — 2026-05-15¶
Added¶
- Admin password protection:
server.admin_passwordin config enables a login overlay for the admin panel, using HMAC-based session tokens - Credential visibility control:
server.credential_visible: falsehides API key viewing/copying across the admin UI - Provider cascade delete: Deleting a provider now shows affected models and cascade-deletes them
Fixed¶
- Base URL overwrite: Switching provider type no longer overwrites user-entered base URLs
- Request log collapse: Expanded error detail rows persist across auto-refresh
Changed¶
- Zero-dependency on Python ≥3.11: Replaced PyYAML with vendored zerodep yaml module
v0.6.1 — 2026-05-15¶
Added¶
/v1/embeddingspassthrough endpoint: Proxy embedding requests directly to upstream providers without IR conversion — the OpenAI embeddings format is universal across compatible providers. Includes metrics and request log instrumentation/v1/modelsenriched response: Model listing now includesapi_standard(e.g."openai_chat","anthropic") and per-modelcapabilitiesfields- "Fetch from Provider" in admin panel: Query upstream
/v1/models(or equivalent) endpoint from the Models tab, browse available models with checkboxes, and bulk-add with optional prefix. Already-existing models shown as disabled - Model management enhancements: Provider filter dropdown and model name search in the Models tab
- Embedding capability and test type:
embeddingcapability in the model editor (mutually exclusive withvision/tools). Embedding models get a single Test button that POSTs to/v1/embeddingsand displays dimension count - Reasoning capability and test type:
reasoningcapability with dedicated test that sendsreasoning_effort: "low". Mutually exclusive withembedding - Admin panel tab persistence: Active tab stored in
localStorage, survives page refresh
Fixed¶
- Missing event loop in SOCKS5 proxy tests: Use
asyncio.new_event_loop()as fallback when prior tests have closed the default event loop - Type assertion for httpclient response in fetch_upstream_models: Resolve ty type-check error for
AsyncClient.get()return type
v0.6.0 — 2026-05-15¶
Added¶
- Provider shim layer with declarative YAML directory: Shims are now defined as
provider.yaml+ optionaltransforms.pyfiles undershims/providers/<name>/, automatically discovered and registered at import time - Transform mechanism for provider-specific field adaptation: Three composable primitives —
strip_fields(),rename_field(),set_defaults()— handle field-level differences between a provider's API dialect and its base standard - 7 new built-in provider shims: xAI (Grok), Qwen (DashScope), Moonshot (Kimi), MiniMax, Zhipu (GLM), OpenRouter, Volcengine — each with provider-specific transforms where needed
- Gateway proxy applies shim transforms: The gateway request/response pipeline now applies
to_transformson outbound requests andfrom_transformson inbound responses and stream chunks - Provider logos in admin panel: Provider shims can declare a
logoURL (SVG), displayed in the admin panel provider cards - SOCKS5 proxy support restored: Updated vendored
httpclientfrom zerodep v0.3.1 to v0.4.0, which includes full SOCKS5 proxy support (RFC 1928/1929, with username/password authentication). Both--proxy socks5://...CLI flag and"proxy": "socks5://..."config entries now work for all upstream requests
Changed¶
- Shim system refactored to declarative YAML: Replaced programmatic
builtins.pywith a directory-based system (shims/providers/*/provider.yaml+transforms.py). Adding a new provider now requires only YAML + optional Python, no changes to core code - Vendored
validateupdated to zerodep v0.5.0: AddsFieldValidatorandmodel_validatorfor field-level transform+validate pipelines
Removed¶
ModelShimclass removed: Model-level metadata removed in favor of simpler provider-only shims. TheProviderShimdataclass no longer has amodelsfield
Refactored¶
- Zero-dependency gateway (#178): Replaced Starlette + uvicorn + httpx with vendored zerodep
httpserverandhttpclientmodules. The[gateway]extra now has zero external runtime dependencies
Fixed¶
- Deep-merge properties in schema flattening (#161): Fix
$ref/$defsresolution to deep-merge properties and strip orphanedrequiredentries - Unconditional usage fallback and StreamContext merge (#176): Guard against missing usage data and ensure StreamContext state is properly merged
Known Issues¶
- Google tool schema
requiredvalidation (#161): Some Anthropic tool schemas haverequiredentries referencing properties not defined in the schema, causing Google API to reject withINVALID_ARGUMENT
v0.5.3 — 2026-04-25¶
Added¶
- OpenAI Chat converter: thinking config support (#170): The OpenAI Chat converter now handles
reasoning_configin IR requests, mapping to OpenAI'sreasoning_effortparameter. Enables thinking/extended thinking configuration when routing through the Chat Completions API - OpenAI Chat converter:
reasoning_contentfield handling: Non-streaming and streaming responses from reasoning models (e.g., o1, o3) now correctly extract thereasoning_contentfield and convert it to IRReasoningPart, preserving chain-of-thought content during cross-provider conversion - Upstream error body in admin request log: When an upstream provider returns an error, the response body is now included in the admin request log entry, making it easier to diagnose upstream failures without checking server logs
- Copy entry buttons for providers and models in admin page: Provider and model entries in the admin panel now have copy/duplicate buttons for quickly creating new entries based on existing configurations
Fixed¶
FilePartexcluded fromUserContentPart(#160, #162):UserContentPartunion type did not includeFilePart, causingvalidate_ir_request()to reject any user message containing file content (e.g., PDF attachments sent by Claude Code as Anthropicdocumentblocks). The bidirectional conversion logic was already implemented for Anthropic (document), Google (inlineData), and OpenAI Responses (input_file) — only the type definition was missinggoogle_genai/content_ops.pyunconditionalhttpximport (#163): Replacedhttpxwithurllib.requestin the Google GenAI content converter for image URL downloads.httpxwas only declared as a[gateway]optional dependency but was imported unconditionally, causingModuleNotFoundErrorwhen installed without[gateway]extra- Emoji icons replaced with SVG in API key management: API key action buttons in the admin panel used emoji characters that rendered inconsistently across platforms. Replaced with inline SVG icons and added a key visibility toggle button
- API key column layout shift: Fixed CSS layout issue where the API key column width changed when toggling key visibility, causing adjacent buttons to shift position
- Wheel path glob collision with extras brackets: Quoted the wheel file path in CI install commands to prevent shell glob expansion when the filename contains
[extras]bracket syntax
Refactored¶
- SQLite persistence backend: Replaced the JSONL-based request log and JSON-based metrics persistence with a unified SQLite backend. Provides better write durability, atomic operations, and eliminates log rotation complexity. Vendored
persistdictfrom zerodep (v0.4.1) as the key-value storage layer
CI/Build¶
- Install smoke tests: Added CI smoke tests that verify
pip installsucceeds for bothllm-rosetta(core) andllm-rosetta[gateway]variants, catching missing or circular dependencies early
v0.5.2 — 2026-04-19¶
Fixed¶
- Streaming round-trip event inflation (#157): Fixed multiple scenarios where
Provider A → IR → Provider Bstreaming conversion produced more output events than input events:- OpenAI Chat, Anthropic, and Google GenAI converters emitted redundant
content_block_endevents when no content block was open, inflating the output stream - Google GenAI compound chunks (text + finish in the same SSE frame) triggered duplicate text and finish events. Deferred text/finish payloads via
StreamContext.pending_text/pending_finishso they merge into a single event - Tool call events generated spurious
content_block_start/content_block_endwrappers in non-Anthropic targets. Suppressed via_startedlifecycle guard
- OpenAI Chat, Anthropic, and Google GenAI converters emitted redundant
Refactored¶
- Unified
stream_response_to_providerdispatch (#157): Extracted identical dispatch logic (10-entry_TO_P_DISPATCHtable + dispatch skeleton) from all 4 provider converters intoBaseConverter. Each converter now only implements a provider-specific_post_process_to_providerhook (OpenAI Chat injects envelope fields; OpenAI Responses injectssequence_number). Net reduction: ~27 lines StreamContextbuffer convenience methods: Addedbuffer_usage()/pop_pending_usage()/buffer_finish()/pop_pending_finish()to replace manual set-and-clear patterns across all converters
Changed¶
- Pinned dev tooling versions:
ty>=0.0.31andruff>=0.15.0now declared inpyproject.tomldev dependencies. CI no longer installs them separately — uses versions frompip install -e ".[all]" - Converter tests added to CI:
tests/converters/(1086+ tests) now runs in GitHub Actions alongsidetests/test_types/ - Roundtrip inflation regression test: New pytest-parametrized test suite (
tests/converters/test_roundtrip_inflation.py, 15 cases) verifieslen(output_events) <= len(input_events)for all 4 providers across text, reasoning, tool call, and compound scenarios
v0.5.1 — 2026-04-15¶
Added¶
tool_opsconvenience API (#148): New top-levelllm_rosetta.tool_opsmodule for standalone tool definition conversion without instantiating full converter pipelines. Providesto_provider()/from_provider()unified dispatch and per-provider shortcuts (to_openai_chat(),to_anthropic(), etc.). All imports are lazy- Multi-key API management: Admin panel now supports multiple API keys per gateway with per-key labels, create/reveal/delete operations, and usage tracking in request logs
- Gateway API key authentication: Configurable API key (
server.api_key) protects AI request endpoints (/v1/*). Supports format-native credential extraction — OpenAIAuthorization: Bearer, Anthropicx-api-key, Googlex-goog-api-key/?key=query param. When no key is configured, all requests pass through (backward compatible) - Provider enable/disable: Each provider now supports an
enabledfield (defaulttrue). Disabled providers and their models are silently excluded from routing - Docker support: Official
Dockerfile,docker-compose.yml, and Makefile targets (build-docker,push-docker,run-docker) for containerized deployment. Alpine-based image with non-root user, config volume mount, and PUID/PGID support - Admin panel enhancements:
- Provider toggle switches (enable/disable without deleting)
- Model search and column sorting
- Provider rename with automatic model reference updates
- Network diagnostics button (connectivity check + proxy test)
- Model testing with collapsible raw request/response details and image preview for vision tests
- Embedded test image (base64 data URI) to avoid external network downloads
reasoning_effort: 'low'for reasoning model tests to limit token budget
Changed¶
- Admin panel authentication removed from gateway: Admin panel endpoints (
/admin/*) no longer require the gateway API key. Admin access control is delegated to the reverse proxy (e.g. Caddy, Nginx). The gateway API key now only authenticates AI request endpoints (/v1/*) - C901 cyclomatic complexity enforced at threshold 15: Progressive reduction from 25 → 20 → 15 across all converters and gateway modules. Extracted cross-provider consistency helpers (
_build_ir_usage,_build_provider_usage,_convert_tools_from_p,_apply_tool_config) with identical names across all 4 converters BaseConverterabstract methods: Four new abstract methods formalize the cross-provider helper pattern. Preserve-mode hooks documented as convention for providers supporting lossless round-trip- Vendored
validate.pyupdated to zerodep v0.4.2: Internal refactor of monolithic_validate()into focused helpers; no functional changes
Fixed¶
- User-Agent header for image URL downloads: Google GenAI content converter now sends
User-Agent: llm-rosetta/1.0 (image fetch)when downloading image URLs for inline base64 conversion, preventing 403 Forbidden from servers like Wikimedia - Image URL download with proxy support: Image downloads in the Google GenAI converter now respect
HTTPS_PROXY/HTTP_PROXYenvironment variables - Empty content fallback for reasoning models: Admin panel test results now correctly handle
content: ""(from reasoning models where allmax_tokensare consumed by reasoning tokens) instead of showing raw JSON - Config file not found error: Gateway now shows a friendly error message when the config file doesn't exist, instead of a Python traceback
- ty type checker compatibility: Added
ty: ignoreannotations for TypedDict vsdict[str, Any]mismatches andFinishReasonLiteral type narrowing - Google converter crash when thinking consumes all tokens (#152): Gemini 2.5 Pro with small
max_tokenscould have all tokens consumed by thinking, producing a response with no content parts. The converter now falls back to an empty assistant message instead of failing IR validation
v0.5.0 — 2026-04-12¶
Added¶
- Gateway Admin Panel: Built-in web admin panel at
/admin/for managing gateway configuration, monitoring traffic, and inspecting request logs without editing config files or restarting the server- Configuration tab: Visual management of providers (add, edit, rename, delete) and model routing with capabilities (text/vision/tools)
- Dashboard tab: Real-time metrics with summary cards (total requests, error rate, active streams, uptime), rolling 60-second throughput and latency charts, per-provider breakdown
- Request Log tab: Filterable request log with model, provider, and status filters, paginated view with color-coded status codes
- 8 themes: Light, Indigo Dark, Dracula, Nord, Solarized, Osaka Jade, One Dark, Rosé Pine — persisted in localStorage
- i18n: English and Chinese language support with localStorage persistence
- File-based persistence: Metrics counters (JSON) and request log (JSONL) are automatically saved to disk alongside the config file. Data survives server restarts. Log rotation with gzip compression (2 MB limit, 3 backups)
- Provider rename: Renaming a provider automatically updates all model routing references
- API key security: Masked keys on provider cards, reveal-on-demand with visibility toggle and copy button in edit modal. Masked values are never written back to config
Changed¶
- Provider names decoupled from API standard types: Provider names are now user-defined strings (e.g.
"my-openai","OpenRouter_anthropic") instead of being constrained to the 4 standard type identifiers. A separatetypefield specifies the API standard (openai_chat,openai_responses,anthropic,google) - Extracted
write_config()toconfig.pyfor shared use by CLI and admin panel
v0.4.2 — 2026-04-11¶
Changed¶
ReasoningConfig.enabledreplaced withmodefield: The booleanenabledfield has been replaced bymode: Literal["auto", "enabled", "disabled"]. This aligns the IR more closely with provider semantics (Anthropic's three-waythinking.type, OpenAI Responses'reasoning.type). Omittingmoderetains the previous "provider default" behavior. Theeffortfield now lives directly inReasoningConfigrather than being nested
Fixed¶
- Responses API
developerrole mapping: The OpenAI Responses API usesrole: "developer"(equivalent to Chat's"system"). Previously this role was passed through to IR unchanged, causing validation failures. Now correctly mapped to IR"system"during Provider→IR conversion - Google GenAI
additionalPropertiesrejection: Google's function_declarations API rejects theadditionalPropertiesJSON Schema keyword. Addedextra_strip_keysparameter tosanitize_schema()so providers can strip provider-specific unsupported keywords. Google tool_ops now stripsadditionalPropertiesrecursively from nested schemas - Google GenAI
prompt_tokens_detailsformat mismatch: Google returns modality token details aslist[ModalityTokenCount](e.g.[{"modality": "TEXT", "token_count": 42}]) but IR expectsdict[str, int](e.g.{"text_tokens": 42}). Added bidirectional conversion helpers_modality_list_to_dict()and_dict_to_modality_list(). Handles both SDK (token_count) and REST API (tokenCount) field names - Cross-format tool call ID prefix mapping: The Responses API enforces
fc_prefix on tool call IDs, but Chat usescall_and Anthropic usestoolu_. Added automatic prefix mapping during Responses conversion to prevent validation failures in cross-format scenarios - Adaptive thinking fallback: When converting IR reasoning config to Anthropic format,
mode: "enabled"withoutbudget_tokensnow correctly falls back to{"type": "adaptive"}with a warning, instead of producing an invalid{"type": "enabled"}without the requiredbudget_tokens
v0.4.1 — 2026-04-10¶
Added¶
force_conversionparameter forconvert(): Newforce_conversion: bool = Falsekeyword-only parameter. WhenTrue, the full source→IR→target pipeline runs even when source and target providers match, ensuring parameter normalization (e.g.max_tokens→max_completion_tokensfor OpenAI Chat). DefaultFalsepreserves existing passthrough behavior
Fixed¶
- Vendored
validate.pyupdated from zerodep v0.4.1: Applied pyupgrade fixes —Callableimported fromcollections.abcinstead oftyping(UP035),@functools.cachereplaces@functools.lru_cache(maxsize=None)(UP033) - Removed unused
sysimport in benchmark script - Applied
ruff formatto benchmark scripts
Changed¶
- Removed incorrect "Related Projects" section from README — LLM-Rosetta is an independent project, not part of the ToolRegistry ecosystem
v0.4.0 — 2026-04-09¶
Added¶
- Metadata preservation for lossless A→IR→A round-trip (#60, PR #119): New
MetadataMode("strip"/"preserve") option inConversionContextthat captures provider-specific fields duringfrom_providerand re-injects them duringto_provider, enabling lossless round-trip conversion. Helper methods onConversionContext:store_request_echo(),store_response_extras(),store_output_items_meta(),get_echo_fields(),get_output_items_meta(). Per-provider coverage:- OpenAI Responses: captures/restores 28+ echo fields (temperature, tools, reasoning, truncation, etc.), per-output-item metadata (id, status, annotations, logprobs),
RESPONSES_REQUIRED_DEFAULTSdict for spec-required fields with sensible defaults,sequence_numberon all SSE events - Anthropic: preserves
stop_sequence,container, citations, and OpenRouter extension usage fields - OpenAI Chat: now re-emits
refusalandannotationsfields inresponse_to_provider(previously dropped) - Google GenAI: preserves
promptTokensDetailsandcachedContentTokenCountin usage metadata - Gateway: automatically enables preserve mode for both streaming and non-streaming paths; bridges metadata between
from_ctxandto_ctxduring streaming
- OpenAI Responses: captures/restores 28+ echo fields (temperature, tools, reasoning, truncation, etc.), per-output-item metadata (id, status, annotations, logprobs),
Fixed¶
- Open Responses spec compliance for streaming and non-streaming: Added required fields to all SSE events (
item_id,logprobs,annotations,status,sequence_number,output_index,content_index), usage detail breakdowns (output_tokens_details,input_tokens_details), message item IDs and status for non-streaming output items,function_callstatus field in tool_ops,service_tierdefault to"default"(string, not null per spec),completed_atin required defaults,created_atfallback to current time when not provided, normalized echoed tools withstrict: null, and metadata bridging fromfrom_ctxtoto_ctxin gateway streaming. All 6 Open Responses compliance tests now pass (schema + semantic)
v0.3.1 — 2026-04-07¶
Fixed¶
service_tier: Noneandsystem_fingerprint: Nonecausing validation errors (PR #118): OpenAI upstream returns these fields asnull, but the existence check (if "key" in dict) passed and assignedNoneto IR'sNotRequired[str]field. Changed to value-not-None check in both OpenAI Chat and OpenAI Responses converters. Discovered via Oaklight/argo-proxy#99- Base
StreamContextmissing provider-specific attributes in Responses streaming (PR #118): When a gateway passes a baseStreamContexttoOpenAIResponsesConverter.stream_response_to_provider(), the method accessesaccumulated_text,output_item_emitted, etc. that only exist onOpenAIResponsesStreamContext. Added auto-upgrade viafrom_base()classmethod with metadata caching to preserve state across calls
v0.3.0 — 2026-04-07¶
Added¶
- Multimodal tool result support across all 4 converters (#92, PR #109): Tools can now return multimodal content (text + images + files) as
ToolResultPart.result. Three providers (Anthropic, OpenAI Responses, Google GenAI) support this natively; content blocks are converted through each provider'scontent_opslayer. See provider support matrix below - Lossless multimodal tool result roundtrip for OpenAI Chat (#92, PR #108): OpenAI Chat Completions only accepts
content: stringfor tool messages. Implements a dual encoding strategy — tool message keepsjson.dumps(result)as data fallback, plus a synthetic user message carries visual content (image_urlparts) wrapped in<tool-content call-id="...">XML tags. Unpacking recovers multimodal structure from the synthetic message (preferred) or falls back to JSON parsing if the synthetic message was trimmed by agent frameworks extract_all_text()helper function (PR #109): Extracts text from bothTextPartandReasoningPartcontent — useful for thinking models (e.g. gemini-2.5-flash) that may place answers in reasoning parts rather than text partsgenerate_chartexample tool (PR #109): New multimodal tool inexamples/tools.pyreturning[TextPart, ImagePart]with inline base64 PNG, plusmultimodal_tools_speccombining all 3 example tools- Multimodal integration tests across all 4 provider SDKs (PR #109): Two new test scenarios per provider — (A) tool returning multimodal content (text + image), (B) image input combined with tool calls. All 30 tests pass against official APIs: OpenAI Chat 9/9, OpenAI Responses 6/6, Anthropic 8/8, Google GenAI 7/7
- Runtime IR validation via vendored zero-dependency validator (#91):
validate_ir_request(),validate_ir_response(), andvalidate_ir_messages()utilities validate IR structures against their TypedDict definitions at runtime. All 4 converters now validate output inrequest_from_provider()andresponse_from_provider(). Replaces manualBaseMessageOps.validate_messages. Includes Python <3.11 compatibility fortyping_extensions.TypedDict - Constants validation tests: 39 new tests across 4
test_constants.pyfiles verifying that all reason mapping values are valid IR finish reasons, mapping coverage is complete, event type constants are well-formed, and ID generation produces correct formats - Finish reason mapping test coverage: 38 tests validating reason mapping correctness as a safety net for the constants refactoring
ConversionContextbase class for conversion pipelines (#106, PR #111): NewConversionContextdataclass withwarnings: list[str],options: dict[str, Any], andmetadata: dict[str, Any]— a structured context container for non-streaming conversions. NewBaseConverter.create_conversion_context(**options)factory method mirrors the existingcreate_stream_context(). All 6 non-streamingBaseConvertermethods now accept an optionalcontext: ConversionContextkeyword parameter; converter implementations sync warnings tocontext.warnings. Gateway proxy creates a shared context per request and passes it through the full source→IR→target→response pipeline
Fixed¶
- Contextual error messages for tool conversion failures (#85, PR #110): When
p_tool_definition_to_ir()fails on a malformed or unsupported tool definition, theValueErrornow includestype=andname=context so users can identify which tool caused the issue. Applied to all 4 converters (OpenAI Chat, OpenAI Responses, Anthropic, Google GenAI) with unit tests - OpenAI Responses
tool_choiceformat (PR #109): Was using Chat Completions format ({"type": "function", "function": {"name": "..."}}); now uses Responses format ({"type": "function", "name": "..."}) - OpenAI Responses tool call ID round-trip (PR #109): Responses API uses
fc_prefix IDs while IR usescall_prefix. The Responsesidis now preserved inprovider_metadataseparately fromcall_id, enabling lossless round-trip conversion - OpenAI Responses reasoning item round-trip (PR #109): Reasoning models (e.g. gpt-5-nano) emit reasoning items with
id(rs_ prefix), structuredsummaryarrays, andencrypted_content. These are now preserved throughprovider_metadatafor lossless round-trip — fixes 400 errors when reasoning items were sent back without their originalid - IR validation accepts
Nonefor optional response fields (PR #109):logprobsandsystem_fingerprintinIRResponsenow acceptNonevalues (previously only accepted missing keys) - OpenAI Responses
content_filterfinish reason mapped to wrong status (#90):content_filterwas incorrectly mapped to"completed"status inresponse_to_providerandstream_response_to_provider. Now correctly maps to"incomplete"status withincomplete_details.reason = "content_filter" - Anthropic streaming missing
refusalreason mapping: The streamingreason_mapwas missing therefusalentry present in the non-streaming path, causing Anthropic refusal stop reasons to be silently dropped during streaming. Fixed as a side effect of the constants extraction (#64) — both paths now share the sameANTHROPIC_REASON_FROM_PROVIDERdict
Changed¶
ReasoningConfig.effortexpanded to 5-level enum (#100): Effort levels now include"minimal","low","medium","high","max". Provider-specific mappings: Anthropic maps tothinking.type="adaptive"withthinking.effort; OpenAI Chat/Responses clamp"minimal"→"low"and"max"→"high"(with warnings); Google GenAI maps tothinking_config.thinking_levelReasoningConfig.typereplaced withReasoningConfig.enabled(#70): Thetype: Literal["enabled", "disabled"]field is replaced withenabled: boolto avoid shadowing the Python built-intypeand provide a more natural API- Merged duplicate IR concepts (#69): Removed
candidate_countfromGenerationConfig— useninstead (Google GenAI converter mapsn↔candidate_countinternally). Unifiedsystem_instructiontype fromstr | list[dict]tostr - Normalized
ImagePart,FilePart,AudioPartto canonical forms (#68): Each part now has exactly two canonical forms — URL reference + structured inline data (e.g.image_data) — plus a unifiedprovider_ref: dict[str, Any]for provider-specific references. Removed redundant top-leveldata/media_typefields and replacedfile_id/audio_idwithprovider_ref - IR type fields changed from
Iterabletolist; function parameters toSequence(#67): TypedDict fields now uselistfor indexable, serialization-friendly semantics; function parameters useSequence(covariant, read-only). Also fixes a latent generator-consumption bug instrip_orphaned_tool_config StreamContextnow inherits fromConversionContext(#106, PR #111):StreamContextis a subclass ofConversionContext(IS-A relationship), unifying the context model for streaming and non-streaming paths. File renamed:base/stream_context.py→base/context.pyStreamContextconverted to dataclass with provider subclass (#65):StreamContextis now a@dataclasswith typed fields (eliminates defensivegetattr/hasattrpatterns). OpenAI Responses-specific state extracted intoOpenAIResponsesStreamContextsubclass. NewBaseConverter.create_stream_context()factory method
Refactored¶
- Warnings single-source convergence (#113, PR #115): All 4 converter
request_to_providermethods now useConversionContextas the single accumulation point for warnings. Eliminates the dual-write pattern where warnings were written to both a local list andcontext.warnings. The returned warnings list IS the same object ascontext.warnings— no duplication possible ProviderMetadataStorereplaces global metadata cache (#112, PR #117): The module-level_provider_metadata_cachedict inproxy.pyis replaced withProviderMetadataStore— a class with TTL-based expiration (30 min), max-size eviction (10k entries), and explicit lifecycle management. The store is created per-app increate_app()and passed viaapp.state, eliminating implicit global mutation.close_clients()renamed toclose_resources()to also clear the store on shutdown- Shrink public API export surface (#114, PR #116): Reduced
__all__exports across converter packages to only the primary converter class, removing internal implementation details (*MessageOps,*ContentOps,*ConfigOps,*ToolOps,*Constants) from the public API. Internal modules remain importable for advanced use but are no longer promoted as public surface - Extracted stream event handlers from monolithic methods (#63): Replaced 8 monolithic
if/elifstream methods (~1,781 lines) across all 4 converters with individual handler methods dispatched via class-level handler tables. Public API unchanged - Extracted shared utility functions in OpenAI Responses converter (#66):
resolve_call_id()andbuild_message_preamble_events()extracted fromconverter.pyintoutils.pywith dedicated unit tests - Extracted per-provider constants for reason mappings and magic values (#64): Inline reason mapping dicts, SSE event type string literals, status-to-reason conditional logic, and ID generation patterns across all 4 converters are now centralized in per-provider
_constants.pymodules. IncludesAnthropicEventTypeandResponsesEventTypeclasses,REASON_FROM_PROVIDER/REASON_TO_PROVIDERdicts, andgenerate_tool_call_id()/generate_message_id()helpers
v0.2.6 — 2026-03-29¶
Fixed¶
- Chat Completions tool message ordering after Responses API conversion (@caidao22): Codex CLI interleaves
function_call_outputwith other items (e.g. user warnings) in Responses API format — valid there since items match bycall_id. But after IR → Chat Completions conversion, the interleaved messages break the OpenAI Chat API constraint thatrole: "tool"messages must immediately follow theirassistanttool_calls, causing upstream 400 errors. Added_reorder_tool_messages()post-processing inOpenAIChatMessageOps.ir_messages_to_p()that groups tool responses back to their corresponding assistant messages - Orphaned
tool_choice/tool_configstripped when no tools defined (@caidao22): Codex context compaction can drop all tool definitions while keepingtool_choice(e.g."auto"), causing upstream APIs to reject with "tool_choice is set but no tools are provided". Addedstrip_orphaned_tool_config()in all four converters — part of the same Codex compaction fix family asfix_orphaned_tool_calls_ir(orphaned tool_call/result pairing) and_reorder_tool_messages(tool message ordering). Also extendedfix_orphaned_tool_calls_irto Google GenAI converter for completeness (#87) - Stream event ordering:
UsageEventis now emitted beforeFinishEventin all four provider converters (OpenAI Chat, OpenAI Responses, Anthropic, Google GenAI). PreviouslyFinishEventwas processed first, causingresponse.completedto carryoutput_tokens=0— downstream consumers (e.g. Codex token tracking) saw stale usage data. For cross-chunk scenarios (OpenAI Chat sendsfinish_reasonandusagein separate chunks),FinishEventnow defersresponse.completedtoStreamEndEventwhich merges any pending usage - Parallel tool calls merged into one in Anthropic/Google → Chat streaming: Anthropic and Google GenAI
stream_response_from_provideremittedToolCallStartEventandToolCallDeltaEventwithouttool_call_index. When routing to Chat Completions, all parallel tool calls defaulted to index 0, causing the client SDK to merge them into a single call. Anthropic now derivestool_call_indexfromcontext._tool_call_orderposition; Google computes it from registration order in context (#88, #89) - Missing
idfield on Responsesfunction_calloutput: Non-streamingresponse_to_providerwas missing theidfield onfunction_calloutput items. Streaming used a syntheticfc_prefix that could leak into IR viap_tool_call_to_irfallback path. Unified both paths to usecall_iddirectly asid(no prefix) - Responses streaming
item_idand emptytool_call_idresolution (@caidao22): Addeditem_idtracking toStreamContext(tool_call_item_id_map, bidirectional mapping). Responsesstream_response_to_providernow emitsitem.idonoutput_item.addedanditem_id(notcall_id) onfunction_call_arguments.delta/doneevents. Defense-in-depth: resolves emptytool_call_idbytool_call_indexvia context (#86) - Non-function tool names mangled with type prefix (@caidao22): Non-function IR tool definitions (e.g.
type="custom",name="apply_patch") were converted with a type prefix (custom_apply_patch), breaking tool_call matching since the client expects the original name. Both OpenAI Chat and Responses converters now useir_tool["name"]directly (#84)
v0.2.5 — 2026-03-23¶
Fixed¶
- Anthropic
input_schemamissingtypefor parameterless tools: MCP tools with no parameters produceinput_schema: {}, but Anthropic requires"type"to be present. Now defaults to{"type": "object"}when the schema dict lacks atypefield — fixestools.0.custom.input_schema.type: Field requirederrors when routing Google GenAI or OpenAI Responses tool calls to Anthropic upstream - Google GenAI camelCase field handling across the full converter stack: Gemini CLI and the Google REST API use camelCase (
inlineData,fileData,mimeType,fileUri,functionCall,functionResponse,finishReason,usageMetadata,responseMimeType,responseSchema,thinkingConfig,maxOutputTokens,stopSequences, etc.), but the converter only accepted snake_case. All P→IR methods in content_ops, config_ops, tool_ops, message_ops, and converter now accept both conventions; all IR→P methods now output camelCase for REST API compatibility - Image/audio/file data lost during Google→IR conversion:
p_part_to_irchecked forinline_data(snake_case) but Gemini CLI sendsinlineData(camelCase) — binary content was silently dropped with a不支持的Part类型warning. Fixed by normalizing camelCase keys at the dispatch entry point - Cross-format image conversion failure (Google → OpenAI/Anthropic): Google's
p_image_to_irproducesImagePartwith top-leveldata+media_typefields, but OpenAI Chat, Anthropic, and OpenAI Responsesir_image_to_ponly checkedimage_urland nestedimage_data— threwValueError. All three target converters now handle top-level fields as a fallback path (#68) - Google GenAI tool_call_id reconciliation: Google
functionCallhas no ID field, so UUIDs are generated during P→IR. But Gemini CLI assigns its own IDs tofunctionResponse(format:name_timestamp_index), creating a mismatch. New_reconcile_tool_call_idsmethod matches tool results to tool calls by function name, fixing orphaned tool_call errors - tool_call_id exceeds OpenAI 40-character limit: Generated IDs used
call_{name}_{8hex}format — MCP tool names likemcp_toolregistry-hub-server_datetime-nowproduced 54-char IDs. Shortened tocall_{24hex}(fixed 29 chars) - Google→IR role mapping for tool results:
functionResponseparts producedrole: "user"IR messages, sofix_orphaned_tool_calls_ir(which checksrole: "tool") couldn't detect them. Now separatesfunctionResponseintorole: "tool"messages with explicit"tool": "user"in_IR_TO_GOOGLE_ROLE - Mixed content message ordering: When a Google message contains both
functionResponseandinlineData, the content parts were emitted before tool results, breaking OpenAI's requiredassistant(tool_calls) → tool(response)ordering. Tool results now precede content parts in the split - Google built-in tools (googleSearch, codeExecution):
p_tool_definition_to_irnow returnsNonefor tool entries without anamefield; converter skips them instead of producing emptyfunction.nameerrors - Gateway: Starlette
on_shutdowndeprecation: Replaced deprecatedon_shutdownparameter withlifespanasync context manager — fixes compatibility with Starlette 0.38+ which removedon_shutdown/on_startup
Added¶
- StreamContext:
get_tool_call_args()andget_pending_tool_calls()methods for querying accumulated tool call state during streaming
Changed¶
BaseToolOps.p_tool_definition_to_irreturn type: NowToolDefinition | list[ToolDefinition] | Noneto support unconvertible tool entries
Added (Documentation)¶
- Provider & CLI Compatibility Matrix: New guide page documenting real-world issues found during live integration testing with Gemini CLI, Claude Code, and OpenCode through format-converting proxies
v0.2.4 — 2026-03-22¶
Added¶
fix_orphaned_tool_calls()utilities: Public functions inconverters/openai_chat/tool_ops.py,converters/openai_responses/tool_ops.py, andconverters/anthropic/tool_ops.pythat detect mismatched tool calls/results and fix them bidirectionally — injecting synthetic placeholder results for orphaned calls and removing orphaned results without matching calls. OpenAI (Chat & Responses) and Anthropic strictly require this pairing (return 400 otherwise); only Google Gemini is lenient. Automatically applied at the IR level duringrequest_to_provider()for all strict-pairing converters; emitsWARNING-level log when orphaned tool calls or results are detected (#82, #84)
Fixed¶
- Anthropic→IR role normalization for
tool_resultmessages: Anthropic placestool_resultblocks inrole: "user"messages, but IR usesrole: "tool"(like OpenAI). The Anthropic converter now normalizes puretool_resultuser messages torole: "tool", and splits mixedtool_result+ text messages into separaterole: "tool"androle: "user"IR messages. This fixesfix_orphaned_tool_calls_ir()failing to detect answered tool calls in cross-format conversions (e.g. Anthropic → OpenAI Chat) (#84) - OpenAI Responses→IR role normalization for
function_call_outputitems:function_call_outputandmcp_call_outputitems were grouped intorole: "user"IR messages, but IR usesrole: "tool"for tool results. The Responses converter now groups these items intorole: "tool"messages, fixingfix_orphaned_tool_calls_ir()failing to detect answered tool calls when converting Responses → other formats (e.g. Responses → OpenAI Chat) (#84)
Added (Documentation)¶
- Provider Dialect Differences guide: New section in the Converters guide (EN + ZH) documenting tool schema sanitization, orphaned tool call handling, and Google camelCase/snake_case differences
v0.2.3 — 2026-03-22¶
Fixed¶
- Tool schema sanitization applied to all converters:
_sanitize_schema()was previously only called in the OpenAI Chat converter. Google GenAI, OpenAI Responses, and Anthropic converters now also sanitize tool parameter schemas before sending to upstream, preventing rejections from strict endpoints like Vertex AI (#80) - Non-standard
refand$schemakeywords stripped: OpenCode's built-in tools use a barereffield (without$prefix) and$schemaat the top level, both rejected by Vertex AI. Added to the unsupported keywords blocklist (#80) $ref/$defsresolved by inlining: JSON Schema$refreferences are now resolved by inlining the referenced definition from$defs/definitions, and both keys are removed from the output. Supports nested and chained references (#80)- Streaming tool call arguments not accumulated: OpenAI Chat, Anthropic, and Google GenAI converters registered tool calls in
StreamContextbut never calledappend_tool_call_args()to accumulate argument deltas during streaming. This caused tool call arguments to arrive empty at upstream (e.g., MCP tools returning'query' is a required property). Only the OpenAI Responses converter was correct (#81) - OpenAI Chat streaming tool call ID resolution: Delta-only chunks (carrying
indexbut noid) produced an empty-stringtool_call_id. Now resolves the effective ID fromStreamContext._tool_call_orderusing the chunk index (#81)
Changed¶
sanitize_schemaextracted toconverters/base/tools.py: The schema sanitization utility (previously_sanitize_schemaprivate toopenai_chat/tool_ops.py) is now a public shared function inconverters/base/tools.py, exported viaconverters.base. All 4 convertertool_ops.pyfiles import from the shared location instead of cross-importing fromopenai_chat(#66)
v0.2.2 — 2026-03-22¶
Fixed¶
- Missing
content_block_stopin Anthropic SSE output: When converting OpenAI Chat streaming responses to Anthropic SSE format,content_block_stopevents were not emitted beforemessage_delta, causing Claude Code to silently discard response content. The Anthropic converter now emitscontent_block_stopfor any open content block when processing aFinishEvent(#77) - Upstream preflight chunk misinterpreted as stream end: Argo API sends a preflight chunk with
choices: []and emptyid/modelbefore actual content. The OpenAI Chat converter now only treats empty-choices chunks as stream-end after the stream has actually started (context.is_startedguard) (#77)
v0.2.1 — 2026-03-20¶
Added¶
- Gateway request/response body logging: configurable debug logging with colorized output, body sanitization and truncation — enable via config (
"debug": {"verbose": true, "log_bodies": true}), env vars (LLM_ROSETTA_VERBOSE,LLM_ROSETTA_LOG_BODIES), or--verboseCLI flag - Google
output_format="rest"forrequest_to_provider(): passoutput_format="rest"to get a REST API–ready request body withtools/tool_configat top level and generation params wrapped ingenerationConfig— eliminates the need for manual SDK→REST fixups
Changed¶
- Gateway modularization: split
app.py(1057 lines) intoproxy.py(proxy engine, SSE handling, upstream requests),cli.py(CLI entry point, argparse, subcommands), and a slimmedapp.py(route handlers, app factory, ~210 lines) - Moved Google REST body fixup to core:
_fixup_google_body()logic moved fromgateway/proxy.pyintoGoogleGenAIConverter._to_rest_body(), removing duplicated SDK→REST transforms from the gateway and all 6 REST examples
Fixed¶
- OpenAI Responses streaming: added missing
id/object/modelfields toresponse.completed,output_index/content_indexto text delta events, and proper lifecycle events (output_item.added,content_part.added,content_part.done,output_item.done) (#56) - OpenAI Chat streaming:
tool_callsentries now always include the requiredindexfield, defaulting to0when not explicitly provided by the upstream IR event (#57) - OpenAI Chat streaming: usage-only chunk now includes
"choices": []to satisfy clients that validate everychat.completion.chunkmust contain achoicesarray (#55) stream_options(Chat Completions-only field) no longer leaks into OpenAI Responses API requests — the Responses converter'sir_stream_config_to_p()was incorrectly emittingstream_options, causing upstream rejection when Chat-format clients (Kilo, OpenCode) were proxied to the Responses API (#58)- Google GenAI converter now handles tools and tool_config in REST-format requests (top-level fields) in addition to SDK format (
config.tools) — previously only SDK format was recognized, silently stripping tool definitions from gateway-proxied requests (#59) - Google camelCase
functionDeclarationsnot parsed:p_tool_definition_to_ir()now handles bothfunctionDeclarations(camelCase/REST) andfunction_declarations(snake_case/SDK), and extracts all declarations instead of only the first. Also added camelCase support forfunctionCallingConfig/allowedFunctionNamesandtoolConfigin request parsing — fixes Gemini CLI tool calling through the gateway (#61) - Google streaming tool calls split into two chunks:
stream_response_to_provider()now deferstool_call_startand emits the completefunction_call(name + args) in a single chunk ontool_call_delta, matching the Google API's native format (#62)
v0.2.0 — 2026-03-18¶
Added¶
- Standalone API test scripts (
llm_api_simple_tests/): 20 test scripts (5 per provider) using official SDKs directly, covering simple query, multi-round chat, image, function calling, and comprehensive scenarios — added as a git submodule from Oaklight/llm_api_simple_tests - LLM-Rosetta Gateway: REST gateway application for cross-provider HTTP proxying
- CLI entry point (
llm-rosetta-gateway) and package structure for the gateway - Gateway config auto-discovery at
./config.jsonc,~/.config/llm-rosetta-gateway/config.jsonc,~/.llm-rosetta-gateway/config.jsonc --edit/-eflag to open config file in$EDITOR(falls back to nano/vi/vim)--version/-Vflag showing current version- ASCII art startup banner with
--no-bannerto suppress add provider <name>subcommand for adding provider entries to config (with--api-key,--base-urlflags or interactive prompts; known providers auto-fill defaults)add model <name>subcommand for adding model routing entries (with--providerflag or interactive prompt)- Gateway providers module (
providers.py): centralized provider definitions with auth-header builders, URL templates, default base URLs, and API key env-var names - API key rotation: round-robin
KeyRingfor comma-separated API keys per provider - Proxy support: global
server.proxyand per-providerproxyconfig for HTTP/SOCKS proxies; CLI--proxyflag overrides config - Makefile
test-integrationtarget usingproxychains(if available) for integration tests initsubcommand to create a templateconfig.jsoncat the XDG default location (~/.config/llm-rosetta-gateway/)- Model listing endpoints:
GET /v1/models(compatible with both OpenAI and Anthropic SDKs) andGET /v1beta/models(Google GenAI SDK format) — enablesclient.models.list()across all three SDKs (#54)
Changed¶
- Bumped minimum Python to 3.10+; migrated to stdlib
typing(removedtyping_extensions) - Applied
ruffformatter across the entire codebase - Updated Makefile with
lint,test, andbuildtargets - Added
ty(type checker) configuration - Configured
rufflint rules (E,F,UP) inpyproject.toml; ignoreUP007(Union syntax) andE501(line length) - Modernized typing imports across
src/,tests/,examples/, andscripts/— replacedtyping.Dict,List,Tuple,Optional,Typewith stdlib builtins
Fixed¶
- Streaming crash with Anthropic provider when usage tokens are
null—TypeError: NoneType + intin all converters (replaced.get("*_tokens", 0)with.get("*_tokens") or 0) - Gateway provider
base_urlvalidation — fail early with clear error on config typos likehttps:example.com(missing//) - Added
socksioto gateway dependencies for SOCKS proxy support (httpx[socks]) - Added missing
__init__.pyfortypespackage - Updated
git cloneURL fromllm-rosettatollm-rosettain documentation - Resolved all
tytype checker diagnostics insrc/(31 → 0):- Fixed
is_part_type()TypeGuard narrowing — replaced with specific type guard functions (is_text_part, etc.) - Added missing TypedDict fields:
provider_metadataonTextPart/ReasoningPart,file_idonImagePart/FilePart - Fixed
IRRequest.messagestype fromRequired[Message]toRequired[Iterable[Message]] - Used
cast()to bridgedict[str, Any]intermediates to TypedDict return types - Fixed dict literal type inference conflicts in converter response builders
- Fixed
- Resolved all
tytype checker diagnostics intests/(1506 → 0):- Added
cast()wrappers on dict literals passed to functions expecting TypedDict parameters (GenerationConfig,IRRequest,IRResponse,ToolDefinition,ToolChoice, etc.) - Narrowed
Message | ExtensionItemunion results withcast(list[Any], ...)orcast(Message, ...) - Converted
Iterablecontent fields tolistfor subscript andlen()access - Added
assert ... is not Noneguards before subscripting optional return types - Fixed
FinishReasonfrom bare string to TypedDict form{"reason": "stop"} - Fixed
IRResponse.objectliteral from"chat.completion"to"response"
- Added
- Resolved all
rufflint violations insrc/andtests/(UP035 deprecated imports, F401 unused imports) - Google
thought_signaturepreservation through gateway round-trips — newer Google models requirethoughtSignatureechoed back in function call parts; the gateway now cachesprovider_metadata(includingthought_signature) keyed bytool_call_idand re-injects it on subsequent requests for both streaming and non-streaming modes (#51) - OpenAI Responses converter now handles all 3
inputformats: bare string ("input": "hello"), shorthand list ([{"role": "user", "content": "hi"}]), and structured list — previously only the structured format was supported, causing the OpenAI Python SDK's shorthand items to be silently dropped and producing empty IR messages when cross-converting to Anthropic or Google providers
2026-03-15 — Rebrand to LLM-Rosetta¶
Changed¶
- Project renamed from LLM-Rosetta to LLM-Rosetta across all code, docs, and configuration
- Package renamed from
llm-rosettatollm_rosetta;pyproject.tomlupdated accordingly - Documentation fully rewritten with Zensical for both English (
docs_en) and Chinese (docs_zh) - README (EN/ZH) updated with new branding, badges, and
pyproject.tomlmetadata
2026-03-06 — Streaming & StreamContext¶
Added¶
StreamContextfor stateful stream chunk processing across all 4 providersstream_response_from_provider()andstream_response_to_provider()methods on all convertersaccumulate_stream_to_assistant_message()helper function- Stream abstract methods (
stream_response_to_provider,stream_response_from_provider) added toBaseConverter - 4 new IR stream event types:
StreamStart,StreamEnd,ContentBlockStart,ContentBlockEnd ReasoningDeltaEventandtool_call_indexfield on IR stream types- Cross-provider streaming examples for all provider pairs (SDK and REST variants)
- Local file cache and retry logic for image downloads in examples
Changed¶
- Stream method signatures updated with optional
contextparameter - Deprecated
from_providermethods removed;auto_detectupdated to new API - Obsolete single-provider example scripts removed (replaced by cross-provider examples)
_normalize()extracted toBaseConverteras a shared utility
Fixed¶
- camelCase fallback for Google GenAI REST stream/response fields
- Anthropic stream converter:
thinking_delta,signature_delta,tool_call_idhandling - OpenAI Chat stream converter:
reasoning_content, empty string,tool_call_indexhandling - Missing
__init__.pyfor test package discovery from_providercalls ingoogle_genai_rest_e2eintegration test
2026-02-14 — Cross-Provider Examples & Stream Converters¶
Added¶
- Stream converters for all 4 providers: OpenAI Chat, Anthropic, Google GenAI, OpenAI Responses
- Stream converter unit tests for all providers
- 6 cross-provider conversation examples (SDK-based): OpenAI Chat ↔ Anthropic, OpenAI Chat ↔ Google GenAI, OpenAI Chat ↔ OpenAI Responses, Anthropic ↔ Google GenAI, Anthropic ↔ OpenAI Responses, Google GenAI ↔ OpenAI Responses
- Common resources module for cross-provider conversation examples
- Image URL to inline base64 conversion helpers for Google GenAI compatibility
- OpenAI Responses E2E integration tests (REST + SDK)
- Unit tests for OpenAI Responses Ops classes and converter
- Examples README in English and Chinese
Changed¶
- OpenAI Responses converter restructured to Bottom-Up Ops Pattern
- Post-refactor cleanup: removed deprecated utils and empty directories
Fixed¶
- Image URLs converted to inline base64 for Google GenAI provider compatibility
2026-02-13 — Bottom-Up Ops Architecture¶
Added¶
- Google GenAI converter rebuilt with Bottom-Up Ops Pattern
- TypedDict replicas of OpenAI Responses API types
- TypedDict replicas of Google GenAI SDK types
- Google GenAI REST and SDK E2E integration tests
- Unit tests for
google_genaiconverter Ops classes - Anthropic SDK and REST E2E integration tests
- OpenAI Chat E2E tests split into SDK and REST versions
- GitHub Actions CI/CD workflows and Dependabot configuration
Changed¶
- Anthropic converter redesigned with bottom-up Ops architecture
- Imports updated to use new
google_genaiconverter module - Old
google/converter and legacy tests removed
2026-02-12 — Converter Redesign¶
Added¶
- TypedDict replicas of Anthropic SDK types
- TypedDict replicas of OpenAI Chat types with backward compatibility and tests
- Legacy body converter design preserved as historical reference
Changed¶
- OpenAI Chat converter redesigned with bottom-up Ops architecture
- Ruff lint errors fixed across entire codebase
2026-01-06 — Layered Architecture & Documentation¶
Added¶
- English and Chinese documentation structures initialized (
docs_en,docs_zh) - Comprehensive error handling documentation
- OpenAI Chat Converter integration tests
- Comprehensive mock implementations for
BaseConvertertest class - File handling functionality in base converter
- Provider-to-IR mapping documentation
Changed¶
- Converter base refined with layered abstract template
- All 4 converters restructured with layered architecture (Anthropic, OpenAI Chat, OpenAI Responses, Google GenAI)
- Type annotations updated for IR content/part conversion methods
- IR type system reorganized and enhanced
- English translations added to code comments and docstrings
Fixed¶
- Reasoning content field assertion corrected
- File content handling in OpenAI Chat Completions converter
2026-01-05 — Auto-Detection & Package Maturity¶
Added¶
detect_provider()for automatic provider format auto-detectionconvert()convenience function for one-step format conversiondeveloperrole support in message validation- Comprehensive validation tests for
BaseConverter, Anthropic, Google GenAI, and OpenAI converters - Tool call and tool definition conversion tests
- pytest configuration and
pytest-covdependency - Competitive analysis document
Changed¶
- Package renamed from
llm-provider-convertertollm-rosetta - IR format usage standardized across all providers
- Message creation standardized using
Messageclass in examples - Test suite migrated from unittest to pytest
- Common logic extracted into shared utility modules
Fixed¶
- Standalone tool calls without current message context in OpenAI Responses converter
- Google GenAI Pydantic model handling reordered for tuple compatibility
- OpenAI content handling logic simplified for single text parts
2026-01-04 — Examples & Packaging¶
Added¶
pyproject.tomlfor package configuration- Multi-turn chat example with tool integration
- Anthropic handover in multi-turn chat example
- Google GenAI function calling in multi-turn chat example
Changed¶
- Utility functions moved from converters to IR types module
- OpenAI Chat converter code formatting improved
- Deprecated multi-provider query and weather tool modules removed
2025-12-24 — Initial Implementation¶
Added¶
- IR type system: intermediate representation types for messages, content parts, tools, configs, request/response
BaseConverterabstract class for LLM provider conversionAnthropicConverter: bidirectional Anthropic Messages API conversionOpenAIChatConverter: bidirectional OpenAI Chat Completions API conversionOpenAIResponsesConverter: bidirectional OpenAI Responses API conversionGoogleGenAIConverter: bidirectional Google GenAI SDK format conversion- Comprehensive test suites for all 4 converters
- Package initialization and exports
- Weather tool example with mock data
2025-12-09 — Research & Design¶
Added¶
- Initial project structure
- LLM provider message typing schemas documentation and comparison
- Provider messages IR design documentation
- MCP support comparison across providers (OpenAI, Anthropic, Google)
- Google GenAI Interactions API type analysis
- Multi-provider query example function
- OpenAI Responses API support in query examples