Skip to content

Live model catalogue from OpenRouter + per-generation cost - #6

Open
pallaoro wants to merge 1 commit into
mainfrom
model-costs
Open

Live model catalogue from OpenRouter + per-generation cost#6
pallaoro wants to merge 1 commit into
mainfrom
model-costs

Conversation

@pallaoro

@pallaoro pallaoro commented Sep 3, 2026

Copy link
Copy Markdown
Member

Why

The model picker was a hardcoded array, and it had rotted. Of its 12 OpenRouter entries, 6 no longer exist upstream — checked against the full 424-model catalogue:

Offered in the picker On OpenRouter
black-forest-labs/flux.2-max gone
black-forest-labs/flux.2-klein-4b gone
sourceful/riverflow-v2-fast gone
bytedance-seed/seedream-4.5 gone
openai/gpt-image-1 gone
openai/gpt-image-2-2026-04-21 gone

No FLUX, SeedDream or gpt-image model is on OpenRouter at all any more. Picking one could only fail at generate time, and the README advertised two of them as headline features.

Separately, the app showed no cost anywhere. For a canvas tool that is the sharpest gap: the recurring complaint about hosted canvas tools (Krea, Flora, Figma Weave) is that you cannot tell what a run will cost until it already has. Adjacent models here differ by 15×gpt-5-image-mini to gemini-3-pro-image — with nothing in the UI to say so.

Both problems have one root: the catalogue was hardcoded. /api/v1/models is the upstream source of truth and carries pricing, so listing live fixes the rot and pays for the cost display in the same request.

What changed

  • /api/models is live. Filtered to image-output models, price attached. A verified static list remains as a fallback only if the fetch fails, so the picker is never empty. Withdrawn models now disappear on their own.
  • Real cost per generation. OpenRouter calls send usage: { include: true }; the cost is returned from /api/generate, persisted on generations.cost_usd, and totalled per run and per workflow.
  • Relative price in both pickers$$$$$ against the cheapest available model.
  • Unit-aware display. Credits when deployed on Clawnify, dollars when self-hosted.

Two deliberate judgement calls

Relative tiers, not a per-image price. Upstream prices per output image token, and the token count varies by model and resolution — Gemini 3.1 Flash Image billed 1120 image tokens at 0.00006 = $0.0672. A "$0.04 per image" label would be invented precision, so the picker compares models (the token price is directly comparable) and the actual figure is only ever shown after a real generation reports it.

Credits vs dollars. On Clawnify the app receives an opaque OPENROUTER_API_KEY and cannot tell a managed sub-key (billed in credits) from an org's own key. CLAWNIFY_TOKEN is injected into every app deployed on Clawnify and absent from a self-hosted clone, so it is the discriminator. Credits are never rendered beside a dollar figure. Known edge, documented in pricing.ts: an org that brings its own OpenRouter key and deploys on Clawnify sees credits — still the correct unit for a Clawnify surface, and rare.

The bug that only a live call would have caught

Reading usage.cost would have shown $0.00 for most generations. On a BYOK provider key OpenRouter bills 0 and reports the real charge under cost_details.upstream_inference_cost:

usage: { cost: 0, is_byok: true,
         cost_details: { upstream_inference_cost: 0.0336 } }

extractCostUsd falls through to the upstream figure.

Two pre-existing bugs fixed

Both surfaced while testing this end to end, and both blocked verifying it:

  1. Every database route was broken. @clawnify/db was pinned at ^0.2.0 while @clawnify/app depends on ^0.4.1, so two copies of the module loaded — createApp initialised one, every route read from the other. All DB routes returned @clawnify/db: not initialized — call initDB(env) first. Aligning the pin fixes it.
  2. Quick Generate never saved. It sent workflow_id: 0 as a number against a z.string() schema, so every save returned 400.

Verified

Against the real OpenRouter API with a live key, not just a green build:

  • /api/models returns 9 live OpenRouter models with prices; the 6 dead IDs are gone
  • A real generation returns costUsd: 0.033601, persists, and reads back
  • Run totals correct: $0.0336 + $0.0672$0.10, shown as "Last run $0.10 (2 images)"
  • Credits path with CLAWNIFY_TOKEN set: 7 and 14 credits per image, 21 credits total, no dollars alongside
  • Tiers read correctly: gpt-5-image-mini $gemini-3-pro-image $$$$
  • pnpm build clean

…generation costs

The model picker was a hardcoded array. Half its OpenRouter entries no longer
exist upstream — flux.2-max, flux.2-klein-4b, riverflow-v2-fast, seedream-4.5,
gpt-image-1 and gpt-image-2-2026-04-21 are all absent from the full 424-model
catalogue, so picking one could only fail at generate time. The README
advertised two of them as headline features.

List the catalogue live instead. `/api/v1/models` is the upstream source of
truth and already carries pricing, so the same request that fixes the rot also
pays for cost visibility — the loudest complaint about hosted canvas tools
being that you cannot tell what a run will cost until it has cost it.

- `/api/models` returns the live image-output models with their price, falling
  back to a verified static list only if the fetch fails, so the picker is
  never empty.
- OpenRouter generate calls now send `usage: { include: true }` and the cost is
  returned, persisted on `generations.cost_usd`, and totalled per run and per
  workflow.
- Models are tagged `$`–`$$$$` by price relative to the cheapest available.
  Not an absolute per-image figure on purpose: upstream prices per output image
  *token* and the token count varies by model and resolution, so a "$0.04 per
  image" label would be invented precision. The token price is comparable
  across models, which is what choosing between them needs.
- Cost renders in credits when the app is deployed on Clawnify (detected via
  the injected CLAWNIFY_TOKEN) and in dollars when self-hosted, since a
  self-hoster pays OpenRouter directly. Credits are never shown next to a
  dollar figure.

Reading `usage.cost` alone would have shown $0.00 for most generations: on a
BYOK provider key OpenRouter bills 0 and reports the real charge under
`cost_details.upstream_inference_cost`. Verified live — a Gemini image call
returns cost 0 with an upstream cost of $0.0336.

Also fixes two pre-existing bugs found while testing this end to end:

- `@clawnify/db` was pinned at ^0.2.0 while `@clawnify/app` depends on ^0.4.1,
  so two copies of the module loaded: createApp initialised one and every route
  read from the other. Every database route in the template returned
  "not initialized — call initDB(env) first". Aligning the pin fixes it.
- Quick Generate sent `workflow_id: 0` as a number against a `z.string()`
  schema, so saving a quick generation always returned 400.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant