Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,9 @@ MOLTO_LOG_LEVEL_STDERR=INFO
# External API keys
ANTHROPIC_API_KEY=
OPENAI_API_KEY=
AWS_BEARER_TOKEN_BEDROCK=
# Alternatively, replace the preceding line with BEDROCK_API_KEY=.
FIREWORKS_API_KEY=
GEMINI_API_KEY=
OPENROUTER_API_KEY=
TOGETHER_AI_API_KEY=
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ thumbs.db

# Cassettes
tests/cassettes/**/*.yaml
# Preserve generated provenance alongside unchanged original capture bytes.
tests/cassettes/provider_recordings/**/manifest.json

# Internal references, dependencies, temporary folders & files
.env
Expand Down
4 changes: 3 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ Four independent choices define a route:
| `provider` | Selects the serving implementation and its immutable capability profile. |
| `base_url` | Selects only the HTTP destination. |

See [hosted provider compatibility](provider-compatibility.md) for provider keys, exact destinations, history constraints, and the recorded/matched/synthetic evidence matrix.

Molto never infers provider behavior from a hostname. When `provider` is omitted, the endpoint supplies a convenience default: OpenAI for Responses and Chat Completions, and Anthropic for Messages. An explicit provider overrides that default without changing the endpoint or base URL.

Provider capabilities matter even when two implementations expose the same schema. For example, Chat Completions `reasoning_content` and Messages `thinking_delta` do not label their text as raw reasoning or a summary. The provider profile supplies that interpretation: OpenAI and Anthropic profiles classify it as a summary, while vLLM classifies it as raw reasoning.
Expand Down Expand Up @@ -232,7 +234,7 @@ Responses events label those representations explicitly and must agree with the

A Responses reasoning lifecycle must contain raw reasoning without a summary or signature, a summary accompanied by a signature without raw reasoning, or a signature without reasoning text. A Messages thinking lifecycle follows the same pairing rule. A completely empty start may be deferred until its representation is known, and a lifecycle must not switch representations.

