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.3.0

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.

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 →

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.3.0 Install →

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 ↗

AV Prompt Refiner

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

AV Prompt Refiner

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. Where that one calls the plain REST feed (GET /v1/models) for LLM routing decisions, this one calls the Modelglass HTTP MCP endpoint directly over JSON-RPC — the tool surface an agent or IDE integration would actually use — via the modelglass_get_model tool. 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.

Cost-Aware VS Code Router

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.

Cost-Aware VS Code Router

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 GET /v1/models?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.

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.

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.

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.

shot-plan-compiler

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.

shot-plan-compiler

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.


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

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.

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.

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.

⚠️ 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 GET /v1/keys 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 GET /v1/keys — 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 GET /v1/models/:modelId/competitors 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.

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.

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 GET /v1/models/:modelId/competitors list (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 GET /v1/keys — 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.