Routers

Install the Modelglass Cost-Aware Router below to route and execute tasks in VS Code today, or read the worked code examples further down to build your own on the live Modelglass feed.

VS Code extension · Shipped, v0.6.3

Modelglass Cost-Aware Router

No Modelglass proxy ever sits in the request path. This isn't a code example — it's an installable extension. Point it at a task and it routes to the cheapest model that clears the right quality bar, then executes the call directly against your own provider key (BYOK — bring your own key) — your code and prompts go straight from your machine to the provider's API, and never touch a Modelglass server.

You only ever pay your provider's own rate: Modelglass adds no markup, no credit-purchase fee, and no per-request fee of any kind.

Because the call goes straight from your machine to the provider's API, there's no added hop and no added latency — and no scenario where a Modelglass outage is the reason a model call fails. The only two parties in that request are you and the provider you chose.

The router also handles async video and audio generation — send a Runway video render or an ElevenLabs voice generation through the same cost-aware logic, and Run Task polls the job for you until it completes.

Run Task does check the live Modelglass feed before picking a model — that check is cached for five minutes and falls back to the last successful fetch if the API is briefly unreachable, so routing itself doesn't become a new point of failure either.

Execution is a Starter and Pro feature — recommendation-only routing (no execution, no provider key needed) is free. The box below has the exact Starter vs Pro breakdown.

The ranking behind Run Task is the same pricing and benchmark feed available directly over REST and MCP — no VS Code required. Get a free API key →

Want the same pricing and benchmark data without leaving VS Code at all? Add the Modelglass MCP →

BYOK router

Run Task ranks nine task categories — bug fix/debug, new code generation, terminal/CLI/DevOps, library-aware feature work, refactor, test generation, documentation generation, chat/explain, and autocomplete — against live SWE-bench Pro/Verified, Terminal-Bench 2.1, Aider Polyglot/ LiveCodeBench, and BigCodeBench scores, then calls the top-ranked model on your own key. Starter configures one provider key and makes one execution attempt — a failure is reported clearly, no silent retry. Pro configures multiple provider keys and automatically retries the next-best model on a different provider on failure, plus unlocks a .modelglass/routing-rules.json override to set your own provider/model priority per category.

8 providers · 9 task categories · v0.6.3 Install →

How Modelglass routing works

New to the idea of routing between models? Start with what BYOK model routing is →

What does the Modelglass VS Code router do?
It is a BYOK (bring-your-own-key) router. Point it at a task and it ranks the models your provider keys can reach against the live Modelglass benchmark and pricing feed, then calls the cheapest one that clears a quality bar — on your own key, with no Modelglass proxy in the request path. For image, video and audio generation it shows the estimated cost before the job runs, so you know what a piece of content will cost before committing to it. Your prompts and code go straight from your machine to the provider; Modelglass never sees or stores them.
Can I switch models mid-project without rewriting my prompts?
That is what the prompt-refiner examples are for. av-prompt-refiner, image-prompt-refiner and shot-prompt-refiner are worked code in the modelglass-router-examples repo (linked below) that rewrite a rough prompt to fit whichever model you are targeting — catching hard limits, like a model that cannot produce a 15-second clip or cannot sing vocals, before you spend a generation on them. Paired with cost-visible routing, that makes it cheap to draft in a smaller model on a local or staging setup and move to something more capable only for the final pass.
What about a polished result that spans several models?
shot-plan-compiler is the example for that. It picks a model per shot in a storyboard, then checks whether the sequence can actually be stitched together — flagging fps, resolution or audio-seam mismatches between consecutive shots — before any video renders.
Do I need to use VS Code?
No. The ranking behind the extension is the same pricing and benchmark data available directly over the REST API and MCP. The extension is one way to consume it; a free API key gets you the rest.
What does it cost?
Recommendation-only routing — no execution, no provider key — is free. Executing tasks on your key is a Starter or Pro feature: Starter configures one provider key, Pro configures several and retries the next-best model on a different provider on failure. You only ever pay your provider's own rate; Modelglass adds no markup, credit-purchase fee, or per-request fee.
Does Modelglass see my code or prompts?
No. Every model call goes straight from your machine to the provider's API using your key. Nothing is proxied through a Modelglass server and nothing is retained, so a Modelglass outage can never be the reason a model call fails.

Worked code examples

Read-and-adapt code, not hosted demos — each one demonstrates a different way of using Modelglass pricing and capability data as grounding context for an LLM-powered tool. There's nothing to run here.

Source repo: modelglass-router-examples ↗

Routes every dev subtask to the cheapest model that clears the quality bar — not the priciest one by default.

Demo task, 4 subtasks: $0.040 total. o4-mini (68.1% SWE-bench Verified, $1.10/1M input) selected over Gemini 2.5 Pro (63.8%, below the 65% bar this task set) — and it was the cheaper model too, not a quality-for-price tradeoff.

  • Curated SWE-bench Verified scores are real and sourced — every score in the routing table carries a provenance link and date, not a vibe.
  • A quality bar is a genuine filter, not a rounding step: an excluded model is named with its real score and the exact threshold it missed.
  • Same model, different host, different price is tracked per offering, not averaged away.
Get a free key → run the demo task
Full README — setup, usage, worked examples

Cost-aware task router for development workflows. Routes each subtask of an incoming dev task to the cheapest LLM that can handle it, using the live Modelglass feed as the model pool.

Coding subtasks (write/debug code) go to the cheapest model with a curated SWE-bench Verified score in the Modelglass registry. Writing subtasks (PR descriptions, commit messages, docs) go to the cheapest model with strong instruction-following. No token spend on premium models for work that doesn't need them.


Background

This is the CLI-script core of SCO-139 (VS Code cost-aware task router). The full design spec is in docs/specs/sco-139-orchestrator-routing-design.md in the main Modelglass repo.

Placement decision (2026-07-01): this repo is the canonical home for the routing logic. The VS Code extension, MCP tool, and CLI wrapper all build on top of this core.


Requirements


Setup

git clone https://github.com/Modelglass/modelglass-router-examples.git
cd modelglass-router-examples
npm install
export MODELGLASS_API_KEY=<your-key>

Usage

Run the built-in demo task:

npm run demo

Route a custom task from a JSON file:

node --import tsx/esm cost-aware-vscode-router/src/route.ts my-task.json

(Run from the repo root — dependencies and npm scripts are shared across all examples in this repo; see the root README for the full example index.)

Task file format:

{
  "description": "One-line description of the overall task",
  "subtasks": [
    {
      "description": "Write the auth middleware",
      "tag": "coding",
      "minSweBenchVerified": 65,
      "estimatedInputTokens": 8000,
      "estimatedOutputTokens": 2000
    },
    {
      "description": "Write the PR description",
      "tag": "writing",
      "estimatedInputTokens": 2000,
      "estimatedOutputTokens": 400
    }
  ]
}

Tags: coding (write/edit/debug code), writing (prose), general (anything else).
Token estimates are optional — omit them to skip cost projection.
minSweBenchVerified is optional (0-100). Coding subtasks with it set only consider models whose curated SWE-bench Verified score clears the threshold — omit it to fall back to "any confirmed-score model qualifies." A model with no curated score at all is still excluded either way (see "How it works").


How it works

The router calls the modelglass_list_models MCP tool (modality: "llm") on the live Modelglass feed, then applies two selection rules:

Coding subtasks — filter capability_profile.coding == "strong", rank by the SWE-bench Verified score read from the feed's structured knowledge.benchmarks field (not quality_tier — qualitative ratings are excluded, and the score is not parsed out of prose notes). These scores are curated in the Modelglass coding-capability registry — the same data behind modelglass.com.au/coding — and every entry carries provenance: a source URL and its type (vendor / leaderboard / paper / independent), which the routing table displays next to each score. Models with no curated SWE-bench Verified score are shown as excluded with the reason, not silently skipped — including the case where a model has a score for a different benchmark (SWE-bench Pro). Each offering in the feed carries its own provider; picking the cheapest offering means picking a specific host, not just a model, and the routing table names it — "same model, different host, different price" is a real Modelglass fact this tool doesn't discard on the way to a recommendation.

If any coding subtask sets minSweBenchVerified, the router takes the highest threshold across all coding subtasks in the task (one model is still selected for every coding subtask — see "What's not here" — so it has to clear the strictest bar any of them set) and filters the ranked pool down to models whose score meets or exceeds it before picking cheapest. A model with a real, confirmed score that still falls short is marked ✗ below quality bar in the table and named explicitly in the exclusion list with its actual score and the required threshold — it's a genuine filter, not a description. Omit minSweBenchVerified entirely to fall back to the original behaviour (any confirmed-score model qualifies, cheapest wins).

Writing / general subtasks — filter instruction_following in [strong, good], ignore the coding filter entirely. Select the cheapest qualifying model.

Escalation — if a coding subtask fails correctness review, retry on the next model up the ranked pool's cost ladder. Walk up one step at a time; don't jump straight to the most expensive option. The suggested next step always still clears the quality bar itself — escalating to a cheaper-in-theory model that fails the same threshold the original pick had to clear would be a worse recommendation, not a better one. If nothing in the pool qualifies as a next step, no escalation is suggested at all (see the worked example below). When you follow an escalation suggestion, log it with npm run report -- ... --escalated so it's tracked as its own category distinct from a plain override — see "Measuring real savings" below.


Worked example — rate-limiting middleware (2026-07-12)

This is the output of npm run demo against the live feed on 2026-07-12 (the model pool moves as the registry does — your run may differ). The demo task's coding subtasks set minSweBenchVerified: 65:

Task: Add per-endpoint rate limiting middleware to the Modelglass API (Redis KV, 429/Retry-After, unit tests, PR description, Slack summary).

────────────────────────────────────────────────────────────────────────────────
  Modelglass Task Router
────────────────────────────────────────────────────────────────────────────────
  Task: Add per-endpoint rate limiting middleware to the Modelglass API (Redis KV, 429/Retry-After, unit tests, PR description, Slack summary).
────────────────────────────────────────────────────────────────────────────────

  CODING MODEL POOL  (coding=strong, ranked by SWE-bench Verified, min. SWE-bench Verified 65%)

  Model                    Provider           SWE-bench Verified (source, type)    Input/1M     Output/1M
  ────────────────────────────────────────────────────────────────────────────────────────────────────────────
  o4-mini                  openai             68.1%  (openai.com, vendor)          $1.1         $4.4  ← selected
  Gemini 2.5 Pro           google-deepmind    63.8%  (deepmind.google, vendor)     $1.25        $10  ✗ below quality bar

  Excluded from the ranked pool:
  ✗ Claude Fable 5: no curated SWE-bench Verified score in the Modelglass registry
  ✗ Claude Sonnet 5: has a curated SWE-bench Pro score (different benchmark) — not SWE-bench Verified
  ✗ Gemini 3.1 Pro: no curated SWE-bench Verified score in the Modelglass registry
  ✗ Gemini 3.5 Flash: no curated SWE-bench Verified score in the Modelglass registry
  ✗ GPT-5.6 Sol: no curated SWE-bench Verified score in the Modelglass registry
  ✗ Mistral Large 3: no curated SWE-bench Verified score in the Modelglass registry
  ✗ Qwen 3 235B-A22B: no curated SWE-bench Verified score in the Modelglass registry
  ✗ Gemini 2.5 Pro: SWE-bench Verified 63.8% is below the required threshold of 65%

  WRITING/GENERAL MODEL  (instruction_following=strong|good, cheapest)

  Llama 4 Scout (together-ai)  Input $0.1/1M  Output $0.3/1M  ← selected

