The Gemini API provider (OneLlm.Providers.Gemini) speaks generateContent and streamGenerateContent?alt=sse on v1beta. These notes record how its wire shapes map onto the message model, where the API reference is silent, and what the tests cover.
Mapping to the message model
- Turns and roles. A user turn becomes a
usercontent; an assistant turn becomes amodelcontent. The results of an assistant turn's tool calls go into one followingusercontent, asfunctionResponseparts in call order. - Call ids. Gemini may omit
functionCall.id. The provider then makes an id (gemini-call-<responseId>-<n>) so the loop can pair results; made-up ids are never sent back, and results are paired by name and position. - Thought signatures. A
thoughtSignatureon a text, call or media part is kept as its own reasoning part (Data.signature,Data.onnaming the part kind) placed just before the part it belongs to, and is attached to that part again on the next request. A thought summary (thought: true) is a reasoning part withDisplayset andData.on = "thought". - Hosted tools. Code execution (
executableCode+codeExecutionResult) and server-sidetoolCall+toolResponsebecome one hosted tool call each; the raw parts are kept inData.replayand sent back unchanged. Google Search, Maps and file-search grounding become hosted calls with no replay data, plusurl_citationannotations. Grounding offsets are UTF-8 byte offsets over the whole answer and are converted to character positions per text block. - Usage.
promptTokenCountincludes cached tokens and, with the URL-context tool,toolUsePromptTokenCountis billed as input. The provider reportsInputTokens = prompt - cached + toolUsePrompt,CacheReadInputTokens = cached,OutputTokens = candidates + thoughts. Modality counters (inputAudioTokens,inputImageTokens, ...) are subsets of the input total. - Extension data. Unknown response fields, safety ratings, citation metadata and prompt feedback are kept under
Message.ExtensionData["gemini"]. - Streaming. Both paths run the same parser, so a streamed message equals the whole-response message. Function-call arguments arrive whole in one part; the reference documents no partial-argument stream.
Request rules
RequestAdjusterruns once per request against the catalog entry; its warnings, and the Gemini-specific ones below, land onChatResponse.Warnings.- Gemini-specific: no switch for
parallelToolCalls=falseand no end-user identifier ongenerateContent(both dropped and noted); effortdisabledmeans budget 0,dynamicmeans -1 on budget models; levels are upper-cased on level models. - Structured output uses
responseJsonSchemaunless the model's catalog entry setsproperties.schemaForm: responseSchema, which converts the schema to the OpenAPI subset. ServiceTieris the top-level request fieldserviceTier(flex,priority; the reference lists it as an optional request field and an output-only usage field).- A bad API key is answered with HTTP 400
INVALID_ARGUMENT(API_KEY_INVALID); it is mapped to an authentication error.
Not settled by the reference
- Whether a
GenerateContentRequestinside a batch needs its ownmodelfield (it is omitted; the batch names the model in the URL). - The embeddings async batch (
asyncBatchEmbedContent) is not implemented;batchEmbedContentsis. - Streamed partial function-call arguments (see above).
Tests
tests/OneLlm.Tests/Gemini/ holds a conformance case list (GeminiCases*.cs), replayed against fixtures in testdata/providers/gemini/direct/<feature>/. Fixtures are written from the API reference (their description says so), not recorded. To rewrite them after a deliberate request change, set ONELLM_WRITE_FIXTURES to that directory and run the conformance tests; read the diff. The upload fixture is written by hand, because the recorder drops the upload address header.