@wavespeed/cli 0.4.27 → 0.4.29
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.
- package/README.md +2 -2
- package/dist/lib/api.js +1 -1
- package/dist/lib/media-models.js +6 -5
- package/dist/lib/media-plan.js +84 -9
- package/package.json +1 -1
- package/skills/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -120,7 +120,7 @@ wavespeed extend video "the camera keeps rising" --video @./clip.mp4 --json
|
|
|
120
120
|
|
|
121
121
|
| | Default | Alternatives (`-m`) |
|
|
122
122
|
|---|---|---|
|
|
123
|
-
| Image | `seedream-5-pro` | `gpt-image-2.5`, `nano-banana-2.1`, `nano-banana-pro`, `nano-banana-2`, `gpt-image-2` |
|
|
123
|
+
| Image | `seedream-5-pro` | `gpt-image-2.6`, `gpt-image-2.5`, `nano-banana-2.1`, `nano-banana-pro`, `nano-banana-2`, `gpt-image-2` |
|
|
124
124
|
| Video | `seedance-2.5` | `seedance-2.5-turbo`, `kling-4.0`, `wan-3.0`, `wan-3.0-prime`, `minimax-h3` (cheap and fast, native stereo audio, 768p by default), Seedance 2.0 tiers |
|
|
125
125
|
|
|
126
126
|
For everything else (audio, 3D, upscaling, a specific endpoint), find the model with `wavespeed models <query>` and call it with `wavespeed run <model-id>`.
|
|
@@ -137,7 +137,7 @@ The skill teaches the agent the shortcuts for images and videos (`generate`, `ed
|
|
|
137
137
|
|
|
138
138
|
**OpenCode** loads the same file with no extra steps — `.claude/skills/` is on its skill search path. For the MCP tools as well, add the plugin to your OpenCode config — `"plugin": ["@wavespeed/opencode"]` — and run `opencode mcp auth wavespeed` ([WaveSpeedAI/wavespeed-opencode](https://github.com/WaveSpeedAI/wavespeed-opencode)).
|
|
139
139
|
|
|
140
|
-
**Codex** reads `.agents/skills/`, which the same command writes. For a one-line install that also wires up the MCP server: `codex plugin marketplace add WaveSpeedAI/codex-
|
|
140
|
+
**Codex** reads `.agents/skills/`, which the same command writes. For a one-line install that also wires up the MCP server: `codex plugin marketplace add WaveSpeedAI/codex-plugins` ([WaveSpeedAI/codex-plugins](https://github.com/WaveSpeedAI/codex-plugins)).
|
|
141
141
|
|
|
142
142
|
**DeepSeek Harness (dsh)**: the same skill is packaged for dsh's `.dsh/skills` layout at [WaveSpeedAI/wavespeed-dsh-skill](https://github.com/WaveSpeedAI/wavespeed-dsh-skill).
|
|
143
143
|
|
package/dist/lib/api.js
CHANGED
|
@@ -150,7 +150,7 @@ export function modelPlaygroundUrl(modelId) {
|
|
|
150
150
|
export async function fetchPricing(modelId, inputs) {
|
|
151
151
|
// /model/price is the documented pricing endpoint; /model/pricing is a
|
|
152
152
|
// legacy contract kept for old clients and absent from the public docs.
|
|
153
|
-
return apiPost('/api/v3/model/price', { model_id: modelId, inputs });
|
|
153
|
+
return apiPost('/api/v3/model/price', { model_id: modelId, input: inputs });
|
|
154
154
|
}
|
|
155
155
|
/** The run time in ms the API reports (`timings.inference`), or undefined when absent or zero. */
|
|
156
156
|
export function inferenceMs(item) {
|
package/dist/lib/media-models.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// The image and video models the dedicated generate/edit tools offer: the
|
|
2
2
|
// same list, in the same order, as the WaveSpeed /chat page (api-dashboard
|
|
3
|
-
// lib/media-models.ts, checked 2026-
|
|
3
|
+
// lib/media-models.ts, checked 2026-11-05). The first of each is the default.
|
|
4
4
|
// This is a vendored copy: when /chat changes its models, change this file and
|
|
5
5
|
// its test (src/lib/media-models.test.ts) with it. Capabilities mirror the
|
|
6
6
|
// endpoints' input schemas; the tools still fit every request to the live
|
|
@@ -18,6 +18,7 @@ export const IMAGE_MODELS = {
|
|
|
18
18
|
'nano-banana-2.1': image('nano-banana-2.1', 'Nano Banana 2.1', 'google/nano-banana-2.1', 'text-to-image', ['1k', '2k', '4k'], NANO_BANANA_2_RATIOS, 14),
|
|
19
19
|
'nano-banana-2': image('nano-banana-2', 'Nano Banana 2', 'google/nano-banana-2', 'text-to-image', ['0.5k', '1k', '2k', '4k'], NANO_BANANA_2_RATIOS, 14),
|
|
20
20
|
'nano-banana-pro': image('nano-banana-pro', 'Nano Banana Pro', 'google/nano-banana-pro', 'text-to-image', ['1k', '2k', '4k'], NANO_BANANA_RATIOS, 14),
|
|
21
|
+
'gpt-image-2.6': image('gpt-image-2.6', 'GPT Image 2.6', 'openai/gpt-image-2.6-sunburst', 'text-to-image', ['1k', '2k', '4k'], WIDE_RATIOS, 16),
|
|
21
22
|
'gpt-image-2.5': image('gpt-image-2.5', 'GPT Image 2.5', 'openai/gpt-image-2.5-sunburst', 'text-to-image', ['1k', '2k', '4k'], WIDE_RATIOS, 16),
|
|
22
23
|
'gpt-image-2': image('gpt-image-2', 'GPT Image 2', 'openai/gpt-image-2', 'text-to-image', ['1k', '2k', '4k'], WIDE_RATIOS, 16),
|
|
23
24
|
};
|
|
@@ -76,16 +77,16 @@ export function videoModelForEndpoint(modelId) {
|
|
|
76
77
|
return VIDEO_MODEL_KEYS.map((k) => VIDEO_MODELS[k]).find((m) => m.turbo === turbo && modelId.startsWith(`${m.base}/`));
|
|
77
78
|
}
|
|
78
79
|
/**
|
|
79
|
-
* A model's endpoints.
|
|
80
|
-
* reference-to-video endpoint
|
|
81
|
-
* endpoints have no extend.
|
|
80
|
+
* A model's endpoints. Every family takes references on a dedicated
|
|
81
|
+
* reference-to-video endpoint. Seedance has turbo ones; the others do not.
|
|
82
|
+
* Turbo endpoints have no extend.
|
|
82
83
|
*/
|
|
83
84
|
export function videoEndpoints(m) {
|
|
84
85
|
const suffix = m.turbo ? '-turbo' : '';
|
|
85
86
|
return {
|
|
86
87
|
text: `${m.base}/text-to-video${suffix}`,
|
|
87
88
|
image: `${m.base}/image-to-video${suffix}`,
|
|
88
|
-
|
|
89
|
+
reference: `${m.base}/reference-to-video${m.family === 'seedance-2.5' || m.family === 'seedance-2.0' ? suffix : ''}`,
|
|
89
90
|
edit: `${m.base}/video-edit${suffix}`,
|
|
90
91
|
...(m.modes.includes('extend') ? { extend: `${m.base}/video-extend` } : {}),
|
|
91
92
|
};
|
package/dist/lib/media-plan.js
CHANGED
|
@@ -5,16 +5,73 @@
|
|
|
5
5
|
// the same words give the same model, endpoint and input on both.
|
|
6
6
|
import { DEFAULT_IMAGE_MODEL, DEFAULT_VIDEO_MODEL, IMAGE_MODELS, IMAGE_MODEL_KEYS, VIDEO_MODELS, VIDEO_MODEL_KEYS, referenceFields, videoEndpoints, } from './media-models.js';
|
|
7
7
|
import { renameRefs } from './prompt-refs.js';
|
|
8
|
-
|
|
8
|
+
// Size tiers: 480p, 720p, 1080p (rank = N) and 0.5k, 1k, 2k, 4k (rank = X*1000),
|
|
9
|
+
// so 480p < 720p < 768p < 1080p < 2k < 4k on one scale.
|
|
10
|
+
function tierRank(v) {
|
|
11
|
+
if (typeof v !== 'string')
|
|
12
|
+
return undefined;
|
|
13
|
+
const m = /^(\d+(?:\.\d+)?)([pk])$/i.exec(v.trim());
|
|
14
|
+
if (!m)
|
|
15
|
+
return undefined;
|
|
16
|
+
const n = Number(m[1]);
|
|
17
|
+
return m[2].toLowerCase() === 'k' ? n * 1000 : n;
|
|
18
|
+
}
|
|
19
|
+
// ln(W/H) of a W:H ratio, so 2:1 and 1:2 are equally far from 1:1.
|
|
20
|
+
function ratioLog(v) {
|
|
21
|
+
if (typeof v !== 'string')
|
|
22
|
+
return undefined;
|
|
23
|
+
const m = /^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?)$/.exec(v.trim());
|
|
24
|
+
if (!m)
|
|
25
|
+
return undefined;
|
|
26
|
+
const w = Number(m[1]);
|
|
27
|
+
const h = Number(m[2]);
|
|
28
|
+
return w > 0 && h > 0 ? Math.log(w / h) : undefined;
|
|
29
|
+
}
|
|
30
|
+
// Distances within this count as a tie (4:3 and 3:4 around 1:1 differ only in the last bit).
|
|
31
|
+
const TIE_EPSILON = 1e-9;
|
|
32
|
+
/** The member whose score is closest to target; on a tie, the one `better` prefers (default: the first). */
|
|
33
|
+
function closestMember(members, score, target, better = () => false) {
|
|
34
|
+
let best;
|
|
35
|
+
for (const v of members) {
|
|
36
|
+
const s = score(v);
|
|
37
|
+
if (s === undefined)
|
|
38
|
+
continue;
|
|
39
|
+
const d = Math.abs(s - target);
|
|
40
|
+
if (!best || d < best.d - TIE_EPSILON || (Math.abs(d - best.d) <= TIE_EPSILON && better({ v, s }, best)))
|
|
41
|
+
best = { v, s, d };
|
|
42
|
+
}
|
|
43
|
+
return best?.v;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Keep only fields the model accepts and fit each value to its schema. The
|
|
47
|
+
* rule is shared with the remote MCP (api-server) and the dashboard /chat:
|
|
48
|
+
* - a size tier (resolution, or an enum of Np / Nk values) the model does not
|
|
49
|
+
* offer becomes the closest tier it does; a tie goes to the lower one;
|
|
50
|
+
* - a W:H aspect ratio becomes the closest ratio (in ln(W/H)) among the
|
|
51
|
+
* members that are W:H; a tie goes to the first in enum order;
|
|
52
|
+
* - any other value outside an enum is left out, so the model default applies;
|
|
53
|
+
* - numbers go to the nearest numeric enum value, or are rounded (integers)
|
|
54
|
+
* and clamped to the range.
|
|
55
|
+
* Every value not sent as requested gets a note.
|
|
56
|
+
*/
|
|
9
57
|
export async function fitInput(modelId, wanted, propsOf, showDefaults = []) {
|
|
10
58
|
const properties = await propsOf(modelId);
|
|
11
59
|
if (!properties)
|
|
12
60
|
throw new Error(`No schema for ${modelId} in the model catalog.`);
|
|
13
61
|
const input = {};
|
|
62
|
+
const notes = [];
|
|
63
|
+
// Defaults filled in only to show the price are not the caller's request: no note for them.
|
|
64
|
+
const fromDefault = new Set();
|
|
65
|
+
const used = (key, from, to) => {
|
|
66
|
+
input[key] = to;
|
|
67
|
+
if (to !== from && !fromDefault.has(key))
|
|
68
|
+
notes.push(`${key} ${String(from)} is not offered by ${modelId}; used ${String(to)}.`);
|
|
69
|
+
};
|
|
14
70
|
// Spell out the defaults that set the price, so the estimate shows them.
|
|
15
71
|
for (const key of showDefaults) {
|
|
16
72
|
if (properties[key]?.default !== undefined && (wanted[key] === undefined || wanted[key] === null || wanted[key] === '')) {
|
|
17
73
|
wanted = { ...wanted, [key]: properties[key].default };
|
|
74
|
+
fromDefault.add(key);
|
|
18
75
|
}
|
|
19
76
|
}
|
|
20
77
|
for (const [key, value] of Object.entries(wanted)) {
|
|
@@ -28,11 +85,28 @@ export async function fitInput(modelId, wanted, propsOf, showDefaults = []) {
|
|
|
28
85
|
continue;
|
|
29
86
|
}
|
|
30
87
|
if (prop.enum?.length) {
|
|
31
|
-
|
|
88
|
+
const members = prop.enum;
|
|
89
|
+
if (members.includes(value)) {
|
|
32
90
|
input[key] = value;
|
|
33
|
-
|
|
34
|
-
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (typeof value === 'number' && members.every((v) => typeof v === 'number')) {
|
|
94
|
+
used(key, value, members.reduce((a, b) => (Math.abs(b - value) < Math.abs(a - value) ? b : a)));
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const rank = tierRank(value);
|
|
98
|
+
const tierMembers = members.filter((v) => tierRank(v) !== undefined);
|
|
99
|
+
if (rank !== undefined && tierMembers.length && (key === 'resolution' || tierMembers.length === members.length)) {
|
|
100
|
+
used(key, value, closestMember(tierMembers, tierRank, rank, (a, b) => a.s < b.s));
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const ratio = ratioLog(value);
|
|
104
|
+
if (ratio !== undefined && members.some((v) => ratioLog(v) !== undefined)) {
|
|
105
|
+
used(key, value, closestMember(members, ratioLog, ratio));
|
|
106
|
+
continue;
|
|
35
107
|
}
|
|
108
|
+
if (!fromDefault.has(key))
|
|
109
|
+
notes.push(`${key} ${String(value)} is not accepted by ${modelId}; the model default is used.`);
|
|
36
110
|
continue;
|
|
37
111
|
}
|
|
38
112
|
if (typeof value === 'number' && (prop.type === 'integer' || prop.type === 'number')) {
|
|
@@ -41,12 +115,12 @@ export async function fitInput(modelId, wanted, propsOf, showDefaults = []) {
|
|
|
41
115
|
v = Math.max(prop.minimum, v);
|
|
42
116
|
if (typeof prop.maximum === 'number')
|
|
43
117
|
v = Math.min(prop.maximum, v);
|
|
44
|
-
|
|
118
|
+
used(key, value, v);
|
|
45
119
|
continue;
|
|
46
120
|
}
|
|
47
121
|
input[key] = value;
|
|
48
122
|
}
|
|
49
|
-
return input;
|
|
123
|
+
return { input, notes };
|
|
50
124
|
}
|
|
51
125
|
function imageModelKey(named) {
|
|
52
126
|
if (named === undefined || named === '')
|
|
@@ -64,10 +138,9 @@ export async function planImage(args, propsOf) {
|
|
|
64
138
|
const wanted = { prompt: args.prompt, aspect_ratio: args.aspect_ratio, resolution: args.resolution };
|
|
65
139
|
if (edit)
|
|
66
140
|
wanted.images = images;
|
|
67
|
-
const input = await fitInput(modelId, wanted, propsOf);
|
|
141
|
+
const { input, notes } = await fitInput(modelId, wanted, propsOf);
|
|
68
142
|
if (typeof input.prompt === 'string')
|
|
69
143
|
input.prompt = renameRefs(input.prompt, input, modelId);
|
|
70
|
-
const notes = [];
|
|
71
144
|
if (edit && Array.isArray(input.images) && input.images.length < images.length) {
|
|
72
145
|
notes.push(`${model.label} takes at most ${input.images.length} images, so only the first ${input.images.length} were used.`);
|
|
73
146
|
}
|
|
@@ -167,7 +240,8 @@ export async function planVideo(args, action, propsOf) {
|
|
|
167
240
|
const model = VIDEO_MODELS[chosen.key];
|
|
168
241
|
const modelId = endpoints(chosen.key)[chosen.mode];
|
|
169
242
|
// Image-to-video follows the first frame's shape: no aspect_ratio, not even the schema default.
|
|
170
|
-
const
|
|
243
|
+
const fitted = await fitInput(modelId, chosen.wanted, propsOf, chosen.mode === 'image' ? ['duration', 'resolution'] : ['duration', 'resolution', 'aspect_ratio']);
|
|
244
|
+
const input = fitted.input;
|
|
171
245
|
if (chosen.mode === 'image')
|
|
172
246
|
delete input.aspect_ratio;
|
|
173
247
|
if (typeof input.prompt === 'string')
|
|
@@ -184,5 +258,6 @@ export async function planVideo(args, action, propsOf) {
|
|
|
184
258
|
? `${model.label} does not take ${dropped.join(', ')} in any mode, so those were left out; the model you named was not switched. ${await whoTakes(dropped, propsOf)}`
|
|
185
259
|
: `No available video model takes ${dropped.join(', ')} for this request, so those were left out.`);
|
|
186
260
|
}
|
|
261
|
+
notes.push(...fitted.notes);
|
|
187
262
|
return { modelId, label: model.label, mode: MODE_LABEL[chosen.mode], input, notes };
|
|
188
263
|
}
|
package/package.json
CHANGED
package/skills/SKILL.md
CHANGED
|
@@ -33,7 +33,7 @@ They use the same models as the WaveSpeed /chat page and pick the endpoint (text
|
|
|
33
33
|
|
|
34
34
|
| | Default | Alternatives (`-m` / `model`) |
|
|
35
35
|
|---|---|---|
|
|
36
|
-
| Image | `seedream-5-pro` (Seedream 5 Pro) | `gpt-image-2.
|
|
36
|
+
| Image | `seedream-5-pro` (Seedream 5 Pro) | `gpt-image-2.6` (GPT Image 2.6, the model behind ChatGPT Images 2.6; strong at text and photorealism), `gpt-image-2.5`, `nano-banana-2.1`, `nano-banana-pro`, `nano-banana-2`, `gpt-image-2` |
|
|
37
37
|
| Video | `seedance-2.5` (Seedance 2.5) | `seedance-2.5-turbo`, `kling-4.0`, `wan-3.0`, `wan-3.0-prime`, `minimax-h3` (cheap and fast, native stereo audio; defaults to 768p, its counterpart of 720p), older Seedance 2.0 tiers |
|
|
38
38
|
|
|
39
39
|
Video resolution is 720p unless the user asks for another (MiniMax H3: 768p). Offer 480p for a cheaper draft.
|