Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions skills/openrouter-images/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ Both `generate.ts` and `edit.ts` accept the same flags. Only pass parameters the
| `--output-compression <n>` | Compression 0–100 for webp/jpeg | Model default |
| `--n <count>` | Number of images to generate (1–10, provider permitting) | 1 |
| `--seed <int>` | Seed for deterministic generation (where supported) | Random |
| `--provider <json>` | Provider routing preferences (`only`, `order`, `ignore`, `sort`, `allow_fallbacks`) | None |
| `--provider-options <json>` | Provider-specific passthrough, keyed by `provider_slug` | None |

`--provider-options` takes a JSON object keyed by provider slug, using keys from that endpoint's `allowed_passthrough_parameters`:
Expand All @@ -113,6 +114,28 @@ cd <skill-path>/scripts && npx tsx generate.ts "a dramatic portrait" \
--provider-options '{"black-forest-labs": {"steps": 40, "guidance": 3}}'
```

### Provider Routing

When a model has multiple providers, `--provider` controls which endpoints can serve the request via the request's `provider` object:

| Field | Meaning |
|---|---|
| `only` | Allow only these provider slugs |
| `order` | Try providers in this order |
| `ignore` | Exclude these provider slugs |
| `sort` | Sort eligible endpoints by `price`, `throughput`, or `latency` |
| `allow_fallbacks` | When `false`, stop after the primary provider instead of trying another |

Use the `provider_slug` values from `discover.ts <model>` as slugs:

```bash
cd <skill-path>/scripts && npx tsx generate.ts "a red panda astronaut" \
--model google/gemini-3.1-flash-image-preview \
--provider '{"only": ["google-ai-studio"], "allow_fallbacks": false}'
```

The response's `X-Provider-Name` header reports which provider served the request.

## Output Format

### generate.ts
Expand Down
2 changes: 1 addition & 1 deletion skills/openrouter-images/scripts/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ if (!imagePath || !prompt) {
" [--aspect-ratio <r>] [--resolution <512|1K|2K|4K>] [--size <s>] [--n <count>]\n" +
" [--quality <auto|low|medium|high>] [--output-format <png|jpeg|webp|svg>]\n" +
" [--background <auto|transparent|opaque>] [--output-compression <n>] [--seed <int>]\n" +
" [--provider-options '<json>']\n\n" +
" [--provider '<json>'] [--provider-options '<json>']\n\n" +
"Editing sends the source image as an image-to-image reference, so pick a model\n" +
"whose input_modalities include \"image\". Run discover.ts <model> to check."
);
Expand Down
2 changes: 1 addition & 1 deletion skills/openrouter-images/scripts/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ if (!prompt) {
" [--aspect-ratio <r>] [--resolution <512|1K|2K|4K>] [--size <s>] [--n <count>]\n" +
" [--quality <auto|low|medium|high>] [--output-format <png|jpeg|webp|svg>]\n" +
" [--background <auto|transparent|opaque>] [--output-compression <n>] [--seed <int>]\n" +
" [--provider-options '<json>']\n\n" +
" [--provider '<json>'] [--provider-options '<json>']\n\n" +
"Run discover.ts <model> first to see which parameters a model accepts."
);
process.exit(1);
Expand Down
24 changes: 19 additions & 5 deletions skills/openrouter-images/scripts/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,25 +190,39 @@ export function buildImageParams(args: Map<string, string | true>): Record<strin
setInt("seed", "seed");
setInt("output-compression", "output_compression");

const provider: Record<string, unknown> = {};

const providerRouting = args.get("provider");
if (typeof providerRouting === "string") {
Object.assign(
provider,
parseJsonObject(providerRouting, "--provider must be a JSON object of routing preferences.")
);
}

const providerOptions = args.get("provider-options");
if (typeof providerOptions === "string") {
const parsed = parseProviderOptions(providerOptions);
params.provider = { options: parsed };
provider.options = parseJsonObject(
providerOptions,
"--provider-options must be a JSON object keyed by provider slug."
);
}

if (Object.keys(provider).length > 0) params.provider = provider;

return params;
}

function parseProviderOptions(raw: string): Record<string, unknown> {
function parseJsonObject(raw: string, errorMessage: string): Record<string, unknown> {
let parsed: unknown;
try {
parsed = JSON.parse(raw);
} catch {
console.error("Error: --provider-options must be a JSON object keyed by provider slug.");
console.error(`Error: ${errorMessage}`);
process.exit(1);
}
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
console.error("Error: --provider-options must be a JSON object keyed by provider slug.");
console.error(`Error: ${errorMessage}`);
process.exit(1);
}
return parsed as Record<string, unknown>;
Expand Down