Fix: tighten the Gemini-1 boundary and split the EAP gate out of isGemini2OrAbove (adk-python parity) - #615
Open
AmaadMartin wants to merge 4 commits into
Conversation
added 4 commits
August 3, 2026 21:25
isGemini1Model used a `startsWith('gemini-1')` prefix test, so `gemini-1`,
`gemini-1-pro`, `gemini-1.` and a future double-digit major such as
`gemini-10.0-pro` were all classified as Gemini 1.x. adk-python matches
`^gemini-1\.\d+` (src/google/adk/utils/model_name_utils.py), which requires the
dotted minor version.
Four built-in tools branch on this predicate, so a `gemini-10` model would have
been routed down the legacy Gemini 1.x path.
isGemini2OrAbove had the EAP naming test folded into it, which widened all
three of its call sites at once. adk-python keeps the two concerns apart: the
built-in code executor and the URL context tool call
is_gemini_eap_or_2_or_above, while the CFC gate in runners.py uses a bare
startswith('gemini-2') and rejects EAP ids.
Restore isGemini2OrAbove to numeric-version semantics, add
isGeminiEapOr2OrAbove alongside it, and migrate only the two call sites Python
routes through the EAP-aware predicate. The runner CFC gate keeps calling
isGemini2OrAbove, so it now matches Python again.
The thrown messages at both migrated call sites are unchanged. No public
export is added, renamed, or removed: isGeminiEapOr2OrAbove is internal to the
package.
The EAP test cases move from the isGemini2OrAbove describe block to the new
predicate with their inputs and expectations intact, and a new block pins
isGemini2OrAbove returning false for EAP ids so the split cannot be silently
undone. New Runner CFC tests assert the gate rejects an EAP id.
…rowing Review follow-ups: - Inline the single-caller isGeminiEapModel into isGeminiEapOr2OrAbove and drop its docblock, which restated EAP_MODEL_NAME_PATTERN's own. Mirroring adk-python's _is_gemini_eap_model is structural parity, not behavioural. - Drop the duplicated "EAP ids carry no numeric version" sentence from isGeminiEapOr2OrAbove; the constant's docblock and the isGemini2OrAbove pointer already carry it. - Drop the "no `g` flag" clause from both pattern docblocks: it explains the absence of a flag nobody wrote. - Inline MODELS_PREFIX, used twice on adjacent lines while the two path regexes beside it are not similarly hoisted. - Pin the isGemini1Model narrowing at a consumer outside this module. Tightening the predicate to /^gemini-1\.\d+/ also loosens the Gemini 1.x branch of GoogleSearchTool for the undotted ids: `gemini-1` and `gemini-1-pro` now take the googleSearch branch instead of googleSearchRetrieval, and no longer throw when other tools are present. Every existing test there uses a dotted gemini-1.5-* id, so nothing pinned it. Type makeRequest's tools parameter so the new cases typecheck, which also clears a pre-existing TS2322 on the same helper.
The new undotted-Gemini-1 cases were passing `{} as never` for toolContext,
matching the file's older tests. That is an unchecked cast standing in for a
type the tool's signature genuinely requires, so construct a real Context the
way url_context_tool_test.ts does. The pre-existing cases keep their own
convention rather than being rewritten here.
This was referenced Aug 4, 2026
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.
Please ensure you have read the contribution guide before creating a pull request.
Link to Issue or Description of Change
Link to an existing issue (if applicable):
N/A
Or, if no issue exists, describe the change:
Problem:
core/src/utils/model_name.tsis the single placeadk-jsclassifies modelids, and it diverges from
adk-python'ssrc/google/adk/utils/model_name_utils.pyin threeindependent ways. Every built-in tool and code executor that gates on those predicates
inherits the divergence.
GAP 3 — the Gemini-1 boundary was a prefix test, not a version test (fixed here; not
covered by either sibling PR).
adk-pythonusesre.match(r'^gemini-1\.\d+', name)(
model_name_utils.py:120);adk-jsusedmodelName.startsWith('gemini-1'). Four built-intools (
GoogleSearchTool,GoogleMapsGroundingTool,VertexAiSearchTool,EnterpriseWebSearchTool) branch on this predicate, so a future double-digit Gemini majorwould be routed down the legacy Gemini-1 path.
GAP 1 — EAP model ids were rejected (already fixed by #372, in the stack base).
gemini-flash-early-expcarries no numeric version, soisGemini2OrAbovereturnedfalseand
UrlContextTool/BuiltInCodeExecutorrefused it. What this PR changes is wherethat acceptance lives — see the design decision.
GAP 2 —
extractModelNameonly understood the Vertex path form (already fixed by#471, in the stack base).
apigee/gemini-2.5-flash,models/gemini-2.5-proandgemini/gemini-2.5-flashnow reduce to their bare model name.Solution:
Concretely:
isGemini1Modelnow tests/^gemini-1\.\d+/against the extracted name.isGemini2OrAboveis restored to numeric-version semantics (the EAP test added in thestack base is removed from it).
isGeminiEapModel+ exported-from-moduleisGeminiEapOr2OrAbove, mirroring_is_gemini_eap_model/is_gemini_eap_or_2_or_above.BuiltInCodeExecutorandUrlContextTool— the two call sitesadk-pythonroutesthrough
is_gemini_eap_or_2_or_above— switch to the new predicate. Their thrownmessages are byte-identical to before.
runner/runner.ts(CFC) deliberately keeps callingisGemini2OrAbove, matchingrunners.py.Cross-language parity, which rule applied where: parity wins for the observable
classification of every model id (the expectation tables are
adk-python's outputs).Local JS/TS convention wins for naming and module layout only —
isGeminiEapModelstays amodule-private function rather than a
_-prefixed one, per the repo style guide.Behavioural narrowing of
isGemini1Model, and what it changes at four call sites thisPR does not otherwise touch.
isGemini1Modelnow returnsfalseforgemini-1,gemini-1-pro,gemini-1.andgemini-10.*. That is the fix, and it matchesadk-python— but because four built-in tools branch on this predicate, tightening it alsoloosens their Gemini 1.x guards for those undotted ids. Called out explicitly:
google_search_tool.ts:40—gemini-1/gemini-1-pronow take thegoogleSearchbranch instead of
googleSearchRetrieval, and no longer throw"Google search tool can not be used with other tools in Gemini 1.x."
google_maps_grounding_tool.ts:29— stops throwing "cannot be used with Gemini 1.xmodels" for those ids.
vertex_ai_search_tool.ts:144andenterprise_web_search_tool.ts:35shift the same way.No such model exists today, so this is latent behaviour rather than a live regression, and
every existing test at those four consumers uses a dotted
gemini-1.5-*id — meaningnothing in the suite pinned it. It is now pinned:
google_search_tool_test.tsgains casesasserting that
gemini-1,gemini-1-proandgemini-10.0-protake thegoogleSearchbranch and coexist with other tools. Those cases fail against the old predicate (see
mutation 1 below).
Public API surface: unchanged. No export was added, renamed, or removed.
isGeminiEapOr2OrAboveis exported from the module but deliberately not added tocore/src/common.ts/core/src/index.ts; it is internal to the package and tests importit by relative path, as 67 other files in
core/testalready do.Modified existing tests, and why. Restoring
isGemini2OrAbovemeans the EAP cases thestack base added under
describe('isGemini2OrAbove')now encode behaviour this changedeliberately moves. They are not deleted: every input and expectation is carried over
verbatim into the new
describe('isGeminiEapOr2OrAbove')block, with only the functionunder test changed, in its own commit (
refactor(utils): split the EAP check out into isGeminiEapOr2OrAbove). A newdescribe('isGemini2OrAbove') > describe('EAP models')blockpins the other half —
isGemini2OrAbove('gemini-flash-early-exp') === false— so the splitcannot be silently undone, and two new
Runner CFC model gatetests assert the CFC gatestill rejects an EAP id at the real call site.
No suppressions. The diff adds no
@ts-expect-error,@ts-ignore,eslint-disable,any,as any,as never, or coverage-ignore comment (verified by grepping the diff).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.
Added:
core/test/utils/model_name_test.ts—describe('isGemini1Model') > describe('version boundary')(the GAP 3 fix, includinggemini-10.0-pro,gemini-10-flash,gemini-1,gemini-1-pro,gemini-1.);describe('isGeminiEapOr2OrAbove')(EAP ids, EAP ids inevery path form, EAP regex boundaries, numeric versions, empty string);
describe('isGemini2OrAbove') > describe('EAP models')pinningfalse;describe('classification consistency')mirroring Python'sTestModelNameUtilsIntegration(mutual exclusivity, version ⇒ Gemini, bare form andprojects/.../models/form classify identically).core/test/tools/url_context_tool_test.ts— extended model-id forms accepted(
models/,gemini/,apigee/.../,models/gemini-flash-early-exp) and two rejectionpaths with their exact error messages (
openrouter/google/gemini-1.5-pro:online→"requires Gemini 2 or above"; a malformed
projects/path → "is not supported formodel").
core/test/runner/runner_test.ts—describe('Runner CFC model gate'): the CFC gaterejects
gemini-flash-early-expandgemini-1.5-pro. This is the test that pins thedesign decision at the real call site.
Coverage.
core/src/utils/model_name.tsmeasures 97.36% stmts / 96.96% branch under--coverage.include='core/src/utils/model_name.ts'. The only uncovered lines are 180-181,the pre-existing
isGeminiModelIdCheckDisabledenv-var helper, which this change does nottouch. Every line and branch of the new code is covered. No
v8 ignore/istanbul ignorewas added and no threshold in
vitest.config.tswas changed.Proof the new tests can fail. Each mutation was applied to
core/src/utils/model_name.ts,the affected suites re-run, then reverted:
isGemini1Modelreverted toextractModelName(modelString).startsWith('gemini-1')→5 failed in
model_name_test.ts:isGemini1Model > version boundary > should return false for model:gemini-10.0-pro,gemini-10-flash,gemini-1,gemini-1-pro,gemini-1.(expected true to be false); and 6 failed at the call site ingoogle_search_tool_test.ts—adds googleSearch, not googleSearchRetrieval, for model: gemini-1(expected [ { googleSearchRetrieval: {} } ] to deeply equal [ { googleSearch: {} } ]) anddoes not reject other tools alongside model: gemini-1(
Google search tool can not be used with other tools in Gemini 1.x.), each for allthree ids.
isGeminiEapOr2OrAbovemade to delegate straight toisGemini2OrAbove(i.e. the EAPbranch dropped) → 13 failed: all 7
isGeminiEapOr2OrAbove > EAP modelsrows, all 3EAP model in path formrows,BuiltInCodeExecutor > processLlmRequest should attach codeExecution for an EAP model, and bothUrlContextToolEAP rows.isGemini2OrAbove(i.e. the split undone) → 5 failed:the four
isGemini2OrAbove > EAP models > should return falserows andRunner CFC model gate > rejects an EAP model, matching the adk-python bare gemini-2 prefix gate— which is exactly the parity regression the split exists to prevent.\d*changed to\d+in the EAP pattern → 11 failed, includingshould return true for EAP model: gemini-flash-early-expand both call-site EAP rows.Mutations of the stack base's
extractModelName(dropping theapigeepattern, theprojects/bail-out, or thestartsWith('gemini-')guard in the provider-prefix branch)belong to #471 and were not re-run here.
Manual End-to-End (E2E) Tests:
Please provide instructions on how to manually test your changes, including any necessary setup or configuration.
This module is pure, synchronous and has no I/O, transport, credential or live-model
dependency, so there is nothing to exercise against a real service; the behavioural
consumers are covered by the call-site regression tests above. To reproduce locally:
npm ci npm run build npx vitest run --project unit:core core/test/utils/model_name_test.ts npx vitest run --project unit:core core/test/code_executors/built_in_code_executor_test.ts npx vitest run --project unit:core core/test/tools/url_context_tool_test.ts npx vitest run --project unit:core core/test/runner/runner_test.ts # broader sweep over every consumer of the changed predicates: npx vitest run --project unit:core core/test/tools core/test/code_executors \ core/test/runner core/test/utils npm run lint npm run format:checkResults on the pushed commit:
core/test/{utils,tools,code_executors,runner}sweep — 69 files, 968 tests, all passed.npm run build— clean.npm run lint— clean, no warnings.npm run format:check— "All matched files use Prettier code style!".npm run ts:check— the repo has 284 pre-existingtsc --noEmiterrors on this branch'sbase (unrelated files such as
core/test/a2a/agent_card_test.ts). Comparing the errorlist before and after this diff, with line numbers normalised, the two are identical:
this change introduces zero new type errors.
Reference for every expectation in the new tables:
adk-pythonsrc/google/adk/utils/model_name_utils.pyandtests/unittests/utils/test_model_name_utils.py.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.
CI status: absent (validated locally instead)
.github/workflows/validation.yamltriggers onpull_request: branches: [main]. This is astacked PR based on
fix/extract-model-name-parity, so therun-testsjob never fires;only
auto-assignran, and that is not validation. The following were therefore run locallyagainst the exact pushed commit
915659382e0f09c24d8dac45288f4244f5a04f9b, with a cleanworking tree:
npm run buildnpx vitest run --project unit:core core/test/tools core/test/code_executors core/test/runner core/test/utilsnpm run lintnpm run format:checknpm run ts:checkTS2322ingoogle_search_tool_test.ts)Once #372 and #471 merge and this PR is retargeted at
main,run-testswill run normally.