Skip to content
skyl

Provider feature matrix

What each adapter does with each part of a Request — including what it quietly does not.

What each adapter actually does with each part of a skyl.Request. Publishing the gaps is the point: skyl's design principles hold that it is better to say "we don't support this" than to ship something that looks supported and quietly does the wrong thing — and a matrix that lists only the green cells is exactly what that rejects.

Reading it#

Meaning
Mapped. Sent to the provider; the wire field is named.
Rejected. Returns ErrUnsupported before any request is made.
⚠️Silently ignored. Accepted, then dropped. Nothing tells you.
Not applicable.

The column worth reading is ⚠️. Filter to it below, or see the dedicated list with workarounds.

The matrix#

Show

Showing 52 of 52 rows.

Request fields

Featureanthropicopenaigeminiopenaicompat
Request.Model

model

model

in the URL path, not the body

model

Request.System

Providers place the system prompt in three different locations; this is why skyl gives it a dedicated field instead of a role.

top-level system

leading system message

systemInstruction

leading system message

Request.Messages

messages

messages

contents

messages

Request.MaxTokens

Anthropic is the only adapter that substitutes a default: its API requires the field, so skyl supplies 4096 rather than failing a request every other provider would accept.

max_tokens — defaults to 4096 when unset

max_completion_tokens, omitted when 0

generationConfig.maxOutputTokens, omitted when 0

max_tokens, omitted when 0

Request.Temperature

temperature

temperature

generationConfig.temperature

temperature

Request.TopP

top_p

top_p

generationConfig.topP

top_p

Request.Stop

stop_sequences

stop

generationConfig.stopSequences

stop

Request.Tools

Only Anthropic reconstructs the schema rather than passing it through verbatim.

schema rebuilt — see tool schemas

parameters verbatim

functionDeclarations, verbatim

parameters verbatim

Request.ToolChoice

all four modes

all four modes

all four modes

all four modes

Request.Thinking

The least uniform field in the library. See the dedicated Thinking table.

⚠️

partly — Effort is dropped

⚠️

mostly — ignored unless Enabled and Effort are both set

fully, including a zero budget

⚠️

mostly — same as openai

Request.ProviderOptions

Two different mechanisms. Anthropic sets by JSON path; the others shallow-merge, which will destroy sibling keys of any object you replace.

JSON-path set

shallow merge

shallow merge

shallow merge

Thinking

Featureanthropicopenaigeminiopenaicompat
Thinking: nil

nothing sent

nothing sent

nothing sent

nothing sent

Thinking: {Enabled: true}, no effort

thinking: {type: adaptive}

⚠️

ignored entirely

budget -1 (model decides)

⚠️

ignored entirely

Thinking: {Enabled: false}

On OpenAI an explicit "off" does nothing. If you are turning reasoning off to control cost, it will not work there.

thinking: {type: disabled}

⚠️

ignored entirely

budget 0

⚠️

ignored entirely

Thinking: {Enabled: true, Effort: low}⚠️

effort ignored, adaptive sent

reasoning_effort: low

budget 1024

reasoning_effort: low

Thinking: … Effort: medium⚠️

effort ignored

reasoning_effort: medium

budget 8192

reasoning_effort: medium

Thinking: … Effort: high⚠️

effort ignored

reasoning_effort: high

budget 16384

reasoning_effort: high

Thinking: … Effort: max

OpenAI does not define a "max" reasoning effort, so expect a 400.

⚠️

effort ignored

sent as max — OpenAI does not define this value

budget 24576

sent as max

Message parts

Featureanthropicopenaigeminiopenaicompat
Text (user, assistant)
Text (tool role)

becomes user content

tool messages may only contain tool results

becomes user content

tool messages may only contain tool results

Image URL (user)

source.type: url

image_url.url

Gemini requires inline image data, not a URL

image_url.url

Image data (user)

base64 source

synthesised data: URI

inlineData

synthesised data: URI

Image (assistant)

accepted — the API may not

images are only supported on user messages

accepted

images are only supported on user messages

ToolCall (assistant)

tool_use — arguments validated as JSON, rejected if malformed

tool_calls, arguments opaque

functionCall, arguments opaque

tool_calls, arguments opaque

ToolCall (user)

accepted

tool calls are only supported on assistant messages

accepted

tool calls are only supported on assistant messages

ToolResult (tool role)

tool_result in a user turn

tool role + tool_call_id