────────────────────────────────────────────────────────────────────────────────
  ROUTING TABLE

  #   Subtask                                            Tag        Model                Est. in    Est. out   Est. cost
  ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  1   Implement rate-limit middleware (Upstash KV, ...   coding     o4-mini              10000      2500       $0.022
  2   Write unit tests (pass/reject/tier-boundary)       coding     o4-mini              8000       2000       $0.018
  3   Write PR description explaining the change an...   writing    Llama 4 Scout        3000       500        $0.00045
  4   Write Slack summary for the team announcing t...   writing    Llama 4 Scout        2000       200        $0.00026
  ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
                                                                                                    Total      $0.040

────────────────────────────────────────────────────────────────────────────────

Why o4-mini, and why no escalation line? o4-mini (68.1%) clears the 65% bar and is the cheapest input price in the pool ($1.10/1M) — cheapest-ranked selection picks it outright. Gemini 2.5 Pro is genuinely excluded, not just out-ranked: its real curated score (63.8%) is below the threshold, named explicitly with both numbers rather than silently losing on price. That's also why no escalation suggestion appears this run — on today's data, Gemini 2.5 Pro is the only other confirmed-score candidate, and it doesn't qualify either, so there's honestly nothing to escalate to. Note the provenance column: both scores are vendor-published numbers (openai.com, deepmind.google), shown as such rather than laundered into unqualified facts, and Claude Sonnet 5's exclusion names the actual data condition (a SWE-bench Pro score is not a SWE-bench Verified score).


Measuring real savings

The router recommends but doesn't execute. After you run each subtask using the recommended model, feed the real token counts back with npm run report:

## After completing subtask 1 of the demo task using o4-mini:
npm run report -- --task demo --subtask 1 \
  --model o4-mini \
  --actual-input 9500 --actual-output 2100

If the actual model differs from the recommendation because you followed an escalation suggestion (the recommended model failed correctness review and you retried on the next model up the cost ladder), add --escalated:

## Recommended model failed review; escalated to Gemini 2.5 Pro per the
## router's suggestion:
npm run report -- --task demo --subtask 1 \
  --model "Gemini 2.5 Pro" \
  --actual-input 9500 --actual-output 2600 --escalated

Without --escalated, a different actual model is logged as an override instead — some other reason the caller used a different model, not a retry-after-failure. This distinction is tracked per entry (deviation_type: "none" | "escalation" | "override") and reported as two separate categories by npm run summary, not folded into one generic "different model than recommended" bucket.

Each call appends one line to logs/routing-log.jsonl (gitignored — stays local). The entry records: recommended model, estimated tokens/cost, actual model used, actual tokens, actual cost, deviation type, and a hypothetical baseline (what those tokens would have cost at the most expensive model in the pool).

Once you've logged a few subtasks, run the summary:

npm run summary

This prints total actual spend, total estimated spend, savings vs the hypothetical always-expensive-model baseline in both $ and %, and separate counts of escalations vs overrides.

Why the router doesn't execute subtasks itself: this stays a minimal, zero-provider-credential reference implementation by design, not because execution is rejected as a goal — execution now genuinely exists, just elsewhere. modelglass-vscode 0.3.0+ (Marketplace: modelglass.cost-aware-router) ships a Run Task command that ranks the same way this script does, then calls the top-ranked model directly using a provider key you supply (BYOK — Starter: one key; Pro: multiple keys with automatic fallback). That execution layer is original code in modelglass-vscode (src/routing-engine.ts, src/run-task*.ts, src/provider-*.ts — see its README), not built on this repo. This script — and the extension's separate, still-recommend-only Route Task command that vendors it — intentionally keeps zero provider credentials, so the report-back workflow below (B2) is what lets real token counts flow in from whatever tool you actually used to run each subtask: Claude Code, a custom agent, direct API calls, or now Run Task itself.


What's not here (intentional)

  • Token-spend measurement/tracking — deferred; see SCO-139.
  • VS Code extensionRoute Task's ranking is surface layer, vendoring this script directly; its separate Run Task command (0.3.0+) adds independent execution using your own provider key, not built on this repo — see "Why the router doesn't execute subtasks itself" above.
  • MCP tool — same (surface layer, not yet built — SCO-235).
  • Task decomposition — the caller tags subtasks at decomposition time. No router-calling-a-router.

Copyright © 2026 Modelglass Pty Ltd. Licensed under the MIT License — see LICENSE.

Picks a model per shot, then checks whether the sequence can actually be stitched together — before a second of video renders.

3-shot demo storyboard: $5.05 total, with 2 flagged handoffs (an fps mismatch and 2 audio seams) caught automatically between shots.

  • Caught a real bug during development: Runway Act Two has no text-to-video mode at all — a naive "cheapest model" script would have picked it for a shot it structurally can't originate.
  • Chain-feasibility is real cross-shot reasoning, not per-shot picks made in isolation — fps/resolution/audio mismatches between consecutive shots are flagged with a fix.
  • Video pricing (per_second / per_clip / per_credit) is honestly normalized — never force-converted when the data to do it honestly isn't there.
Get a free key → plan your own storyboard
Full README — setup, usage, worked examples

Storyboard in, execution plan out. Takes a shot-list JSON and, for each shot, picks the cheapest model in the live Modelglass video registry that can actually serve it — then checks whether the sequence of picks can be stitched together at all, flagging fps mismatches, resolution steps, silent-to-native-audio seams, and shots that need to be split across multiple generations. Ends with a total job cost.

This tool plans. It does not generate or composite anything. No generation calls, no provider keys beyond the Modelglass API key, no real spend, fully reproducible output — see "What's not here" below.

Pairs with shot-prompt-refiner (SCO-357): plan the shots here, then refine each shot's rough prompt to fit its picked model there. Separate tool, separate codebase, separate opt-in step — this tool's no-generation-call, no-LLM-call scope is unchanged by that pairing.


Background

This is SCO-190 — "shot-plan compiler," which absorbed an earlier idea (SCO-189, multi-model video routing via last-frame handoff) after a review concluded the two ideas were one example, not two, and that the planner half — the one that actually needs nothing but registry data to be real — is the right half to build. Actually rendering a chained video is a one-off marketing artifact, not repo code; this tool stops at the plan.


Requirements


Setup

git clone https://github.com/Modelglass/modelglass-router-examples.git
cd modelglass-router-examples
npm install
export MODELGLASS_API_KEY=<your-key>

(Run from the repo root — dependencies and npm scripts are shared across all examples in this repo; see the root README for the full example index.)


Usage

Run the built-in demo storyboard:

npm run plan

Plan a custom storyboard from a JSON file:

node --import tsx/esm shot-plan-compiler/src/plan.ts my-storyboard.json

Get budget/balanced/premium alternates instead of a single plan:

npm run plan -- --demo --alternates

Emit machine-readable JSON instead of the text report (SCO-357 — feeds shot-prompt-refiner: plan the shots here, refine each shot's prompt there):

npm run plan -- --demo --json > plan.json
npm run refine-shots -- plan.json

Not valid together with --alternates — a downstream consumer needs one concrete plan, not three budget-level variants, to know which model each shot was actually assigned to.

Storyboard file format:

{
  "title": "Product teaser — 3 shots",
  "shots": [
    {
      "id": "shot-1",
      "description": "Wide establishing shot, slow push-in",
      "durationSeconds": 5,
      "resolution": "1080p",
      "fps": 24,
      "audio": false
    },
    {
      "id": "shot-2",
      "description": "Continues from shot-1's last frame into a close-up",
      "durationSeconds": 12,
      "resolution": "1080p",
      "fps": 24,
      "audio": false,
      "continuityFromPrevious": true
    }
  ]
}

resolution must be one of the registry's resolution strings ("480p", "720p", "1080p", "4K", etc. — whatever the live feed's knowledge.supported_resolutions actually uses). continuityFromPrevious is optional — set it when a shot is meant to continue directly from the previous shot's last frame (a frame-conditioned continuation) rather than start fresh from a text description; it's the one flag that toggles whether image-to-video support is a hard requirement on the model serving that shot.


How it works

The planner calls GET /v1/models?modality=video (via the MCP endpoint, modelglass_list_models) once, then applies the same per-shot selection to every shot in the storyboard:

Per-shot model pick. A candidate model/tier must clear every hard requirement the shot's own spec implies, each cited by field when it fails:

  • resolutionknowledge.supported_resolutions
  • fpsknowledge.fps_options
  • a fresh shot (no continuityFromPrevious) needs "text-to-video" in knowledge.generation_modes — it starts from a text description. A continuity shot needs "image-to-video" instead — it's conditioned on the previous shot's last frame, not text. This gate caught a real bug during development: Runway Act Two's generation_modes are ["image-to-video", "video-to-video"] with no text-to-video mode at all — without this check it could get picked for an ordinary fresh shot it structurally cannot originate.
  • offering.model.status !== "deprecated"
  • the tier's own attributes.resolution/attributes.fps (when a model prices different resolutions on different tiers) must match, not just the model-level capability

The cheapest qualifying candidate wins. Every offering is field-cited in the rationale — cost, capability, host — same style as cost-aware-vscode-router and av-prompt-refiner.

Cost math — honest-unit discipline, extended for video. image-batch-coster never force-converts a price unit without a documented basis in the registry's own data; this tool applies the same principle to video's units, which differ from image's (per_second / per_clip / per_credit, not per_image / per_megapixel):

  • per_second — directly comparable: amount × duration.
  • per_clip — comparable when the tier records attributes.clip_seconds (every per_clip tier in the live registry does): cost = amount × ceil(duration / clip_seconds), i.e. however many clip generations the shot's duration actually needs.
  • per_credit — comparable when the tier records attributes.credits_per_second (every per_credit tier in the live video registry does, unlike image's per_credit case where the credit-per- generation rate isn't tracked at all): cost = amount × credits_per_second × duration.
  • Anything missing the attribute needed to convert it honestly — or billed in a unit this tool doesn't recognize — is listed in that shot's excluded candidates with the specific reason, never guessed.

Chain-feasibility — the novel part. For every shot-to-shot handoff:

  • fps mismatch — the two shots' fps differ (e.g. a 24fps shot cut into a 30fps one). Flagged with a recommendation to insert a frame-rate conversion pass or re-author one shot to match.
  • resolution step — the two shots' resolution differ. Flagged with a recommendation to upscale/downscale to match, or accept the step as an intentional hard cut (a crossfade across a resolution step reads as soft focus, so that's explicitly not the suggested fix).
  • audio seam — one shot's pick produces native audio and the adjacent one doesn't. Flagged with a recommendation to fade the audio track across the cut, or run a separate audio-generation pass (see av-prompt-refiner) to cover the silent side.
  • infeasible continuity — a continuityFromPrevious shot has no qualifying model at all.

Split required. When a shot's duration exceeds its picked model's knowledge.max_clip_duration, the shot needs multiple back-to-back generations from the same model, each conditioned on the previous segment's last frame — which additionally requires "image-to-video" support for the self-chaining, regardless of whether the shot itself is a continuity shot. Flagged per shot with the exact segment count needed.

Alternate budget levels (--alternates, stretch scope). budget picks the cheapest qualifying candidate per shot (the default). premium picks the priciest qualifying candidate per shot, used as a price-as-quality proxy — the registry has no single per-shot-type quality scalar to rank on instead, and this tool says so rather than pretending otherwise. balanced picks the middle-ranked candidate per shot.


