SPB Git

spb/zyquo-cloud Public MIT

Native macOS AI chat client for 12 cloud providers — your keys, every cloud model, one beautiful chat.

Swift 97.4% Shell 1.7% Makefile 1%
13.2 KB

# Google Gemini — API Research (Phase 0)

Researched 2026-07-30 against official docs (ai.google.dev) and a live /v1beta/models probe with a real key (/tmp/zyquo-probe/gemini.json). Where docs and the live list disagree, the live list wins for model existence.


# 1. Base URL & Endpoints

# Native Gemini API (v1beta)

Base: https://generativelanguage.googleapis.com

Endpoint Method Purpose
/v1beta/models/{model}:generateContent POST Non-streaming chat
/v1beta/models/{model}:streamGenerateContent?alt=sse POST SSE streaming chat
/v1beta/models GET List models (paginated, pageSize/pageToken)
/v1beta/models/{model} GET Model metadata
/v1beta/models/{model}:countTokens POST Token counting
/v1beta/cachedContents POST/GET Explicit context caching

Docs: https://ai.google.dev/api/generate-content

Base: https://generativelanguage.googleapis.com/v1beta/openai/

Endpoint Purpose
/v1beta/openai/chat/completions Chat (streaming + non-streaming)
/v1beta/openai/models and /models/{id} Model listing/retrieval
/v1beta/openai/embeddings Embeddings
/v1beta/openai/images/generations, /videos, /batches Media/batch (not needed for chat)

Docs: https://ai.google.dev/gemini-api/docs/openai

Compat-endpoint limitations (verified 2026-07): officially "still in beta while we extend feature support". Supported: chat completions with streaming, function calling (tools), structured outputs (JSON schema), vision (base64 data URIs), reasoning_effort, and Gemini-specific extras via extra_body (thinking_config, safety_settings, cached_content, Google Search grounding tools on Gemini 3+). Batch file upload/download requires the native genai SDK. Native-only features (thought summaries as thought: true parts, thoughtSignature round-tripping, fine-grained safety feedback, usageMetadata.thoughtsTokenCount) are not fully exposed — if the app needs rich thinking display for Gemini, the native GeminiClient is the better path.

# 2. Authentication

  • Native API: header x-goog-api-key: $GEMINI_API_KEY (preferred) or query param ?key=$GEMINI_API_KEY.
  • OpenAI-compat endpoint: Authorization: Bearer $GEMINI_API_KEY.

Same API key (from Google AI Studio) works for both.

# 3. Chat Model Catalog (text-generation chat models only)

Source of truth: live /models probe (2026-07-30) + https://ai.google.dev/gemini-api/docs/models + https://ai.google.dev/gemini-api/docs/pricing. All context/output limits below are the exact inputTokenLimit/outputTokenLimit from the live probe. All listed models support streaming, vision (multimodal input), function calling, and JSON mode (structured output); the "Thinking" column is the probe's thinking flag. Pricing = standard tier, USD per 1M tokens, text input (audio input often costs more; noted where documented). Prices from the official pricing page unless marked.

Model ID Status Context Max out Input $/1M Output $/1M Thinking
gemini-3.6-flash Stable — flagship 1,048,576 65,536 $1.50 $7.50 ✅ (default: medium)
gemini-3.5-flash Stable 1,048,576 65,536 $1.50 $9.00
gemini-3.5-flash-lite Stable 1,048,576 65,536 $0.30 $2.50 ✅ (default: minimal)
gemini-3.1-pro-preview Preview 1,048,576 65,536 $2.00 (≤200k) / $4.00 (>200k) $12.00 / $18.00 ✅ (default: high)
gemini-3.1-flash-lite Stable 1,048,576 65,536 $0.25 (text/img/video), $0.50 (audio) $1.50
gemini-2.5-pro Stable 1,048,576 65,536 $1.25 (≤200k) / $2.50 (>200k) $10.00 / $15.00 ✅ (cannot be disabled)
gemini-2.5-flash Stable 1,048,576 65,536 $0.30 (text), $1.00 (audio) $2.50
gemini-2.5-flash-lite Stable 1,048,576 65,536 $0.10 (text), $0.30 (audio) $0.40 ✅ (off by default)

# Rolling aliases (auto-track latest release — good defaults for the app)

Alias Currently points at (probe) Context / Max out
gemini-pro-latest latest Pro release 1,048,576 / 65,536
gemini-flash-latest latest Flash release 1,048,576 / 65,536
gemini-flash-lite-latest latest Flash-Lite release 1,048,576 / 65,536

# Preview / secondary (available, lower priority)

