Skip to content

feat(coding-agent): Vertex Model Garden open models via ADC - #3296

Merged
code-yeongyu merged 14 commits into
code-yeongyu:mainfrom
sleegme:feat/vertex-model-garden
Oct 16, 2026
Merged

code-yeongyu merged 14 commits into
code-yeongyu:mainfrom
sleegme:feat/vertex-model-garden

Conversation

@sleegme

@sleegme sleegme commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds the builtin vertex-model-garden extension for Vertex Model Garden open models via Google Application Default Credentials only. Configure ADC (gcloud auth application-default login); the lane joins the same shared credential chain the builtin google-vertex provider already uses — no token script or local proxy.

The vertex-garden starter list is exactly:

  • deepseek-ai/deepseek-v4.1-flash-maas — DeepSeek V4.1 Flash (Vertex), global-only, reasoning enabled, 1,000,000-token context from senpi's existing deepseek-v4-1-flash model data. Published list price $0.30/$1.20 per 1M tokens, cache hit $0.03 (promotional: at no charge through 2026-10-16).
  • zai-org/glm-5.2-maas — GLM 5.2 (Vertex), published list price $1.40/$4.40 per 1M tokens, cached input $0.14.

Marketplace partner models, including Claude and Grok, are excluded because they are card-billed and promotional credits do not apply.

Request path: global OpenAI-compatible endpoint, ADC bearer auth, x-goog-user-project quota header, existing OpenAI streaming. The project comes from GOOGLE_CLOUD_PROJECT/GCLOUD_PROJECT, else the ADC quota project, else getProjectId() — never from an API-key slot. google-auth-library stays a pinned direct dependency, reached through a Node-only lazy boundary.

Maintainer review fixes (head 4a0780618, merged with origin/main 2fc705f55)

  1. The provider is ambient, not configured. vertex-garden no longer registers apiKey: "$GOOGLE_CLOUD_PROJECT". It declares an ambientAuth hook on the extension provider config (packages/coding-agent/src/core/provider-composer.ts, .../extensions/types.ts, composed into an ambient ApiKeyAuth in .../core/provider-api-key-auth.ts), mirroring the builtin google-vertex ADC lane (packages/ai/src/providers/google-vertex.ts:92). Availability is the resolution of the ADC chain itself (.../vertex-model-garden/auth.ts), so the models are no longer listed without ADC, the auth status carries ambient: true, and the lane never outranks a provider the user configured nor becomes the automatic default over it. fallbackEligible: () => false is unchanged.
  2. The project no longer travels through the API-key slot. .../vertex-model-garden/transport.ts (defaultResolveVertexAccess) passes options.env only, so the ADC quota-project and getProjectId() fallbacks are reachable and a stored secret can never reach the request URL.
  3. Offline tests for the real ADC path. packages/ai/test/google-vertex-adc.test.ts (5 cases: token minted by the file's own token endpoint, GOOGLE_CLOUD_PROJECT/GCLOUD_PROJECT precedence, explicit-option precedence, ADC quota-project fallback, empty-token error). packages/coding-agent/test/suite/vertex-model-garden-extension.test.ts adds: default resolver ignores the apiKey slot, quota fallback reachable, registration/availability gating (ambient + available with ADC, unavailable without), and default-model selection unchanged for a user with only Vertex ADC + GOOGLE_CLOUD_PROJECT (+ location for google-vertex). The fixture is a workload-identity external_account ADC file whose token_url and subject-token URL point at a loopback server — a service_account file cannot be used offline because that flow hardcodes Google's token endpoint — so no request leaves the machine. No network, no real credentials.
  4. Paid models no longer show $0. Both models carry the prices published on the Agent Platform pricing page ("Deepseek's models" and "GLM's models"; cited in .../builtin/changes.md).

Also, since the optional note applied: every thinking level is marked unsupported (thinkingLevelMap all null) because these endpoints accept no thinking parameters, so no level is offered that would be silently ignored. The 403/404 hint suggestion was left out to keep the diff scoped; it needs a wire-error seam the transport does not own today.

Validation at 4a0780618

  • Scoped vitest green: packages/ai 5/5 (test/google-vertex-adc.test.ts), packages/coding-agent 7/7 (test/suite/vertex-model-garden-extension.test.ts). Provider/auth-adjacent files also run green (82 + 33 tests).
  • Failing-first proof: the new default-selection/gating cases fail on the pre-fix extension code (4 failed | 3 passed, exit 1) — red-pre-fix.txt.
  • node scripts/check-pr-changelog.mjs exactly as CI runs it (CHANGELOG_GATE_BASE = PR base 322c56ef…, no label): exit 0, 16 production paths covered; node scripts/audit-changes-md.mjs: 489/489 covered, exit 0.
  • NODE_OPTIONS=--max-old-space-size=6144 bun run check: exit 2 — the only failures are two pre-existing tsc errors from origin/main's catalog regeneration (packages/ai/test/context-overflow.test.ts:581 and packages/ai/test/total-tokens.test.ts:749 still name anthropic/claude-sonnet-4, which main's regeneration dropped from the OpenRouter catalog). A clean origin/main worktree fails identically, and both files are untouched by this branch. Every other step (biome, pinned deps, ts-imports, lockfiles, platform lock, browser smoke) passed; full receipt in root-check.txt.
  • Live QA through ADC project able-brace-426917-c5 on the global endpoint, isolated senpi sandbox, real auth asserted unchanged: both IDs exist and answer —