Worked example — product teaser (2026-07-12)

This is the output of npm run plan against the live feed on 2026-07-12 (18 video models; the pool moves as the registry does — your run may differ):

Storyboard: 3 shots — a wide establishing shot (1080p/24fps, silent), a close-up that continues from the first shot's last frame (1080p/24fps, silent, 12s — longer than most models' clip cap), and a final hero shot with voiceover (1080p/30fps, audio).

────────────────────────────────────────────────────────────────────────────────────────────────
  shot-plan-compiler
────────────────────────────────────────────────────────────────────────────────────────────────
  Storyboard: Product teaser — 3 shots
────────────────────────────────────────────────────────────────────────────────────────────────

  SHOT PLAN

  shot-1
    Wan 2.5 (fal): USD 0.05/s × 5s; clears knowledge.supported_resolutions (1080p) and knowledge.fps_options (24); cheapest of 13 qualifying candidate(s)
    Cost: $0.2500
    Excluded (9):
      ✗ Act Two (runway/act-two): knowledge.generation_modes [image-to-video, video-to-video] does not include 'text-to-video' — required for a fresh shot generated from a text prompt
      ✗ CogVideoX-5B (thudm/cogvideox-5b): knowledge.supported_resolutions [720p] does not include '1080p'
      ✗ Gemini Omni Flash (google-deepmind/gemini-omni-flash): knowledge.supported_resolutions [720p] does not include '1080p'
      ✗ Gen-4 Turbo (runway/gen-4-turbo): knowledge.generation_modes [image-to-video] does not include 'text-to-video' — required for a fresh shot generated from a text prompt
      ✗ Hailuo-02 (minimax/hailuo-02): minimax offering: no tier attributes compatible with '1080p' / 24fps
      ✗ Kling 2.1 (klingai/kling-2-1): klingai offering: no tier attributes compatible with '1080p' / 24fps
      ✗ LTX Video 0.9.7 (lightricks/ltx-video-0-9-7): knowledge.supported_resolutions [480p, 720p] does not include '1080p'
      ✗ Mochi 1 (genmo/mochi-1): knowledge.supported_resolutions [480p] does not include '1080p'
      ✗ Veo 3 (google-deepmind/veo-3): google-deepmind offering: model.status is 'deprecated'

  shot-2
    Act Two (runway): USD 0.05/s × 12s; clears knowledge.supported_resolutions (1080p) and knowledge.fps_options (24); cheapest of 15 qualifying candidate(s); knowledge.max_clip_duration 10s < shot duration 12s — split into 2 self-chained generations
    Cost: $0.6000
    Excluded (7):
      ✗ CogVideoX-5B (thudm/cogvideox-5b): knowledge.supported_resolutions [720p] does not include '1080p'
      ✗ Gemini Omni Flash (google-deepmind/gemini-omni-flash): knowledge.supported_resolutions [720p] does not include '1080p'
      ✗ Hailuo-02 (minimax/hailuo-02): minimax offering: no tier attributes compatible with '1080p' / 24fps
      ✗ Kling 2.1 (klingai/kling-2-1): klingai offering: no tier attributes compatible with '1080p' / 24fps
      ✗ LTX Video 0.9.7 (lightricks/ltx-video-0-9-7): knowledge.supported_resolutions [480p, 720p] does not include '1080p'
      ✗ Mochi 1 (genmo/mochi-1): knowledge.supported_resolutions [480p] does not include '1080p'
      ✗ Veo 3 (google-deepmind/veo-3): google-deepmind offering: model.status is 'deprecated'

  shot-3
    Sora 2 (openai): USD 0.7/s × 6s; clears knowledge.supported_resolutions (1080p) and knowledge.fps_options (30); cheapest of 1 qualifying candidate(s); shot needs audio but knowledge.native_audio is false — needs a separate audio pass
    Cost: $4.20
    Excluded (17):
      ✗ Act Two (runway/act-two): knowledge.fps_options [24] does not include 30
      ✗ CogVideoX-5B (thudm/cogvideox-5b): knowledge.supported_resolutions [720p] does not include '1080p'
      ✗ Gemini Omni Flash (google-deepmind/gemini-omni-flash): knowledge.supported_resolutions [720p] does not include '1080p'
      ✗ Gen-4 Turbo (runway/gen-4-turbo): knowledge.fps_options [24] does not include 30
      ✗ Gen-4.5 (runway/gen-4-5): knowledge.fps_options [24] does not include 30
      ✗ Hailuo-02 (minimax/hailuo-02): knowledge.fps_options [24] does not include 30
      ✗ HappyHorse 1.0 (happyhorse/happyhorse-1-0): knowledge.fps_options [24] does not include 30
      ✗ HunyuanVideo 1.5 (tencent/hunyuan-video-1-5): knowledge.fps_options [24] does not include 30
      ✗ Kling 2.1 (klingai/kling-2-1): klingai offering: no tier attributes compatible with '1080p' / 30fps
      ✗ LTX Video 0.9.7 (lightricks/ltx-video-0-9-7): knowledge.supported_resolutions [480p, 720p] does not include '1080p'
      ✗ Mochi 1 (genmo/mochi-1): knowledge.supported_resolutions [480p] does not include '1080p'
      ✗ Pika 2.2 (pika/pika-2-2): knowledge.fps_options [24] does not include 30
      ✗ Ray 3.2 (luma/ray-3-2): knowledge.fps_options [24] does not include 30
      ✗ Seedance 2 (runway/seedance-2): knowledge.fps_options [24] does not include 30
      ✗ Veo 3 (google-deepmind/veo-3): knowledge.fps_options [24] does not include 30
      ✗ Veo 3.1 (google-deepmind/veo-3-1): knowledge.fps_options [24] does not include 30
      ✗ Wan 2.5 (wan-video/wan-2-5): knowledge.fps_options [16, 24] does not include 30

────────────────────────────────────────────────────────────────────────────────────────────────
  SHOT FLAGS (2)

  [split-required] shot-2: Shot exceeds Act Two's max_clip_duration — needs 2 self-chained generations.
    → Generate 2 segments back to back, each conditioned on the previous segment's last frame (image-to-video), then trim the internal splice points.

  [needs-separate-audio-pass] shot-3: Shot needs audio but Sora 2 has no native audio.
    → Run a separate audio-generation pass (see av-prompt-refiner) and mux it onto this shot.

────────────────────────────────────────────────────────────────────────────────────────────────
  CHAIN-FEASIBILITY (2 handoff(s), 2 flagged)

  shot-1 → shot-2:
    [audio-seam] shot-1's pick (Wan 2.5) does not produce native audio; shot-2's pick (Act Two) produces native audio.
      → Add a fade on the audio track across this cut, or run a separate audio-generation pass (see av-prompt-refiner) to cover the silent side rather than an abrupt audio cut.

  shot-2 → shot-3:
    [fps-mismatch] shot-2 is 24fps, shot-3 is 30fps.
      → Insert a frame-rate conversion pass at the cut (or re-author one shot to match fps) — a raw fps step will visibly judder.
    [audio-seam] shot-2's pick (Act Two) produces native audio; shot-3's pick (Sora 2) does not produce native audio.
      → Add a fade on the audio track across this cut, or run a separate audio-generation pass (see av-prompt-refiner) to cover the silent side rather than an abrupt audio cut.

────────────────────────────────────────────────────────────────────────────────────────────────
  TOTAL COST: $5.05
────────────────────────────────────────────────────────────────────────────────────────────────

