ottoport 1.15.1 → 1.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ottoport",
3
3
  "description": "One API for every model. Call chat, image, video, speech and music models through the OttoPort gateway \u2014 as MCP tools, slash commands, or the bundled CLI.",
4
- "version": "1.15.1",
4
+ "version": "1.15.2",
5
5
  "author": {
6
6
  "name": "LITBOX LLC",
7
7
  "email": "support@ottoport.ai"
package/README.md CHANGED
@@ -29,7 +29,7 @@ ottoport video "<prompt>" [--model kling-3.0] [--duration 5] [--resolution 1080p
29
29
  [--webhook-url https://…] [--no-wait]
30
30
  ottoport webhook status|retry <job-id> # delivery attempts / redeliver now
31
31
  ottoport speech "<text>" [--model gpt-4o-mini-tts] [--voice alloy] [--out speech.mp3]
32
- ottoport music "<prompt>" [--model suno-v5] [--duration 15] [--out song.mp3]
32
+ ottoport music "<prompt>" [--model suno-v6] [--duration 15] [--out song.mp3]
33
33
  ottoport music --model mirelo-sfx-2.0 --video <url> [--duration 8] # soundtrack for a video
34
34
 
35
35
  ottoport install <host> # wire the MCP server into an agent
@@ -118,6 +118,12 @@ raster, from a prompt alone: `output_format: "gif"` is an animation (`frame_coun
118
118
  request. A GIF picks its own frame size, so `size`, `resolution` and
119
119
  `aspect_ratio` do not apply to it.
120
120
 
121
+ ```sh
122
+ ottoport image "a paper boat bobbing on water, seamless loop" --format gif --wait --out boat.gif
123
+ ```
124
+
125
+ Asking for either format with no model named uses `gpt-image-2.6`.
126
+
121
127
  Which modes and tiers a model accepts differs per model, so
122
128
  `ottoport_list_models` — and `ottoport models` in the CLI — prints each one's
123
129
  menu beside its price. For one model in full, including every parameter it
package/cli/ottoport.mjs CHANGED
@@ -9,7 +9,7 @@
9
9
  // ottoport image "prompt" [--model gpt-image-2] [--resolution 2k] [--ref a.png,b.png] [--out img.png]
10
10
  // ottoport video "prompt" [--model kling-3.0] [--duration 5] [--out clip.mp4]
11
11
  // ottoport speech "text" [--model gpt-4o-mini-tts] [--voice alloy] [--out speech.mp3]
12
- // ottoport music "prompt" [--model suno-v5] [--duration 15] [--out song.mp3]
12
+ // ottoport music "prompt" [--model suno-v6] [--duration 15] [--out song.mp3]
13
13
  // ottoport music --model mirelo-sfx-2.0 --video <url> [--duration 8]
14
14
  // ottoport collections [list | create "name"]
15
15
  // ottoport mcp
@@ -116,6 +116,7 @@ async function cmdModel(id, flags) {
116
116
  : caps.resolutions;
117
117
  console.log(`resolution: ${tiers.join(", ")}${caps.base_resolution ? ` (base ${caps.base_resolution})` : ""}`);
118
118
  }
119
+ if (caps?.outputFormats?.length) console.log(`outputs: ${caps.outputFormats.join(", ")}, besides images (--format)`);
119
120
  console.log("parameters:");
120
121
  for (const p of m.parameters ?? []) {
121
122
  console.log(` ${String(p.name).padEnd(21)}${p.type}${p.required ? ", required" : ""} — ${p.note}`);
@@ -150,7 +151,8 @@ function capabilityLine(caps) {
150
151
  return `accepts: ${caps.modes.join(", ")}${refs} · ${caps.resolutions.join("/")}`;
151
152
  }
152
153
  const inputs = caps.maxInputImages ? `up to ${caps.maxInputImages} input images` : "text only";
153
- return `accepts: ${inputs} · ${caps.resolutions.join("/")}`;
154
+ const formats = caps.outputFormats?.length ? ` · also outputs ${caps.outputFormats.join("/")}` : "";
155
+ return `accepts: ${inputs} · ${caps.resolutions.join("/")}${formats}`;
154
156
  }
155
157
 
156
158
  async function cmdChat(prompt, flags) {
@@ -300,9 +302,17 @@ async function cmdImage(prompt, flags) {
300
302
  // A layer-decomposition model needs only --image; the prompt is a caption.
301
303
  if (!prompt && !last(flags.image)) die("image requires a prompt: ottoport image \"a red fox\" (or --image <url> on a layer model)");
302
304
  const { baseUrl, apiKey } = config(flags);
305
+ const format = last(flags.format);
306
+ const wantsFile = format === "gif" || format === "svg";
307
+ // Checked before anything is spent: the file would hold one format under another's name.
308
+ const outExtension = String(last(flags.out) ?? "").match(/\.(png|jpe?g|webp|gif|svg)$/i)?.[1].toLowerCase();
309
+ if (wantsFile && outExtension && outExtension !== format) {
310
+ die(`--format ${format} returns a .${format} file, but --out names a .${outExtension} one. Change one of them.`);
311
+ }
303
312
  const body = {
304
313
  async: true,
305
- model: last(flags.model) || "gpt-image-2",
314
+ // The default model has no GIF or SVG output, so asking for one picks a model that does.
315
+ model: last(flags.model) || (wantsFile ? "gpt-image-2.6" : "gpt-image-2"),
306
316
  ...(prompt ? { prompt } : {}),
307
317
  ...(last(flags.layers) ? { num_layers: Number(last(flags.layers)) } : {}),
308
318
  ...(last(flags.size) ? { size: last(flags.size) } : {}),
@@ -437,7 +447,7 @@ async function cmdAudio(kind, prompt, flags) {
437
447
  const { baseUrl, apiKey } = config(flags);
438
448
  const body = {
439
449
  async: true,
440
- model: last(flags.model) || (kind === "speech" ? "gpt-4o-mini-tts" : "suno-v5"),
450
+ model: last(flags.model) || (kind === "speech" ? "gpt-4o-mini-tts" : "suno-v6"),
441
451
  ...(prompt ? { prompt } : {}),
442
452
  ...(video ? { video_url: video } : {}),
443
453
  ...(last(flags.voice) ? { voice: last(flags.voice) } : {}),
@@ -528,7 +538,7 @@ Usage:
528
538
  ottoport image "<prompt>" [--model gpt-image-2] [--size 1024x1024 | --resolution 2k] [--quality high]
529
539
  [--aspect-ratio 16:9] [--n 1] [--seed 7]
530
540
  [--image <url>] [--ref <url>[,<url>…]] [--out file.png]
531
- ottoport image "<prompt>" --model gpt-image-2.6 --format gif|svg [--frames 8] [--frame-ms 200]
541
+ ottoport image "<prompt>" --format gif|svg [--model gpt-image-2.6] [--frames 8] [--frame-ms 200] [--wait --out loop.gif]
532
542
  ottoport image --model qwen-image-layered --image <url> [--layers 4]
533
543
  split one image into RGBA layers (prompt optional)
534
544
  ottoport video "<prompt>" [--model kling-3.0] [--duration 5] [--resolution 1080p]
@@ -545,7 +555,7 @@ Usage:
545
555
  ottoport webhook status <job-id> [--json] delivery attempts and responses
546
556
  ottoport webhook retry <job-id> [--json] redeliver a finished job's webhook now
547
557
  ottoport speech "<text>" [--model gpt-4o-mini-tts] [--voice alloy] [--format mp3] [--out speech.mp3]
548
- ottoport music "<prompt>" [--model suno-v5] [--duration 15] [--format mp3] [--out song.mp3]
558
+ ottoport music "<prompt>" [--model suno-v6] [--duration 15] [--format mp3] [--out song.mp3]
549
559
  ottoport music ["<prompt>"] --model mirelo-sfx-2.0 --video <url> [--duration 8] [--out sfx.wav]
550
560
  a soundtrack synced to the video, dialogue kept;
551
561
  set --duration to the clip's length (it bills)
package/commands/image.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Generate an image through the OttoPort gateway
3
- argument-hint: "<prompt> [--model <id>] [--resolution 2k] [--quality high] [--ref <url>…]"
3
+ argument-hint: "<prompt> [--model <id>] [--resolution 2k] [--quality high] [--ref <url>…] [--format gif|svg]"
4
4
  allowed-tools: mcp__ottoport__ottoport_generate_image, mcp__ottoport__ottoport_list_models, Bash(curl:*)
5
5
  ---
6
6
 
@@ -9,6 +9,12 @@ Generate an image with the `ottoport_generate_image` MCP tool.
9
9
  Request: $ARGUMENTS
10
10
 
11
11
  - Pass `model` only if the user named one; the tool defaults to `gpt-image-2`.
12
+ - An animated GIF or an SVG file comes from this tool, not from a video model:
13
+ set `output_format` to `gif` or `svg`, and the tool picks `gpt-image-2.6`. Only
14
+ that model and `gpt-image-2.6-flare` offer them, from a prompt alone, one file
15
+ per request. Put the motion and the loop in a GIF's prompt, and send no `size`,
16
+ `resolution` or `aspect_ratio` with it. `frame_count` is 2–16 (default 8; above
17
+ 8 bills double) and `frame_duration_ms` is 20–2000.
12
18
  - An edit of an existing image goes through `image_url`, not the prompt. Several
13
19
  references at once — a character plus the scene, a product plus a style — go
14
20
  through `reference_image_urls`.
@@ -16,7 +22,7 @@ Request: $ARGUMENTS
16
22
  `size` instead when the user named exact pixels. A higher tier costs more, so
17
23
  send one only when the request asks for it.
18
24
  - On the GPT Image models, `quality` buys compute rather than pixels — `low`,
19
- `medium` (the default), `high`, and `xhigh`/`max` on `gpt-image-2.5`. It is
25
+ `medium` (the default), `high`, and `xhigh`/`max` on `gpt-image-2.6` and `gpt-image-2.5`. It is
20
26
  billed steeply, so send it only when the user asked for a draft or a finish.
21
27
  - Models differ in how many references and which resolutions they take. If a
22
28
  request needs more than one reference, or 4k, check `ottoport_list_models`
@@ -11,7 +11,7 @@ backing track — or for sound effects, ambience or a soundtrack for a video, wi
11
11
  `model` `mirelo-sfx-2.0` (pass the video as `video_url`, and `duration` as its
12
12
  length); otherwise use `ottoport_generate_speech` to read the text aloud.
13
13
  Pass `voice` and `format` only when named — speech defaults to `gpt-4o-mini-tts`
14
- with the `alloy` voice, music to `suno-v5`.
14
+ with the `alloy` voice, music to `suno-v6`.
15
15
 
16
16
  Print the returned audio URL, and save it to disk with `curl -sSL` only if the
17
17
  user asked for a file.
package/mcp/server.mjs CHANGED
@@ -185,6 +185,9 @@ function accepts(capabilities) {
185
185
  if (Array.isArray(capabilities.resolutions) && capabilities.resolutions.length) {
186
186
  parts.push(capabilities.resolutions.join("/"));
187
187
  }
188
+ if (Array.isArray(capabilities.outputFormats) && capabilities.outputFormats.length) {
189
+ parts.push(`output_format ${capabilities.outputFormats.join("/")}`);
190
+ }
188
191
  return parts.length ? ` · accepts ${parts.join(", ")}` : "";
189
192
  }
190
193
 
@@ -217,6 +220,7 @@ async function describeModel(id) {
217
220
  : caps.resolutions;
218
221
  lines.push(`Resolutions: ${tiers.join(", ")}${caps.base_resolution ? ` (base ${caps.base_resolution})` : ""}`);
219
222
  }
223
+ if (caps?.outputFormats?.length) lines.push(`Output formats: ${caps.outputFormats.join(", ")}, besides images, via output_format`);
220
224
  lines.push("Parameters:");
221
225
  for (const p of m.parameters ?? []) {
222
226
  lines.push(` ${p.name} (${p.type}${p.required ? ", required" : ""}) — ${p.note}`);
@@ -329,7 +333,8 @@ async function generateImage({ prompt, model, collection, wait = false, ...rest
329
333
  // Queued as a job and polled: a 4K or layered image can take longer than
330
334
  // a proxy in front of the gateway holds a request open, and the hold is
331
335
  // then settled on what actually came back.
332
- body: JSON.stringify({ model: model || "gpt-image-2", ...(prompt ? { prompt } : {}), ...rest, ...withCollection(await collectionId(collection)), async: true }),
336
+ // The default model has no GIF or SVG output, so asking for one picks a model that does.
337
+ body: JSON.stringify({ model: model || (rest.output_format === "gif" || rest.output_format === "svg" ? "gpt-image-2.6" : "gpt-image-2"), ...(prompt ? { prompt } : {}), ...rest, ...withCollection(await collectionId(collection)), async: true }),
333
338
  });
334
339
  if (!res.ok) throw new Error(await gwError(res));
335
340
  let json = await res.json();
@@ -397,7 +402,7 @@ async function generateAudio(kind, { prompt, model, voice, duration, format, vid
397
402
  headers: { ...headers(), "idempotency-key": crypto.randomUUID() },
398
403
  body: JSON.stringify({
399
404
  async: true,
400
- model: model || (kind === "tts" ? "gpt-4o-mini-tts" : "suno-v5"),
405
+ model: model || (kind === "tts" ? "gpt-4o-mini-tts" : "suno-v6"),
401
406
  ...(prompt ? { prompt } : {}),
402
407
  ...(voice ? { voice } : {}),
403
408
  ...(video_url ? { video_url } : {}),
@@ -459,13 +464,13 @@ const TOOLS = [
459
464
  },
460
465
  {
461
466
  name: "ottoport_generate_image",
462
- description: "Generate an image through an OttoPort image model (GPT Image, Nano Banana, …). Text-to-image, editing from one reference, or several references at once. Also layer decomposition: qwen-image-layered and seedream-5.0-layers take `image_url` alone and return the image split into RGBA layers, one URL per layer, billed per layer. Vectors: ideogram-vector-1 returns editable SVG from a prompt (shape it with `aspect_ratio`, not `size`), and ideogram-vector-1-vectorize takes `image_url` alone and returns it traced as SVG. Which a model accepts is in its capabilities — call ottoport_list_models. Defaults to asynchronous submission: returns job_id and polling instructions. Use ottoport_get_job for results.",
467
+ description: "Generate an image through an OttoPort image model (GPT Image, Nano Banana, …). Text-to-image, editing from one reference, or several references at once. Also layer decomposition: qwen-image-layered and seedream-5.0-layers take `image_url` alone and return the image split into RGBA layers, one URL per layer, billed per layer. Animated GIFs and SVG files from a prompt: set output_format on gpt-image-2.6 or its Flare tier, rather than generating a video. Vectors: ideogram-vector-1 returns editable SVG from a prompt (shape it with `aspect_ratio`, not `size`), and ideogram-vector-1-vectorize takes `image_url` alone and returns it traced as SVG. Which a model accepts is in its capabilities — call ottoport_list_models. Defaults to asynchronous submission: returns job_id and polling instructions. Use ottoport_get_job for results.",
463
468
  inputSchema: {
464
469
  type: "object",
465
470
  properties: {
466
471
  wait: { type: "boolean", description: "Default false: return job immediately. True waits up to 45 seconds, then returns the same job for polling." },
467
472
  prompt: { type: "string", description: "What to generate. On a layer-decomposition model, an optional caption of the input image." },
468
- model: { type: "string", description: "Model id, e.g. gpt-image-2, nano-banana-2.1, nano-banana-pro. Default gpt-image-2." },
473
+ model: { type: "string", description: "Model id, e.g. gpt-image-2, nano-banana-2.1, nano-banana-pro. Default gpt-image-2, or gpt-image-2.6 when output_format is gif or svg." },
469
474
  size: { type: "string", description: 'Exact pixels, e.g. "1024x1024". Wins over `resolution`.' },
470
475
  // No `enum`: the vocabulary is "1k"/"2k"/"4k", but which of them a given
471
476
  // model actually offers differs per model, and an enum here reads as a
@@ -565,7 +570,7 @@ const TOOLS = [
565
570
  properties: {
566
571
  wait: { type: "boolean", description: "Default false: return job immediately. True waits up to 45 seconds, then returns the same job for polling." },
567
572
  prompt: { type: "string", description: "Music-generation prompt, or the sounds to make. Optional with video_url." },
568
- model: { type: "string", description: "Music model id. Default suno-v5; mirelo-sfx-2.0 for sound effects." },
573
+ model: { type: "string", description: "Music model id. Default suno-v6; mirelo-sfx-2.0 for sound effects." },
569
574
  duration: { type: "number", description: "Requested duration in seconds." },
570
575
  video_url: { type: "string", description: "mirelo-sfx-2.0 only: an http(s) video to score. Set duration to its length — duration is what bills." },
571
576
  format: { type: "string", description: 'e.g. "mp3" or "wav".' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ottoport",
3
- "version": "1.15.1",
3
+ "version": "1.15.2",
4
4
  "description": "Claude Code plugin, CLI and MCP server for OttoPort — one OpenAI-compatible API for every LLM, image, video, and speech model.",
5
5
  "homepage": "https://ottoport.ai",
6
6
  "repository": {
@@ -89,9 +89,10 @@ sensible default.
89
89
  | --- | --- | --- |
90
90
  | chat | `claude-sonnet-5` | `claude-haiku-4.5` (cheap), `gpt-5.5`, `gemini-3.5-flash` |
91
91
  | image | `gpt-image-2` | `nano-banana-2.1` (fast generation and edits), `nano-banana-pro` (high fidelity) |
92
+ | GIF or SVG file | `gpt-image-2.6`, with `output_format` on the image tool | `gpt-image-2.6-flare` (faster) |
92
93
  | video | `kling-3.0` | `seedance-2.0-fast` (cheap), `veo-3.1` (with audio) |
93
94
  | speech | `gpt-4o-mini-tts` | `eleven-v3` |
94
- | music | `suno-v5` | `lyria-2`, `mirelo-sfx-2.0` (sound effects, or a soundtrack for a video) |
95
+ | music | `suno-v6` | `lyria-2`, `mirelo-sfx-2.0` (sound effects, or a soundtrack for a video) |
95
96
 
96
97
  Never invent a model id. Call `ottoport_list_models` when unsure — the catalog
97
98
  changes, and a wrong id is a failed billable call. Rates come back in credits (the unit the ledger