deepseek-ai/deepseek-v4.1-flash-maas: exit=0 timedOut=false ok=true output=VERTEX_DEEPSEEK_OK
zai-org/glm-5.2-maas: exit=0 timedOut=false ok=true output=VERTEX_GLM_OK
real auth.json unchanged: PASS
  • git diff scan for home paths, tokens and credential patterns: clean. Evidence stays ignored and uncommitted.

Receipt excerpts:

Test Files  1 passed (1)   Tests  5 passed (5)
Test Files  1 passed (1)   Tests  7 passed (7)
RED_PRE_FIX_EXIT_CODE=1
changelog-gate: PASS - changes.md coverage complete (16 production path(s) covered); changelog entry updated (packages/ai/CHANGELOG.md, packages/coding-agent/CHANGELOG.md)
CHANGELOG_GATE_EXIT_CODE=0
ROOT_CHECK_EXIT_CODE=2

Receipt hashes (local-ignore/qa-evidence/20261016-vertex-garden-maintainer-fixes/, also archived in the shared checkout):

7124df60afe64b94edb0788d79721e60f4d4ee035850809acb7fa70a1110b6e7  red-pre-fix.txt
751ad236007c9c4e75631e02687f501a989bced44365c4f16358dafa5341a7af  root-check.txt
bd114cf0e15d284444f64459b4c7e29f7e9da9d3e5abacfcedf1dbf06ad6fc41  deepseek-cli.json
f1d8c3b4d2a2ae018f0542d4c79b8d9ac2e175df04074a0b752ada2a47d035e8  glm-cli.json
7ec903ab55ea715fcee7a38746c690e4cecf447ee9b32d1ebc36be485119f68b  live-cli.txt

Branch history

  • a638f4dfa: merged origin/main (earlier base) with a merge commit, preserving both sides of both tracker conflicts.
  • ada7c709f: trimmed the implementation to the two ADC open models.
  • 308fc3f7: earlier gate fixes (changelog entries, tracker coverage, duplicate-test removal).
  • cd403e1b7: merge of origin/main at 2fc705f55; tracker conflicts resolved by keeping both sides.
  • 4a0780618: this review round — ambient registration, project off the key slot, offline ADC tests, published prices, thinking-level consistency.

Normal push; no force-push or rebase. Do not merge: maintainer re-review is required.

…on Vertex via ADC

Adds the vertex-model-garden builtin extension (vertex-garden: openapi global endpoint; vertex-claude: rawPredict/streamRawPredict) and a lazy ADC helper in pi-ai shared with google-vertex.
Copilot AI balanced review requested due to automatic review settings October 13, 2026 23:15

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@sleegme sleegme changed the title feat(coding-agent): Vertex Model Garden and Claude via ADC feat(coding-agent): Vertex Model Garden open models via ADC Oct 15, 2026

@code-yeongyu code-yeongyu left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks a lot for this, @sleegme! Open Vertex Model Garden models via ADC is a really useful addition. The credential handling is careful: tokens go only to aiplatform.googleapis.com, nothing is logged or written to disk, and the new dependency adds no install scripts. A few changes are needed before we can merge.

