Gemini API skill for Codex
Gemini integrations on the current SDK and API. Codex writes Gemini code against the google-genai SDK and the Interactions API, keeps model ids in config because they change often, and picks one state mode per conversation so thought signatures are never dropped. It sends large media through the Files API, runs capped tool loops with function_result items, validates JSON output against Pydantic schemas, and logs cached and thinking tokens to keep costs visible.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- gemini-api Installs to ~/.agents/skills/gemini-api/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install gemini-api The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project gemini-api Installs to .agents/skills/gemini-api/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/gemini-api.zipunzip -o gemini-api.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) gemini-api PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $gemini-api, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Builds on the Google Gemini API with the google-genai Python SDK and @google/genai for JavaScript - the Interactions API (interactions.create, typed steps, previous_interaction_id state, streaming step events), multimodal input with inline data and the Files API, function calling with function_result items, JSON Schema structured output via response_format, thinking_level, implicit caching and explicit caches on generateContent. Use when writing or migrating Gemini code, moving off the deprecated google-generativeai package or from generateContent to Interactions, sending images, PDFs, audio or video to Gemini, or debugging tool calls and token costs.
Use it when
- Writing new code against the Gemini API in Python or JavaScript
- Migrating from google-generativeai or from generateContent to Interactions
- Sending images, PDFs, audio or video to Gemini
- Debugging Gemini tool calls, lost context or unexpected token costs
Use something else when
- You are building on OpenAI's API instead: use openai-responses-api
- You only need a provider-agnostic streaming UI layer: use vercel-ai-sdk
How to check the result
A smoke script covering text, an image, a tool round trip and structured output passes against the live API, and logged usage shows cached tokens on repeated long-prefix requests.
The mistake it prevents
Mixing previous_interaction_id state with manually replayed history, or stripping thought blocks in stateless mode, which silently degrades multi-turn reasoning and tool calls.
A Gemini integration is done when the model id lives in config, every call goes through one wrapper on the current SDK, conversation state is owned on purpose (server-side interactions or client-replayed history, not both), large media goes through the Files API, tool loops terminate, and token usage including cached and thinking tokens is logged.
Before you write code
- Read
AGENTS.md, then grep for the SDK in use:rg -n "google.generativeai|google.genai|@google/genai|@google/generative-ai" .The legacy packages (google-generativeai,@google/generative-ai) are deprecated; migrate them togoogle-genai/@google/genai. - Check which API surface the code uses. The Interactions API
(
client.interactions.create) is Google’s recommended interface for new work; the olderclient.models.generate_content(generateContent) remains supported and is still needed for explicit context caches. Do not mix both in one conversation flow. - Model ids change often (Gemini 2.5 access is restricted to existing users; Gemini 3.x and 4 are current at the time of writing). Read the model id from config and check ai.google.dev/gemini-api/docs/models before hardcoding anything.
- Installs need network:
pip install -U google-genaiornpm install @google/genai. The SDK readsGEMINI_API_KEYfrom the environment. Never commit the key.
First call
from google import genai
client = genai.Client() # GEMINI_API_KEY from env
interaction = client.interactions.create(
model=MODEL, # e.g. "gemini-3.8-flash", from config
system_instruction="You are a concise assistant for an internal ops tool.",
input="Summarize the incident in two sentences: ...",
generation_config={"thinking_level": "low", "max_output_tokens": 800},
)
print(interaction.output_text)
print(interaction.usage.total_input_tokens, interaction.usage.total_output_tokens,
interaction.usage.total_cached_tokens, interaction.usage.total_thought_tokens)
import { GoogleGenAI } from "@google/genai";
const ai = new GoogleGenAI({});
const interaction = await ai.interactions.create({ model: MODEL, input: "Hello" });
console.log(interaction.output_text);
Output is a list of typed steps (model output, function calls, tool results).
output_text concatenates only the final text block; iterate steps when text and tool
calls interleave.
State
| Mode | How | Use when |
|---|---|---|
Server-side (default store=true) | Pass previous_interaction_id=prev.id with only the new input | Chat and agents; simplest; thought signatures handled for you |
Stateless (store=false) | Resend full history each turn, including every thought and tool block exactly as received | Data-retention rules forbid storage, or you need full control |
In stateless mode, dropping or editing thought blocks (thought signatures) degrades reasoning and can fail tool calls. Pass them back verbatim, even when switching models.
Multimodal input
import base64
img = base64.b64encode(open("receipt.jpg", "rb").read()).decode()
interaction = client.interactions.create(
model=MODEL,
input=[
{"type": "text", "text": "Extract merchant, date and total."},
{"type": "image", "mime_type": "image/jpeg", "data": img},
],
)
# Large or reused media: upload once, reference by URI
f = client.files.upload(file="meeting.mp4")
interaction = client.interactions.create(
model=MODEL,
input=[{"type": "video", "uri": f.uri, "mime_type": f.mime_type},
{"type": "text", "text": "List action items with timestamps."}],
)
- Use the Files API when the total request exceeds ~100 MB, for PDFs over 50 MB, or when the same file is used in several requests. Files are deleted after 48 hours; 2 GB per file, 20 GB per project. Re-upload rather than storing the URI long term.
- Video and long audio cost tokens per second of media; sample or trim before sending.
- Uploaded files are processed asynchronously; check the file’s state with
files.getbefore using a large video, or the request may fail.
Function calling
import json
tools = [{
"type": "function",
"name": "get_order",
"description": "Look up an order by id. Use for any question about a specific order.",
"parameters": {"type": "object",
"properties": {"order_id": {"type": "string"}},
"required": ["order_id"]},
}]
interaction = client.interactions.create(model=MODEL, input=question, tools=tools)
for _ in range(8): # hard cap on tool rounds
calls = [s for s in interaction.steps if s.type == "function_call"]
if not calls:
break
results = []
for c in calls: # parallel calls arrive together
out = dispatch(c.name, c.arguments) # validate args before executing
results.append({"type": "function_result", "name": c.name, "call_id": c.id,
"result": [{"type": "text", "text": json.dumps(out)}]})
interaction = client.interactions.create(
model=MODEL, tools=tools, input=results, previous_interaction_id=interaction.id)
print(interaction.output_text)
- Control calling with
generation_config={"tool_choice": "auto" | "any" | "none" | "validated"}. - Return errors as results (
{"error": "order not found"}) so the model can recover. - Side-effecting tools (refunds, emails, deletes) need a user confirmation step in your
code before
dispatchruns them. See agent-tool-design for schema design. - Built-in tools are declared by type, e.g.
{"type": "google_search"}; grounded answers carry citations you must display per Google’s terms.
Structured output
from pydantic import BaseModel
class Receipt(BaseModel):
merchant: str
date: str
total_cents: int
interaction = client.interactions.create(
model=MODEL, input=[...],
response_format={"type": "text", "mime_type": "application/json",
"schema": Receipt.model_json_schema()},
)
receipt = Receipt.model_validate_json(interaction.output_text)
Keep schemas flat and small; deeply nested optional fields increase refusals and latency. Validate anyway: schema support covers a subset of JSON Schema.
Streaming
for event in client.interactions.create(model=MODEL, input=prompt, stream=True):
if event.event_type == "step.delta" and event.delta.type == "text":
send_to_client(event.delta.text)
elif event.event_type == "interaction.completed":
log_usage(event)
Event types include step.start, step.delta, step.stop and interaction.completed.
For SSE delivery to browsers and cancellation, see llm-streaming.
Thinking and cost
generation_config.thinking_level:minimal,low,medium,high(allowed values vary by model; defaults vary too). Start withlowfor extraction and classification, raise only where evals improve.thinking_summaries: "auto"returns readable summaries; never show raw thoughts as facts.- Implicit caching is automatic on Gemini 2.5 and newer when a request repeats a long
prefix (minimum 2,048-4,096 input tokens depending on model). Put stable content
(system instruction, documents) first and the variable question last. Check
usage.total_cached_tokens. - Explicit caches (
client.caches.create(...)with a TTL, referenced throughcached_contentingenerate_content) exist only on the generateContent API and only for supported Gemini 3 and 2.5 models. Use them for a large document queried many times within the TTL; you pay storage per hour. service_tier(flex,standard,priority) trades latency for price on supported models; batch backfills belong on flex or batch modes.
Errors and limits
| Symptom | Cause | Fix |
|---|---|---|
429 RESOURCE_EXHAUSTED | Rate or quota limit | Exponential backoff with jitter; check tier quotas |
| Empty text, finish reason safety | Safety filter | Inspect safety ratings; adjust safety_settings only within policy |
| 400 on file reference | File expired (48 h) or still processing | Re-upload; wait for active state |
| Tool loop never ends | Model keeps calling tools | Cap rounds; tool_choice: "none" on the final turn |
| Lost context across turns | Mixed stateful and stateless | Pick one state mode per conversation |
Workflow
- Read
AGENTS.md; identify SDK, API surface and model ids in use - Migrate legacy SDK imports to
google-genai/@google/genai - Centralize client creation, model id and usage logging
- Choose state mode; implement tool loop with caps and confirmations
- Route large media through the Files API
- Add structured output and validation where code consumes the result
- Measure tokens (cached, thought) and latency on representative inputs
Verification
- Run a smoke script against the real API (needs network and a key; ask before running it in CI with spend) covering text, one image, one tool round trip and structured output.
- Log
usageper call and confirm cached tokens appear on repeated long-prefix requests. - Unit-test the tool loop with a fake client that returns two parallel function calls, then text.
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Gemini API"
short_description: "Gemini integrations on the current SDK and API"
default_prompt: "$gemini-api Migrate our google-generativeai code to the new google-genai SDK and the Interactions API" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.