Why this run is honest, not contrived. Shot-1 initially resolved to Runway Act Two during development — the cheapest raw per-second rate in the pool — until running this planner against real data surfaced that Act Two has no text-to-video mode at all (it's a performance-transfer model driven by a video, not a prompt) and genuinely cannot originate a fresh shot. That gap is what the primary-mode gate documented above exists to catch, and it's exactly the kind of thing a naive "cheapest model" script would miss. The fps-mismatch and audio-seam flags on shot-2 → shot-3 are also real: shot-3's 30fps requirement rules out every 24fps-only model, leaving Sora 2 as the only qualifying candidate, and Sora 2 has no native audio — so both flags fire honestly, not for demonstration purposes. Run npm run plan -- --demo --alternates to see budget ($5.05) / balanced ($6.24) / premium ($16.20) side by side.


What's not here (intentional)

  • Generation calls — this tool never calls a video/audio provider. It only reads the Modelglass registry and computes a plan.
  • Compositing/rendering — stitching, fades, and trims are recommended, not executed. No video-editing library is a dependency here.
  • Provider keys — only MODELGLASS_API_KEY is needed. No Runway, OpenAI, Google, or any other generation-provider credential.
  • Real spend — nothing in this tool can cost money to run.
  • A quality-ranked "premium" tier--alternates's premium level uses price as a proxy for quality because the registry doesn't expose a single quality scalar per shot type; this is a stated simplification, not a hidden one.


Repo note: merges to this repo's main now publish to modelglass.com.au/routers automatically (SCO-197, 2026-07-13) — no manual nudge to the main site repo required.

Copyright © 2026 Modelglass Pty Ltd. Licensed under the MIT License — see LICENSE.

Pairs with shot-plan-compiler: once the plan picks your models, this writes prompts that actually fit them.

A few cents and a few seconds per refinement job — and unlike every other refiner example here, the LLM doing the rewrite is itself chosen live: Claude 3.5 Haiku ($0.80/1M) for one shot, Claude Sonnet 5 ($2/1M) for a multi-shot storyboard.

  • The refiner LLM itself is routed off the live feed by a reasoning-capability rating — the first example here that doesn't hardcode which LLM does the rewrite.
  • Cross-shot consistency is handled in one combined call across the whole storyboard, not disconnected per-shot rewrites.
  • Honest about what it can't call — only Anthropic-hosted models are candidates, stated plainly rather than silently narrowed.
Get a free key → pair it with shot-plan-compiler
Full README — setup, usage, worked examples

Storyboard-plan in, refined prompts out. Takes the plan shot-plan-compiler computed for a storyboard — which model was picked for each shot — and has an LLM rewrite each shot's rough prompt to fit the model actually assigned to it, grounded in that model's live capability data from the Modelglass registry.

This tool refines prompts. It does not generate or render video. Same "plans/refines, doesn't generate" discipline as shot-plan-compiler — see "What's not here" below.


Why this exists, and why it's opt-in

This is SCO-357 — the video half of it. A human's rough prompt into a video model is usually weaker than what a capability-grounded rewrite produces, the same way av-prompt-refiner and image-prompt-refiner already demonstrate for their verticals. This tool is a demonstrably better way to get consistent, reliable output from the models shot-plan-compiler picked — it's not novel for its own sake, and it isn't the default step in the storyboard workflow.

Be honest about the trade-off. An extra LLM call adds cost and latency on top of whatever the actual video generation costs — this tool doesn't pretend otherwise:

  • Cost: one combined LLM call across the whole storyboard (not one call per shot — see How it works), routed to the cheapest Anthropic model that clears the reasoning bar the job needs. For the worked example below (3 shots, "complex" tier), that's roughly 1,500–2,500 input tokens (grounding data + rough prompts) and 1,000–2,000 output tokens at Claude Sonnet 5's $2/$10 per-million rates — call it a few cents, not a rounding error at scale, and it's on top of whatever the actual video generation itself costs.
  • Latency: seconds, not instant — a real reasoning-model completion, not a registry lookup.

Use this when a shot's output quality matters enough to justify that — final assets, hero shots, anything you'd otherwise iterate on by hand. Skip it for cheap/fast exploratory passes where the rough prompt is good enough.

Access model: this is a Pro-gated capability in the product sense (a Modelglass API key still authenticates every registry/routing call this tool makes either way — actual plan enforcement is a site-side concern, not something this CLI example enforces itself; see packages/api's plan-gating for where that actually lives).


The pairing with shot-plan-compiler

┌─────────────────────┐        ┌──────────────────────┐
│  shot-plan-compiler  │  plan  │  shot-prompt-refiner  │
│  storyboard → picks  │ ─────► │  picks → refined      │
│  + chain-feasibility │  .json │  prompts, per shot    │
└─────────────────────┘        └──────────────────────┘
npm run plan -- my-storyboard.json --json > plan.json
npm run refine-shots -- plan.json

shot-plan-compiler's Storyboard/Shot schema is reused as-is here — a storyboard written for one tool is valid input to the other, no transformation needed. plan.ts --json is the machine-readable companion to its normal text report (added alongside this tool, not instead of it — the default npm run plan behavior is unchanged).


Requirements

  • Node.js 20+
  • A Modelglass API key (get a free one)
  • An Anthropic API key (ANTHROPIC_API_KEY) — BYOK, same as av-prompt-refiner/image-prompt-refiner

Setup

git clone https://github.com/Modelglass/modelglass-router-examples.git
cd modelglass-router-examples
npm install
export MODELGLASS_API_KEY=<your-key>
export ANTHROPIC_API_KEY=<your-key>

Usage

Run the built-in demo (the real product-teaser storyboard + picks from shot-plan-compiler's own worked example — see its README — so this runs without needing to invoke shot-plan-compiler first):

npm run refine-shots -- --demo

Refine a real plan:

npm run plan -- my-storyboard.json --json > plan.json
npm run refine-shots -- plan.json

Input file format — the exact { storyboard, plan } shape shot-plan-compiler's plan.ts --json emits:

{
  "storyboard": {
    "title": "Product teaser — 3 shots",
    "shots": [
      { "id": "shot-1", "description": "Wide establishing shot...", "durationSeconds": 5, "resolution": "1080p", "fps": 24, "audio": false }
    ]
  },
  "plan": {
    "storyboard_title": "Product teaser — 3 shots",
    "selections": [
      { "shot_id": "shot-1", "picked": { "model_id": "wan-video/wan-2-5", "name": "Wan 2.5", "provider": "fal" } }
    ]
  }
}

A shot with "picked": null (shot-plan-compiler found no feasible model) or missing from plan.selections entirely is reported and skipped, not silently dropped or refined against a guessed model.


How it works

Grounding each shot's rewrite. For every distinct model picked across the storyboard, this tool calls the live Modelglass MCP endpoint (modelglass_get_model) for that model's capability profile — the same video-field allowlist av-prompt-refiner uses (max_clip_duration, supported_resolutions, fps_options, generation_modes, native_audio, capability_profile, routing_guidance, limitations, etc.), never the full untrusted registry payload.

One combined LLM call, not one per shot. A multi-shot storyboard needs cross-shot narrative and style consistency reasoned about together — the same principle behind av-prompt-refiner's combined video+audio mode, applied across N shots instead of two modalities. All shots being refined go into a single system prompt with every relevant grounding block; the model returns a refined prompt + "what changed and why" per shot, plus one "Consistency notes" section when there's more than one shot.

Routing the refiner LLM itself — the part av-prompt-refiner and image-prompt-refiner don't do (both hardcode claude-opus-4-8). This tool selects it from the live Modelglass LLM feed instead, the same way cost-aware-vscode-router routes a coding subtask: filter by a capability_profile "reasoning" rating tier, cheapest qualifying candidate wins, every exclusion cited by field.

  • Complexity signal: shot count in the job being refined. A single shot is "simple cleanup" (reasoningmoderate required). Two or more shots is "complex" (reasoning = strong required) — cross-shot consistency is the thing that actually gets harder, not prompt length, so that's the signal used, not an LLM-judged complexity score (which would add its own cost/latency just to decide whether to spend cost/latency — circular).
  • Anthropic-hosted candidates only. This tool executes the refinement call itself via the Anthropic SDK (BYOK, ANTHROPIC_API_KEY) — the same single-provider convention every LLM-calling example in this repo uses. Routing to a model this tool then can't actually call would be a recommendation nobody can act on, so the candidate pool is restricted to provider: anthropic offerings, and every non-Anthropic exclusion says so explicitly rather than silently narrowing the pool.

Verified against the live registry (2026-08-07) — the actual routing outcome, computed from the same ontology/models/*.yaml + registry/models/*.yaml data the MCP tool serves (not simulated):

Job Reasoning bar Cheapest qualifying Anthropic model Input price
Single shot (simple) ≥ moderate Claude 3.5 Haiku $0.80 / 1M tokens
2+ shots (complex) = strong Claude Sonnet 5 $2.00 / 1M tokens

Claude Haiku 4 ($1.00) and Claude 3.5 Sonnet/Sonnet 4/Sonnet 4.6 ($3.00 each) are real qualifying candidates too — Claude 3.5 Haiku and Claude Sonnet 5 just clear their respective bars at the lowest price in the live pool today. This pool moves as the registry does; run --demo (or a real plan) against your own key to see the current picks and full excluded-candidate list.


Worked example — demo storyboard, routing decision

This is the real routing step for the built-in demo (3 shots, "complex" — see --demo's output on stderr before the refinement call starts):

────────────────────────────────────────────────────────────────────────────────────────────────
  shot-prompt-refiner
────────────────────────────────────────────────────────────────────────────────────────────────
  Storyboard: Product teaser — 3 shots
  3 shot(s) to refine, 0 skipped.

Fetching capability profiles for 3 model(s) ...

Routing refiner LLM (complexity: complex, 3 shot(s)) ...
  Selected: Claude Sonnet 5 (anthropic/claude-sonnet-5) — reasoning: strong, $2/1M input tokens;
  cheapest of 6 qualifying Anthropic-hosted candidate(s) clearing the 'strong' reasoning bar.

Refining 3 shot prompt(s) with claude-sonnet-5 ...

The refined-prompt transcript itself is a follow-up, not fabricated here — same convention image-prompt-refiner's README follows for its Claude-rewrite half: this session has no live ANTHROPIC_API_KEY (or a production MODELGLASS_API_KEY — the routing table above was verified directly against the registry source files instead, not a live MCP call). Whoever runs this next with real keys should add the actual output as a follow-up commit:

export MODELGLASS_API_KEY=<your-key>
export ANTHROPIC_API_KEY=<your-key>
npm run refine-shots -- --demo

What's not here (intentional)

  • Video generation, compositing, or rendering — this tool never calls a video provider. It only reads Modelglass registry data and produces refined prompt text.
  • Provider keys beyond Modelglass + Anthropic — no Runway, OpenAI, Google, or other generation-provider credential is needed or used, even though shots may be assigned to non-Anthropic-hosted video models; this tool only refines the prompt for whatever model shot-plan-compiler picked, it doesn't call that model.
  • Routing across LLM providers for the refinement call itself — the candidate pool is Anthropic-hosted only, for the reason stated above. A genuinely multi-provider version would need this repo to carry more than one LLM SDK/key, which no example here does today.
  • A default, always-on enhancement step — this is opt-in, invoked separately from shot-plan-compiler, for shots where the result matters enough to justify the extra cost/latency.


Repo note: merges to this repo's main now publish to modelglass.com.au/routers automatically (SCO-197, 2026-07-13) — no manual nudge to the main site repo required.

Copyright © 2026 Modelglass Pty Ltd. Licensed under the MIT License — see LICENSE.

Rewrites your rough prompt to fit the exact model you picked — catching hard limits before you waste a generation on them.

Stable Audio 3.0 can't generate sung vocals (vocal_generation: false) — caught automatically, the prompt rewritten as an instrumental with a routing note pointing to Suno v4 instead.

  • Capability data is granular enough to catch a hard constraint before you spend money on a failed generation — a 15s request trimmed to Kling 2.1's real 10s clip ceiling.
  • Every prompt change cites the exact registry field that drove it, not generic prompting advice.
  • Coordinated video+audio mode reasons across two capability profiles in one pass — matched duration and timed sync cues, not two prompts bolted together.
Get a free key → try it on your own model
Full README — setup, usage, worked examples

Capability-aware prompt rewriting for video and audio generation models, grounded in live Modelglass capability data.

You've already picked your target model(s) — this doesn't do model selection (see cost-aware-vscode-router for that). What it does: pulls the chosen model's real capability profile (prompt conventions, supported parameters, known limitations) from the live Modelglass MCP endpoint, and has Claude rewrite your rough prompt to fit that model specifically — citing the exact capability-data field behind every change, not generic advice.


Three modes

  • Video only — refine a prompt for one video model.
  • Audio only — refine a prompt for one audio model.
  • Both (coordinated) — you want video and audio for the same piece (e.g. a silent video model paired with a separate music/sound-design model). This reasons across both capability profiles at once — not two independent rewrites bolted together — to produce a matched-duration, matched-mood pair of prompts plus explicit sync notes (e.g. "audio swell at 0:05 matches the video's camera push-in").

Background

Second example in this repo, alongside cost-aware-vscode-router. Both call the Modelglass HTTP MCP endpoint directly over JSON-RPC — the tool surface an agent or IDE integration would actually use — but different tools for different shapes of data: this one via modelglass_get_model (a single model's full profile, to ground the prompt-refinement guidance), where cost-aware-vscode-router uses modelglass_list_models (the bulk pool, for ranking/selection). See docs/mcp-usage.md in the main repo for the full MCP contract.


Requirements


Setup

git clone https://github.com/Modelglass/modelglass-router-examples.git
cd modelglass-router-examples
npm install
export MODELGLASS_API_KEY=<your-key>
export ANTHROPIC_API_KEY=<your-key>

Usage

## Video only
npm run refine -- --mode video --video-model <model_id> --prompt "<rough prompt>"

## Audio only
npm run refine -- --mode audio --audio-model <model_id> --prompt "<rough prompt>"

## Both, coordinated
npm run refine -- --mode both --video-model <model_id> --audio-model <model_id> --prompt "<rough prompt>"

<model_id> is the Modelglass cross-host id, e.g. klingai/kling-2-1 or stability-ai/stable-audio-3-0. Find ids via GET /v1/models?modality=video|audio on the live feed, or the modelglass_list_models MCP tool. Model ids must have capability data in the registry (a pricing-only entry errors out — there's nothing to ground the rewrite in).


Worked examples (live runs, 2026-07-05)

Video only — klingai/kling-2-1

Rough prompt intentionally asks for 15 seconds; Kling 2.1's clip-duration ceiling is 10 seconds.

Input: --mode video --video-model klingai/kling-2-1 --prompt "A woman walks through a bustling night market, camera slowly zooming in on her face as neon lights reflect in her eyes, 15 seconds long"

Output (excerpt):

## Refined Video Prompt

A young woman moves at an unhurried pace through a bustling night market,
weaving between food stalls and crowds. Warm steam rises from vendor carts
as neon signage in pink, cyan, and amber washes over the scene. The camera
holds on her from a medium distance, then executes a slow, steady push-in
toward her face over the length of the shot, tightening to a close-up as
the vivid neon lights reflect and shimmer in her eyes. ...

## What Changed and Why

- Trimmed from 15s to 10s — max_clip_duration / clip-duration-ceiling caps
  Kling 2.1 at 10 seconds per call.
- Wrote the zoom as cinematic prose ("slow, steady push-in") rather than a
  parameter — camera-control is only moderate, so pacing the move in
  descriptive language gives it the best chance of landing.
- Emphasized consistent facial identity and grounded walking motion — leans
  on the model's strong character-consistency and motion-quality ratings.

Audio only — stability-ai/stable-audio-3-0

Rough prompt asks for sung vocals; Stable Audio 3.0's vocal_generation is false.

Input: --mode audio --audio-model stability-ai/stable-audio-3-0 --prompt "An upbeat pop song with vocals about summer, 3 minutes long"

Output (excerpt):

## Refined Audio Prompt

An energetic, feel-good instrumental pop track that captures the spirit of
summer ... Build through a bright, hook-driven lead melody (carried by synth
and guitar in place of a vocal line) that stands in for the "chorus" ...

## What Changed and Why

- Converted from a vocal song to an instrumental track with a lead-melody
  "hook" replacing the sung chorus — vocal-support is rated "weak" and
  features_and_differences.vocal_generation is false.
- Dropped the lyric-driven "about summer" narrative and expressed the theme
  through instrumentation and mood instead — lyric_control is false.

Tradeoff: This prompt cannot deliver a sung, lyric-based vocal about summer.
If sung vocals and lyrics are essential, the routing guidance points to
Suno v4 as the better choice.

Both, coordinated — klingai/kling-2-1 + stability-ai/stable-audio-3-0

Rough prompt again asks for 12 seconds — Kling's 10s ceiling becomes the binding constraint for the whole piece, not just the video half.

Input: --mode both --video-model klingai/kling-2-1 --audio-model stability-ai/stable-audio-3-0 --prompt "A dog running joyfully on a beach at sunset, with fun upbeat music, 12 seconds long"

Output (excerpt):

## Refined Video Prompt

A golden retriever bounds joyfully across a wide sandy beach at sunset ...
10 seconds, 1080p, 24fps, no native audio track (scored separately).

## Refined Audio Prompt

Fun, upbeat instrumental cue for a playful beach scene at golden hour ...
- seconds-start: 0
- seconds-total: 10

## Consistency Notes

- Total length locked to 10s on both sides — the video's ceiling is the
  binding constraint, and Stable Audio's seconds-total timing conditioning
  is set explicitly to 10 to match.
- Because Stable Audio 3.0 exposes seconds-start/seconds-total timing
  conditioning, sync points are given as real instants, not just prose:
  the two-second musical build (0:00–0:02) covers the dog's initial burst
  into a run; the resolving flourish at ~0:09–0:10 lands under the
  camera's slow push-in.

## What Changed and Why

- Trimmed 12s → 10s (video) — Kling 2.1's clip-duration-ceiling caps at 10s.
- Set audio seconds-total to 10s — matched to the video ceiling.
- Scored audio separately instead of Kling native audio — Kling's
  native-audio is only moderate/"variable quality," so the dedicated audio
  model (strong audio-quality) carries the music.

What's not here (intentional)

  • Model selection — the caller has already chosen their target model(s). See cost-aware-vscode-router for cost-aware LLM routing; nothing equivalent exists here for video/audio model choice.
  • Hosted/live demo — CLI example only, meant to be read and adapted.

Copyright © 2026 Modelglass Pty Ltd. Licensed under the MIT License — see LICENSE.

The same grounded rewrite, for image models — including a model's own documented weaknesses, not just its strengths.

DALL·E 3's photorealism is rated "moderate" ("tends to stylise / over-beautify") — an "ultra-photorealistic" request was honestly softened to match, tradeoff stated plainly instead of over-promised.

  • Capability ratings reflect real weaknesses, not just strengths — the rewrite states the tradeoff instead of promising what the model can't deliver.
  • Exact constraints get caught even with no dedicated schema field — DALL·E 3 has only three fixed output resolutions, read and cited straight from prose.
  • 30 image models now carry real 7-dimension capability ratings in the registry, not just pricing.
Get a free key → try it on your own model
Full README — setup, usage, worked examples

Capability-aware prompt rewriting for image generation models, grounded in live Modelglass capability data.

You've already picked your target model — this doesn't do model selection (see cost-aware-vscode-router for that, or image-batch-coster if you're choosing on cost). What it does: pulls the chosen model's real capability profile (rated strengths, known limitations, prompting conventions where the registry states them) from the live Modelglass MCP endpoint, and has Claude rewrite your rough prompt to fit that model specifically — citing the exact capability-data field behind every change, not generic advice.

Sibling example to av-prompt-refiner (video/audio) — same shape, image vertical.


Two modes

  • Generate — pure text-to-image models (e.g. bfl/flux-1-1-pro, google/imagen-4). Describe the complete desired image from scratch.
  • Edit — image-editing models that take an existing image plus an instruction (e.g. bfl/flux-kontext, bytedance/seedream-4-0). Rewrites the prompt as an edit instruction — describing only what should change and explicitly preserving what shouldn't — rather than a from-scratch scene description.

This split mirrors a real distinction the image ontology itself draws, not an invented category: FLUX Kontext's own routing_guidance explicitly says to route there "for image-editing workloads... rather than pure text-to-image generation," and Seedream 4.0's data describes "editing and text-to-image generation" as two different post-training tasks. There's no combined/coordinated mode here the way av-prompt-refiner has for video+audio — a single image has no second-modality counterpart to coordinate with.


What's genuinely different from av-prompt-refiner (video/audio)

The image ontology schema (ontology/schema/model-knowledge.schema.json in the main repo) has no modality-specific structured fields the way video and audio do. Video entries carry typed fields like max_clip_duration, supported_resolutions, and generation_modes; audio entries carry voice_cloning, ssml_support, sub_modality. Image entries carry only the fields shared across all three modalities — architecture, capability_profile, use_cases, routing_guidance, limitations, notes — and nothing image-specific beyond them.

Concretely, that means:

  • No structured aspect-ratio or resolution-ceiling field to check against the way av-prompt-refiner checks a rough prompt's requested duration against max_clip_duration exactly. Resolution/aspect-ratio information, when a model's data has it at all, is prose inside capability_profile's resolution-ceiling dimension notes (e.g. FLUX 1.1 Pro's "Ultra variants push to ~4MP; base ~1-2MP") or limitations — this tool reads and cites that prose the same way it cites any other field, but can't do exact numeric constraint-checking the way the video tool can.
  • No structured negative-prompt-support or reference-image-count field. Multi-reference-image support (e.g. Seedream 4.0's "up to a dozen reference images") is stated as prose in capability_profile notes and use_cases, not a typed field — the system prompt is instructed to read for this rather than expect a dedicated key.
  • The generate/edit split takes the place of av-prompt-refiner's video/audio/both mode split — the genuinely useful image-specific distinction is generation-vs-editing, not a coordinated multi-modality mode (there's nothing for a single image to coordinate with).

This isn't a gap this tool works around silently — see PROMPT_FIELDS in src/lib.ts for the exact allowlist and why it's shorter than av-prompt-refiner's.


Which models this works for

Needs a model with real ontology data in the Modelglass registry — a pricing-only entry (join_status: pricing_only) has nothing to ground the rewrite in and errors out clearly. As of 2026-08-05, 30 image models in the registry carry ontology entries with real capability_profile ratings across seven dimensions (prompt-adherence, photorealism, artistic-range, text-rendering, compositional-accuracy, resolution-ceiling, inference-speed) — including both pure-generation models (the FLUX 1.1/1.pro line, Imagen 4, Ideogram 3.0, Seedream 4.0/4.5) and editing-capable models (FLUX Kontext, Seedream's own editing mode, GPT Image 2). Find ids via GET /v1/models?modality=image on the live feed, or the modelglass_list_models MCP tool.


Requirements


Setup

git clone https://github.com/Modelglass/modelglass-router-examples.git
cd modelglass-router-examples
npm install
export MODELGLASS_API_KEY=<your-key>
export ANTHROPIC_API_KEY=<your-key>

Usage

## Generate mode — pure text-to-image
npm run refine-image -- --mode generate --model <model_id> --prompt "<rough prompt>"

## Edit mode — image-editing models
npm run refine-image -- --mode edit --model <model_id> --prompt "<rough prompt>"

<model_id> is the Modelglass cross-host id, e.g. bfl/flux-1-1-pro or bfl/flux-kontext. Model ids must have capability data in the registry (a pricing-only entry errors out — there's nothing to ground the rewrite in).


Worked examples (live runs, 2026-08-05)

Generate mode — openai/dall-e-3

Rough prompt asks for square 512×512 and "ultra-photorealistic"; DALL·E 3's data rates photorealism: moderate ("tends to stylise / over-beautify") and only supports three fixed output resolutions (1024×1024, 1024×1792, 1792×1024) — no 512×512.

Input: --mode generate --model openai/dall-e-3 --prompt "An ultra-photorealistic macro close-up photo of a dewdrop on a spider web at dawn, square 512x512, tiny readable text caption in the corner reading 'Morning Dew'"

Output:

## Refined Prompt

A macro close-up of a single glistening dewdrop clinging to a strand of a spider's web at dawn. The dewdrop acts as a tiny lens, faintly refracting the soft golden-pink morning light and the blurred silhouette of the web behind it. Fine silk threads radiate outward, beaded with smaller droplets, catching cool backlight against a dreamy, softly blurred background of muted dawn colors. Shallow depth of field with delicate, painterly bokeh; gentle, luminous, slightly idealized lighting. In the lower-right corner, a small clean caption in a simple sans-serif font reads "Morning Dew" in soft white lettering. Square 1024×1024 composition.

## What Changed and Why

- **Resolution changed from 512×512 to 1024×1024 (square).** — Driven by `resolution-ceiling` notes: "Fixed outputs: 1024×1024, 1024×1792, 1792×1024"; 512×512 is not an available output, so the nearest square option was used.
- **Softened "ultra-photorealistic" to a stylised, luminous, slightly idealized macro look.** — Driven by `photorealism` rating "moderate" with notes "tends to stylise / over-beautify" and the limitation "weaker raw photorealism"; the prompt leans into the model's strength rather than promising true photorealism it delivers weakly. Tradeoff: expect a beautified/artistic macro rather than a clinically realistic photograph.
- **Kept the corner text caption and specified it plainly ("Morning Dew," simple sans-serif, white).** — Driven by `text-rendering` rating "strong" ("Notably better at legible in-image text"); the caption is a good fit, though I dropped the word "tiny" and asked for a clean simple font since very small type still renders more reliably when kept legible-sized.
- **Expanded into one rich, descriptive scene paragraph.** — Driven by `prompt-adherence` rating "strong" and `use_cases` ("Complex prompts that must be followed faithfully"); the model responds well to detailed, caption-style descriptions.

Edit mode — bytedance/seedream-4-0

Rough prompt asks to combine three product photos into one lifestyle shot; Seedream 4.0's data specifically calls out support for up to a dozen reference images for multi-image composition/fusion.

Input: --mode edit --model bytedance/seedream-4-0 --prompt "Combine these three product photos into one clean lifestyle photo showing all three items together, keeping each item's appearance exactly as shown in its source photo"

Output:

## Refined Edit Instruction

Using the three provided product photos as reference images, compose a single clean lifestyle scene that arranges all three items together in a natural, cohesive setting.

- Fuse the three source photos into one unified image, placing all three products together as if photographed in the same lifestyle setting.
- Preserve each item's appearance exactly as shown in its own source photo — identical shape, color, materials, proportions, branding, and any on-product text or logos. Do not restyle, recolor, or redesign any product.
- Generate a clean, uncluttered lifestyle background and consistent lighting that ties the three items together, without introducing extra props that compete with the products.
- Render at high resolution (up to 4K) for a crisp, presentation-ready result.
- If any product carries printed labels or text, keep that text sharp and correctly rendered.

## What Changed and Why

- **Framed as a multi-reference composition rather than a single-image edit** — driven by `compositional-accuracy` ("Supports up to a dozen reference images for organic multi-image composition/fusion") and the `use_cases` entry "Multi-image composition/fusion from up to a dozen reference images"; three source photos fall well within the dozen-image ceiling.
- **Explicitly instructed to preserve each item's exact appearance per its source** — the rough prompt required identity preservation, and the multi-image fusion capability is the specific feature that carries per-item fidelity across the composite.
- **Added guidance to keep any on-product text/labels sharp** — leverages `text-rendering` (rated "strong," with correct in-image text rendering called out).
- **Specified "up to 4K" high-resolution output** — grounded in `resolution-ceiling` ("4K generation," rated strong).
- **Kept requirements modest on photographic realism and did not over-promise a polished photoreal render** — `photorealism` and `artistic-range` are rated "unknown" (see `limitations`: unrated), so a clean lifestyle look was requested without asserting guaranteed photographic realism.
- **No negative-prompt or aspect-ratio directives added** — the capability data contains no prose in architecture/routing/limitations describing negative-prompt syntax or aspect-ratio limits, so none were invented.

Background

Sibling example to av-prompt-refiner, following the same pattern: calls the Modelglass HTTP MCP endpoint directly over JSON-RPC via modelglass_get_model (a single model's full profile, to ground the prompt-refinement guidance) — the tool surface an agent or IDE integration would actually use. See docs/mcp-usage.md in the main repo for the full MCP contract.


What's not here (intentional)

  • Model selection — the caller has already chosen their target model. See cost-aware-vscode-router for cost-aware LLM routing or image-batch-coster for cost-ranked image-model selection; nothing equivalent exists here for choosing which image model to use.
  • Exact numeric constraint-checking on aspect ratio/resolution — unlike av-prompt-refiner's exact max_clip_duration check, the image ontology has no structured field for this (see "What's genuinely different" above). The tool grounds in whatever prose the registry provides instead of inventing precision the data doesn't have.
  • Hosted/live demo — CLI example only, meant to be read and adapted.
  • Actual generation calls — this rewrites a prompt; it never calls an image-generation provider or spends money on a generation.

Copyright © 2026 Modelglass Pty Ltd. Licensed under the MIT License — see LICENSE.

image-batch-coster

Cross-host cost ranking for an image-generation batch job, using the live Modelglass image feed. Estimating what a batch job will actually cost is genuinely fiddly: the same model is often priced differently across hosts, and offerings bill in incompatible units — the live image feed today mixes per_image, per_megapixel, per_credit, and per_month tiers in the same pool. Cross-host arbitrage on identical models is real, current data — this surfaces it directly, with the repo's standard grounded rationale for every inclusion, exclusion, and unit conversion.

Cross-host cost ranking for an image-generation batch job, using the live Modelglass image feed. Estimating what a batch job will actually cost is genuinely fiddly: the same model is often priced differently across hosts, and offerings bill in incompatible units — the live image feed today mixes per_image, per_megapixel, per_credit, and per_month tiers in the same pool. Cross-host arbitrage on identical models is real, current data — this surfaces it directly, with the repo's standard grounded rationale for every inclusion, exclusion, and unit conversion.

Free-tier friendly — only current prices are needed, no pricing-history lookback, no LLM call. Pure data + arithmetic.


Requirements

  • Node.js 20+
  • A Modelglass API key (get a free one) — any plan works

Setup

git clone https://github.com/Modelglass/modelglass-router-examples.git
cd modelglass-router-examples
npm install
export MODELGLASS_API_KEY=<your-key>

Usage

Run the built-in demo job:

npm run cost

Cost a real job from a JSON file:

node --import tsx/esm image-batch-coster/src/cost.ts my-job.json

(Run from the repo root — dependencies and npm scripts are shared across all examples in this repo; see the root README for the full example index.)

Job spec format:

{
  "count": 250,
  "resolution": "1536x1536",
  "requirements": {
    "photorealism": "strong"
  }
}
  • count — how many images the job will generate.
  • resolution"WIDTHxHEIGHT", converted to megapixels internally (the unit per_megapixel tiers actually price on).
  • requirements — optional; a capability dimension → minimum acceptable rating map (weak / moderate / strong). Keys are validated against whatever dimensions actually exist in the live feed at run time — nothing is hardcoded here. Today that's artistic-range, compositional-accuracy, inference-speed, photorealism, prompt-adherence, resolution-ceiling, text-rendering, but this tool doesn't assume that list stays fixed; an unrecognized key (or an unrecognized minimum rating) fails fast with the actual current list rather than silently excluding everything.

How it works

  1. Fetches the full image-modality, current-generation offering pool via the live Modelglass MCP endpoint (modelglass_list_models, one call).
  2. Validates the job spec's requirements keys and minimum ratings against what's actually present in that response.
  3. Capability filtering — excludes any model that doesn't meet the job's stated requirements, citing the specific capability_profile.<dimension> field (or its absence) behind every exclusion.
  4. Cost normalization for the surviving candidates:
    • per_imageamount × count
    • per_megapixelamount × count × megapixels (from the spec's resolution)
    • per_credit / per_monthnever converted. Routed to a separate "not directly comparable" section with the specific reason, instead of guessed. See What's not here.
  5. Ranks the comparable offerings by cost-per-job (and shows cost-per-1k-images for scale-independent comparison).
  6. Cross-host callout — groups ranked offerings by model_id; when the same model is offered at ≥2 hosts and they land on different job costs, calls out the cheapest vs priciest host and the spread, by name.

Worked example (real run, against the live feed)

This is the actual output of npm run cost -- --demo, run 2026-07-06 against the live Modelglass feed — not hand-written:

Fetching the image-modality offering pool from https://modelglass-api.vercel.app/mcp ...

────────────────────────────────────────────────────────────────────────────────────────────────
  image-batch-coster
────────────────────────────────────────────────────────────────────────────────────────────────
  Job: 250 image(s) at 1536x1536 (2.36 MP) — requires photorealism: strong+

  RANKED (18 offering(s), cheapest first):
  MODEL                             HOST         UNIT           COST/JOB  COST/1K IMAGES
  Z-Image-Turbo                     siliconflow  per_image      $1.25     $5.00
  GPT Image 1                       openai       per_image      $2.75     $11.00
  Adobe Firefly Image 3             adobe        per_image      $5.00     $20.00
  Imagen 4                          google       per_image      $5.00     $20.00
  FLUX.1 [dev]                      replicate    per_image      $6.25     $25.00
  FLUX 1.1 [pro]                    replicate    per_image      $10.00    $40.00
  Imagen 4                          google       per_image      $10.00    $40.00
  Stable Diffusion 3.5 Large Turbo  replicate    per_image      $10.00    $40.00
  GPT Image 1                       openai       per_image      $10.50    $42.00
  FLUX.1 [dev]                      together     per_megapixel  $14.75    $58.98
  Ideogram 3.0                      ideogram     per_image      $15.00    $60.00
  Ideogram 3.0                      ideogram     per_image      $15.00    $60.00
  Ideogram 3.0                      ideogram     per_image      $15.00    $60.00
  Imagen 4                          google       per_image      $15.00    $60.00
  Stable Diffusion 3.5 Large        replicate    per_image      $16.25    $65.00
  FLUX 1.1 [pro]                    fal          per_megapixel  $23.59    $94.37
  GPT Image 1                       openai       per_image      $41.75    $167.00
  HunyuanImage 3.0                  fal          per_megapixel  $58.98    $235.93

  CROSS-HOST PRICE SPREAD (2):
  FLUX 1.1 [pro] (bfl/flux-1-1-pro): replicate $10.00 vs fal $23.59 for this job — same model, 57.6% spread
  FLUX.1 [dev] (bfl/flux-1-dev): replicate $6.25 vs together $14.75 for this job — same model, 57.6% spread

  NOT DIRECTLY COMPARABLE (6) — not ranked:
  Leonardo Phoenix (leonardo, per_credit @ USD 0.00257): billed in provider credits, not a fixed per-image/per-megapixel rate — converting to a per-job dollar estimate would require guessing how many credits one generation actually consumes, which Modelglass does not track. Listed at face value instead of guessed.
  Midjourney v7 (midjourney, per_month @ USD 10): a flat subscription rate, not a per-generation charge — amortizing it into a per-job cost would require assuming a generation volume this tool has no basis for. Listed at face value instead of guessed.
  Midjourney v7 (midjourney, per_month @ USD 30): a flat subscription rate, not a per-generation charge — amortizing it into a per-job cost would require assuming a generation volume this tool has no basis for. Listed at face value instead of guessed.
  Midjourney v7 (midjourney, per_month @ USD 60): a flat subscription rate, not a per-generation charge — amortizing it into a per-job cost would require assuming a generation volume this tool has no basis for. Listed at face value instead of guessed.
  Midjourney v7 (midjourney, per_month @ USD 120): a flat subscription rate, not a per-generation charge — amortizing it into a per-job cost would require assuming a generation volume this tool has no basis for. Listed at face value instead of guessed.
  Runway Gen-4 Image (runway, per_credit @ USD 0.01): billed in provider credits, not a fixed per-image/per-megapixel rate — converting to a per-job dollar estimate would require guessing how many credits one generation actually consumes, which Modelglass does not track. Listed at face value instead of guessed.

  EXCLUDED (6) — didn't meet requirements:
  DALL·E 3 (openai/dall-e-3): capability_profile.photorealism: 'moderate' below required 'strong'
  FLUX.1 [schnell] (bfl/flux-1-schnell): capability_profile.photorealism: 'moderate' below required 'strong'
  Recraft V3 (recraft/recraft-v3): capability_profile.photorealism: 'moderate' below required 'strong'
  Stable Diffusion 3.5 Medium (stability-ai/stable-diffusion-3-5-medium): capability_profile.photorealism: 'moderate' below required 'strong'
  Stable Image Core (stability-ai/stable-image-core): no capability_profile in the registry (join_status: pricing_only) — cannot verify photorealism
  Stable Image Ultra (stability-ai/stable-image-ultra): no capability_profile in the registry (join_status: pricing_only) — cannot verify photorealism
────────────────────────────────────────────────────────────────────────────────────────────────

Exit code: 0 (offerings were ranked; exit is only non-zero when nothing meets the job's requirements at all).

Two things worth calling out about this specific run:

  • The cross-host spread here isn't about the same numeric rate meaning different things — FLUX 1.1 Pro is $0.04 at both Replicate (per_image) and fal (per_megapixel); FLUX.1 [dev] is $0.025 at both Replicate and Together, same pattern. At exactly 1 megapixel these would cost the same. The 57.6% spread only appears because this job's resolution (1536×1536 ≈ 2.36 MP) is above 1 MP, so the per_megapixel host's cost scales up while the per_image host's doesn't. The spread is resolution-dependent — a lower-resolution job could just as easily flip which host is cheaper.
  • FLUX.1 [schnell] is also offered at both Replicate and fal (same per_image/per_megapixel pattern as the other two), but doesn't appear in the cross-host section here — it's excluded from this specific job for photorealism: moderate (below this job's strong requirement) before cost comparison even runs.

What's not here (intentional)

  • No estimated per_credit/per_month conversion, ever. Leonardo Phoenix and Runway Gen-4 Image bill in provider credits; Midjourney v7 bills a flat monthly subscription across four tiers. None of these map to a fixed per-image or per-megapixel rate without guessing how many credits one generation actually consumes, or how many images a subscription month is meant to cover — Modelglass doesn't track either, so this tool reports the raw rate honestly instead of inventing a number that looks precise but isn't. This mirrors known-debt item #7 in the main modelglass repo (cross-unit price sorting is approximate) and turns a limitation into an explicit, permanent data-honesty stance rather than a gap to quietly paper over later.
  • No coding/science/agentic benchmark data — not applicable here; this tool only ever looks at the image modality's own capability_profile ratings, which the live feed does expose in full (unlike stack-watch's documented gap for the other capability verticals).
  • No LLM call — pure data + arithmetic. Cheapest example in this repo to run and to maintain.
  • No live/hosted demo — CLI example only, meant to be read and adapted.

Copyright © 2026 Modelglass Pty Ltd. Licensed under the MIT License — see LICENSE.

stack-watch

Price-drift and deprecation watchdog for a fixed set of models your product uses. Teams pick a model once and rarely re-evaluate — prices move (the live Modelglass feed keeps append-only, provenance-stamped history for exactly this reason), models get deprecated or superseded, and cheaper capable alternatives appear. Nothing else surfaces that automatically; it's the dataset's most defensible asset and easy to leave invisible. Requires a Starter or Pro Modelglass key — unlike the other two examples in this repo, there's no Free-tier mode, since Free's ~2-day pricing-history window isn't enough for meaningful drift detection.

Price-drift and deprecation watchdog for a fixed set of models your product uses. Teams pick a model once and rarely re-evaluate — prices move (the live Modelglass feed keeps append-only, provenance-stamped history for exactly this reason), models get deprecated or superseded, and cheaper capable alternatives appear. Nothing else surfaces that automatically; it's the dataset's most defensible asset and easy to leave invisible. Requires a Starter or Pro Modelglass key — unlike the other two examples in this repo, there's no Free-tier mode, since Free's ~2-day pricing-history window isn't enough for meaningful drift detection.

⚠️ Requires a Starter or Pro Modelglass key. Unlike the other two examples in this repo, there's no Free-tier mode here — see Requirements for why.


Why Free isn't enough (read before setup)

Free-plan keys only see pricing history from the last ~2 days (ADR 0004). Meaningful drift detection needs to look back further than that for any realistic check-in cadence — a daily or weekly cron run needs to see whatever changed since the last run, and a 2-day window doesn't reliably cover that gap. There's no honest "degraded but useful" middle ground the way the other two examples have one: a Free-tier attempt here would either silently miss real price changes that fell outside the window, or report "no drift" when the window just didn't reach back far enough — worse than not running at all.

stack-watch checks the calling key's actual tier via the modelglass_get_account MCP tool before attempting anything — not a guess based on the mg_free_/ mg_starter_/mg_pro_ key-string prefix, but the tier the account was actually provisioned at. On Free, it exits immediately with that explanation and a link to upgrade. It does not run in a caveated/degraded mode.


Requirements

  • Node.js 20+
  • A Modelglass Starter or Pro key — pricing (Starter: 12-month pricing-history window. Pro: full history, no window limit — the pricing-history gate, ADR 0004 internally)

Setup

git clone https://github.com/Modelglass/modelglass-router-examples.git
cd modelglass-router-examples
npm install
export MODELGLASS_API_KEY=<your-starter-or-pro-key>

Usage

Run against the built-in demo stack (one model per modality — openai/o4-mini, bfl/flux-1-1-pro, klingai/kling-2-1, stability-ai/stable-audio-3-0):

npm run watch

Run against your own stack:

node --import tsx/esm stack-watch/src/watch.ts my-stack.json

(Run from the repo root — dependencies and npm scripts are shared across all examples in this repo; see the root README for the full example index.)

Stack file format:

{
  "models": [
    "openai/o4-mini",
    "bfl/flux-1-1-pro",
    "klingai/kling-2-1",
    "stability-ai/stable-audio-3-0"
  ]
}

models is a flat, cross-modality list of Modelglass model ids — the same id format used across the whole feed (creator/model-name), regardless of whether the model is llm/image/video/audio, or also tracked by one of the capability-vertical registries (coding/science/agentic — see What's excluded for what that case does and doesn't cover).

Testing against a non-production API instance: set MODELGLASS_API_URL to override the base URL (default: https://modelglass-api.vercel.app) — e.g. to point at a local pnpm dev:api instance from the main modelglass repo. Not needed for normal use.

Drop it into cron:

## crontab: check daily at 9am, alert only when there's something actionable
0 9 * * * cd /path/to/modelglass-router-examples && MODELGLASS_API_KEY=... npm run watch -- my-stack.json || echo "stack-watch found drift" | mail -s "Stack alert" you@example.com

Exit code is 0 when the stack is unchanged (or on the first run — see below), non-zero whenever there's something actionable, so it drops straight into cron/CI without extra plumbing.


How it works

  1. Checks the key's tier via the modelglass_get_account MCP tool — exits immediately on Free (see above).
  2. Fetches every model in the stack via one GET /v1/models call (the bulk response has the same per-model shape as GET /v1/models/:modelId, so one fetch covers a cross-modality stack regardless of size).
  3. Compares against the prior run's snapshot (logs/stack-snapshot.json, gitignored, same pattern as the cost-aware router's logs/routing-log.jsonl) across three axes:
    • Price — current active price per tier vs. the snapshot's recorded price, citing the new effective_from and source.url.
    • Lifecyclestatus (ga → deprecated/retired) and generation (current → previous).
    • Capability — any capability_profile dimension whose rating changed (e.g. coding: strong → good).
  4. For each stack model rated "strong" on any dimension, checks the modelglass_get_competitors MCP tool for cheaper alternatives, then fetches each cheaper candidate's own capability_profile and suggests a switch only when the candidate matches "strong" on the same dimension — citing the specific fields (capability_profile.<dimension>, tiers.pricing), not just "cheaper."
  5. First run: no prior snapshot exists, so there's nothing to diff against. Reports the current state as a baseline and says so explicitly — it does not report every model's current price/status/rating as "new." Writes the snapshot either way, so the next run has something to compare against.
  6. Writes the new snapshot at the end of every run (baseline or not).

Worked example — tier gate (real, verified run)

This is the actual, live output of npm run watch -- --demo against a real Free-plan key, run 2026-07-06:

> node --import tsx/esm stack-watch/src/watch.ts --demo

stack-watch requires a Starter or Pro Modelglass key — this key is on the Free plan.

Why: meaningful drift detection needs to look back further than Free's ~2-day
pricing-history window covers for any realistic check-in cadence (daily/weekly
cron). There's no useful degraded mode here, unlike the other examples in this
repo — a Free-tier run would either miss real price changes silently or report
"no drift" when the window just didn't reach back far enough, which is worse
than not running at all.

Upgrade at https://modelglass.com.au/signup (Starter: 12-month pricing-history
window. Pro: full history, no window limit).

Exit code: 1.

Worked example — drift report (real, verified run against a local dev instance)

No Starter/Pro key existed against production while building this, so this section was verified a different way: a local instance of the Modelglass API (the exact same packages/api code, run via pnpm dev:api in the main modelglass repo), authenticated with mg_starter_devkey — a fixed, non-secret dev key that repo seeds automatically for local development only (confirmed no-op in production, since it only seeds when UPSTASH_REDIS_REST_URL is unset). GET /v1/keys against that instance returns "tier": "starter" for it, same real signal requireStarterOrPro() checks in production — so the tier gate itself, not just the report logic downstream of it, is exercised for real here, just against a local server instead of modelglass-api.vercel.app. Pointed at it via the optional MODELGLASS_API_URL override (see Usage); everything else — fetch, tier check, snapshot diffing, report rendering, exit code — is the same code path production would run.

Two models already in that repo's own local registry (bfl/flux-1-1-pro, stability-ai/stable-image-ultra) stood in for the stack. First run recorded a real baseline against that (unmodified) local data. To produce a genuine second-run diff without touching any source-of- truth registry data, the tool's own local snapshot file (logs/stack-snapshot.json — gitignored state, not registry data) was then edited to record different prior values — the same effect as if the tool had genuinely run once, weeks earlier, and the registry had moved on since. The second run below diffed those edited prior values against the real, unmodified current data from the local API — the drift computation, every number in the output, and the exit code are all genuinely produced by the tool, not hand-typed:

> node --import tsx/esm stack-watch/src/watch.ts local-dev-stack.json

Fetching 2 model(s) from http://localhost:8787 ...

────────────────────────────────────────────────────────────────────────────────
  stack-watch
────────────────────────────────────────────────────────────────────────────────

  PRICE CHANGES (1):
  FLUX 1.1 [pro] (replicate, default): $0.035/image → $0.04/image on 2026-06-09 — source: https://replicate.com/black-forest-labs/flux-1.1-pro

  LIFECYCLE CHANGES (1):
  Stable Image Ultra (stability-ai): status deprecated → ga

  CAPABILITY RATING CHANGES (1):
  FLUX 1.1 [pro]: photorealism good → strong

────────────────────────────────────────────────────────────────────────────────

Exit code: 1 (there's something actionable) — verified.

No switch suggestion appeared, and that's the honest result, not an omission: GET /v1/models/bfl%2Fflux-1-1-pro/competitors was checked for real against the same local instance, and every listed competitor was the same price or more expensive — computeSwitchSuggestions only surfaces a candidate when one is genuinely cheaper, so it correctly stayed silent here rather than manufacturing a suggestion.

On a run with nothing to report, the tool prints No drift since last run — stack unchanged (N model(s) checked). and exits 0 (covered by the computeDrift "no drift" test in src/lib.test.ts, not separately re-verified live).

What's still unverified: this confirms the tier gate and the full drift/report pipeline work end-to-end against a real Starter-tier account — just not against modelglass-api.vercel.app itself, and not yet against Pro, against a stack spanning llm/video/audio (the demo stack does, but wasn't used for this run — see below), or against a longer-lived snapshot across a real time gap. Scott can confirm those once he has a production Starter/Pro key.


What's not here (intentional)

  • Coding/science/agentic benchmark-score drift — SCO-166's original scope named coding/science/agentic alongside llm/image/video/audio as modalities a stack can reference. A model id can absolutely belong to one of those capability verticals — but the paid API/MCP feed doesn't expose SWE-bench/GAIA/HLE benchmark data at all, for any modality; that data lives only in the sibling repos' own artifacts (modelglass-coding/-science/-agentic), consumed exclusively by the modelglass.com.au website build, never surfaced through /v1/ or the MCP tools. So a coding-focused stack entry still gets full pricing/lifecycle/ capability_profile.coding (qualitative rating) drift — the same as any other model — just not a numeric benchmark-score delta, because the live feed a real customer calls has no such field to diff. (This mirrors cost-aware-vscode-router's own approach: it extracts a SWE-bench Verified percentage from capability_profile.coding.notes free text via regex, when present, rather than from a dedicated benchmark endpoint — there isn't one.) Re-confirmed live against production on 2026-07-06: GET /v1/benchmarks still returns only the seven image-modality ontology benchmarks (clip-score, dpg-bench, fid, geneval, hpsv2, pickscore, t2i-compbench); openai/o4-mini's knowledge.benchmarks field is null; its SWE-bench Verified score (68.1%) only exists as free text inside capability_profile.coding.notes, exactly the shape cost-aware-vscode-router's regex already handles.
  • Provider-level outage/latency monitoring — this watches the registry's pricing/lifecycle/capability data, not live API uptime for each provider.
  • Automatic switching — suggestions are grounded and cited, but nothing here calls a provider API or changes your actual routing; that decision stays with you.
  • Multi-account/team snapshot sharing — the snapshot is a local gitignored file, same as the cost-aware router's log. No shared state.
  • Hosted/live demo — CLI example only, meant to be read and adapted.

Copyright © 2026 Modelglass Pty Ltd. Licensed under the MIT License — see LICENSE.

switch-check

Grounded migration diff for a model switch you're considering. stack-watch and the news feed tell you a switch might be worth it — nothing evaluates one. switch-check takes --from and --to model ids and prints what the live Modelglass feed can actually prove about the move: the unit-matched price delta, how stable each price has been (from the append-only, provenance-stamped history — the part no scraper of current pricing pages can answer), the capability ratings you'd gain or lose, billing-unit changes that reshape the cost curve, and lifecycle status in both directions. Every line cites the feed field it came from, and it deliberately stops short of a recommendation — evidence, not a verdict. Works on every plan tier, including Free.

Grounded migration diff for a model switch you're considering. stack-watch and the news feed tell you a switch might be worth it — nothing evaluates one. switch-check takes --from and --to model ids and prints what the live Modelglass feed can actually prove about the move: the unit-matched price delta, how stable each price has been (from the append-only, provenance-stamped history — the part no scraper of current pricing pages can answer), the capability ratings you'd gain or lose, billing-unit changes that reshape the cost curve, and lifecycle status in both directions. Every line cites the feed field it came from, and it deliberately stops short of a recommendation — evidence, not a verdict. Works on every plan tier, including Free.

Works on a Free key. The current-price delta, capability diff, unit warnings, and lifecycle checks are all computed from data every tier sees in full. The price-stability section is computed from whatever slice of the pricing history your plan's window exposes (ADR 0004 internally), and says so in the output: on Free it states, for that specific run, exactly what Starter (12-month window) and Pro (full history) would add.


Requirements

  • Node.js 20+
  • A Modelglass API key on any tierget a free one. (Free: ≈2-day pricing-history window. Starter: 12 months. Pro: full history. The current price is always visible on every tier.)

Setup

git clone https://github.com/Modelglass/modelglass-router-examples.git
cd modelglass-router-examples
npm install
export MODELGLASS_API_KEY=<your-key>

Usage

Diff a specific pair:

npm run check -- --from bfl/flux-1-1-pro --to bfl/flux-1-dev

Or give only --from — candidates then come from the feed's own modelglass_get_competitors MCP tool (the registry's curated closest_competitors), and each candidate that resolves to a distinct model in the feed gets the full diff. Candidates that don't resolve (or that are the same model on a different host — a hosting decision, not a migration) are listed with the reason, not silently dropped:

npm run check -- --from bfl/flux-1-1-pro

Model ids are the standard cross-modality feed format (creator/model-name) — llm, image, video, and audio models all work.

(Run from the repo root — dependencies and npm scripts are shared across all examples in this repo; see the root README for the full example index.)

Testing against a non-production API instance: set MODELGLASS_API_URL to override the base URL (default: https://modelglass-api.vercel.app) — e.g. to point at a local pnpm dev:api instance from the main modelglass repo. Not needed for normal use.

Exit codes: 0 whenever the diff was produced — a warning in the output is evidence for you to weigh, not a failure state (this tool renders no verdict, so it doesn't gate CI the way stack-watch's drift exit code does). Non-zero only on real errors (unknown model id, missing key, API failure).


How it works

  1. Looks up the calling key's actual plan tier via the modelglass_get_account MCP tool — the same real account-record signal stack-watch checks. Unlike stack-watch this is not a gate: every tier runs. The tier decides how the price-stability section is framed — which history window the numbers were computed under, and (on Free) what a wider window would add to this specific run.
  2. Fetches the whole feed via one GET /v1/models?generation=all call — generation=all because a migration diff must be able to say "the model you're moving TO is previous-gen," which requires previous-gen models to be in the pool at all.
  3. Prints four sections, every claim citing its field:
    • Price — for every billing unit priced on both sides, the cheapest current price on each, compared apples-to-apples (the same same-unit-only rule the API's own competitor ranking uses). Then stability, from the append-only history: how long each current price has been in force (effective_from), and — where the plan window shows it — what it changed from, in which direction, and per which source. A 40%-cheaper price that was cut three weeks ago and a 40%-cheaper price that's held for a year are different facts, and only time-versioned history can tell them apart.
    • Capability diff — per-dimension capability_profile comparison across the union of both models' rated dimensions: lose / gain / same, with dimensions rated on only one side reported as cannot verify — never assumed either way.
    • Billing units — units priced on one side only are never force-converted (image-batch-coster's honest-unit discipline); instead the cost-curve change is named, e.g. per_image → per_megapixel means cost starts scaling with resolution.
    • Lifecyclestatus and generation in both directions: non-ga or previous-gen on the TO side is a warning (you'd be migrating onto a model already on its way out); the same on the FROM side is informational context (it explains the pressure to switch).
  4. Ends every diff with the same line: evidence, not a verdict. Nothing here recommends, scores, or ranks the migration — that call stays with you.

Worked example — Free key (real, verified run against production)

Actual, live output of the command below against modelglass-api.vercel.app, authenticated with a Free-plan key provisioned that day, run 2026-07-10:

> npm run check -- --from bfl/flux-1-1-pro --to bfl/flux-1-dev

Fetching feed from https://modelglass-api.vercel.app ...
Plan tier (via GET /v1/keys): free

────────────────────────────────────────────────────────────────────────────────────────────────
  switch-check — bfl/flux-1-1-pro → bfl/flux-1-dev
  (FLUX 1.1 [pro] → FLUX.1 [dev])
────────────────────────────────────────────────────────────────────────────────────────────────

  1. PRICE — current, unit-matched (fields: tiers.pricing[].amount, .unit)
  per_image: $0.04/image (replicate) → $0.025/image (replicate)  -37.5% cheaper
  per_megapixel: $0.04/megapixel (fal) → $0.025/megapixel (together)  -37.5% cheaper

  PRICE STABILITY — window: Free plan — ≈2-day window (current price always visible)
  (fields: tiers.pricing[].effective_from, .effective_to, .source.url)
  [from] FLUX 1.1 [pro]:
    fal/default: $0.04/megapixel since 2026-06-09 (31 days) — no earlier entry in window — source: https://fal.ai/models/fal-ai/flux-pro/v1.1
    replicate/default: $0.04/image since 2026-06-09 (31 days) — no earlier entry in window — source: https://replicate.com/black-forest-labs/flux-1.1-pro
  [to  ] FLUX.1 [dev]:
    replicate/default: $0.025/image since 2026-06-10 (30 days) — no earlier entry in window — source: https://replicate.com/pricing
    together/default: $0.025/megapixel since 2026-06-10 (30 days) — no earlier entry in window — source: https://docs.together.ai/docs/serverless-models

  This key's ≈2-day Free window shows each price's current entry only (unless it changed
  within the window). The current entry keeps its real effective_from — so the price AGES
  above are honest — but anything a price changed FROM is outside the window.

  On this exact run, Starter (12-month window) would show every price change since
  2025-07-10 for FLUX 1.1 [pro] and FLUX.1 [dev] (if any) — including whether either
  current price is a recent cut or a long-standing rate. Pro removes the window entirely:
  the full append-only history, every entry with effective_from + source provenance.
  Upgrade: https://modelglass.com.au/signup — fields unlocked: tiers.pricing[] (earlier entries)

  2. CAPABILITY DIFF (fields: knowledge.capability_profile[].dimension, .rating)
  LOSE  resolution-ceiling: strong → moderate
  LOSE  text-rendering: strong → moderate
  same  artistic-range: strong; compositional-accuracy: strong; inference-speed: moderate; photorealism: strong; prompt-adherence: strong

  3. BILLING UNITS (fields: tiers.pricing[].unit)
  No cost-curve change: every unit priced on one side is also priced on the other (per_image, per_megapixel). Deltas in section 1 are all same-unit.

  4. LIFECYCLE (fields: model.status, model.generation)
  Both directions clear: every offering on both sides is status=ga, generation=current.

────────────────────────────────────────────────────────────────────────────────────────────────
  Evidence, not a verdict — every line above cites the feed field it came from;
  whether to migrate stays your call.
────────────────────────────────────────────────────────────────────────────────────────────────

Exit code: 0.

Worked example — the same pair on Pro (real, verified run against a local dev instance)

No Pro key existed against production while building this, so — same approach as stack-watch's paid-tier worked example — this run used a local instance of the Modelglass API (the exact same packages/api code, run via pnpm dev:api in the main modelglass repo), authenticated with mg_pro_devkey, a fixed non-secret dev key that repo seeds for local development only (a no-op in production, where the KV key store takes over). GET /v1/keys against that instance returns "tier": "pro" — the same real signal the framing logic reads in production. Same command, same pair; the only line that changes in section 1's stability block is the one the Free window was hiding:

  [to  ] FLUX.1 [dev]:
    replicate/default: $0.025/image since 2026-06-10 (30 days) — CUT from $0.03/image (-16.7%) — source: https://replicate.com/pricing

That's the whole pitch in one line: on Free, FLUX.1 [dev] simply looks 37.5% cheaper. With history access, the feed shows that price is a one-month-old cut — recent enough that "is this a teaser rate?" is a fair question to ask before migrating, and answerable only because the registry keeps append-only, provenance-stamped history (the Pro view also shows the superseded $0.03 entry's own source URL, so the cut itself is verifiable, not asserted).


What's not here (intentional)

  • A verdict — no score, no recommendation, no "you should switch." The output is the evidence a migration decision needs, with fields cited so you can check every claim; weighing it is your job (the same propose-don't-decide stance the Modelglass ingestion agents apply to registry writes, applied to output tone).
  • Cost-per-job arithmetic — this diffs per-unit rates; sizing a specific batch job's cost across units and hosts is image-batch-coster's whole job.
  • Drift over time — this is a point-in-time evaluation of one candidate switch; watching a stack for changes between runs is stack-watch's job. Detect (stack-watch) → evaluate (switch-check) → act (you).
  • Quality benchmarking — the capability diff reports the registry's qualitative capability_profile ratings; it does not run generations or fetch benchmark scores (the paid feed exposes no numeric benchmark fields — see stack-watch's README for the details).
  • Automatic switching — nothing here calls a provider API or changes your routing.
  • Hosted/live demo — CLI example only, meant to be read and adapted.

Copyright © 2026 Modelglass Pty Ltd. Licensed under the MIT License — see LICENSE.