Gemini Adapter Worker
Source catalog · Knowledge graph
Facts
| Fact | Value |
|---|---|
| Status | active |
| Hosting platform | cloudflare |
| Repository | https://github.com/meta4m/gemini-adapter-worker |
| Tech stack | language: typescript, runtime: cloudflare-workers, gateway: cloudflare-ai-gateway, secrets_store: cloudflare-secrets-store, build_tool: wrangler, testing: vitest, ci: github-actions, package_manager: npm |
| Tags | llm, gemini, cloudflare-workers, ai-gateway, openai-compat, rotation, rate-limits |
| Last audited | 2026-09-15 |
| Upstream dependencies | None declared |
| Downstream dependencies | ai-job-mvp |
Secrets manifest
Key names and metadata only. Encrypted records are available in this project’s vault below.
| Key | Description | Environments | Required |
|---|---|---|---|
GEMINI_KEY_T1 | Gemini API key for Google Cloud project 1, held in Cloudflare Secrets Store (store 4e22739e) and bound to the Worker as env.GEMINI_KEY_T1; physical slot T1 in the (key x model) rotation ring | production | Yes |
GEMINI_KEY_T2 | Gemini API key for Google Cloud project 2, bound as env.GEMINI_KEY_T2; independent per-model quota, physical slot T2 in the rotation ring | production | Yes |
GEMINI_KEY_T3 | Gemini API key for Google Cloud project 3, bound as env.GEMINI_KEY_T3; independent per-model quota, physical slot T3 in the rotation ring | production | Yes |
GATEWAY_SECRET | Shared secret injected by the four aijobs-gateway custom providers in the X-Gateway-Secret header and verified by the adapter on every request; rotation is two-sided (Secrets Store PATCH + provider headers PATCH) | production | Yes |
CF_CLIENT_TOKEN | Cloudflare API token with AI Gateway: Run permission; the bearer credential clients use for runtime inference through aijobs-gateway (sample app consumes it as VITE_GATEWAY_TOKEN) | production, development | Yes |
CF_ADMIN_TOKEN | Cloudflare admin API token (AI Gateway: Edit, Secrets Store: Edit, Workers AI: Read) used for gateway custom-provider config, Secrets Store rotation, and ops runbook calls | production, development | Yes |
CLOUDFLARE_API_TOKEN | Cloudflare API token consumed by the GitHub Actions wrangler-action for deploys (secrets.CLOUDFLARE_API_TOKEN in deploy.yml); holds the same credential value as CF_ADMIN_TOKEN | production | Yes |
SCRAPLING_MCP_TOKEN | Auth token for the Scrapling MCP job-scraping service used by the local sample app (.env.example, sample-app/.env.example); a literal token value is committed in the repo and should be rotated | development | No |
Overview
Project Overview: Gemini Adapter Worker
1. Executive Summary
- Mission: A Cloudflare Worker translation adapter behind the
aijobs-gatewayCloudflare AI Gateway that gives OpenAI-SDK clients access to Google Gemini with zero Gemini keys, model names, or JSON payloads — translating OpenAI ↔ Gemini wire formats and rotating a (API key × model) ring per pool on 429/5xx so free-tier quotas multiply across three Google Cloud projects. - Target Audience: The operator’s own LLM-backed products — the README repeatedly references an unnamed “job-search product” (resume scoring, JD parsing, cover letters, candidate ranking), consistent with ai-job-mvp and the
aijobs-gatewayid — plus any OpenAI SDK / cURL / Ollama client that can point at a custom baseURL. - Primary Maintainer: The operator (meta4m) — solo developer; runs on the operator’s personal Cloudflare account (“Irfan”).
2. Current Status & Roadmap
- Lifecycle State:
active— Phase 7 (pool-level ring rotation) shipped 2026-09-01 as deployed adapter version6db85fc2; the latest commit (2026-09-03) movedGATEWAY_SECRETinto the Secrets Store; CI (typecheck + tests) and path-filtered auto-deploy to Workers run on every push tomain. - Ongoing: translation hardening (Phase 2a/2b complete; Phase 2 marked in progress in the roadmap table). Deliberately deferred with documented revisit triggers: SSE streaming (never on this adapter — a separate provider is planned), Cloudflare Access (deferred indefinitely per the 2026-09-03 auth-posture decision), Workers AI cross-provider failover (future Phase 6).
3. Core Capabilities
- Four pools — lite (2 flash-lite models, 3,000 RPD), pro (2 flash models, 120 RPD), gemma (2 Gemma-4 models, 86,400 RPD for background work), embed (
gemini-embedding-2, 3,000 RPD) — selected by clients via the OpenAImodelfieldcustom-gemini-<pool>/<label>. - Ring rotation: per request, walk the (3 keys × N models) cross-product from a randomly-seeded per-isolate counter; 429/5xx advances the ring, non-retryable 4xx surfaces immediately; pinned legacy tiers (
l1…e3) force a single key for debugging and deterministic tests. - OpenAI-compat translation (
src/translate.ts): system role, multimodal content parts, generation params, full tool calling (tools/tool_choice/tool_calls/role:"tool"),response_format: json_object, finish-reason mapping;n > 1,logprobs, andjson_schemarejected with 400. - Embeddings: OpenAI-shaped
/v1/embeddings→ Gemini:embedContent/:batchEmbedContents, fixed 3072-dim output, chars/4 token approximation. - Strict gateway-only auth: every request must carry
X-Gateway-Secretmatching the Secrets Store value (injected by the four gateway custom providers); direct browser calls to the*.workers.devURL 401 by design. - Observability:
X-Adapter-Slot: pool/key/modelresponse header, structuredconsole.loglines into Workers Observability, and the gateway’s 7-day Global Edge Cache absorbing duplicate prompts before they reach the adapter. - In-repo sample app (
sample-app/, Vite + React): resume PDF parsing, gateway chat/embed calls, and Scrapling MCP job search with cosine-similarity match scoring — a demo of the intended product usage.
4. Key References
- Repository: meta4m/gemini-adapter-worker
- Production URL:
https://gemini-adapter.iu8.workers.dev(adapter), fronted byhttps://gateway.ai.cloudflare.com/v1/0649c741a1ecb087ea2d37002e0517d5/aijobs-gateway/compat(chat) and…/aijobs-gateway/custom-gemini-embed(embeddings) — documented in README §2. - The operator’s 2brain is likewise routed through a Cloudflare AI Gateway (its own
2brain-gatewayinstance); this repo never references 2Brain, and no shared infrastructure or code dependency between the two gateways is evidenced. - Architecture Docs:
architecture.md· Deployment Guide:deployment.md
Architecture
Architecture & System Design: Gemini Adapter Worker
1. System Topology
graph TD Client["Clients: OpenAI SDK / cURL / sample app<br/>model: custom-gemini-pool/label<br/>cf-aig-authorization: CF_CLIENT_TOKEN"] --> GW["Cloudflare AI Gateway: aijobs-gateway<br/>4 pool-level custom providers (X-Pool, X-Gateway-Secret)<br/>7-day Global Edge Cache"] GW --> ADP["gemini-adapter Worker<br/>src/index.ts: gateway-secret gate, CORS,<br/>body-shape dispatch, ring walk on 429/5xx"] ADP --> TR["src/translate.ts: OpenAI-Gemini translation,<br/>SLOTS cross-product, per-isolate counter"] SS["Cloudflare Secrets Store 4e22739e<br/>GEMINI_KEY_T1/T2/T3, GATEWAY_SECRET"] --> ADP ADP --> GEM["Google Generative Language API<br/>generativelanguage.googleapis.com/v1beta"]2. Core Components & Subsystems
- Entrypoint (
src/index.ts): strict gateway-only gate (X-Gateway-Secretvsenv.GATEWAY_SECRET.get(), skipped in vitest), CORS headers, tier resolution (X-Pool→ legacyX-Tier→ path segment → model-name fallback), body-shape dispatch (embeddings iffinputand nomessages), and the slot-ring walk with per-attemptconsole.log. - Translation layer (
src/translate.ts): hand-rolled OpenAI ↔ Gemini translator (chat multimodal + tools +response_format, embeddings) plus the pool model —SLOTSas the (T1..T3 × models) cross-product,tierToKeypinned-tier mapping, randomly-seeded per-isolate rotation counter onglobalThis. - Secrets Store bindings (
wrangler.jsonc): foursecrets_store_secretsbindings (GEMINI_KEY_T1/T2/T3,GATEWAY_SECRET) against store4e22739e295443a0a42bbbf9c5ac298e; keys are read per request viaenv.<BINDING>.get()so rotation needs no redeploy. - Gateway config (managed outside the repo via the Cloudflare API): four pool-level custom providers
custom-gemini-{lite,pro,gemma,embed}pointing athttps://gemini-adapter.iu8.workers.dev/<pool>/v1, each injectingX-PoolandX-Gateway-Secretheaders; the 12 legacy per-key providers are kept disabled as a fast rollback path. - Sample app (
sample-app/): Vite + React demo (pdfjs-dist, localforage, OpenAI SDK, Scrapling MCP client) exercising resume parsing, chat extraction, embeddings, and cosine match scoring through the gateway. - Tests (
tests/, vitest): index routing/auth/rotation, security input-validation vectors, chat and embed translation, and pool/slot invariants.
3. Data Flow & Storage Contracts
- Stateless Worker — no KV, D1, Durable Objects, or R2; the only mutable state is the per-isolate rotation counter in
globalThis(deliberately not shared across isolates). - Secrets: read at request time from Cloudflare Secrets Store bindings; rotation is a Secrets Store
PATCH(plus matching provider-headerPATCHforGATEWAY_SECRET) with no code change or redeploy. - Caching: the gateway’s 7-day Global Edge Cache (604,800 s TTL) is the only cache layer; clients can override with
cf-aig-skip-cache/cf-aig-cache-ttl. This is why the Workers Free plan’s 100 k requests/day is not a constraint. - Logs: metadata-only
console.loglines (endpoint,pool,key,model,slot,status) into Workers Observability; request bodies are never logged (explicit privacy decision in README §16).
4. Architectural Decision Records (ADRs)
ADR-01: Adapter-side ring rotation instead of gateway dynamic-route cascade
- Context: AI Gateway’s native dynamic routing collapses same-provider nodes onto one secret (
{gateway}_{provider}_{default}), so a 3-node cascade ofgoogle-ai-studioall burned key 1. - Decision: Four pool-level custom providers forward to the adapter, which owns rotation over the (3 keys × N models) cross-product — rotation IS the failover, no client opt-in.
- Consequences: RPD/RPM spread evenly across three Google Cloud projects; gateway analytics groups all keys per pool, so the real per-request key is observable only via the
X-Adapter-Slotheader andwrangler tail.
ADR-02: Hand-rolled OpenAI ↔ Gemini translator
- Context: The gateway pass-through for custom providers does not convert OpenAI shapes to Gemini’s
contents/partsformat nor injectx-goog-api-key(403/404 from Google); Phase 2 research found no good drop-in library. - Decision: Maintain
src/translate.tsin-repo with explicit field mappings and 400-rejections for unsupported fields (n > 1,logprobs,json_schema). - Consequences: Full control over the compatibility surface; the cost is ongoing maintenance for every new OpenAI/Gemini field, and SSE streaming is explicitly out of scope.
ADR-03: Per-isolate rotation counter, not KV or Durable Objects
- Context: Rotation must spread load without adding hot-path latency; Workers run many isolates with separate
globalThis. - Decision: Randomly-seeded round-robin counter per isolate; no shared state.
- Consequences: No cross-isolate ordering guarantee, but even spreading at the target concurrency (~15 simultaneous users); a Durable Object counter is the documented upgrade path if traffic grows.
ADR-04: Strict gateway-only auth via shared secret; Cloudflare Access deferred
- Context: The adapter is on a public
*.workers.devURL; the gateway is the only intended entry point. - Decision: Every request must present
X-Gateway-Secretmatching the Secrets Store value; Cloudflare Access service-token auth deferred indefinitely (README §16, decided 2026-09-03) with documented revisit triggers (multi-user PII, paid providers, chat surface, compliance). - Consequences: Simple two-token model (client
Runtoken + adminEdittoken); rotation is two-sided, and a mismatch produces 401s at the adapter.
Deployment
Deployment & Operational Runbook: Gemini Adapter Worker
1. Hosting & Environments
| Environment | Platform | URL / Endpoint | Auto-Deploy Branch |
|---|---|---|---|
| Production | Cloudflare Workers (Free plan), Worker gemini-adapter | https://gemini-adapter.iu8.workers.dev behind the aijobs-gateway AI Gateway | main — deploy.yml on pushes touching src/**, wrangler.jsonc, worker-configuration.d.ts, package.json, tsconfig.json |
| Local dev | Vitest (node env) — no wrangler dev script exists | N/A (tests run the Worker in-process with mocked env bindings) | N/A |
Fronting infrastructure (managed in the Cloudflare dashboard/API, not in this repo): AI Gateway aijobs-gateway with four pool-level custom providers, and Secrets Store 4e22739e295443a0a42bbbf9c5ac298e.
2. Prerequisites & Toolchain
- Node.js 22 + npm (CI uses
npm ci;package-lock.jsoncommitted). - Wrangler 4.x (devDependency) for deploys; a Cloudflare API token with Workers Scripts:Edit (+ Secrets Store for secret provisioning).
- Optional for the sample app: Node + npm inside
sample-app/(Vite dev server).
3. Local Development Setup
git clone https://github.com/meta4m/gemini-adapter-worker.gitcd gemini-adapter-workernpm installnpm run typecheck # tsc -p tsconfig.json --noEmitnpm test # vitest run (index, security, translate.chat/embed/pools)
# Optional sample app (gateway + Scrapling MCP demo):cd sample-app && npm install && npm run dev # needs VITE_GATEWAY_TOKEN in sample-app/.env4. Environment & Secrets Provisioning
Refer to spec.yml for the full manifest (GEMINI_KEY_T1/T2/T3, GATEWAY_SECRET, CF_CLIENT_TOKEN, CF_ADMIN_TOKEN, CLOUDFLARE_API_TOKEN, SCRAPLING_MCP_TOKEN).
- Runtime secrets (adapter): already provisioned in Cloudflare Secrets Store and bound via
wrangler.jsoncsecrets_store_secrets— the repo needs no local secret file to deploy or test. - Ops tokens (
.env.example/ CI): Retrieve ciphertext from the 2Brain vault and decrypt locally into.env.local—CF_ADMIN_TOKEN/CLOUDFLARE_API_TOKENfor deploys and gateway/secrets management,CF_CLIENT_TOKENfor gateway inference calls; GitHub repo secretsCLOUDFLARE_API_TOKEN+CLOUDFLARE_ACCOUNT_IDfeeddeploy.yml’s wrangler-action. - Hygiene note:
.env.exampleandsample-app/.env.examplecommit a literalSCRAPLING_MCP_TOKENvalue — rotate it and move to.env.local.
5. Deployment Commands
npm run deploy # wrangler deploy (same command CI runs via cloudflare/wrangler-action@v3)Secrets rotation (no redeploy needed — live Secrets Store PATCH, then matching PATCH of the four custom providers’ headers for GATEWAY_SECRET):
curl -X PATCH "https://api.cloudflare.com/client/v4/accounts/0649c741a1ecb087ea2d37002e0517d5/secrets_store/stores/4e22739e295443a0a42bbbf9c5ac298e/secrets/${SECRET_ID}" \ -H "Authorization: Bearer ${CF_ADMIN_TOKEN}" -H "Content-Type: application/json" \ -d '{"value":"<NEW_VALUE>"}'6. Verification & Health Checks
- Rotation check: send ~12 unique chat requests with
cf-aig-skip-cache: trueand grep theX-Adapter-Slotresponse header — all 6 lite slots should appear (README §8 runbook). wrangler tail gemini-adapter --format=prettyfor liveadapter endpoint=… pool=… key=… model=… slot=… status=…lines.- Dashboard: Workers & Pages →
gemini-adapter→ Metrics/Logs (observability enabled inwrangler.jsonc). - Expected failure modes:
401 gateway only= missing/mismatchedX-Gateway-Secret;400 unknown tier= bad pool naming;429after a full ring walk = all three keys’ per-model quota exhausted (resets midnight Pacific); embeddings via/compat/embeddingsreturn501 code 2023— use the provider-specific path.
7. Incident Response & Rollback
- Rollback: redeploy the previous git revision (
git checkout <good-sha> && npm run deploy), or re-enable the 12 legacy per-key custom providers (custom-gemini-{pool}-1|2|3) kept disabled in the gateway as a fast rollback path. - 1102 Worker exceeded resource limits (Free plan: 10 ms CPU / 50 subrequests / 100 k req/day): follow the README §7 checklist — suspect a new tight loop or fetch-in-a-loop; the current adapter does one subrequest per attempt.
- GATEWAY_SECRET mismatch (401s at adapter): rotate both sides — Secrets Store value and the four providers’
headersconfig must match. - Burned key/quota: PATCH the affected
GEMINI_KEY_T*secret in the store; no code change or redeploy required. - Streaming request: not supported by design — a
stream: truerequest gets buffered behavior; the documented plan is a separate non-pooled provider for any future chat surface.
Project vault
Cloudflare Access authorization and your passphrase are required to retrieve and decrypt stored values.
Client-Side Encrypted Secret Vault
Zero-knowledge architecture. Secrets are encrypted client-side using Web Crypto AES-256-GCM before leaving this browser tab. Edge databases store only ciphertext, IV, and salt.
Encrypted Records for gemini-adapter-worker (0)
Encrypted records have not been loaded.