Skip to content

Fix: accept GOOGLE_API_KEY in geminiInitParams so adk create .env files work - #313

Open
AmaadMartin wants to merge 1 commit into
mainfrom
fix/gemini-google-api-key-fallback
Open

Fix: accept GOOGLE_API_KEY in geminiInitParams so adk create .env files work#313
AmaadMartin wants to merge 1 commit into
mainfrom
fix/gemini-google-api-key-fallback

Conversation

@AmaadMartin

Copy link
Copy Markdown
Owner

Please ensure you have read the contribution guide before creating a pull request.

Link to Issue or Description of Change

  1. Link to an existing issue (if applicable):
    Closes: #issue_number
    Related: #issue_number
  2. Or, if no issue exists, describe the change:
    Problem: An agent scaffolded by the officially supported on-ramp cannot construct its own model.

generateEnvFile() in dev/src/cli/cli_create.ts writes the Gemini-API credentials as:

GOOGLE_API_KEY=<key>
GOOGLE_GENAI_USE_VERTEXAI=0

But geminiInitParams() in core/src/models/google_llm.ts only consulted GOOGLE_GENAI_API_KEY and GEMINI_API_KEY on the non-Vertex branch. GOOGLE_API_KEY — the name the CLI had just written — was not among them, so params.apiKey stayed undefined and the Gemini constructor threw:

API key must be provided via constructor or GOOGLE_GENAI_API_KEY or GEMINI_API_KEY environment variable.

Reproduced end to end against a local build (see the E2E section below): adk create demo --api_key <KEY> --yes, then loading the generated .env and doing new Gemini({model: 'gemini-2.5-flash'}) throws.

The underlying SDK would have accepted the key on its own — ADK's own pre-flight validation is what rejected the CLI-generated .env. @google/genai@1.52.0 (the version in this repo's lockfile) resolves it in getApiKeyFromEnv():

function getApiKeyFromEnv() {
  const envGoogleApiKey = getEnv('GOOGLE_API_KEY');
  const envGeminiApiKey = getEnv('GEMINI_API_KEY');
  if (envGoogleApiKey && envGeminiApiKey) {
    console.warn(
      'Both GOOGLE_API_KEY and GEMINI_API_KEY are set. Using GOOGLE_API_KEY.',
    );
  }
  return envGoogleApiKey || envGeminiApiKey || undefined;
}

Solution: Accept GOOGLE_API_KEY as an additional fallback inside geminiInitParams(), rather than changing what generateEnvFile() emits.

params.apiKey =
  process.env['GOOGLE_GENAI_API_KEY'] ||
  process.env['GOOGLE_API_KEY'] ||
  process.env['GEMINI_API_KEY'];

Why fix the resolver and not the CLI:

  • GOOGLE_API_KEY is the name @google/genai reads, the name adk-python's CLI writes, and a name already used elsewhere in this repo for Vertex Express mode (core/src/utils/vertex_ai_utils.ts). Renaming the CLI output would make adk-js the odd one out in its own ecosystem.
  • Fixing only the CLI would leave every already-generated .env, and every hand-written .env following the SDK/python convention, still broken. Fixing the resolver repairs both populations.

Resulting precedence (non-Vertex branch, !isBrowser() only):

  1. params.apiKey (explicit constructor argument) — always wins.
  2. GOOGLE_GENAI_API_KEY — keeps top env priority, so no current setup changes meaning.
  3. GOOGLE_API_KEYnew.
  4. GEMINI_API_KEY.

GOOGLE_API_KEY is placed ahead of GEMINI_API_KEY deliberately, mirroring the SDK ordering quoted above.

The throw message at core/src/models/google_llm.ts enumerated the accepted names and would have become wrong, so it now reads ... GOOGLE_GENAI_API_KEY, GOOGLE_API_KEY or GEMINI_API_KEY .... Doc comments on GeminiParams.apiKey and ApigeeLlmParams.apiKey, which enumerate the same names, were updated to match. No key value is ever interpolated into a message or log.

Scope and invariants held

  • The Vertex/enterprise branch is untouched. The new fallback lives strictly inside the existing non-Vertex else, so Vertex Express mode still resolves GOOGLE_API_KEY only through getExpressModeApiKey(). Pinned by a test (see mutation M6).
  • The !isBrowser() guard still wraps every process.env read — core ships a browser bundle and must not touch process.env there.
  • No change to what generateEnvFile() writes, and no change to GOOGLE_GENAI_USE_VERTEXAI handling (the ..._USE_ENTERPRISE rename is a separate, in-flight change).
  • No API surface, export, or dependency change; no package.json/lockfile churn.

