Skip to content

fix(gemini): honor apiBase/headers/timeout/proxy/TLS on all paths - #13060

Open
aaronlippold wants to merge 1 commit into
continuedev:mainfrom
aaronlippold:fix/gemini-adapter-proxy-tls
Open

fix(gemini): honor apiBase/headers/timeout/proxy/TLS on all paths#13060
aaronlippold wants to merge 1 commit into
continuedev:mainfrom
aaronlippold:fix/gemini-adapter-proxy-tls

Conversation

@aaronlippold

@aaronlippold aaronlippold commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Description

Part 1 of 4 of #13052 (tracking issue with full root-cause analysis and the PR split plan).

The Gemini adapter built the @google/genai client with only apiKey, so apiBase, requestOptions.headers, timeout, and all proxy/TLS options were silently dropped on the chat/stream path (while embed() honored them). Users behind corporate proxies or custom gateways couldn't reach Gemini even though the same key worked in Google's own samples — Python's requests honors HTTPS_PROXY/REQUESTS_CA_BUNDLE out of the box, which is why the plain sample works where a gateway-fronted Continue config does not.

This PR makes the adapter honor the full config on every path:

  • httpOptions passed to the SDK clientapiBasebaseUrl, plus requestOptions.headers and timeout
  • Proxy/TLS support on all paths — SDK calls run under a scoped fetch that applies requestOptions (proxy, caBundlePath, clientCertificate, verifySsl), since @google/genai exposes no public fetch-injection hook. The swap window is serialized with a promise-chain mutex so concurrent configs can never observe each other's credentials; only establishment is serialized — stream consumption stays concurrent.
  • HTTPS_PROXY/HTTP_PROXY/NO_PROXY env vars honored — the adapter engages the proxy-capable fetch path when the standard proxy env vars are set; request-time precedence (config over env) and NO_PROXY bypass stay entirely with @continuedev/fetch's fetchwithRequestOptions
  • Nested error surfacing — Gemini quota/RESOURCE_EXHAUSTED responses carry the real message double-nested in JSON; the adapter now unwraps it (depth-capped) instead of surfacing raw JSON / "Unknown error"
  • SDK-native embed() — the hand-rolled REST path parsed bare model names as a URL scheme (discarding apiBase) and read the wrong response field; models.embedContent fixes both and deletes the duplicated path
  • Thinking-token accountingcompletion_tokens now includes thoughtsTokenCount, matching the OpenAI-compatible usage spec (Google's totalTokenCount already includes thoughts, so total != prompt + completion before this)

Notes for reviewers (also in #13052):

  • Token usage change is non-breaking — no shape change; values rise for thinking-enabled models, which were previously undercounted.
  • main.test.ts's live suite moves from gemini-2.5-* to the -latest aliases so it runs for newly created API keys (gemini-2.5-* is gated for new keys); trade-off is the suite is no longer pinned to a fixed model.
  • This PR is self-contained in packages/openai-adapters — no changes to other packages. (An earlier revision re-exported an env-proxy helper from @continuedev/fetch; that coupling was removed since dependent packages consume the published @continuedev/fetch, and the tracking issue's mention of a new export no longer applies.)
  • Relationship to existing PRs: addresses Provider gemini does not pass custom headers to gemini API #12008; builds on the direction of [codex] fix(gemini): pass custom http options to sdk #12014 (credit to its author) and extends it to proxy/TLS on all paths; incorporates the embed fix from fix(openai-adapters): read Gemini embeddings from correct response field #12517 (credit to its author). Glad to coordinate with those authors.

Closes #12008

AI Code Review

  • Team members only: AI review runs automatically when PR is opened or marked ready for review
  • Team members can also trigger a review by commenting @continue-review

Checklist

Screen recording or screenshot

N/A — library-level change in packages/openai-adapters (no UI). Behavior is demonstrated by the no-mocks e2e tests below.

Tests

All automated tests run against no-mocks local rigs (real local proxy servers, real TLS handshakes against a self-signed mini-CA) — they exercise the real SDK and real sockets, no fetch mocks:

  • gemini-adapter.vitest.ts (25) — httpOptions construction, error normalization, embed request/response shape, usage accounting
  • gemini-fetch-adaptation.vitest.ts (10) — response adaptation, fast-path vs wrapped-path selection, env-proxy engagement, restore-on-throw, swap serialization (concurrent configs observe only their own fetch), lock released at establishment not stream-end
  • gemini-proxy-e2e.vitest.ts (9) — real local proxy with request hit-counters: proxy routing, custom headers on the wire, embed via proxy, env-var proxy with NO_PROXY bypass, config-over-env precedence, timeout abort, cross-config credential isolation via two simultaneous proxies
  • gemini-tls-e2e.vitest.ts (6) — mini-CA: caBundlePath trust, verifySsl: false, mutual TLS with clientCertificate (including passphrase-protected key), handshake rejection without a client cert

openssl is used by the TLS rig at beforeAll (present on the CI image; same approach as packages/fetch's existing e2e tests).

Additionally verified manually against the live Google API (real key, outside CI): Google's own live model tests pass once pointed at current model IDs, including tool calls on two models; embeddings return 3072-dim vectors through the SDK-native path.

@aaronlippold
aaronlippold requested a review from a team as a code owner July 29, 2026 15:53
@aaronlippold
aaronlippold requested review from Copilot and sestinj and removed request for a team July 29, 2026 15:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes the Gemini OpenAI-adapter integration so that all Gemini request paths (chat/stream + embed) consistently honor Continue configuration: apiBase, requestOptions.headers, timeout, and proxy/TLS settings. It also improves Gemini error surfacing by unwrapping nested SDK error payloads, and aligns token usage accounting with OpenAI-compatible semantics for “thinking” tokens.

Changes:

  • Add a requestOptions-aware fetch wrapper that can apply proxy/TLS via customFetch, adapt node-fetch-style Responses back to native WHATWG Responses, and (attempt to) prevent cross-config credential bleed during global fetch swaps.
  • Pass apiBase/headers/timeout into the @google/genai SDK via httpOptions, switch embeddings to the SDK-native embedContent path, and normalize nested Gemini errors.
  • Expand no-mocks e2e coverage for proxy + TLS behaviors and update live model IDs in the main adapter test suite.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/openai-adapters/src/util/requestOptionsFetch.ts Introduces requestOptions-aware fetch swapping + Response adaptation for SDK streaming under proxy/TLS/env-proxy conditions.
packages/openai-adapters/src/apis/Gemini.ts Uses requestOptions-aware fetch for SDK calls, passes httpOptions, normalizes nested errors, adjusts usage accounting, and switches embeddings to SDK-native path.
packages/fetch/src/index.ts Re-exports getProxyFromEnv for shared env-proxy detection semantics.
packages/openai-adapters/src/test/main.test.ts Updates Gemini live test model IDs to *-latest aliases.
packages/openai-adapters/src/test/gemini-adapter.vitest.ts Adds unit tests for httpOptions construction, nested error normalization, usage accounting, and SDK-native embeddings mapping.
packages/openai-adapters/src/test/gemini-fetch-adaptation.vitest.ts Adds tests for node-fetch→native Response adaptation, wrapper engagement, restoration-on-throw, and swap serialization behavior.
packages/openai-adapters/src/test/gemini-proxy-e2e.vitest.ts Adds no-mocks proxy e2e suite (real local forward proxies + stub Gemini server) including env proxy and concurrent-config isolation coverage.
packages/openai-adapters/src/test/gemini-tls-e2e.vitest.ts Adds no-mocks TLS/mTLS e2e suite using an openssl-generated mini CA and HTTPS stubs.
packages/openai-adapters/src/test/gemini-test-helpers.ts Shared SSE helper for Gemini proxy/TLS e2e rigs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +6 to +12
import {
nativeFetch,
nativeHeaders,
nativeRequest,
nativeResponse,
withNativeFetch,
} from "./nativeFetch.js";
Comment on lines +128 to +130
if (!hasProxyOrTlsOptions(requestOptions) && !hasEnvironmentProxy()) {
return withNativeFetch(fn);
}
Comment on lines +180 to +186
const normalized: Error & { status?: number; cause?: unknown } = new Error(
extracted.message,
);
normalized.status = status;
normalized.cause = error;
return normalized;
}
The Gemini adapter built the @google/genai client with only apiKey, so
apiBase, requestOptions.headers, timeout, and all proxy/TLS options were
dropped on the chat/stream path while embed() honored them. Corporate
proxy and custom-gateway users could not reach Gemini even though the same
key worked in Google's own samples.

- Pass httpOptions (baseUrl/apiVersion/headers/timeout) into the SDK client
- Route SDK calls through a serialized global-fetch swap so proxy/TLS
  requestOptions apply; a promise-chain mutex serializes only the
  establishment window to prevent cross-config credential leaks
- Honor HTTPS_PROXY/HTTP_PROXY env vars (request-time precedence and
  NO_PROXY bypass stay with customFetch)
- Surface nested Gemini error JSON instead of "Unknown error"
- SDK-native embedContent (fixes bare-model-name URL parsing and wrong
  response field); include thoughtsTokenCount in completion_tokens

Tests: no-mocks e2e rigs for proxy routing, TLS + mTLS (mini-CA),
custom headers, embed-via-proxy, env-proxy precedence, timeout abort,
and cross-config credential isolation.

Refs continuedev#13052
Closes continuedev#12008

Authored by: Aaron Lippold<lippold@gmail.com>
@aaronlippold
aaronlippold force-pushed the fix/gemini-adapter-proxy-tls branch from 1242ea4 to f0f8c31 Compare July 29, 2026 16:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Provider gemini does not pass custom headers to gemini API

2 participants