Blocking

1. The provider counts as configured instead of ambient, so it takes over the automatic default model.
packages/coding-agent/src/core/extensions/builtin/vertex-model-garden/index.ts:11,21 registers apiKey: "$GOOGLE_CLOUD_PROJECT". Any set env-var key is treated as an explicitly configured provider, while the existing google-vertex ADC provider is deliberately marked ambient: true (packages/ai/src/providers/google-vertex.ts:92). Ambient providers never outrank configured ones.

As a result, anyone who has GOOGLE_CLOUD_PROJECT set, including people who only use it for unrelated gcloud tooling, silently gets a Model Garden model (deepseek-ai/deepseek-v4.1-flash-maas) as their automatic default instead of their current one. The two models also show as available when ADC is missing; the failure only appears on the first request.

Suggested fix: make the provider ambient like the other cloud providers, or gate it on an explicit opt-in (a setting or models.json entry). Please add a default-model-selection test for a user with only Vertex ADC plus GOOGLE_CLOUD_PROJECT, asserting the default stays unchanged.

Should fix

2. The project ID travels through the API-key slot.
vertex-model-garden/transport.ts:16-17 sends options?.apiKey as the project. A value stored for this provider via /login or auth.json would win over GOOGLE_CLOUD_PROJECT and end up in the request URL path, even if it is a real secret. Because the env var always provides apiKey, the ADC quota-project / getProjectId() fallbacks in packages/ai/src/api/google-vertex-adc.ts:41-44 are also unreachable from this provider. Suggestion: read the project from its own option or env var, not from the key slot.

3. The real ADC path has no tests.
Both tests inject a fake access resolver (test/suite/vertex-model-garden-extension.test.ts:10-14, 113-116). The wire-shape test against the real transport is great. Nothing exercises getVertexAdcAccess (empty-token error, project precedence, quota project; google-vertex-adc.ts:36-51), the default resolver, or the registration/availability gating. This can run offline, for example by pointing GOOGLE_APPLICATION_CREDENTIALS at a fixture with a fake token endpoint.

4. Paid models show a cost of zero.
vertex-model-garden/models.ts:6,22-24 uses ZERO_COST for two paid MaaS models, so session cost and budget displays would report $0 for real spend. Please use the published prices, or leave cost unset rather than showing zero.

Optional

  • models.ts:7 declares reasoning: true with supportsReasoningEffort: false, so the user's thinking-level choice is silently ignored for both models.
  • 403 (API not enabled / missing aiplatform.endpoints.predict) and 404 (model not enabled in Model Garden) come back through the generic error path. A Vertex-specific hint, such as "enable the model in Model Garden" or "run gcloud auth application-default login", would help users.
  • The GoogleAuth cache in google-vertex-adc.ts:15-27 lives for the whole process, so switching identity with a later gcloud auth application-default login is not picked up.
  • We could not confirm that both model IDs exist on the global endpoint. Worth a quick check.

Thanks again, and thanks for your patience!

sleegme and others added 2 commits October 16, 2026 02:05
# Conflicts:
#	packages/ai/CHANGELOG.md
#	packages/ai/changes.md
Review fixes for code-yeongyu#3296:

- Register `vertex-garden` through a new `ambientAuth` provider-config hook
  (composer + extension types) instead of an env-var apiKey slot, so the lane
  is available while Google ADC is present without counting as configured and
  never takes the automatic default over a provider the user configured.
- Read the request project from GOOGLE_CLOUD_PROJECT/GCLOUD_PROJECT, the ADC
  quota project or getProjectId(), never from the apiKey slot, so a stored
  secret cannot reach the request URL.
- Cover the real ADC path offline (external-account fixture on a loopback token
  endpoint): token/project precedence, quota project, empty-token error,
  registration/availability gating and automatic default selection.
- Price both models at their published Vertex MaaS list prices and mark every
  thinking level unsupported, so no level is offered that the wire ignores.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
@sleegme

sleegme commented Oct 15, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review — all four points are addressed at 4a0780618 (branch merged with origin/main 2fc705f55; mergeable again). Point by point:

1 (blocking) — ambient, not configured. vertex-garden no longer registers apiKey: "$GOOGLE_CLOUD_PROJECT". It now declares the ADC chain as an ambientAuth source instead:

  • packages/coding-agent/src/core/extensions/builtin/vertex-model-garden/index.ts:22 registers ambientAuth: { name: "Google Cloud credentials (ADC)", resolve: resolveVertexModelGardenAmbient }, with the resolver in .../vertex-model-garden/auth.ts:14 (same ADC file the builtin google-vertex provider looks for; undefined while the chain is absent).
  • Since no extension API could express ambient provenance, the hook is the small existing-config extension: packages/coding-agent/src/core/provider-composer.ts:135 / .../extensions/types.ts:2713 (config surface) and .../core/provider-api-key-auth.ts:45 (composed into an ambient-only ApiKeyAuth with no login). That mirrors how the builtin providers do it (packages/ai/src/providers/google-vertex.ts:92): availability is the resolution, and the ambient marker reaches ModelRuntime.getProviderAuthStatus, so the lane never outranks a configured provider and never becomes the automatic default. Both models are also no longer listed when ADC is missing.

Tests (all in packages/coding-agent/test/suite/vertex-model-garden-extension.test.ts):

  • #given Vertex ADC #when the provider is registered #then it is available through ambient auth (status {configured: true, ambient: true}, both models available)
  • #given GOOGLE_CLOUD_PROJECT without ADC credentials #when the provider is registered #then it stays unavailable
  • #given only Vertex ADC, GOOGLE_CLOUD_PROJECT and a location #when no default is saved #then the automatic default stays unchanged

The last one is the requested default-model-selection test. On the pre-fix code it fails exactly as you described — the Model Garden model wins (expected 'vertex-garden' to be 'google-vertex'); the pre-fix run is captured in red-pre-fix.txt (4 failed | 3 passed, exit 1) and all 7 pass after the fix.

2 — project no longer travels through the API-key slot. .../vertex-model-garden/transport.ts:22 now resolves with getVertexAdcAccess({ env: options?.env }) only. Project precedence is GOOGLE_CLOUD_PROJECT/GCLOUD_PROJECT → ADC quota project → getProjectId(), so the quota-project/getProjectId fallbacks are reachable and a stored secret can never end up in the URL. Covered by #given an ADC file and a named project #when the resolver runs #then the apiKey slot never becomes the project (a hostile apiKey: "stored-secret-never-a-project" is ignored) and #given an ADC file without a project env var #when the resolver runs #then the ADC quota project supplies the target project.

3 — offline tests for the real ADC path.

  • packages/ai/test/google-vertex-adc.test.ts (5 cases): token minted by the file's own token endpoint; GOOGLE_CLOUD_PROJECT/GCLOUD_PROJECT precedence; explicit-option precedence; ADC quota-project fallback; empty-token error.
  • Coding-agent side adds the default resolver, gating, and default-selection cases listed above.
  • Fixture: test/support/google-adc-fixture.ts writes a workload-identity external_account ADC file whose token_url and subject-token URL point at a loopback server, so the real google-auth-library exchange runs with no network. I originally used a service_account fixture and found it cannot be used offline — that flow hardcodes Google's token endpoint — hence the external-account shape (noted in the fixture comment).

4 — no $0 cost. Both models now carry the published list prices from cloud.google.com ("Deepseek's models" / "GLM's models" on the Agent Platform pricing page, cited in .../builtin/changes.md): DeepSeek V4.1 Flash $0.30/$1.20 per 1M tokens (cache hit $0.03), GLM 5.2 $1.40/$4.40 (cached input $0.14). The DeepSeek promo footnote (no charge through 2026-10-16) is recorded there too.

Optional items. Done: models.ts now marks every thinking level unsupported, because these endpoints take no thinking parameters — nothing is offered that the wire ignores. Not done: the Vertex-specific 403/404 hint. It needs a wire-error seam the transport does not own today, so I left it out to keep the diff scoped; happy to add it in a follow-up if you want it.

Live QA (isolated sandbox, your ADC project able-brace-426917-c5, global endpoint, real auth asserted unchanged):

deepseek-ai/deepseek-v4.1-flash-maas: exit=0 timedOut=false ok=true output=VERTEX_DEEPSEEK_OK
zai-org/glm-5.2-maas: exit=0 timedOut=false ok=true output=VERTEX_GLM_OK
real auth.json unchanged: PASS