functionResponse in a user turn

tool role + tool_call_id

ToolResult.IsError

The one to watch. Its whole purpose is telling the model a tool failed so it can adapt instead of building on a result that is not there. On Gemini that signal never arrives.

real is_error boolean

⚠️

lossy — prefixes "error: ", and drops the flag entirely when content is empty

⚠️

dropped — never reaches the wire

⚠️

lossy — same as openai

Image with both URL and Data set⚠️

URL dropped

⚠️

Data dropped

⚠️

URL dropped

⚠️

Data dropped

Responses

Featureanthropicopenaigeminiopenaicompat
Response text

handles string and block-array shapes

handles string and block-array shapes

Response tool calls

ToolCall.ID is set to the function name — Gemini issues no call IDs

Response.ID

responseId when present

Response.Model

from the response

from the response

modelVersion

from the response

Response.Raw

always

always

always

always

Reasoning / thinking text

Discarded by all four adapters. It remains in Response.Raw. On Gemini specifically, if you enable thought output through ProviderOptions the thought text arrives as an ordinary Text part and is indistinguishable from the answer.

⚠️

dropped

⚠️

dropped

⚠️

dropped, and indistinguishable if re-enabled

⚠️

dropped

Refusals

returned as StopRefusal

empty refusal becomes ErrRefusal

blocked prompt becomes ErrRefusal

empty refusal becomes ErrRefusal

Usage

Featureanthropicopenaigeminiopenaicompat
Usage.InputTokens

Providers disagree on the wire, so the adapters normalise: InputTokens is always the total input including cache.

cache added in by the adapter

copied — cache is already inside the prompt count

copied — cache is already inside the prompt count

copied

Usage.CacheReadTokens
Usage.CacheWriteTokens

Anthropic is the only source. The other wire formats have no such field.

⚠️

always 0 — no wire field

⚠️

always 0 — no wire field

⚠️

always 0 — no wire field

Reasoning-token counts

On Gemini, thoughtsTokenCount is excluded from candidatesTokenCount, so OutputTokens under-reports what you are billed.

⚠️

not surfaced — inside OutputTokens

⚠️

not surfaced — inside OutputTokens

⚠️

not surfaced, and not counted

⚠️

not surfaced — inside OutputTokens

Streaming

Featureanthropicopenaigeminiopenaicompat
Text deltas
Thinking deltas (EventThinkingDelta)

Anthropic is the only adapter that ever emits these.

the only adapter that emits these

⚠️

never emitted

⚠️

never emitted

⚠️

never emitted

Streaming tool calls

buffered, emitted whole

accumulated across frames by index

arrive whole

accumulated across frames by index

Terminal EventDone
Usage on the terminal event

needs the host to honour stream_options.include_usage; zero if it does not

needs the host to honour stream_options.include_usage

Truncation detected

A stream that ends without its terminal signal is reported as an error on all three, rather than looking like a complete short answer.

Mid-stream errors surfaced
StreamEvent.Raw⚠️

never populated

text deltas only

text and tool-call events

text deltas only

Model listing

Featureanthropicopenaigeminiopenaicompat
ModelInfo.ID / Provider / Raw
ModelInfo.DisplayName⚠️

empty — OpenAI's response has no such field

host-dependent (OpenRouter supplies it)

ModelInfo.ContextWindow⚠️

empty

host-dependent

ModelInfo.MaxOutputTokens⚠️

never set

⚠️

never set

Pagination beyond 1000 models⚠️

nextPageToken ignored; the list is silently truncated

Tool schemas#

Your Tool.Parameters is a JSON Schema. Whether it arrives intact differs:

  • openai / openaicompat / gemini — passed through verbatim. $defs, $ref, oneOf, additionalProperties, everything.
  • anthropicreconstructed. properties and required are read into the SDK's typed struct and every other key is re-attached, so $defs, $ref and oneOf do survive. Two things do not: the top-level type is forced to "object", and non-string entries in required are dropped.

When a gap matters#

Everything in the ⚠️ list is reachable another way.

Response-side gapsResponse.Raw carries the provider's untouched body. Nothing is lost, only unmodelled.

Request-side gapsProviderOptions sends anything skyl does not model, subject to the shallow-merge caveat on three of the four adapters.

That is the deal skyl makes: it unifies the common 90% and gets out of the way for the rest. A gap in this table should cost you a few lines, never a fork.

Edit this page on GitHub