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
132 lines
5.2 KiB
TypeScript
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',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|