One heads-up, not from this PR: bun run check is currently red on origin/main itself — two tsc errors in packages/ai/test/context-overflow.test.ts:581 and packages/ai/test/total-tokens.test.ts:749 still name anthropic/claude-sonnet-4, which main's catalog regeneration (2fc705f55) dropped from the OpenRouter catalog. A clean origin/main worktree fails identically; I left those files alone to keep this PR scoped. Everything else in the check passes, and the full receipt is in the PR body.

Evidence lives under local-ignore/qa-evidence/20261016-vertex-garden-maintainer-fixes/ (gitignored, hashes in the PR body). Ready for your re-review whenever you have time — no rush, and thanks again!

@code-yeongyu code-yeongyu left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks a lot, @sleegme! Every blocking point from the first review is fixed. Making the provider ambient means it no longer takes over anyone's default model, the project ID has its own path, the real ADC flow is tested offline, and the prices and thinking levels are right. We found no security concerns in the new diff.

One thing left: a changelog entry

The required changelog gate fails at this head:

$ node scripts/check-pr-changelog.mjs --base 2fc705f55d
missing changes.md coverage: packages/coding-agent/src/core/extensions/types.ts

The new entry in packages/coding-agent/src/core/extensions/changes.md names that path under "What changed", but the policy requires the exact path under all four headings. Add a - \packages/coding-agent/src/core/extensions/types.ts`: ...` bullet under "Why", "Why an extension could not handle it" and "Expected merge conflict zones". If you'd rather not deal with it, "Allow edits by maintainers" is on, so just say so and we'll add the lines ourselves.

A suggestion (not blocking)

Models shown as available with no project set. auth.ts:20-22 only checks that the ADC file exists, while the built-in google-vertex also needs a project and a location (packages/ai/src/providers/google-vertex.ts:84-89). A gcloud user with nothing else configured and no GOOGLE_CLOUD_PROJECT would get these models as the automatic default, and the first request would fail with the project-ID error. Consider reporting the provider as unavailable when no project can be resolved.

Thanks again for the quick turnaround!

@code-yeongyu

Copy link
Copy Markdown
Owner

Thanks again, @sleegme. Since "Allow edits by maintainers" is on, we added the missing changelog coverage ourselves to save you a round: commit b03514c ("docs: changes.md coverage for vertex-garden") puts the packages/coding-agent/src/core/extensions/types.ts path under the three remaining headings. Nothing else changed. The gate passes now:

$ node scripts/check-pr-changelog.mjs --base 2fc705f55d
changelog-gate: PASS - changes.md coverage complete (4 production path(s) covered)

We're running the full checks on this head next. The suggestion about reporting the provider as unavailable when no project is set is still open if you'd like to take it, but it isn't blocking.

# Conflicts:
#	packages/coding-agent/src/core/changes.md
#	packages/coding-agent/src/core/extensions/builtin/changes.md
@gitguardian

gitguardian Bot commented Oct 15, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

Since your pull request originates from a forked repository, GitGuardian is not able to associate the secrets uncovered with secret incidents on your GitGuardian dashboard.
Skipping this check run and merging your pull request will create secret incidents on your GitGuardian dashboard.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
38252180 Triggered Generic High Entropy Secret 1b1e12e packages/coding-agent/test/suite/regressions/3408-subscription-requested-window.test.ts View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@code-yeongyu

Copy link
Copy Markdown
Owner

Heads-up on the GitGuardian finding, @sleegme: nothing to do on your side. It points at a dummy token in one of our own regression tests (3408-subscription-requested-window.test.ts), which is only sent to a local 127.0.0.1 mock server. It came into this branch with the main merge I pushed, not from your change. We'll make that fixture obviously fake in a separate PR so scanners stop flagging it.

@sleegme

sleegme commented Oct 15, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for adding the changes.md lines and the heads-up on GitGuardian!

I took the non-blocking suggestion too:

  • vertex-model-garden/auth.ts: the ambient resolver now also requires GOOGLE_CLOUD_PROJECT or GCLOUD_PROJECT, mirroring the builtin google-vertex check (packages/ai/src/providers/google-vertex.ts:84-89). Without a project, the lane reports nothing, so ADC alone no longer makes these models selectable or the automatic default. No location is required, because Model Garden MaaS uses the global endpoint.
  • New test: vertex-garden registration and availability > #given Vertex ADC without a project #when the provider is registered #then it stays unavailable. It fails on the previous head (1 failed / 7 passed) and passes now (8/8).

