Changelog¶
All notable changes to LLM-Rosetta are documented here. This project follows Keep a Changelog conventions.
[Unreleased]¶
v0.7.2 — 2026-07-20¶
Added¶
custom_headinjection for admin panel (#378):setup_admin()accepts an optionalcustom_headHTML fragment injected before</head>. Downstream projects can inject<style>/<script>tags to customize admin UI without modifying the referenceadmin.html. Cached per value — no per-request overhead.brandingdict for admin panel identity (#378):setup_admin(..., branding={title, subtitle, version, links, attribution})customizes the header, login screen, and settings footer. Serialized aswindow.__brandingviacustom_head; consumer script inadmin.htmlpatches the DOM. Element IDs:brandTitle,brandLoginTitle,brandFooterName,brandFooterLinks. Without branding, the default llm-rosetta identity is unchanged.
Changed¶
- Bump vendored
httpclient0.4.4 → 0.4.5 — fixes fd leak whereclose()did not close_async_writer, preventing__del__from cleaning up leaked async streaming responses. - Extract
ConfigIOprotocol for admin panel config I/O (#376): Admin routes now use aConfigIOprotocol instead of importingload_config/load_config_raw/write_configdirectly. DefaultJsoncConfigIOimplementation preserves existing behavior; downstream projects (e.g. argo-proxy) can supply alternative implementations viasetup_admin(..., config_io=...). Internal helpers_get_config_pathand_get_config_ionow raise descriptiveRuntimeErroron missing values instead of returningNone, removing 16 redundant guard blocks across route handlers. - Replace Unicode emoji (🔍) with inline SVG in the content capture table for consistent cross-platform rendering.
Fixed¶
- Escape
</in branding JSON serialization to prevent<script>tag breakout when branding values contain</script>.
v0.7.1 — 2026-07-16¶
Fixed¶
- Tool schema sanitization for Anthropic and Google (#372): Anthropic rejects the OpenAPI
nullableextension in tool parameter schemas (e.g. from Pydantic-generated JSON Schema). Newconvert_nullable_to_type_array()helper recursively converts"nullable": trueto standard JSON Schema"type": [T, "null"]. Anthropic converter now stripstitlefields and convertsnullableto type arrays; Google GenAI converter stripstitle(keepsnullable— Google supports it). Also handles the edge case wherenullable: trueappears alongsideanyOf/oneOfwithout atypefield. flatten_systemcheckbox layout and i18n in the gateway admin panel.
Changed¶
- Bump vendored
validate0.6.0 → 0.6.1 (dataclass instance support). - Restrict Dependabot to LLM SDK dependencies only.
Added¶
- SDK type coverage scanner and manual CI workflow for tracking provider SDK type alignment.
v0.7.0 — 2026-07-10¶
Added¶
- Anthropic
cache_controlpreservation (#362): Newcache_hintfield on IR parts (TextPart,ImagePart,FilePart,ReasoningPart,ToolCallPart,ToolResultPart,ToolDefinition) enables round-tripping Anthropic's block-levelcache_controlthrough the IR pipeline. The Anthropic converter readscache_control→cache_hinton ingest and writes it back on output; non-Anthropic converters silently ignorecache_hint, ensuring cross-format safety. flatten_system_content()transform (#370): New body-level transform factory that flattens system message content arrays to plain strings. OpenAI Chat converter now outputs structured content arrays for system messages (preserving block boundaries forcache_hint);flatten_system_content()downgrades to plain strings for upstream compatibility. Per-modelflatten_systemgateway config with auto-detection for Gemini models. Admin panel toggle included.
Fixed¶
- OpenAI SDK 2.45+ compatibility: Added
cache_write_tokensfield toInputTokensDetails(Responses API) andPromptTokensDetails(Chat Completions API) TypedDict replicas to match upstream SDK changes.
Changed¶
- Transform fields renamed —
from_transforms→pre_ir_transforms,to_transforms→post_ir_transformsonProviderShim. Old names accepted as backward-compatible aliases in both constructor kwargs andtransforms.pyexports. system_instructionunified tolist[TextPart](#364): The canonical IR form ofsystem_instructionis nowlist[TextPart]instead ofstr. A single string"You are helpful."is represented as[TextPart(type="text", text="You are helpful.")]. This ensures consistent structure across all converters and enables block-level metadata (e.g.cache_hintfor Anthropic prompt caching) to flow through the IR pipeline. All 4 converters updated. Breaking: code that readsir_request["system_instruction"]asstrmust handlelist[TextPart].
v0.7.0a1 — 2026-06-27¶
Added¶
- Hybrid profiling system (#339): Always-on
perf_counterphase timing inConversionPipeline.profile(source_to_ir_ms, ir_transforms_ms, ir_to_target_ms, etc.) plus on-demand per-request pyinstrument deep profiling via admin API.DeepProfilercontext manager inllm_rosetta.profiling; new[profiling]optional dependency group. Admin endpoints: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 - Profiling admin UI (#339): New "Profiling" section in admin dashboard with enable/disable controls, result listing, flamegraph download (single and bulk), and restart hint
- Error dump capability (#341): Fire-and-forget error dump system that captures full request context on upstream/conversion failures. Image offload before hashing for content-based dedup, zlib compression, 10K entry cap with cascade pruning. Four trigger points covering upstream errors, stream header errors, stream chunk errors, and conversion errors. New functions
dump_error(),offload_images(),compute_body_hash(),compress_body(),decompress_body()exported fromllm_rosetta.observability - Metrics rebuild (#340):
POST /admin/api/metrics/rebuildendpoint and "Rebuild Counters" button in admin dashboard. Reconstructs all metrics counters from request log history using batched iteration and atomic swap to avoid exposing half-rebuilt state - Observability package (#341): Extracted
MetricsCollector,RequestLog,RequestLogEntry,PersistenceManager, andProfilerStatefromgateway/admin/into a new top-levelllm_rosetta.observabilitypackage. These modules are framework-agnostic and can be used by any LLM proxy consumer (e.g. argo-proxy) without depending on the gateway's config system or HTTP server. Thegateway/admin/modules now re-export fromobservability/for full backward compatibility
Fixed¶
- Metrics breakdown by provider name (#340): Dashboard breakdown section now groups by provider display name instead of API type, which was merging all Anthropic-format providers into one row
- Config file write safety:
write_config()now uses file locking for cross-process safety - Vendored httpserver updated to 0.2.1: Returns proper HTTP error responses instead of silent disconnects on malformed requests
- Vendored SSE updated to 0.3.2: Uses constructor arguments for parser initialization instead of post-init mutation
Changed¶
- Dev tool versions pinned:
ruff==0.15.20andty==0.0.54pinned in[project.optional-dependencies]to prevent CI drift from upstream tool releases
v0.7.0a0 — 2026-06-25¶
Added¶
- ConversionPipeline class (#322): High-level orchestration class encapsulating the full Phase 1→2→4 conversion lifecycle.
convert_request(),convert_response(),create_stream_processor()withon_ir_readycallbacks for metadata store integration. One-shot guard prevents accidental reuse - Routing layer (#323):
ResolvedRoutefrozen dataclass andRouterprotocol in the core library.GatewayConfig.resolve()consolidates model lookup, provider type, shim binding, capabilities, and reasoning overrides into a single typed result - Capabilities module (#335):
capabilities.pywithenforce_reasoning()(pre-IR) andenforce_vision()(post-IR) — platform-level capability enforcement separated from provider-specific shim transforms - IRTransform system (#330):
TransformContextdataclass,IRTransformcallable type,apply_ir_transforms()executor, and_NamedIRTransformwrapper. IR-level transforms are now declarative onProviderShim.ir_transforms, separate from body-levelTransform - IR transform factories:
strip_non_vision_images(),truncate_images(max, pattern),unwind_parallel_tool_calls(pattern)— factory functions producingIRTransformcallables - Message-level transform primitives (#328):
replace_message_field(),default_message_field(),strip_fields_for_model()for nested field operations onmessages[] - Transport layer (#321):
UpstreamTransportprotocol,HttpTransportimplementation,UpstreamResponse/UpstreamStreamtypes,HttpClientPool,send_passthrough()for non-conversion endpoints resolve_shim()public function: Promoted from private_resolve_shim()to public API onprovider_shim.py
Breaking Changes¶
ProviderShimfields removed:max_images,max_images_pattern,unwind_parallel_tool_calls,unwind_parallel_tool_calls_patterndeleted — these capabilities are now declared viair_transformstuple using factory functions (truncate_images(),unwind_parallel_tool_calls())apply_shim_to_ir()behavior changed: No longer hardcodes image/unwind operations; readsshim.ir_transformsdeclaratively. Renamed toapply_ir_transforms()(old name is a deprecated alias)- Gateway handler signatures changed:
handle_non_streamingandhandle_streamingtakeroute: ResolvedRouteinstead of 6 separate parameters (source_provider,target_provider,model,target_shim_name,reasoning_config_override,model_capabilities)
Refactored¶
- Pipeline renamed (#330):
apply_shim_to_ir()→apply_ir_transforms(),setup_shim_context()→configure_context(). Old names emitDeprecationWarning - Gateway proxy.py: Handlers use
ConversionPipelineinternally._resolve_target_transforms,process_stream_chunkdeleted - Embeddings handler: Uses
transport.send_passthrough()instead of reaching intoHttpTransport._pool. Migrated fromresolve_model()to unifiedresolve()API and replaced inline telemetry with shared_record_telemetry() - Auth functions renamed:
_openai_auth→openai_authetc. (dropped underscore, public API) - Removed
GatewayConfig.resolve_model(): Legacy 5-tuple API superseded byresolve()which returnsResolvedRoute+ProviderInfo. DuplicateDEFAULT_CAPABILITIESclass variable removed
Fixed¶
- Restored legacy
converters/base/import paths (#317): Backward-compatible shim modules at old paths (converters.base.tools,.schema,.tool_content,.cache) sanitize_schemastripsexclusiveMinimum/exclusiveMaximum(#337): Google GenAI API rejects JSON Schema draft 6+ numeric constraints in tool definitions- Stop emitting
reasoning.typefor OpenAI Responses API (#337): OpenAI and Volcengine Responses APIs rejectreasoning.type— reasoning is controlled viareasoning.effortonly. Historical bug from v0.6.8
v0.6.12 — 2026-06-23¶
Fixed¶
- Restored legacy
converters/base/import paths (#310): The v0.6.11 helpers/ reorganization unintentionally broke import paths that external callers relied on.sanitize_schema,extract_part_ids,log_orphan_warnings,fix_orphaned_tool_calls_ir, andstrip_orphaned_tool_configare re-exported fromconverters.base.toolsagain, and compatibility shim modules atconverters.base.schema,converters.base.tool_content, andconverters.base.cacheredirect to their newhelpers/locations. The canonical import path remainsllm_rosetta.converters.base.helpers; existing code importing from the old paths (e.g.from llm_rosetta.converters.base.tools import sanitize_schema) keeps working without changes. The cache singletons are shared across both paths
v0.6.11 — 2026-06-21¶
Added¶
- Admin panel provider UX enhancements (#292): Three improvements to the provider tab:
- Multi-key entry list: API key field auto-detects comma-separated keys (rotation) and switches to multiple
<input type="password">entries.+ Add keybutton always visible for manual promotion. Eye toggle and copy button in unified footer - Provider search bar: appears when provider count exceeds 6, filters by name, type, and base URL
- Grid/list view toggle: two icon buttons switch between card grid and compact single-column list view. Preference persisted in localStorage
- Multi-key entry list: API key field auto-detects comma-separated keys (rotation) and switches to multiple
- Request ID propagation (#296, #122): Every proxy request generates or honours an
X-Request-IDheader. Propagated to upstream providers, included in all response headers (including error responses), and logged with[request_id]prefix for end-to-end traceability - Enhanced health check endpoints (#297, #127):
/health— returns uptime, request counts, errors in the last hour, and per-provider health snapshot (success rate, avg latency, last error). Always HTTP 200;statusfield shows"ok"or"degraded"/health/live— always 200 (Kubernetes liveness probe)/health/ready— 200 when all providers healthy, 503 when any provider is critically unhealthy (Kubernetes readiness probe)
- CORS restriction on admin API (#294, #233):
/admin/api/*endpoints no longer sendAccess-Control-Allow-Origin: *. New config optionserver.admin_cors_origins(list, default[]) allows explicit origin allow-listing./v1/*proxy endpoints unchanged - Image count enforcement via shim (#301, #299):
ProviderShimgainsmax_imagesandmax_images_patternfields. When set, images exceeding the limit are replaced with[image omitted due to limit]text placeholders (oldest first, most recent kept). Argo OpenAI shim declaresmax_images: 50with pattern^(gpt|o\d)— only GPT/o models are truncated; Gemini and Claude through the same provider pass through unaffected - Vision capability enforcement (#314, #313): Models without
visioncapability now have all images automatically stripped and replaced with[image not available]instead of being forwarded to upstream where they cause opaque errors (e.g. DeepSeek's "unknown variantimage_url"). Gateway logs a warning with image count and model name - Unix domain socket support (#315): Gateway can listen on a Unix socket instead of TCP via
--socket/-SCLI flag orserver.socketconfig field. Enables secure deployments on shared multi-user hosts where127.0.0.1still exposes the service to all local users. Socket file is restricted to owner-only (0600) and cleaned up on shutdown - Parallel tool call unwind (#303, #300):
ProviderShimgainsunwind_parallel_tool_callsandunwind_parallel_tool_calls_patternfields. When enabled, parallel tool calls (multipletool_callparts in one assistant message) are unwound into sequential call-result pairs before forwarding. Argo OpenAI shim enables this with pattern^gemini— Gemini models through Argo get sequential pairs; GPT/o models pass through unchanged
Changed¶
-
converters/base/reorganized into helpers/ subpackage (#311, #312, #310): Utility functions extracted from the flatconverters/base/directory intoconverters/base/helpers/. Abstract base classes (the Ops pattern contract) stay at the top level; implementation utilities (cache,schema,tool_orphan_fix,tool_content,tool_call_unwind,image_limit,reasoning) move tohelpers/.tools.pyreduced from 428→185 lines (pure ABC).reasoning_helpers.pymoved fromconverters/root.orphan_fix.pyrenamed totool_orphan_fix.pyfor consistenttool_*prefix.helpers/__init__.pyre-exports public functions -
Retired Argo
_normalize_thinkingtransform (#304, #192): Removed dead code from Argo Anthropic shim — the_normalize_thinkingfunction,_BUDGET_RATIO, and_ADAPTIVE_THINKING_MODELSwere replaced by declarativereasoning.model_overridesinprovider.yamlbut the code and 19 tests remained - Speculative extension types marked experimental (#302, #71):
SystemEvent,BatchMarker,SessionControl,ToolChainNodemoved fromtypes.ir.extensionstotypes.ir.extensions_experimental. Old import path still works but emitsDeprecationWarning. Types removed from defaulttypes.irnamespace; available viafrom llm_rosetta.types.ir import experimental - Admin panel i18n: Chinese translation updated from "服务商" to "服务方" (more neutral for mixed commercial and self-hosted providers)
- Request log timestamps (#298): Now show date and time (e.g. "06/19, 20:25:29") instead of time only
Fixed¶
- Admin panel auth flash (#291): Eliminated flash of unauthenticated content when
admin_passwordis configured. Main UI is hidden via CSS (body.auth-pending) until the async auth check completes - Admin password unresolved env var (#293): Gateway now refuses to start if
admin_passwordcontains an unresolved${...}placeholder, preventing a predictable literal string from being used as the password is_image_parttype guard for OpenAI format (#306):is_image_part()now matches bothtype: "image"(IR canonical) andtype: "image_url"(OpenAI format retained in IR), fixing image truncation being silently skipped for OpenAI-format requests- Tool result images counted for truncation (#308, #299):
truncate_images()now scans images insidetool_result.resultlists, not just direct message content. Fixes requests that had ≤50 images at IR level but exceeded 50 after the OpenAI Chat converter unpacked tool result images into synthetic user messages. Also optimizeddeepcopyto only copy affected messages instead of the entire conversation - Argo Gemini parallel tool call failures (#303, #300): All Gemini models through Argo gateway failed with "function response parts ≠ function call parts" when Claude Code made parallel tool calls. Root cause: Argo's internal OpenAI→Gemini conversion doesn't merge separate tool result messages into a single
functionResponseContent block. Fixed by unwinding parallel tool calls to sequential pairs at IR level before forwarding
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