Although a provider profile can classify Chat `reasoning_content` as a summary, Chat has no signature field. Current translated routes through Chat therefore support only unsigned raw reasoning. Summary translation through Chat fails at the Responses or Messages signature boundary rather than silently discarding required continuation state.
Although a provider profile can classify Chat `reasoning_content` as a summary, standard Chat has no signature field. Generic translated Chat routes therefore support only unsigned raw reasoning. Summary translation through Chat fails at the Responses or Messages signature boundary rather than silently discarding required continuation state. The [Gemini client](provider-compatibility.md#provider-differences) additionally translates its Google thought-signature extension into Responses `encrypted_content` or Messages `signature` and restores the original text/tool attachment on continuation.

### Opaque continuation state

Expand Down
4 changes: 3 additions & 1 deletion docs/protocols/anthropic-messages.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ The client preserves the response ID, model, and supported block order. Each emi

`thinking_delta` does not say whether its text is raw reasoning or a summary. Molto classifies it using the explicit provider capability profile: Anthropic and OpenAI profiles use summaries, while vLLM uses raw reasoning. The endpoint or base URL does not select this behavior.

When OpenRouter Responses is the upstream, its explicit raw/summary events already determine the normalized representation. Messages output preserves that distinction, including signatures for summaries. This exception applies to output projection only: incoming unsigned Messages thinking still follows the configured summary-input contract and rejects, so this route does not yet support a full raw-thinking Messages round trip.

A thinking block that starts with text or a signature begins immediately so the observed snapshot cannot be lost. A completely empty thinking start is deferred until a non-empty thinking or signature delta establishes content. If the block stays empty, Molto omits it and assigns dense normalized indexes to the remaining blocks. Native `ping` events are ignored on this semantic path.

Only the six handled raw events are inspected here; any other `RawMessageStreamEvent` variant is silently ignored rather than rejected. This matches the fact that `ping` is transport-only, but it diverges from `python/CONVENTIONS.md`'s "reject unsupported event types explicitly" rule and from the Responses client, which raises for unknown stream events. The silent-ignore is a documented current behavior, not a deliberate contract to rely on.
Expand Down Expand Up @@ -368,7 +370,7 @@ When Messages is the translated upstream and the provider is Anthropic, missing,

Cross-protocol Messages support includes streaming and non-streaming requests for the explicitly mapped text, portable image input, thinking, custom function tools, direct tool calls, successful tool results, limited failed-result paths, conversation history, lifecycle metadata, termination, and usage semantics described above.

A route targeting Messages must supply a downstream output-token limit because Messages requires `max_tokens`. Reasoning summaries are not currently portable through Chat Completions in either direction because Chat has no signature slot. Molto also does not enforce the same-provider requirement for opaque continuation state; callers must keep signatures with their issuing provider.
A route targeting Messages must supply a downstream output-token limit because Messages requires `max_tokens`. Reasoning summaries are not currently portable through generic Chat Completions because standard Chat has no signature slot. Gemini signature-only state is supported: a thinking block with empty `thinking` and the original `signature` follows its associated text or tool-use block, and echoing that order restores the Gemini attachment. Molto also does not enforce the same-provider requirement for opaque continuation state; callers must keep signatures with their issuing provider.

The normalized path does not currently implement image output, non-web citations, refusals as content blocks, redacted thinking, hosted tools other than basic web search, structured output, or multiple candidates. Request fields accepted by the local Messages wrapper are not cross-protocol features unless the adapter and client map them explicitly.

Expand Down
8 changes: 6 additions & 2 deletions docs/protocols/openai-chat-completions.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ The provider or model renderer can still merge, move, or reinterpret accepted wi

### Reasoning and continuation state

Chat's single `reasoning_content` extension can be classified as either the normalized raw reasoning field or summary field according to the provider profile. Chat has no reasoning signature or encrypted continuation slot. In the current implementation, only unsigned raw reasoning is portable through Chat on a translated route. A Chat summary has no signature and therefore cannot satisfy the completed-summary contract required by Responses or Messages; in the reverse direction, a Responses or Messages summary carries a signature that Chat projection rejects rather than silently discarding. Opaque continuation state and summarized reasoning are therefore not currently portable through Chat.
Chat's single `reasoning_content` extension can be classified as either the normalized raw reasoning field or summary field according to the provider profile. Standard Chat has no reasoning signature or encrypted continuation slot. Generic translated Chat routes support only unsigned raw reasoning. A Chat summary has no signature and therefore cannot satisfy the completed-summary contract required by Responses or Messages; in the reverse direction, a Responses or Messages summary carries a signature that Chat projection rejects rather than silently discarding. The Gemini upstream client is an exception for signature-only state: it maps `extra_content.google.thought_signature` through Responses or Messages and restores its original attachment on continuation. This does not add signed reasoning support to generic downstream Chat projection.

### Tools and tool results

Expand Down Expand Up @@ -307,7 +307,7 @@ For representable output, translated Chat retains answer text and inline links w
These are implementation gaps rather than inherent claims about the Chat protocol:

- An upstream Chat stream silently uses `choices[0]`; additional choices are not rejected and are lost. Requests with `n > 1` should not use translation.
- Translated reasoning through Chat currently supports only unsigned raw reasoning, such as the `vllm` provider profile. Summary profiles fail at the target protocol's signature boundary in either direction.
- Generic translated Chat reasoning supports only unsigned raw reasoning, such as the `vllm` provider profile. Gemini additionally supports signature-only continuation through Responses and Messages; reasoning-text translation is unverified.
- Refusals, hosted tools other than basic web search, moderation, non-web annotations, audio, prediction output, and log probabilities are not carried through the normalized path. Hosted-search structured annotations are deliberately omitted from translated Chat output.
- Chat text chunks therefore use `logprobs=None`, even when another protocol could theoretically represent compatible token log probabilities.
- Chat has no normalized per-part status. Target lifecycle and status fields are synthesized where required, and pauses map to `finish_reason="length"`; other terminal reasons without an exact Chat mapping become `finish_reason="stop"`.
Expand Down Expand Up @@ -338,3 +338,7 @@ External protocol references:

- [OpenAI Chat Completions create reference](https://developers.openai.com/api/reference/resources/chat/subresources/completions/methods/create)
- [vLLM OpenAI-compatible server](https://docs.vllm.ai/en/latest/serving/online_serving/openai_compatible_server/)

## Hosted extensions

See [provider compatibility](../provider-compatibility.md) for Google `extra_content` at message/part/call attachments, Gemini missing index repair, and native history preservation. Native acceptance does not establish translated portability; populated opaque state rejects where there is no lossless mapping.
4 changes: 2 additions & 2 deletions docs/protocols/openai-responses.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ Responses is explicit where Chat Completions and Messages are ambiguous:
| `response.reasoning_summary_*` | Summary in `Thinking.summary` |
| `encrypted_content` | Opaque snapshot in `Thinking.signature` |

Every explicit Responses event must agree with the selected provider capability profile. OpenAI is classified as summarized reasoning and vLLM as raw reasoning. A base URL never chooses this behavior.
Explicit Responses events must agree with the selected client's reasoning policy. OpenAI is classified as summarized reasoning and vLLM as raw reasoning. OpenRouter's Responses client accepts either wire-labeled representation, preserving the distinction per item rather than assuming every routed model uses summaries. A base URL never chooses this behavior. See the [provider compatibility policy](../provider-compatibility.md#history-and-reasoning) for format restrictions and continuation limits.

A Responses reasoning lifecycle must use exactly one valid representation:

Expand All @@ -265,7 +265,7 @@ A Responses reasoning lifecycle must use exactly one valid representation:

The item must not combine or switch representations between its added, delta, and done states. A completely empty start may remain deferred until its representation becomes known. When Responses is the downstream target, summary providers require a signature for a completed reasoning summary.

Chat Completions has one `reasoning_content` extension and no signature. Messages has one `thinking_delta` field plus signature lifecycle. Their configured provider capability decides whether their text is classified as Responses raw reasoning or summary. Only unsigned raw Chat reasoning is currently portable into Responses: a Chat summary cannot supply the signature required at Responses part completion. Responses-to-Chat summary translation also fails because Chat rejects the source signature instead of dropping it. Supported delta boundaries remain observable and are preserved by matched parity coverage.
Standard Chat Completions has one `reasoning_content` extension and no signature. Messages has one `thinking_delta` field plus signature lifecycle. Their configured provider capability decides whether their text is classified as Responses raw reasoning or summary. Generic Chat reasoning supports only unsigned raw text into Responses: a Chat summary cannot supply the signature required at Responses part completion. Responses-to-Chat summary translation also fails because Chat rejects the source signature instead of dropping it. Supported delta boundaries remain observable and are preserved by matched parity coverage. Gemini additionally maps its Google thought signatures to signature-only reasoning items with unchanged `encrypted_content`. These items follow their associated text or function call; echoing the ordered output back to Gemini restores the original attachment.

### Opaque continuation state

Expand Down
Loading
Loading