feat: add firebase ailogic:providers CLI commands#10778
Conversation
Wiz Scan Summary
To detect these findings earlier in the dev lifecycle, try using Wiz Code VS Code Extension. |
There was a problem hiding this comment.
Code Review
This pull request introduces the firebase ailogic:providers:* CLI commands (enable, disable, and list) to manage Gemini API providers, along with updates to the help command to list namespace subcommands. Feedback on these changes suggests removing the ensureAILogicApiEnabled check from the disable and list commands to prevent unnecessary enablement prompts, and removing a redundant billing check in listProviders that could cause permission issues. Additionally, the reviewer recommends simplifying the associated unit tests and replacing an any cast in help.spec.ts with a type-safe alternative to adhere to the repository's style guide.
Add `firebase ailogic:providers:{enable,disable,list}` to manage the
Gemini API providers (Gemini Developer API and Agent Platform Gemini API)
for Firebase AI Logic from the CLI.
- providers:enable enables the Firebase AI Logic API and the selected
provider's underlying API; agent-platform-gemini-api requires the Blaze
(pay-as-you-go) plan.
- providers:disable prompts for confirmation (--force to skip) and turns
off the proxy when no providers remain enabled.
- providers:list reports which providers are enabled.
- Adds a shared interactive enablement flow (used when the API is not yet
enabled) that checks enable permission up front before prompting.
- Adds `firebase help <namespace>` listing of subcommands under a prefix
so ailogic commands are discoverable.
Requires the firebasevertexai and serviceusage IAM permissions; commands
support --json and --non-interactive.
2efdcd1 to
79fbcdc
Compare
|
Thanks for the review! Addressed all the feedback:
|
abc2d66 to
6621514
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #10778 +/- ##
=======================================
Coverage ? 58.20%
=======================================
Files ? 615
Lines ? 40386
Branches ? 8192
=======================================
Hits ? 23508
Misses ? 14920
Partials ? 1958 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| .before(requirePermissions, ["serviceusage.services.disable", "firebasevertexai.config.update"]) | ||
| .action(async (providerType: string, options: Options) => { | ||
| const projectId = needProjectId(options); | ||
| if (providerType !== "gemini-developer-api" && providerType !== "agent-platform-gemini-api") { |
There was a problem hiding this comment.
It would be more symmetric to use "gemini-developer-api" and "gemini-agent-platform-api"
There was a problem hiding this comment.
Done in dc501fe. Renamed agent-platform-gemini-api to gemini-agent-platform-api everywhere, so both provider ids share the gemini- prefix. It's a CLI-facing value only (never sent to a backend), so the rename is safe.
| throw new FirebaseError( | ||
| `Disabling provider ${clc.bold(providerType)} requires confirmation.\n\n` + | ||
| `To proceed in non-interactive mode, rerun with --force:\n\n` + | ||
| ` firebase ailogic:providers:disable ${providerType} --force`, |
There was a problem hiding this comment.
Singular "provider" would be better, since this is an action on a single provider.
There was a problem hiding this comment.
I kept the namespace plural (ailogic:providers:*) to stay consistent with the existing CLI conventions like appdistribution:groups, firestore:databases, and database:instances, and because providers:list acts on all of them. Happy to switch if you feel strongly.
| .before(requirePermissions, ["serviceusage.services.disable", "firebasevertexai.config.update"]) | ||
| .action(async (providerType: string, options: Options) => { | ||
| const projectId = needProjectId(options); | ||
| if (providerType !== "gemini-developer-api" && providerType !== "agent-platform-gemini-api") { |
There was a problem hiding this comment.
Re-use ailogic.ProviderType here instead of copying
There was a problem hiding this comment.
Done in dc501fe. Added ailogic.parseProviderType() (plus isProviderType() and PROVIDER_TYPES) in gcp/ailogic.ts, and both commands now call it instead of copying the union.
| `Invalid provider type: ${clc.bold(providerType)}. Must be 'gemini-developer-api' or 'agent-platform-gemini-api'.`, | ||
| ); | ||
| } | ||
| if (options.nonInteractive && !options.force) { |
There was a problem hiding this comment.
This seems to disallow non-interactive without also adding --force. Generally I think one or the other would be acceptable, but if this is particularly dangerous we could do both
There was a problem hiding this comment.
Removed the bespoke guard in dc501fe. confirm() already aborts in non-interactive mode unless --force is passed, so the behavior is now handled by the standard path with no duplicated check.
| .before(requirePermissions, ["serviceusage.services.enable", "firebasevertexai.config.update"]) | ||
| .action(async (providerType: string, options: Options) => { | ||
| const projectId = needProjectId(options); | ||
| if (providerType !== "gemini-developer-api" && providerType !== "agent-platform-gemini-api") { |
There was a problem hiding this comment.
Re-use ailogic.ProviderType here
There was a problem hiding this comment.
Done in dc501fe, same ailogic.parseProviderType() helper.
| matched = false; | ||
| break; | ||
| } | ||
| } |
There was a problem hiding this comment.
This block is a little vague in its intent. Needs a comment to describe what the goal is here. The block below this too
There was a problem hiding this comment.
Done in dc501fe. Added comments to both blocks: the first walks the nested client command tree to resolve a namespace (for example ailogic:providers), and the second lists every registered command under that prefix.
| } | ||
| client.ailogic = {}; | ||
| client.ailogic.providers = {}; | ||
| client.ailogic.providers.enable = loadCommand("ailogic-providers-enable"); |
There was a problem hiding this comment.
Will need to add an experiment flag here (see "internaltesting" above as an example) until this is API council approved, since the API could change in the near term
There was a problem hiding this comment.
Done in dc501fe. Added an ailogic experiment and gated registration behind experiments.isEnabled("ailogic"), modeled on internaltesting. Enable it with FIREBASE_CLI_EXPERIMENTS=ailogic until the API is council-approved.
| "firebasevertexai.googleapis.com", | ||
| prefix, | ||
| ); | ||
| ensureApiEnabled.uncacheEnabledAPI(projectId, "firebasevertexai.googleapis.com"); |
There was a problem hiding this comment.
can we call this inside the disableServiceAndPoll rather than calling it afterwards each time? Similarly to the other 4 callsites
There was a problem hiding this comment.
Done in dc501fe. Moved uncacheEnabledAPI into serviceusage.disableServiceAndPoll and removed the per-callsite calls (ailogic is its only caller). Added a test in serviceusage.spec.ts covering it.
| export async function disableServiceAndPoll( | ||
| projectId: string, | ||
| service: string, | ||
| prefix: string, |
There was a problem hiding this comment.
rename to logging prefix
There was a problem hiding this comment.
Done in dc501fe. Renamed the param to loggingPrefix.
| projectId: string, | ||
| providerType: ProviderType, | ||
| ): Promise<void> { | ||
| const prefix = "ailogic"; |
There was a problem hiding this comment.
nit: rename AILOGIC_LOGGING_PREFIX and move to top of the class
There was a problem hiding this comment.
Done in dc501fe. Added an exported AILOGIC_LOGGING_PREFIX constant at the top of the module and replaced the repeated "ailogic" literals with it.
- Rename provider id agent-platform-gemini-api -> gemini-agent-platform-api for symmetry with gemini-developer-api (paulb777). - Centralize provider-type validation in ailogic.parseProviderType/isProviderType and reuse it in the enable/disable commands instead of copying the union (christhompsongoogle). - Add AILOGIC_LOGGING_PREFIX constant; replace the repeated 'ailogic' literals. - Move enablement-cache invalidation into serviceusage.disableServiceAndPoll and drop the per-callsite uncache calls; rename its 'prefix' param to loggingPrefix. - Drop the redundant non-interactive guard in disable; confirm() already enforces --force in non-interactive mode. - Gate the ailogic commands behind a new 'ailogic' experiment until API-council approval. - Document the help.ts namespace-listing blocks. - Update/extend unit tests accordingly.
# Conflicts: # CHANGELOG.md
- listProviders now reports a provider as enabled only when the Firebase AI Logic API (firebasevertexai.googleapis.com) is also enabled, so providers:list can no longer disagree with other ailogic commands about whether AI Logic is enabled on the project - enableProvider enables the AI Logic API before the provider's service API, so a partial failure cannot land in that inconsistent state
677ea38 to
5187f7a
Compare
|
While working on the config PR I found an inconsistency bug in the provider commands and fixed it here (commit 5187f7a). Enabling a provider turns on both the provider's service API and the Firebase AI Logic API (firebasevertexai.googleapis.com), but providers:list derived provider status purely from the provider service APIs. So a project with generativelanguage.googleapis.com enabled but the AI Logic API off (enabled outside the CLI, or a partial enable failure) would report the provider as enabled while the AI Logic commands report AI Logic as disabled on the same project. Two changes:
Added tests for both. |
|
Closing in favor of #10849, which moves this to a branch on the main repo as @joehan requested. The branch is identical (all review feedback addressed, plus the provider-state consistency fix). @christhompsongoogle FYI. Review approvals unfortunately do not carry over, so a fresh stamp on #10849 would be appreciated. |
Description
Adds the
firebase ailogic:providers:*command group to manage the Gemini API providers for Firebase AI Logic from the CLI, so developers and CI pipelines can enable, disable, and inspect providers without using the Firebase Console.ailogic:providers:enable <providerType>— enables the Firebase AI Logic API (firebasevertexai.googleapis.com) if needed, then the selected provider's underlying API.gemini-developer-apienablesgenerativelanguage.googleapis.com;agent-platform-gemini-apirequires the Blaze (pay-as-you-go) plan and enablesaiplatform.googleapis.com.ailogic:providers:disable <providerType>— prompts for confirmation (skippable with--force) and turns off the proxy when no providers remain enabled.ailogic:providers:list— reports which providers are enabled.Supporting changes:
ensureAILogicApiEnabled) used when the API isn't yet enabled, which checks theserviceusage.services.enablepermission up front so callers get a clear error before any prompts.firebase help <namespace>now lists the subcommands under a prefix (e.g.firebase help ailogic:providers) for discoverability.ensureApiEnabled.uncacheEnabledAPIto keep the local API-enablement cache consistent after enabling/disabling an API.All commands live under the fixed
globallocation (no--locationflag), support--jsonoutput, and reject client-breaking actions in--non-interactivemode unless--forceis passed. Permissions map to thefirebasevertexaiandserviceusageIAM permissions.This is the first of a series of scoped PRs bringing Firebase AI Logic management (providers, configuration, prompt templates, triggers) to the CLI.
Scenarios Tested
providers:enable gemini-developer-apion a project with the API disabled — enables the AI Logic API andgenerativelanguage.googleapis.com.providers:enable agent-platform-gemini-apion a Blaze project succeeds; on a Spark project it fails with an upgrade link and does not change the plan.providers:disable— confirmation prompt shown;--forceand--non-interactivepaths covered; proxy is disabled only when no providers remain.providers:list— reflects enablement state with and without billing.serviceusage.services.enable.firebase help ailogic:providers— lists the namespace subcommands.src/gcp/ailogic.spec.ts,src/gcp/serviceusage.spec.ts, andsrc/commands/help.spec.ts.npm test(lint + compile + mocha) passes.Sample Commands