Files
atelier/sidecar/catalogue/shipped.ts
T
pastilhasandClaude Opus 5 d7f3223b6a qwen-local: the first provider that runs on our own GPU
sd-server (stable-diffusion.cpp, Vulkan) serving Qwen-Image 2.1 on this
machine. It already speaks OpenAI's image API, which is the vocabulary
`ImageRequest` is written in, so there is no translation layer — only the parts
that are true of a local GPU and not of a hosted one.

── keyless is a category, not an exception ──

`credentials.ts` decided a provider was usable by asking whether a key existed,
so a service that takes none read as unconfigured forever. The fix is an
adapter declaring `keyless`, not a dummy secret: a dummy would record a
credential that does not exist and make the secret store lie about what is
configured, and that store is the one place meant to be the truth about them.

A keyless adapter never reaches `keyFor` at all. That function's own comment
says why it uses `getSecret` rather than `getKey` — so a missing FOREIGN
credential is never minted — and a provider with no credential by design has no
business in that path.

whisper, OCR and TTS already run on this machine, so this will not be the last.

── measured, not assumed ──

The integration doc said "tens of seconds, not two". The server's own log says
512x512 completes in ~185s and 1024x1024 samples at ~41.4s/step over 20 steps,
about 14 minutes. And it serialises strictly: a second caller waits for the
first to finish entirely, so a queued 512 can wait 17 minutes before a byte
comes back.

So: 512 is the default and the substitute for `auto`, 1024 is offered with the
cost in its label, and `timeout: false` is a requirement rather than caution —
every default anyone would reach for is far under 17 minutes. The caller's
`signal` still aborts, because a user cancelling is not a client giving up.

── the model id moved, because only it could ──

`GET /v1/models` returns `sd-cpp-local`, hardcoded in sd-server with no
override. The catalogue now agrees with the server and carries "Qwen-Image 2.1
(local)" in the label, so requests name a model the server knows while the UI
still says something meaningful.

── not shipped: edit() ──

Same GPU, same queue. It doubles the ways generation can be blocked while the
generate path is unproven in anger, and `ImageAdapter` makes absence the way to
say so — the route answers 400 instead of the queue finding out late.

Verified: /v1/models answers, provider statuses report qwen-local as
configured/keyless/usable, and the shipped entry survives the merge with the
stored catalogue.json. NOT verified end to end — a generation is occupying the
GPU and I left it alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: de3ed4e6-f88a-429c-94e6-a760336ba83b
2026-09-21 13:00:05 +01:00

132 lines
5.2 KiB
TypeScript

import { CATALOGUE_REVISION, type Catalogue } from './types';
/**
* The catalogue as it ships with the plugin — the floor a refresh builds on, and the whole of it when
* no refresh has ever run or every refresh has failed.
*
* Written by hand and reviewed in git, because none of it is fetchable: `GET /v1/models` returns ids,
* an owner and a timestamp, and says nothing about price, sizes, transparency, or whether a model is
* one you would still choose. That is the entire reason this file exists rather than a nightly fetch.
*
* The prices are USD per million tokens, from OpenAI's published pricing, and three of the five models
* have none — deliberately. `PRICING` in providers/openai.ts had the same gap and the same reason: a
* guessed price produces a number that looks authoritative and sums into a wrong total.
*/
export const SHIPPED_CATALOGUE: Catalogue = {
revision: CATALOGUE_REVISION,
providers: [
{
name: 'openai',
label: 'OpenAI',
envVar: 'OPENAI_API_KEY',
defaultModel: 'gpt-image-1',
models: [
{
id: 'gpt-image-1.5',
label: 'GPT Image 1.5',
kind: 'image',
sizes: ['auto', '1024x1024', '1536x1024', '1024x1536'],
qualities: ['auto', 'low', 'medium', 'high'],
transparency: true,
edit: true,
pricing: { unit: 'token', textInput: 5, imageInput: 8, imageOutput: 32 },
status: 'current',
source: 'shipped',
},
{
id: 'gpt-image-1',
label: 'GPT Image 1',
kind: 'image',
sizes: ['auto', '1024x1024', '1536x1024', '1024x1536'],
qualities: ['auto', 'low', 'medium', 'high'],
transparency: true,
edit: true,
pricing: { unit: 'token', textInput: 5, imageInput: 10, imageOutput: 40 },
status: 'current',
source: 'shipped',
},
{
id: 'gpt-image-1-mini',
label: 'GPT Image 1 mini',
kind: 'image',
sizes: ['auto', '1024x1024', '1536x1024', '1024x1536'],
qualities: ['auto', 'low', 'medium', 'high'],
transparency: true,
edit: true,
pricing: { unit: 'token', textInput: 2, imageInput: 2.5, imageOutput: 8 },
status: 'current',
source: 'shipped',
},
{
// No transparency and a different size set — which is exactly what the form was getting
// wrong by offering one hard-coded list of sizes and qualities for every model.
id: 'dall-e-3',
label: 'DALL·E 3',
kind: 'image',
sizes: ['1024x1024', '1792x1024', '1024x1792'],
qualities: ['standard', 'hd'],
transparency: false,
edit: false,
status: 'current',
source: 'shipped',
},
{
id: 'dall-e-2',
label: 'DALL·E 2',
kind: 'image',
sizes: ['256x256', '512x512', '1024x1024'],
qualities: ['standard'],
transparency: false,
edit: true,
status: 'current',
source: 'shipped',
},
],
},
{
/**
* The first LOCAL provider: sd-server on this machine's GPU, not a vendor.
*
* No `envVar`, because there is no key and never will be — see `keyless` on the adapter. It is
* `configured` by existing.
*
* The id is `sd-cpp-local` and not `qwen-image-2.1` because that is what `GET /v1/models`
* returns, hardcoded in sd-server with no override. The catalogue is the only side that can
* move, so it moves: the id agrees with the server, and the LABEL carries what the thing
* actually is.
*/
name: 'qwen-local',
label: 'Qwen-Image (local)',
defaultModel: 'sd-cpp-local',
models: [
{
id: 'sd-cpp-local',
/**
* The timings are in the label because there is nowhere else for them, and a user choosing
* 1024 should know before they wait rather than after. Measured from the server's own log on
* this machine: 512 completes in ~185s; 1024 samples at ~41.4s/step over 20 steps.
*
* A `note` field on CatalogueModel would be the cleaner home for this. Not added on a guess —
* it needs the UI to render it, and a field nothing displays is worse than a long label.
*/
label: 'Qwen-Image 2.1 (local) — 512 takes ~3 min, 1024 ~14 min',
kind: 'image',
/**
* 512 first, and the adapter substitutes it for `auto` or for nothing. 1024 is offered
* because it is genuinely better and sometimes worth the wait — but a default nobody chose
* should not spend a quarter of an hour of the only GPU there is.
*/
sizes: ['512x512', '768x768', '1024x1024'],
transparency: false,
/** No `edit()` in this version — same GPU, same queue, and the generate path is unproven. */
edit: false,
/** Zero is the true answer, not a missing one: it runs on hardware we already own. */
pricing: { unit: 'image', perImage: 0 },
status: 'current',
source: 'shipped',
},
],
},
],
};