Skip to content

Gemini Adapter Worker

Source catalog · Knowledge graph

Facts

FactValue
Statusactive
Hosting platformcloudflare
Repositoryhttps://github.com/meta4m/gemini-adapter-worker
Tech stacklanguage: typescript, runtime: cloudflare-workers, gateway: cloudflare-ai-gateway, secrets_store: cloudflare-secrets-store, build_tool: wrangler, testing: vitest, ci: github-actions, package_manager: npm
Tagsllm, gemini, cloudflare-workers, ai-gateway, openai-compat, rotation, rate-limits
Last audited2026-09-15
Upstream dependenciesNone declared
Downstream dependenciesai-job-mvp

Secrets manifest

Key names and metadata only. Encrypted records are available in this project’s vault below.

KeyDescriptionEnvironmentsRequired
GEMINI_KEY_T1Gemini 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 ringproductionYes
GEMINI_KEY_T2Gemini API key for Google Cloud project 2, bound as env.GEMINI_KEY_T2; independent per-model quota, physical slot T2 in the rotation ringproductionYes
GEMINI_KEY_T3Gemini API key for Google Cloud project 3, bound as env.GEMINI_KEY_T3; independent per-model quota, physical slot T3 in the rotation ringproductionYes
GATEWAY_SECRETShared 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)productionYes
CF_CLIENT_TOKENCloudflare 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, developmentYes
CF_ADMIN_TOKENCloudflare admin API token (AI Gateway: Edit, Secrets Store: Edit, Workers AI: Read) used for gateway custom-provider config, Secrets Store rotation, and ops runbook callsproduction, developmentYes
CLOUDFLARE_API_TOKENCloudflare 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_TOKENproductionYes
SCRAPLING_MCP_TOKENAuth 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 rotateddevelopmentNo

Overview

Project Overview: Gemini Adapter Worker

1. Executive Summary

  • Mission: A Cloudflare Worker translation adapter behind the aijobs-gateway Cloudflare 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-gateway id — 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 version 6db85fc2; the latest commit (2026-09-03) moved GATEWAY_SECRET into the Secrets Store; CI (typecheck + tests) and path-filtered auto-deploy to Workers run on every push to main.
  • 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 OpenAI model field custom-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, and json_schema rejected 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-Secret matching the Secrets Store value (injected by the four gateway custom providers); direct browser calls to the *.workers.dev URL 401 by design.
  • Observability: X-Adapter-Slot: pool/key/model response header, structured console.log lines 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 by https://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-gateway instance); 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-Secret vs env.GATEWAY_SECRET.get(), skipped in vitest), CORS headers, tier resolution (X-Pool → legacy X-Tier → path segment → model-name fallback), body-shape dispatch (embeddings iff input and no messages), and the slot-ring walk with per-attempt console.log.
  • Translation layer (src/translate.ts): hand-rolled OpenAI ↔ Gemini translator (chat multimodal + tools + response_format, embeddings) plus the pool model — SLOTS as the (T1..T3 × models) cross-product, tierToKey pinned-tier mapping, randomly-seeded per-isolate rotation counter on globalThis.
  • Secrets Store bindings (wrangler.jsonc): four secrets_store_secrets bindings (GEMINI_KEY_T1/T2/T3, GATEWAY_SECRET) against store 4e22739e295443a0a42bbbf9c5ac298e; keys are read per request via env.<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 at https://gemini-adapter.iu8.workers.dev/<pool>/v1, each injecting X-Pool and X-Gateway-Secret headers; 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-header PATCH for GATEWAY_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.log lines (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 of google-ai-studio all 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-Slot header and wrangler 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/parts format nor inject x-goog-api-key (403/404 from Google); Phase 2 research found no good drop-in library.
  • Decision: Maintain src/translate.ts in-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.dev URL; the gateway is the only intended entry point.
  • Decision: Every request must present X-Gateway-Secret matching 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 Run token + admin Edit token); rotation is two-sided, and a mismatch produces 401s at the adapter.

Deployment

Deployment & Operational Runbook: Gemini Adapter Worker

1. Hosting & Environments

EnvironmentPlatformURL / EndpointAuto-Deploy Branch
ProductionCloudflare Workers (Free plan), Worker gemini-adapterhttps://gemini-adapter.iu8.workers.dev behind the aijobs-gateway AI Gatewaymain — deploy.yml on pushes touching src/**, wrangler.jsonc, worker-configuration.d.ts, package.json, tsconfig.json
Local devVitest (node env) — no wrangler dev script existsN/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.json committed).
  • 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

Terminal window
git clone https://github.com/meta4m/gemini-adapter-worker.git
cd gemini-adapter-worker
npm install
npm run typecheck # tsc -p tsconfig.json --noEmit
npm 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/.env

4. 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.jsonc secrets_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_TOKEN for deploys and gateway/secrets management, CF_CLIENT_TOKEN for gateway inference calls; GitHub repo secrets CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID feed deploy.yml’s wrangler-action.
  • Hygiene note: .env.example and sample-app/.env.example commit a literal SCRAPLING_MCP_TOKEN value — rotate it and move to .env.local.

5. Deployment Commands

Terminal window
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):

Terminal window
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: true and grep the X-Adapter-Slot response header — all 6 lite slots should appear (README §8 runbook).
  • wrangler tail gemini-adapter --format=pretty for live adapter endpoint=… pool=… key=… model=… slot=… status=… lines.
  • Dashboard: Workers & Pages → gemini-adapter → Metrics/Logs (observability enabled in wrangler.jsonc).
  • Expected failure modes: 401 gateway only = missing/mismatched X-Gateway-Secret; 400 unknown tier = bad pool naming; 429 after a full ring walk = all three keys’ per-model quota exhausted (resets midnight Pacific); embeddings via /compat/embeddings return 501 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’ headers config 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: true request 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.

Add / Rotate Encrypted Secret