Model ID Status Context Max out Pricing Notes
gemini-3-pro-preview Preview 1,048,576 65,536 $2.00/$12.00 (≤200k), $4.00/$18.00 (>200k) — corroborated by third-party trackers; not on official pricing page (semi-verified) Superseded by 3.1 Pro
gemini-3-flash-preview Preview 1,048,576 65,536 $0.50 (text) / $3.00 Superseded by 3.5/3.6 Flash
gemini-3.1-pro-preview-customtools Preview 1,048,576 65,536 unverified (assume 3.1 Pro pricing) Tool-use-optimized variant
gemini-3.1-flash-lite-preview Preview 1,048,576 65,536 unverified (assume 3.1 Flash-Lite pricing) Preview channel of stable 3.1 Flash-Lite
gemini-omni-flash-preview Preview 131,072 65,536 unverified Omni multimodal preview
gemini-2.0-flash / gemini-2.0-flash-001 Stable, legacy 1,048,576 8,192 $0.10 (text), $0.70 (audio) / $0.40 No thinking
gemini-2.0-flash-lite / -001 Stable, legacy 1,048,576 8,192 $0.075 / $0.30 No thinking
gemma-4-26b-a4b-it Open model 262,144 32,768 unverified (Gemma historically free tier) Text-focused; function calling unverified
gemma-4-31b-it Open model 262,144 32,768 unverified Text-focused; function calling unverified

Excluded from the chat catalog (present in live list but not chat-app material): TTS models (gemini-2.5-*-tts, gemini-3.1-flash-tts-preview), image models (gemini-2.5-flash-image, gemini-3-pro-image[-preview], nano-banana-pro-preview, gemini-3.1-flash[-lite]-image[-preview]), Imagen/Veo/Lyria, embeddings (gemini-embedding-001/2), Live/native-audio (bidiGenerateContent-only), robotics (gemini-robotics-er-1.5/1.6), gemini-2.5-computer-use-preview-10-2025, aqa, and agentic specials (antigravity-preview-05-2026, deep-research[-max|-pro]-preview-* — generateContent-capable but agent products, not chat models).