Cross-language parity: adk-python has no such gap — its CLI writes GOOGLE_API_KEY and its Gemini.api_client constructs google.genai.Client(**kwargs) without an explicit api_key, so the SDK's own resolution applies. This change makes adk-js match on the observable behaviour (which env var names are accepted, and in what order), which is the side that parity governs.

Two behaviour deltas, both intended

  1. Precedence corner case: if both GOOGLE_API_KEY and GEMINI_API_KEY are set to different values, ADK previously used GEMINI_API_KEY and now uses GOOGLE_API_KEY — which is what @google/genai itself would have done. Deliberate alignment, not a regression.
  2. ApigeeLlm: apigeeToGeminiInitParams() substitutes the fake key '-' when no key resolves. With GOOGLE_API_KEY set in the environment, the gemini-provider path now uses that real key instead of '-' — the same treatment GOOGLE_GENAI_API_KEY already got. Consistent and intended.

Collision check (required before implementation): scanned all 221 open PRs on the fork for any that touch core/src/models/{google_llm,apigee_llm}.ts or dev/src/cli/cli_create.ts, then grepped each candidate diff for GOOGLE_API_KEY / geminiInitParams. No open PR lands this change. Adjacent-but-distinct work exists and this PR is branched from main rather than stacked, because it depends on none of it:

Deviations from the elaborated plan, both narrowing:

  • core/test/models/apigee_llm_test.ts was not modified. The plan proposed adding GOOGLE_API_KEY / GEMINI_API_KEY deletions to its afterEach for hermeticity, but this was tested rather than assumed: running that suite with GOOGLE_API_KEY=ambient-key exported passes 43/43 both before and after this change, because no assertion in the file observes the resolved key. The addition would have been dead code.
  • The dev test tightening was reduced to a contract comment over the existing GOOGLE_API_KEY=my-api-key assertion, for the Feat: Emit GOOGLE_GENAI_USE_ENTERPRISE from adk create and adk deploy #286 reason above. That assertion already fails on a rename (verified — mutation M7).

Testing Plan

Please describe the tests that you ran to verify your changes. This is required for all PRs that are not small documentation or typo fixes.
Unit Tests:
[x] I have added or updated unit tests for my change.
[x] All unit tests pass locally.

Commands run (targeted, on the exact pushed commit):

npx vitest run --project unit:core core/test/models/google_llm_test.ts core/test/models/apigee_llm_test.ts   # 71 passed
npx vitest run --project unit:dev  dev/test/cli/cli_create_test.ts                                           # 10 passed
npm run build        # exit 0
npm run lint         # exit 0
npm run format:check # exit 0 ("All matched files use Prettier code style!")
npm run docs:check   # exit 0

Added to core/test/models/google_llm_test.ts:

  • A four-case table pinning the whole resolution chain: GOOGLE_API_KEY alone; GOOGLE_GENAI_API_KEY over GOOGLE_API_KEY; GOOGLE_API_KEY over GEMINI_API_KEY; GEMINI_API_KEY alone.
  • 'should prefer the constructor apiKey over GOOGLE_API_KEY'.
  • 'should not use GOOGLE_API_KEY on the Vertex AI path' — pins the invariant that the fallback stays inside the non-Vertex branch.
  • 'should construct from the .env written by adk create' — sets exactly the two lines the CLI emits and asserts the model constructs and the client receives cli-key. This is the bug's contract, expressed hermetically.
  • The existing missing-key throw test was strengthened (not weakened) from /API key must be provided/ to the full updated message including GOOGLE_API_KEY.
  • delete process.env['GOOGLE_API_KEY'] added to clearEnv(). This is required, not cosmetic: without it the pre-existing tests 'should throw error if apiKey is missing in constructor' and 'should return undefined apiKey if missing' start failing on any machine that exports GOOGLE_API_KEY.

No existing test was deleted, skipped, or weakened.

Coverage: 100% of the changed regions. Measured with @vitest/coverage-v8 scoped to core/src/models/google_llm.ts, then inspected per-line from the JSON report: zero uncovered statements and zero uncovered branches in both changed regions (the throw at lines 118–122 and the fallback chain at lines 396–404).

Proof each test can fail. Every new test was run against mutated source and confirmed to FAIL. Recorded mutations and their failure messages:

