{
  "markdown": "# vertex-doctor\n\n> **Prototype. Personal hobby project. Not a Google product, not supported, not affiliated with\n> anything official.** It makes real API calls against your project (tiny ones, with cost gates)\n> and can mutate project settings only when you pass `--yes`. Read the output before you trust it;\n> file issues with the JSON records when it is wrong.\n\n`vertex-doctor` answers one question about Google Cloud **Agent Platform** (the product formerly\nbranded Vertex AI; the API host is still `aiplatform.googleapis.com`):\n\n> Which publisher models can *this credential + this project* call, in *which location*, through\n> *which invocation surface*, and for every cell that fails, what is the one action that unblocks it?\n\nModel access is the intersection of seven independent gates (credential binding, API enablement\nand quota project, IAM, org policy, per-publisher agreements, per-location per-model quota, model\nlifecycle), each failing with its own status code and message shape. The console, `gcloud ai\nmodel-garden` and gateway health checks do not compose them. This tool probes a matrix of\n`projects x credentials x models x locations`, classifies every result into a fixed taxonomy, and\nprints the fix once per cause.\n\n## Start here: one model, one location\n\n```sh\nvertex-doctor journey -m claude-fable-5-1@default -l global --rpm 400        # show the path\nvertex-doctor journey -m claude-fable-5-1@default -l global --rpm 400 --fix  # walk it (asks before each change)\n```\n\nThe journey is the same ten steps every time, in the order the gates are actually hit. Each step\nis `[ok]`, `[next]` (the one thing to do now, and who does it), `[later]`, or `[you]` (a human\nstep the tool will not take: linking billing, a quota request the automated adjuster refuses).\n\n<p align=\"center\">\n  <img src=\"docs/gifs/journey.gif\" alt=\"vertex-doctor journey: ten ordered steps from credential to gateway config, with the one human step called out and the quota request form ready to paste\" width=\"900\">\n  <br><sub>A brand-new project, a brand-new Claude lineage: nine steps handled, one left for a person, form ready to paste.</sub>\n</p>\n\n<details>\n<summary>The happy path (all ten green, then the curl and SDK call that work)</summary>\n<p align=\"center\"><img src=\"docs/gifs/journey-ok.gif\" alt=\"vertex-doctor journey for gemini-flash-latest: all ten steps green and the LiteLLM entry printed\" width=\"900\"></p>\n</details>\n\n| # | step | who fixes it |\n|---|------|--------------|\n| 1 | credential resolves and is not certificate-bound | you (`gcloud auth application-default login`) |\n| 2 | billing account linked | you, in the console (the tool prints the two links) |\n| 3 | APIs enabled | the tool |\n| 4 | caller holds `aiplatform.endpoints.predict` | a project admin (the tool prints the command) |\n| 5 | model exists in the catalog and is spelled the way the REST path wants | the tool (re-probes the corrected id) |\n| 6 | model is served at this location | the tool (tells you which location works) |\n| 7 | project is entitled to the publisher (data sharing / EULA / enablement) | the tool, after you confirm |\n| 8 | quota bundle exists and is above zero | the tool files it; if the adjuster denies, you send the printed form to your account team |\n| 9 | a real request returns 200 | the tool |\n| 10 | ready to use | the tool prints the curl that worked and the official SDK call; any other client on request (see below) |\n\n`--rpm N` is the only number you need: token quotas are sized from what sibling models already\nhave in the project. Without `--rpm` the tool recommends a value the same way.\n\n**What can surprise you, and what the tool does about it**\n\n- Changes take a minute or two to propagate (an enabled API, the data-sharing flag, a quota\n  grant). After every change the tool re-probes until the answer changes instead of reporting the\n  stale one.\n- A brand-new project fails on five things before the first real gate (Resource Manager, Billing,\n  Agent Platform, Cloud Quotas and procurement APIs, then billing itself). The tool enables the\n  APIs; billing is yours.\n- The catalog is not access: models listed there can be uncallable, and callable models can be\n  missing from it. Only the real request counts, so every step 5 to 9 is proven by one.\n- `claude-x@default` is only valid when the catalog `versionId` is `default`; older models want\n  the dated version. The tool re-probes the right spelling.\n- Newer Claude lineages are served at `global` (and `us`/`eu`), not in most regions, and their\n  quota bucket is the lineage (`anthropic-claude-fable`), not the model id. The tool re-targets the\n  request and names the bucket.\n- New projects have zero quota for partner models. The automated adjuster grants some requests in\n  a minute and denies others just as fast without saying why, and it phrases a refusal at least two\n  ways (\"Quota request denied\" / \"We cannot grant ... '0' was granted\"), so the tool decides on\n  the granted numbers, never the sentence. When it denies, re-filing does not help; the tool says\n  so and hands you the form instead of looping.\n\n<details>\n<summary>Probing a whole matrix instead (models x locations)</summary>\n\n<p align=\"center\">\n  <img src=\"docs/gifs/probe.gif\" alt=\"vertex-doctor probe: a matrix of models by location, each failure classified, one fix per cause\" width=\"900\">\n</p>\n\n```\n$ vertex-doctor probe -m claude-sonnet-4-6@default,claude-sonnet-4-5@default,gemini-flash-latest -l global,us-east5\n\nmy-gcp-project  anthropic  [adc]\nmodel                       global      us-east5\nclaude-sonnet-4-6@default   ok 2292ms   ok 1345ms\nclaude-sonnet-4-5@default   x A17 404   x A17 404\n\nmy-gcp-project  google  [adc]\nmodel                 global      us-east5\ngemini-flash-latest   ok 523ms    x A16 404\n\nWhat to do\n1. [A17] WRONG_SURFACE_OR_SPELLING (high)  2 cell(s)\n   version suffix '@default' is not valid for this model; the catalog versionId is '20250929'\n   run: vertex-doctor probe --project my-gcp-project --model anthropic/claude-sonnet-4-5@20250929 --location global\n2. [A16] MODEL_NOT_SERVED_IN_LOCATION (high)  1 cell(s)\n   google/gemini-flash-latest is not served in us-east5 but works at global\n   run: vertex-doctor probe --project my-gcp-project --model google/gemini-flash-latest --location global\n```\n</details>\n\n<p align=\"center\"><b><a href=\"https://zeroasterisk.github.io/vertex-doctor/\">Watch the full walkthrough</a></b> (asciinema, with chapters), recorded on a project created minutes earlier.</p>\n\n## Install\n\nPython 3.11+. No published package yet; run from the repo or from git with `uv`:\n\n```sh\nuvx --from git+https://github.com/zeroasterisk/vertex-doctor vertex-doctor --help\n# or\nuv tool install git+https://github.com/zeroasterisk/vertex-doctor\n# or, from a checkout\nuv sync && uv run vertex-doctor\n```\n\n## Quick start\n\n```sh\nvertex-doctor                       # interactive wizard on a TTY (help when piped); the journey is the first option\nvertex-doctor journey -m MODEL -l global --rpm 60 [--fix]   # one model: the ordered path to callable\nvertex-doctor doctor                # many models x locations: env -> probe -> diagnose -> plan fixes (dry-run) -> report\nvertex-doctor doctor --fix          # also re-probe corrected cells; mutations ask on a TTY (or need --yes)\nvertex-doctor env                   # zero-cost environment check: gcloud/CBA, ADC, APIs, IAM\nvertex-doctor discover -P anthropic # catalog with the exact spelling for each surface\nvertex-doctor probe -m anthropic,google -l default --save probe.jsonl\nvertex-doctor quota-plan -m claude-fable-5-1@default -l us-east5 --rpm 100 --input-tpm 40000000 --output-tpm 1000000\nvertex-doctor client-config --from probe.jsonl                    # curl + how to call it; --format list for SDKs, harnesses, gateways, frameworks\n```\n\n### The doctor loop\n\n`doctor` is the \"walk through everything\" command. It runs the environment check, fixes blockers\nit is allowed to fix (API enablement, ADC quota project), probes the matrix, turns every failing\ncell into a **Fix** of one of four kinds, applies the allowed ones, re-probes the affected cells and\nprints before -> after:\n\n| kind | examples | needs |\n|------|----------|-------|\n| `mutate` | enable an API, set Anthropic data sharing, accept a EULA + `enableModel` | `--fix` plus `--yes` or a TTY confirmation |\n| `shell` | `gcloud auth application-default set-quota-project` | same |\n| `reprobe` | try `global` when a region 404s/400s, re-send with the corrected `@version`, re-send with ADC after a CBA 401 | `--fix` (costs one tiny probe per cell) |\n| `advice` | IAM grants, org policy, retired models, quota requests (with the exact `quota-plan` command) | a human |\n\nWithout `--fix` every mutation is dry-run and printed with its HTTP request and `gcloud`\nequivalent, so an agent can show the plan and a human can approve it.\n\n<p align=\"center\"><img src=\"docs/gifs/doctor.gif\" alt=\"vertex-doctor doctor --fix: a wrong @version suffix and a wrong region are re-probed and fixed; an enablement is left as a dry-run\" width=\"900\"></p>\n\nProject, credential and locations are all configurable; the wizard sniffs what the machine already\nhas (env vars, gcloud config, the ADC file, client configs and previous runs in the current\ndirectory) and leads with a concrete recommendation such as \"use my-gcp-project (gcloud config)\"\nor \"global + us-east5 ($CLOUDSDK_COMPUTE_REGION)\". The flags use the same detection:\n\n| Thing      | Flag             | Default                                                                 |\n|------------|------------------|-------------------------------------------------------------------------|\n| project    | `-p/--project`   | `$VERTEX_DOCTOR_PROJECT`, `$GOOGLE_CLOUD_PROJECT`, ADC quota project, `gcloud config` project |\n| credential | `-c/--credential`| `auto` = `$VERTEX_DOCTOR_ACCESS_TOKEN`, then ADC, then the gcloud user token (warned) |\n| location   | `-l/--location`  | `default` = `global` plus a small curated region list per publisher     |\n| models     | `-m/--model`     | required; any spelling, a bare publisher name (newest catalog models), or `@file` |\n\nCredential specs: `auto`, `adc`, `gcloud`, `sa:/path/key.json`, `impersonate:SA_EMAIL`, `token:RAW`.\n`impersonate:` is the one to use when the question is \"what can the *gateway's* service account\ncall?\" from a developer laptop.\n\n### Cost gates\n\nProbing sends one tiny fixed prompt (\"Reply with the single word: pong\", `max_tokens` 512,\nthinking disabled where the surface allows it) per cell. Three gates:\n\n- `--no-cost`: only free calls (`countTokens`, catalog, config reads). Proves auth, API\n  enablement and IAM. Gemini `countTokens` also validates model + location; the Anthropic\n  `count-tokens` endpoint accepts any model name, so it proves less. No free surface exists for the\n  OpenAI-compatible MaaS endpoint; those cells are skipped.\n- `--low-cost` (default): real predictions on non-premium models. Premium patterns (`opus`,\n  image/video/audio/TTS/OCR) are skipped.\n- `--any-cost`: everything.\n\nPlus `--max-cells` (200), `--concurrency` (4), `--max-total-output-tokens` (20k) and `--dry-run`,\nwhich prints every request with the token redacted and sends nothing.\n\n### Output for agents\n\n`--json` / `--jsonl` emit one schema-versioned record per cell (see `src/vertex_doctor/schema.py`).\nDiagnostics go to stderr. Color is off when stdout is not a TTY or `NO_COLOR` is set. Exit codes:\n\n| code | meaning |\n|------|---------|\n| 0 | every probed cell is callable |\n| 1 | at least one cell failed with a known diagnosis |\n| 2 | nothing could be probed (auth/config/plan error) |\n| 3 | at least one cell is `UNKNOWN` (please file the record) |\n\n## Commands\n\n| command | what it does | mutates? |\n|---------|--------------|----------|\n| `env` | gcloud version and CBA config, ADC file and quota project, principal per credential, live zero-cost differential (`countTokens`), Service Usage state for aiplatform / cloudquotas / procurement, `testIamPermissions`, Anthropic publisher config | no |\n| `discover` | Model Garden catalog (`publishers.models.list`, `v1beta1`) with `versionId`, `launchStage` and the canonical spelling for each surface | no |\n| `probe` | the matrix probe, corroborating reads, classification, a client-neutral `client` block per callable cell | sends predictions |\n| `diagnose --from` | re-classify saved records offline (no network) | no |\n| `client-config --from` | how to use the callable cells from a client: `curl` (default), official SDKs, agent harnesses, gateways, app frameworks; `--format list` | no |\n| `diff --config` | read a client's config file (`--format litellm` or `bifrost`), probe exactly those cells, bucket them: dead, misplaced, unenabled, truncating | sends predictions |\n| `watch --baseline` | re-probe a saved matrix and exit 1 on regressions (`--interval` to loop, `--update` to roll the baseline) | sends predictions |\n| `enable-api` | Service Usage `batchEnable` for aiplatform / cloudquotas / procurement | with `--yes` |\n| `enable-publisher` | Anthropic: `setPublisherModelConfig(dataSharingEnabledProvider=ANTHROPIC)`; others: `modelGardenEula:check`/`:accept` then `enableModel` | with `--yes` |\n| `journey` | one model, one location: the ten ordered gates with status, the next action and who does it; `--fix` walks them (propagation-aware re-probes; billing and denied quota are human steps) | with `--fix` (mutations ask, or `--yes`) |\n| `doctor` | many models x locations: env -> probe -> fix plan -> apply (`--fix`, `--yes`) -> re-probe -> before/after | with `--fix --yes` |\n| `quota-plan` | the quota **bundle** a model runs on (QPM, input TPM, output TPM, or combined TPM) at this endpoint and comparable ones, the `base_model` bucket to name, what to ask for, a filled request form | no |\n| `request-quota` | file one `QuotaPreference` per ask from the plan (`--rpm/--input-tpm/--output-tpm/--tpm`) | with `--yes` |\n| `interactive` | the wizard (also what bare `vertex-doctor` runs on a TTY) | asks |\n\nEvery mutation has an idempotent pre-check, `--dry-run` that prints the exact HTTP request, a\n`gcloud`/`curl` equivalent, and the IAM it needs.\n\n## Diagnosis taxonomy\n\nMatching is on HTTP status + gRPC status + `google.rpc.ErrorInfo.reason` + message fragment +\ncorroborating reads (catalog `get`, `fetchPublisherModelConfig`, `modelGardenEula:check`,\n`QuotaInfo`, sibling cells in the same run). Precedence: auth > API/quota project > IAM/org\npolicy > publisher agreement > location > quota > existence > response shape.\n\n| rule | code | trigger | primary remediation |\n|------|------|---------|---------------------|\n| A1 | `AUTH_CBA_BOUND_TOKEN` | 401 from a `gcloud` token with `reason=ACCESS_TOKEN_TYPE_UNSUPPORTED`, or ADC succeeds on the same request, or `context_aware/use_client_certificate=true` | use ADC / mTLS host / a service account |\n| A2 | `AUTH_INVALID_OR_EXPIRED` | other 401 | `gcloud auth application-default login` |\n| A3 | `QUOTA_PROJECT_MISSING_OR_DISABLED` | 403 `SERVICE_DISABLED` naming a consumer that is not the target project, or \"requires a quota project\" | `set-quota-project`, `x-goog-user-project` |\n| A3b | `QUOTA_PROJECT_USE_DENIED` | 403 `USER_PROJECT_DENIED`: the caller can see the project but lacks `serviceusage.services.use` on it | grant `roles/serviceusage.serviceUsageConsumer` (Editor/Owner include it) |\n| A4 | `API_DISABLED` | 403 `SERVICE_DISABLED` naming the target project | `enable-api` |\n| A4b | `BILLING_DISABLED` | 403 `BILLING_DISABLED`: no billing account linked (every brand-new project) | `gcloud billing projects link P --billing-account=ID`; the doctor only does this with an explicit `--billing-account` |\n| A5 | `IAM_PREDICT_DENIED` | 403 naming `aiplatform.endpoints.predict` | grant `roles/aiplatform.user` |\n| A6 | `PUBLISHER_DATA_SHARING_REQUIRED` | 403 \"requires data sharing to be enabled for publisher\" | `enable-publisher --publisher anthropic` |\n| A7 | `EULA_NOT_ACCEPTED` | 403/400 mentioning EULA / license / terms | `modelGardenEula:accept` |\n| A8 | `MODEL_NOT_ENABLED_FOR_PROJECT` | 403 mentioning enablement / questionnaire / private offer / procurement | `enableModel`, then console |\n| A9 | `ORG_POLICY_BLOCKED` | 403 mentioning organization policy / `vertexai.allowedModels` / procurement API | org admin |\n| A10 | `PERMISSION_DENIED_UNCLASSIFIED` | any other 403 | `env`; file as a new rule |\n| A11 | `REGIONAL_QUOTA_ZERO` | 429 in a region where `QuotaInfo` has no row or 0 for `(region, base_model)`, or the same model is OK at `global` | use `global` |\n| A12 | `QUOTA_EXHAUSTED` | 429 where `QuotaInfo` shows a positive limit | `quota-plan`, then `request-quota`, or lower concurrency |\n| A12b | `QUOTA_ZERO_FOR_PROJECT` | 429 at `global` where the project's row is unset/0 (every brand-new project, for partner models) | `quota-plan` for the bundle, then `request-quota` or the form |\n| A13 | (annotation `RATE_LIMITED_TRANSIENT`) | 429 then success on the backoff retry | none |\n| A14 | `GLOBAL_ONLY_MODEL` | 400 \"only available via global endpoint\" | use `global` |\n| A15 | `MODEL_RETIRED_OR_UNKNOWN` | 404 and the catalog has no such model (only for publishers the catalog lists) | `discover` |\n| A16 | `MODEL_NOT_SERVED_IN_LOCATION` | 404 / 400 \"not servable in region\" / \"not available in region\" with success elsewhere | move location |\n| A17 | `WRONG_SURFACE_OR_SPELLING` | 404 with a surface that does not match the publisher; 404 where the `@version` suffix does not match the catalog `versionId`; 400 \"cannot be called directly via\" | use the printed canonical spelling |\n| A18 | `NOT_FOUND_POSSIBLY_ENTITLEMENT` | 404 where the catalog knows the model and no location works | `enable-publisher --dry-run`, try `global` |\n| A19 | `REQUEST_SHAPE_INVALID` | 400 `INVALID_ARGUMENT` about the body | tool bug or wrong `--surface`; file it |\n| A20 | `OK_TRUNCATED_BY_REASONING` | 200 with `finish_reason` length/MAX_TOKENS, no text, reasoning tokens spent | access confirmed; raise budgets, set `health_check_max_tokens` |\n| A21 | `OK` | 200 with content | ready to use; `client-config` prints how |\n| A21f | `OK_FREE_PROBE` | `--no-cost` success | run a real probe to confirm |\n| A22 | `OK_PREVIEW_OR_UNSTABLE` | 200 but catalog says EXPERIMENTAL / PRIVATE_PREVIEW / UNSTABLE | pin before production |\n| A23 | `UPSTREAM_ERROR` | 5xx / transport error after one retry | retry later |\n| A24 | `UNKNOWN` | anything else | exit 3; file the record |\n\nAnnotations that ride along independently: `CATALOG_FLAG_MISMATCH` (callable but not in the\ncatalog, or vice versa: listing is not access), `PUBLIC_PREVIEW`, `TRUNCATED_AT_DEFAULT_BUDGET`,\n`RATE_LIMITED_TRANSIENT`.\n\n## Model ID spellings\n\nAll of these normalize to the same `(publisher, model_id)`; `discover` prints the right one for\neach surface:\n\n| where | spelling |\n|-------|----------|\n| Model Garden catalog | `anthropic/claude-sonnet-4-6` (no version; `versionId` is a field) |\n| REST path (Anthropic, Gemini, embeddings, Mistral) | `claude-sonnet-4-6@default`, `claude-sonnet-4-5@20250929`, `gemini-flash-latest` |\n| OpenAI-compatible endpoint body (`xai`, `openai`, `meta`, `deepseek-ai`, `qwen`) | `xai/grok-4.1-fast-non-reasoning` |\n| third-party clients | each has its own spelling; `client-config --format <name>` prints it (LiteLLM: `vertex_ai/claude-sonnet-4-6@default`, `vertex_ai/xai/grok-4.1-fast-non-reasoning`; Anthropic SDK: `claude-sonnet-4-6`; Bifrost: `vertex/claude-sonnet-4-6@default`) |\n\nSurfaces: Anthropic `rawPredict` (`anthropic_version: vertex-2023-10-16`), Gemini\n`generateContent` (`thinkingBudget: 0`, dropped on 400), embeddings `predict`, Mistral\n`rawPredict` with a Mistral-native body, and `endpoints/openapi/chat/completions` for the rest.\nHost is `aiplatform.googleapis.com` for `global`, `{location}-aiplatform.googleapis.com` otherwise.\n\n## Things the probe taught us (and now checks for)\n\n- **`@default` is not universal.** The Anthropic REST path takes `name@versionId` from the\n  catalog. `@default` works only for models whose catalog `versionId` is `default`; older ones want\n  the dated version (`claude-sonnet-4-5@20250929`). The wrong suffix is a 404 that looks exactly\n  like a retired model; A17 tells them apart from the catalog.\n- **CBA tokens have a server-side tell.** A certificate-bound `gcloud auth print-access-token`\n  token fails `tokeninfo` with `invalid_token` and fails `aiplatform` with 401\n  `reason=ACCESS_TOKEN_TYPE_UNSUPPORTED`, while ADC works. `env` shows both.\n- **Listing is not access, in both directions.** `openai`, `meta`, `deepseek-ai` and `qwen` MaaS\n  models are absent from the un-projected catalog yet callable; some catalog entries are not\n  callable by a given project; a retired dated Claude version was still callable while absent from\n  the catalog. Only a real request is ground truth.\n- **429 messages name the quota *metric*; `QuotaInfo` ids are CamelCase.** The message says\n  `online_prediction_input_tokens_per_minute_per_base_model`; the id you file against is\n  `OnlinePredictionInputTokensPerMinutePerRegionPerBaseModel`. `quota-plan` resolves one from the\n  other (and prefers the per-minute quota when a per-day one shares the metric).\n- **Newest Claude lineages have no regional quota rows at all.** `QuotaInfo` for this project\n  lists `anthropic-claude-fable`, `-sonnet`, `-opus` buckets only under the `Global...` and\n  `Us/Eu...` multi-region quotas; regional rows exist only for older per-version buckets. A regional\n  429 for those models is \"quota is zero here\", not \"you are burning it\". The lineage fallback in\n  `request-quota` maps `anthropic-claude-fable-5-1` to `anthropic-claude-fable`.\n- **Data sharing is per model generation.** In the same project, older Claude models answered\n  while the newest returned `PUBLISHER_DATA_SHARING_REQUIRED`. `fetchPublisherModelConfig` returns\n  404 until something is set, so absence of a config does not predict the gate.\n- **The OpenAI-compatible endpoint wraps errors in a list** (`[{\"error\": {...}}]`). Handled.\n- **A brand-new project fails five times before the first real gate.** On a project created\n  minutes earlier the doctor found, in order: `cloudresourcemanager`, `cloudbilling`, `aiplatform`,\n  `cloudquotas` and the procurement API disabled (it enabled them), then `BILLING_DISABLED` on every\n  cell. Billing is a money decision, so that one is never taken automatically: the doctor prints the\n  console links and stops. Once billing was linked, Gemini, embeddings and Grok answered\n  immediately while Claude returned 429 with an *unset* global quota row: partner models start at\n  zero and the project has to ask for the bundle (`QUOTA_ZERO_FOR_PROJECT`).\n- **Some catalog entries want another API entirely** (`... only supported in the Interactions API\n  and cannot be called directly via generateContent`). Classified A17, not treated as a tool bug.\n\n## Where will you use the model? (clients)\n\nThe core never names a client. Once a cell is callable, `client-config` (or the last question of\nthe wizard) renders the `client` block through an emitter. `--format list` prints the registry:\n\n| category | emitters | notes |\n|---|---|---|\n| raw | `curl` (default) | the exact request that worked, with the ADC token |\n| official SDKs | `google-genai`, `anthropic-sdk`, `openai-sdk`, `mistral-sdk` | one per surface; what Google and the publishers document |\n| agent harnesses | `opencode`, `gemini-cli` | `opencode.json` provider block; env + `settings.json` |\n| gateways / proxies | `litellm`, `bifrost`, `envoy-ai-gateway` | `litellm` and `bifrost` also have importers for `diff --config` |\n| app frameworks | `langchain`, `adk`, `ai-sdk`, `pydantic-ai` | constructor lines with project/location/credentials |\n\nEach emitter only drives the surfaces its target documents (an SDK for Claude does not pretend to\ncall Grok); cells it cannot drive are listed on stderr. Every emitter was written against the\ntarget's primary docs; the module docstring cites the URL and marks anything unverified. Adding one\nis a ~40-line module in `src/vertex_doctor/emitters/` that registers itself; the selection above is\nthe top of each category by adoption and documented Vertex partner-model support, not an endorsement.\n\n## Quotas: what to ask for\n\nA real quota request is a bundle for exactly one project, one `base_model` bucket and one endpoint\ntype, not one number. `quota-plan` reads `QuotaInfo` and answers the four questions people get\nwrong:\n\n1. **Which family?** Anthropic / Mistral run on `online_prediction_*` quotas; Gemini and the\n   OpenAI-compatible MaaS partners (grok, gpt-oss, ...) on `generate_content_*`; embeddings on\n   `embed_content_*` (regional request quota under `online_prediction`). A model always needs the\n   **requests/min** quota plus either **input + output tokens/min** (current lineages) or one\n   **combined tokens/min** (older ones).\n2. **Which endpoint type?** Prefix `\"\"` (regional, dimensioned on `region` + `base_model`),\n   `global_`, `us_multi_region_`, `eu_multi_region_`. Each is a different quota with a different\n   CamelCase id (`OnlinePredictionInputTokensPerMinutePerRegionPerBaseModel` vs\n   `GlobalOnlinePredictionInputTokensPerMinutePerBaseModel`).\n3. **Which bucket?** The `base_model` dimension is not the model id: `claude-fable-5-1@default`\n   is metered as `anthropic-claude-fable`, `mistral-small-2503` as `mistralai-mistral-small-2503`,\n   `gemini-embedding-001` as `gemini-embedding`, `grok-4.1-fast-reasoning` as itself. The planner\n   walks the lineage (`-5-1` -> `-5` -> family) until QuotaInfo has a row.\n4. **Does the row exist where you are asking?** For the newest Claude lineages there are no\n   regional rows at all; a regional request cannot be granted. The plan re-targets to the endpoint\n   that has rows (global / us / eu) and says so on the form.\n\n<p align=\"center\"><img src=\"docs/gifs/quota-plan.gif\" alt=\"vertex-doctor quota-plan re-targets a regional request to global, names the lineage bucket and prints a filled request form\" width=\"900\"></p>\n\nThat form is the same shape support / account-team request templates use; `request-quota --yes`\nfiles the same three values as `QuotaPreference`s, waits up to 90 s for the automated adjuster,\nand reports granted / denied / pending per quota. With no targets, `quota-plan` **recommends**\nvalues: the median of what sibling buckets of the same publisher already have at that endpoint in\nthis project (a number that was granted before is the most defensible ask), or a starter tier\n(60 QPM, 2M input, 200k output tokens/min) when there is nothing to compare with;\n`request-quota --recommended` files those. Existing requests for the bucket are listed with their\nstate, and a denial comes with the three usual reasons (not yet entitled to the model, bucket not\nself-serve, ask too large). Other per-base-model quota varieties exist\n(`responses_*` for the Responses API, bidi/live concurrency, image/audio/video generation and\nper-resolution input quotas, long-running predictions for video); they are listed by `QuotaInfo`\nand out of scope for the planner today.\n\n## Seed data\n\n`src/vertex_doctor/ids.py` carries a small table per publisher: surface, default locations, a\nfallback model list for publishers the catalog does not list, id style (bare vs publisher-prefixed), quota\n`base_model` prefix. It is a starting point the catalog and the probe correct, not a serving\nguarantee. `gemini-flash-latest` is a good evergreen alias for Gemini and is global-only.\n\n## For agents\n\nUse `--json`/`--jsonl`, read `diagnosis.code`, `diagnosis.remediation.command` and the exit code.\nNever run `enable-*` / `request-quota` without `--dry-run` first. See\n[`skills/vertex-doctor/SKILL.md`](skills/vertex-doctor/SKILL.md) and [`AGENTS.md`](AGENTS.md).\n\nThe repo is packaged the way [google/skills](https://github.com/google/skills) packages its\n`google-cloud-developer` plugin (`plugin.json`, `.claude-plugin/`, `.codex-plugin/`,\n`gemini-extension.json`, `skills/<name>/SKILL.md` with a category and a \"Use when / Don't use for\"\ndescription), and the skill defers to that catalog's `google-cloud-recipe-auth`, `gcloud`,\n`agent-platform-inference` and `agent-platform-troubleshooting` skills for what they own:\n\n```sh\nnpx skills add google/skills          # the official companions\nclaude plugin marketplace add zeroasterisk/vertex-doctor && claude plugin install vertex-doctor@vertex-doctor\n```\n\n## Demos and recordings\n\nThe GIFs above come from [`demo/*.tape`](demo) (rendered with [vhs](https://github.com/charmbracelet/vhs))\nand the walkthrough at [zeroasterisk.github.io/vertex-doctor](https://zeroasterisk.github.io/vertex-doctor/)\nfrom [`demo/record.py`](demo/record.py), which drives the real CLI in a pty, answers its prompts,\nredacts identifiers and refuses to export if one survives. Re-record with:\n\n```sh\nfor t in journey journey-ok quota-plan probe doctor; do VD_DEMO_PROJECT=... vhs demo/$t.tape; done   # -> docs/gifs/\nuv run python demo/record.py all                                          # -> docs/casts/\n```\n\nBoth make the same tiny real requests the tool always makes, against `VD_DEMO_PROJECT`, and\nreplace every `real=replacement` pair listed in `demo/redact.local` (gitignored; see\n`demo/redact.local.example`) before anything is written. The recorder refuses to export a cast\nin which a real identifier survived.\n\n## Development\n\n```sh\nuv sync\nuv run pytest            # offline: fixtures under tests/fixtures are real, scrubbed responses\nuv run ruff check src tests && uv run ruff format src tests\n```\n\nAdding a rule: put the scrubbed error body in `tests/fixtures/errors/`, add a branch to\n`classify()` in `diagnose.py` in precedence order, add a test in `tests/test_diagnose.py`, add the\nrow to the table above.\n\n## Not goals\n\nDeploying self-hosted Model Garden models, benchmarking, Provisioned Throughput, replacing the\ngateway's own runtime health checks (the emitters configure them instead), being a general\n`gcloud` wrapper.\n\n## License\n\nApache-2.0. Contributions welcome but expectations should be low: this is a hobby prototype.\n",
  "bytes": 30073,
  "sha": "fb8092a83b4624dc3906038008f9a9252d689dd602cb53014a76fdfb7bd18b73",
  "repo_slug": "zeroasterisk/vertex-doctor",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/plg_zeroasterisk_vertex_doctor_43f80473/readme"
}