Context caching: cached input is heavily discounted (e.g. 3.1 Flash-Lite $0.025/1M cached + $1.00/1M/hr storage; 2.0 Flash $0.025/1M). Batch/Flex modes: 50% off. (https://ai.google.dev/gemini-api/docs/pricing)

# 4. Request / Response Format

# Native (v1beta generateContent) — NOT OpenAI-compatible

Roles are user and model (not assistant). System prompt goes in top-level systemInstruction. Text and images are parts.

json
POST /v1beta/models/gemini-3.6-flash:generateContent
{
  "systemInstruction": { "parts": [{ "text": "You are a helpful assistant." }] },
  "contents": [
    { "role": "user", "parts": [
        { "text": "What is in this image?" },
        { "inline_data": { "mime_type": "image/jpeg", "data": "<base64>" } }
    ]}
  ],
  "generationConfig": {
    "temperature": 1.0,
    "topP": 0.95,
    "maxOutputTokens": 8192,
    "responseMimeType": "application/json",
    "thinkingConfig": { "thinkingLevel": "medium", "includeThoughts": true }
  },
  "safetySettings": [ { "category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE" } ]
}

Response: candidates[0].content.parts[].text, candidates[0].finishReason (STOP, MAX_TOKENS, SAFETY, …), usageMetadata (promptTokenCount, candidatesTokenCount, thoughtsTokenCount, cachedContentTokenCount, totalTokenCount), plus modelVersion, responseId, optional promptFeedback.blockReason. Thought summaries arrive as parts with "thought": true. Structured output: responseMimeType: "application/json" + responseSchema.

Vision (native): inline_data: { mime_type, data } with raw base64 (no data-URI prefix), or file_data: { mime_type, file_uri } via the Files API.

# OpenAI-compat endpoint

Standard OpenAI chat schema (model, messages with system/user/assistant roles, temperature, top_p, max_tokens, stream, tools, response_format). Vision uses OpenAI style: {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,<b64>"}}. Gemini extras go in extra_body (thinking_config, safety_settings, cached_content).

json
POST /v1beta/openai/chat/completions
{
  "model": "gemini-3.6-flash",
  "reasoning_effort": "low",
  "messages": [
    { "role": "system", "content": "You are a helpful assistant." },
    { "role": "user", "content": "Reply with exactly: OK" }
  ],
  "stream": true
}

# 5. Streaming

  • Native: :streamGenerateContent?alt=sse → SSE stream of data: {GenerateContentResponse} chunks; each chunk carries incremental candidates[0].content.parts (thought-summary parts stream too, as rolling incremental summaries with thought: true). usageMetadata arrives on chunks (final chunk carries the complete counts). No data: [DONE] sentinel — stream ends when the HTTP body closes and the last chunk has a finishReason. Without ?alt=sse the endpoint returns a streamed JSON array instead — always pass alt=sse.
  • OpenAI-compat: standard OpenAI SSE — data: {chat.completion.chunk} with choices[0].delta.content, terminated by data: [DONE]; usage on the final chunk.

# 6. Special Parameters

  • Thinking (https://ai.google.dev/gemini-api/docs/generate-content/thinking):
    • Gemini 3.x models: thinkingConfig.thinkingLevel = "minimal" | "low" | "medium" | "high" (minimal ≈ no thinking for most queries; default varies — 3.6/3.5 Flash: medium, 3.5 Flash-Lite: minimal, 3.1 Pro: high). Setting both thinkingLevel and thinkingBudget on a Gemini 3 model → error.
    • Gemini 2.5 models: thinkingConfig.thinkingBudget (token cap). 2.5 Pro: dynamic default, range 128–32,768, cannot be disabled; 2.5 Flash: 0–24,576 (0 disables); 2.5 Flash-Lite: off by default, 512–24,576. -1 = dynamic.
    • thinkingConfig.includeThoughts: true → thought-summary parts (thought: true). Full thinking tokens are billed as output even though only summaries are returned.
    • thoughtSignature: encrypted reasoning state returned on parts; must be echoed back in multi-turn function-calling flows to preserve reasoning context.
    • Compat endpoint: reasoning_effort = minimal|low|medium|high maps to thinking levels; or extra_body.thinking_config.
  • Safety settings: safetySettings[] with category (HARM_CATEGORY_HARASSMENT, _HATE_SPEECH, _SEXUALLY_EXPLICIT, _DANGEROUS_CONTENT, _CIVIC_INTEGRITY) and threshold (BLOCK_NONE, BLOCK_ONLY_HIGH, BLOCK_MEDIUM_AND_ABOVE, BLOCK_LOW_AND_ABOVE). Available via extra_body.safety_settings on the compat endpoint.
  • Context caching: cachedContent: "cachedContents/{id}" (native) / extra_body.cached_content (compat).
  • Sampling: temperature 0–2 (default 1.0 per probe), topP (default 0.95), topK (default 64 on 2.5+/3.x, 40 on 2.0), candidateCount, stopSequences.
  • Google Search grounding via tools (Gemini 3+ on compat endpoint).

# 7. Rate Limits & Errors

(https://ai.google.dev/gemini-api/docs/rate-limits) Limits are per-project across three dimensions: RPM, TPM, RPD — exceeding any one triggers HTTP 429 RESOURCE_EXHAUSTED. Tiers auto-assigned by spend: Free; Tier 1 (billing linked, ~$250 cap); Tier 2 ($100+ spent, 3+ days); Tier 3 ($1,000+ spent, 30+ days). Additional spend-based rolling 10-minute caps: Tier 1 $10/10min, Tiers 2–3 $200/10min. Per-model RPM/TPM values are no longer published universally — shown live at https://aistudio.google.com/rate-limit.

Error format (native): standard Google API error envelope:

json
{ "error": { "code": 429, "message": "...", "status": "RESOURCE_EXHAUSTED", "details": [ ... ] } }

Common statuses: 400 INVALID_ARGUMENT, 403 PERMISSION_DENIED (bad key), 404 NOT_FOUND (bad model), 429 RESOURCE_EXHAUSTED, 500 INTERNAL, 503 UNAVAILABLE. The compat endpoint returns OpenAI-style {"error": {"message", "type", "code"}} (shape unverified in docs — confirm in Phase 7). Retry with exponential backoff on 429/5xx.

# 8. /models Listing

Yes — both endpoints.

  • Native: GET /v1beta/models{ "models": [ { "name": "models/<id>", "displayName", "description", "inputTokenLimit", "outputTokenLimit", "supportedGenerationMethods": ["generateContent", ...], "temperature", "topP", "topK", "maxTemperature", "thinking" } ], "nextPageToken"? }. Rich: Zyquo Cloud can derive context window, max output, thinking support, and chat capability (generateContent in supportedGenerationMethods) directly. Filter out non-chat models by method (predict, bidiGenerateContent, embedContent, generateAnswer) and by name patterns (-tts, -image, imagen-, veo-, lyria-, embedding, robotics, computer-use, deep-research, antigravity, aqa).
  • Compat: GET /v1beta/openai/models → OpenAI-style {"object":"list","data":[{"id","object":"model","owned_by"}]} (IDs only, no metadata).