fix(gemini): honor apiBase/headers/timeout/proxy/TLS on all paths - #13060
Open
aaronlippold wants to merge 1 commit into
Open
fix(gemini): honor apiBase/headers/timeout/proxy/TLS on all paths#13060aaronlippold wants to merge 1 commit into
aaronlippold wants to merge 1 commit into
Conversation
aaronlippold
requested review from
Copilot and
sestinj
and removed request for
a team
July 29, 2026 15:53
There was a problem hiding this comment.
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/genaiSDK viahttpOptions, switch embeddings to the SDK-nativeembedContentpath, 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
force-pushed
the
fix/gemini-adapter-proxy-tls
branch
from
July 29, 2026 16:05
1242ea4 to
f0f8c31
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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/genaiclient with onlyapiKey, soapiBase,requestOptions.headers,timeout, and all proxy/TLS options were silently dropped on the chat/stream path (whileembed()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'srequestshonorsHTTPS_PROXY/REQUESTS_CA_BUNDLEout 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:
httpOptionspassed to the SDK client —apiBase→baseUrl, plusrequestOptions.headersandtimeoutrequestOptions(proxy,caBundlePath,clientCertificate,verifySsl), since@google/genaiexposes 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_PROXYenv vars honored — the adapter engages the proxy-capable fetch path when the standard proxy env vars are set; request-time precedence (config over env) andNO_PROXYbypass stay entirely with@continuedev/fetch'sfetchwithRequestOptionsRESOURCE_EXHAUSTEDresponses carry the real message double-nested in JSON; the adapter now unwraps it (depth-capped) instead of surfacing raw JSON / "Unknown error"embed()— the hand-rolled REST path parsed bare model names as a URL scheme (discardingapiBase) and read the wrong response field;models.embedContentfixes both and deletes the duplicated pathcompletion_tokensnow includesthoughtsTokenCount, matching the OpenAI-compatible usage spec (Google'stotalTokenCountalready includes thoughts, sototal != prompt + completionbefore this)Notes for reviewers (also in #13052):
main.test.ts's live suite moves fromgemini-2.5-*to the-latestaliases 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.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.)Closes #12008
AI Code Review
@continue-reviewChecklist
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 accountinggemini-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-endgemini-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 withNO_PROXYbypass, config-over-env precedence, timeout abort, cross-config credential isolation via two simultaneous proxiesgemini-tls-e2e.vitest.ts(6) — mini-CA:caBundlePathtrust,verifySsl: false, mutual TLS withclientCertificate(including passphrase-protected key), handshake rejection without a client certopensslis used by the TLS rig atbeforeAll(present on the CI image; same approach aspackages/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.