Checks on this head: scoped tests 8/8, agent-session-services-providers.test.ts 2/2, biome clean, tsc --noEmit with no errors, and check-pr-changelog.mjs --base $(git merge-base origin/main HEAD) PASS.

One trade-off: a user whose project comes only from the ADC quota project (no env var) will now see the lane as unavailable, even though a request would resolve it. AuthContext can't read the ADC file, so I matched the builtin's env-based rule. Happy to change that if you'd prefer another trade-off.

@code-yeongyu

Copy link
Copy Markdown
Owner

Thank you @sleegme, requiring a project for the ambient path is the right call, and mirroring the builtin google-vertex check keeps the two consistent. We'll review the new commit and run it at the new head, then it goes into the merge queue (merges are paused briefly for a release right now).

@code-yeongyu

Copy link
Copy Markdown
Owner

Thanks again @sleegme. We just published a senpi release, which moved CHANGELOG.md and changes.md on main, so this branch conflicts again. Could you merge main in once more? The conflicts should only be in those changelog files, so keep both sides. Your new project check is already in the review queue, and once the merge lands we'll take it from there. If it's inconvenient, no worries: we'll push that one merge ourselves in about an hour and note it here.

@sleegme

sleegme commented Oct 16, 2026

Copy link
Copy Markdown
Contributor Author

Merged main in (51244ad). The only textual conflict was packages/ai/CHANGELOG.md; both sides are kept. Git's auto-merge had also dropped our packages/coding-agent/CHANGELOG.md entry into the released [2026.10.16] section, so I moved it back under [Unreleased]. node scripts/check-pr-changelog.mjs --base <merge-base> passes. No source changes beyond the merge.

@code-yeongyu

code-yeongyu commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

@sleegme, I owe you a correction: the project check we suggested turns out to be wrong for this lane, sorry for the churn.

Our review of the new commit found that the lane's own request path resolves the project more broadly than the new gate. getVertexAdcAccess (packages/ai/src/api/google-vertex-adc.ts:33-48) tries:

  1. GOOGLE_CLOUD_PROJECT/GCLOUD_PROJECT;
  2. then the ADC quota_project_id;
  3. then the project gcloud is configured with.

It throws "Vertex AI requires a project ID…" only when all three are missing. The new check in vertex-model-garden/auth.ts:28-31 looks only at the env vars. So a common setup (gcloud auth application-default login, which writes quota_project_id, with no env var) worked before and would now lose the lane silently. The new test's fixture shows it: it is created with quotaProjectId: "adc-quota-project", a project the transport would use. The builtin google-vertex needs the env var because it goes through @google/genai; this lane has its own resolver, so mirroring it doesn't fit. AuthContext only offers env/fileExists, so the gate can't read quota_project_id either.

Suggested fix: revert 68ace292a4 (the auth.ts gate and its test), so availability goes back to "an ADC file exists", and a missing project keeps failing at request time with the clear message above. The CHANGELOG and changes.md already describe that fallback chain, so they stay correct.

Everything else in the PR passed review and local evidence (install, bun run check, Vertex tests 14/0 and 20/0, changelog gate) at 51244ada5c.

@code-yeongyu

Copy link
Copy Markdown
Owner