# Mutation Test(s) killed Failure message
M1 Delete the process.env['GOOGLE_API_KEY'] || term should construct from the .env written by \adk create`; should resolve GOOGLE_API_KEY when it is the only key set; should resolve GOOGLE_API_KEY over GEMINI_API_KEY` API key must be provided via constructor or GOOGLE_GENAI_API_KEY, GOOGLE_API_KEY or GEMINI_API_KEY environment variable. / expected undefined to be 'google-api-key' / expected 'gemini-api-key' to be 'google-api-key'
M2 Revert the throw message to the old wording should throw error if apiKey is missing in constructor expected [Function] to throw error matching /API key must be provided via construc…/ but got 'API key must be provided via construc…'
M3 Reorder GOOGLE_API_KEY after GEMINI_API_KEY should resolve GOOGLE_API_KEY over GEMINI_API_KEY expected 'gemini-api-key' to be 'google-api-key'
M4 Reorder GOOGLE_API_KEY before GOOGLE_GENAI_API_KEY should resolve GOOGLE_GENAI_API_KEY over GOOGLE_API_KEY expected 'google-api-key' to be 'genai-api-key'
M5 Drop the !params.apiKey guard (env would beat the constructor arg) should prefer the constructor apiKey over GOOGLE_API_KEY (+9 pre-existing) expected 'google-api-key' to be 'explicit-key'
M6 Hoist the fallback out of the non-Vertex else branch should not use GOOGLE_API_KEY on the Vertex AI path expected 'google-api-key' to be undefined
M7 Make the CLI write GOOGLE_GENAI_API_KEY= instead should set Google AI env vars if api key provided (dev) expected "spy" to be called with arguments: [ StringContaining ".env", …(1) ]
M8 Remove GOOGLE_API_KEY from clearEnv(), with it exported ambiently should throw error if apiKey is missing in constructor; should return undefined apiKey if missing; should resolve GEMINI_API_KEY when it is the only key set expected [Function] to throw an error / expected 'cli-key' to be undefined / expected 'google-api-key' to be 'gemini-api-key'
M9 Delete the trailing || process.env['GEMINI_API_KEY'] term should resolve GEMINI_API_KEY when it is the only key set expected undefined to be 'gemini-api-key'

Manual End-to-End (E2E) Tests:
Please provide instructions on how to manually test your changes, including any necessary setup or configuration.

Run with no mocks, against the real built package and a real adk create scaffold. Both directions were verified.

  1. npm install && npm run build at the repo root.
  2. In a scratch directory:
    node <repo>/dev/dist/esm/cli_entrypoint.js create demo --api_key <PLACEHOLDER> --yes
    Confirm demo/.env contains GOOGLE_API_KEY=<PLACEHOLDER> and GOOGLE_GENAI_USE_VERTEXAI=0 — unchanged by this PR. (Constructing a model does not call the API, so a placeholder key is sufficient; do not paste a real key anywhere.)
  3. From demo/, run a script that mirrors what the generated agent.ts does — dotenv.config(), then new Gemini({model: 'gemini-2.5-flash'}) — resolving @google/adk to the local build:
    import dotenv from 'dotenv';
    dotenv.config();
    const {Gemini} = await import('<repo>/core/dist/esm/index.js');
    new Gemini({model: 'gemini-2.5-flash'});
    console.log('constructed ok');

Results:

  • With this change: prints constructed ok.
  • Negative control — rebuilt core from the pre-fix google_llm.ts and re-ran the identical script: Error: API key must be provided via constructor or GOOGLE_GENAI_API_KEY or GEMINI_API_KEY environment variable. Restoring the fix and rebuilding returns it to constructed ok.

No new integration or tests/e2e test was added: the behaviour is fully determined by process.env and is covered hermetically by the unit tests, and a live-credential test would be skipped in CI. Confirmed no existing tests/** case asserts the old error string.

Checklist

[x] I have read the CONTRIBUTING.md document.
[x] I have performed a self-review of my own code.
[x] I have commented my code, particularly in hard-to-understand areas.
[x] I have added tests that prove my fix is effective or that my feature works.
[x] New and existing unit tests pass locally with my changes.

Notes for the reviewer:

geminiInitParams() only consulted GOOGLE_GENAI_API_KEY and GEMINI_API_KEY
on the non-Vertex branch, so the .env that `adk create` generates -- which
writes GOOGLE_API_KEY -- could not construct a Gemini model. GOOGLE_API_KEY
is the name @google/genai reads itself and the name adk-python's CLI writes.

Insert it between the two existing names: GOOGLE_GENAI_API_KEY keeps top
env priority so no current setup changes meaning, and GOOGLE_API_KEY
precedes GEMINI_API_KEY to match getApiKeyFromEnv() in the SDK.
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.

1 participant