Thanks for the update. We reviewed the delta (1b1e12e..68ace29; your main merge at 51244ad leaves the PR's own lines unchanged). One change is needed before merge.

HIGH: the new availability gate is stricter than the request path.
The gate in auth.ts:28-31 accepts only GOOGLE_CLOUD_PROJECT / GCLOUD_PROJECT. But the transport resolves the project through getVertexAdcAccess (packages/ai/src/api/google-vertex-adc.ts, ~40-46): env vars, then the ADC quotaProjectId, then gcloud's configured project, and it already throws a clear "Vertex AI requires a project ID" error when none resolve. So a common setup (gcloud auth application-default login writes quota_project_id, no env var) worked before and now loses the lane. The new test's fixture also sets quotaProjectId: "adc-quota-project", so "Vertex ADC without a project" actually has one.

Two ways forward, either is fine:

  • (a) drop the gate and rely on the resolver's existing error, or
  • (b) keep it on purpose, and update packages/coding-agent/CHANGELOG.md:19 and builtin/changes.md:24 to say an env project is required for the lane to appear, and rename/rewrite the test so it does not claim the ADC file has no project.
    (AuthContext only exposes env and fileExists, so the gate cannot read quota_project_id without an API change.)

MEDIUM: a hidden lane gives no message. With (b), a user who ran gcloud auth application-default login just sees no models and no reason. Please surface why (for example a reason in auth status).

LOW: empty-string handling differs from the builtin (?? vs ?.trim() ||); yours is the more sensible one, just worth a note.

@code-yeongyu

Copy link
Copy Markdown
Owner

Review verdict for the new commit 68ace292a4 (head 51244ada5c): changes requested. To be clear, the cause is our own earlier suggestion, not your implementation. Thanks @sleegme for picking it up so quickly; the rest of the PR (the Vertex Model Garden lane itself) passed review and local evidence at 51244ada5c: install, bun run check, Vertex tests 14/0 and 20/0, changelog gate.

HIGH: the availability gate is stricter than the request path. It hides a working setup and contradicts the documented contract.

  • packages/coding-agent/src/core/extensions/builtin/vertex-model-garden/auth.ts:28-31 makes the lane available only when GOOGLE_CLOUD_PROJECT or GCLOUD_PROJECT is set.
  • The request path resolves the project more broadly. packages/ai/src/api/google-vertex-adc.ts:33-48 (getVertexAdcAccess) tries the env vars, then the ADC quota_project_id, then gcloud's configured project, and throws "Vertex AI requires a project ID…" only when all three are missing.
  • So a user who ran gcloud auth application-default login (which writes quota_project_id) with no env var has a working lane at 1b1e12e4b3 and loses it here.
  • The test shows the mismatch: packages/coding-agent/test/suite/vertex-model-garden-extension.test.ts:166 builds the "no project" case with createGoogleAdcFixture({ quotaProjectId: "adc-quota-project" }), a project the transport would use.
  • It also contradicts the documented contract: packages/coding-agent/CHANGELOG.md:19 and packages/coding-agent/src/core/extensions/builtin/changes.md:24 both say the project may come from the ADC quota project or gcloud.

MEDIUM: a hidden lane gives the user no signal. When the gate drops the lane, it vanishes from listing and auth status (configured: false, no reason). The clear "set GOOGLE_CLOUD_PROJECT…" error only happens at request time, which this gate now makes unreachable. So the user just doesn't see the models and isn't told why.

LOW: empty strings are handled differently from the builtin. ?.trim() || treats GOOGLE_CLOUD_PROJECT="" as unset; the builtin's ?? treats it as set. This version is the more sensible of the two, so this is only a note on the "mirrors the builtin" claim.

Suggested patch: revert 68ace292a4. Availability goes back to "an ADC file exists", and a missing project fails at request time with the clear message. AuthContext only offers env and fileExists, so the gate can't read quota_project_id without an API change. The CHANGELOG and changes.md already describe this behaviour, so no doc change is needed.

patch (equivalent to git revert 68ace292a4)
diff --git b/packages/coding-agent/src/core/extensions/builtin/vertex-model-garden/auth.ts a/packages/coding-agent/src/core/extensions/builtin/vertex-model-garden/auth.ts
index 65ffc4270e..f7b79fdb4a 100644
--- b/packages/coding-agent/src/core/extensions/builtin/vertex-model-garden/auth.ts
+++ a/packages/coding-agent/src/core/extensions/builtin/vertex-model-garden/auth.ts
@@ -10,11 +10,6 @@ export const VERTEX_MODEL_GARDEN_ADC_SOURCE = "gcloud application default creden
  * gcloud tooling too, so the lane is selectable but never outranks a provider the
  * user configured, and it reports nothing while the chain is absent — the failure
  * belongs at selection time, not at the first request.
- *
- * Like the builtin google-vertex lane, it also needs a project: without
- * GOOGLE_CLOUD_PROJECT/GCLOUD_PROJECT the first request would fail on the
- * project ID, so the lane reports nothing instead. No location is required
- * because Model Garden MaaS is served from the global endpoint.
  */
 export async function resolveVertexModelGardenAmbient(input: {
 	ctx: AuthContext;
@@ -24,10 +19,5 @@ export async function resolveVertexModelGardenAmbient(input: {
 	const path = (await input.ctx.env("GOOGLE_APPLICATION_CREDENTIALS")) ?? VERTEX_MODEL_GARDEN_ADC_PATH;
 	input.signal.throwIfAborted();
 	if (!(await input.ctx.fileExists(path))) return undefined;
-	input.signal.throwIfAborted();
-	const project =
-		(await input.ctx.env("GOOGLE_CLOUD_PROJECT"))?.trim() || (await input.ctx.env("GCLOUD_PROJECT"))?.trim();
-	input.signal.throwIfAborted();
-	if (!project) return undefined;
 	return { auth: {}, source: VERTEX_MODEL_GARDEN_ADC_SOURCE, ambient: true };
 }
diff --git b/packages/coding-agent/test/suite/vertex-model-garden-extension.test.ts a/packages/coding-agent/test/suite/vertex-model-garden-extension.test.ts
index 9d09966224..621b02575b 100644
--- b/packages/coding-agent/test/suite/vertex-model-garden-extension.test.ts
+++ a/packages/coding-agent/test/suite/vertex-model-garden-extension.test.ts
@@ -243,20 +243,6 @@ describe("vertex-garden registration and availability", () => {
 			runtime.getAvailableSnapshot().some((available) => available.provider === VERTEX_MODEL_GARDEN_PROVIDER_ID),
 		).toBe(false);
 	});
-
-	// senpi#3296 second review: ADC alone must not make the models selectable when no project resolves.
-	it("#given Vertex ADC without a project #when the provider is registered #then it stays unavailable", async () => {
-		vi.stubEnv("GOOGLE_APPLICATION_CREDENTIALS", fixture.path);
-		vi.stubEnv("GOOGLE_CLOUD_PROJECT", "");
-		vi.stubEnv("GCLOUD_PROJECT", "");
-
-		const runtime = await runtimeWithGarden();
-
-		expect(runtime.getProviderAuthStatus(VERTEX_MODEL_GARDEN_PROVIDER_ID)).toMatchObject({ configured: false });
-		expect(
-			runtime.getAvailableSnapshot().some((available) => available.provider === VERTEX_MODEL_GARDEN_PROVIDER_ID),
-		).toBe(false);
-	});
 });
 
 describe("vertex-garden automatic default selection", () => {

Once that's on the branch, we'll do a quick re-check and queue it for the next senpi release, with credit to you.

…roject resolves"

This reverts commit 68ace29.

The env-only project gate is stricter than the lane request path: getVertexAdcAccess resolves the project from GOOGLE_CLOUD_PROJECT/GCLOUD_PROJECT, then the ADC quota project, then the gcloud project, and only then fails with a clear error. The gate hid a working ADC + quota-project setup. The suggestion behind it was ours; see the review on code-yeongyu#3296. Co-authored with @sleegme, whose Vertex Model Garden work this PR is.
@code-yeongyu

Copy link
Copy Markdown
Owner

@sleegme, as mentioned, we've pushed the revert ourselves so this can make the next senpi release: 9a497be00a reverts 68ace292a4. auth.ts and its test are now byte-identical to 1b1e12e4b3, and the changelog gate passes. Nothing else changed; the Vertex Model Garden lane is still entirely your work, and you'll be credited when it merges. Thanks for your patience with our wrong turn.

# Conflicts:
#	packages/ai/CHANGELOG.md
#	packages/coding-agent/src/core/extensions/changes.md
@code-yeongyu

Copy link
Copy Markdown
Owner

@sleegme, one more housekeeping push from our side so this can merge right away: 1c69d8854c merges main (another release had moved the changelogs) and moves the PR's changelog entries back under [Unreleased]. No code changed; your Vertex Model Garden change is line-for-line the reviewed one. Thanks again!

@code-yeongyu
code-yeongyu merged commit 16ea3f2 into code-yeongyu:main Oct 16, 2026
6 checks passed
@code-yeongyu

Copy link
Copy Markdown
Owner

Merged as 16ea3f259a. Thank you @sleegme! This adds the vertex-garden provider: DeepSeek V4.1 Flash and GLM 5.2 through Vertex Model Garden's global OpenAI-compatible endpoint, authenticated straight from Google Application Default Credentials (no token script or proxy). It ships in the next senpi release. Thanks also for your patience with our review detour on the project check; that one was on us.

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.

3 participants