@wavespeed/cli 0.4.29 → 0.4.30
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 -1
- package/dist/commands/price.js +23 -1
- package/dist/commands/run.js +21 -35
- package/dist/lib/validate-inputs.js +19 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -190,7 +190,8 @@ wavespeed skill install Drop SKILL.md for coding agents
|
|
|
190
190
|
models bill from an input — audio/video duration, frame count, text length. Pass
|
|
191
191
|
the same inputs you would pass to `wavespeed run`: a quote made without them can
|
|
192
192
|
be lower than the real charge. The amount actually charged for a run is
|
|
193
|
-
authoritative.
|
|
193
|
+
authoritative. Like `run`, `price` refuses an input the model does not accept (a typo would
|
|
194
|
+
otherwise be ignored by the quote); `--no-validate` skips the check.
|
|
194
195
|
|
|
195
196
|
## Auth
|
|
196
197
|
|
package/dist/commands/price.js
CHANGED
|
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import ora from 'ora';
|
|
3
3
|
import { fetchPricing, modelPlaygroundUrl } from '../lib/api.js';
|
|
4
4
|
import { parseInputs } from '../lib/inputs.js';
|
|
5
|
-
import { modelInputProps } from '../lib/validate-inputs.js';
|
|
5
|
+
import { modelInputProps, unknownInputsFor } from '../lib/validate-inputs.js';
|
|
6
6
|
import { resolveModelToken } from '../lib/config.js';
|
|
7
7
|
import { collectLocalFiles, resolveLocalFiles } from '../lib/local-files.js';
|
|
8
8
|
import { uploadWithCache } from '../lib/upload-cache.js';
|
|
@@ -15,6 +15,7 @@ export function registerPrice(program) {
|
|
|
15
15
|
.option('-i, --input <pair...>', 'Inputs as key=value (same syntax as `wavespeed run`)', [])
|
|
16
16
|
.option('-p, --prompt <text>', 'Shorthand for --input prompt=<text>')
|
|
17
17
|
.option('--upload', 'Upload @file inputs first so media-based pricing is exact (quota applies; identical bytes cached 24h)')
|
|
18
|
+
.option('--no-validate', 'Skip checking the inputs against the model schema')
|
|
18
19
|
.option('--json', 'Emit JSON')
|
|
19
20
|
.action(async (modelArg, opts) => {
|
|
20
21
|
const resolved = resolveModelToken(modelArg);
|
|
@@ -30,6 +31,27 @@ export function registerPrice(program) {
|
|
|
30
31
|
Object.assign(inputs, parseInputs(opts.input ?? []));
|
|
31
32
|
if (opts.prompt)
|
|
32
33
|
inputs.prompt = opts.prompt;
|
|
34
|
+
// A key the model does not declare is dropped by the pricer, so a typo would quote a price that ignores it.
|
|
35
|
+
if (opts.validate !== false) {
|
|
36
|
+
const report = await unknownInputsFor(resolved.model, inputs);
|
|
37
|
+
if (report) {
|
|
38
|
+
const msg = `Model ${resolved.model} does not accept: ${report.unknown.join(', ')}. ` +
|
|
39
|
+
`Unknown inputs are ignored, so this quote would not account for them.`;
|
|
40
|
+
if (opts.json) {
|
|
41
|
+
process.stdout.write(JSON.stringify({ error: msg, unknown_inputs: report.unknown, accepted_inputs: report.known }, null, 2) + '\n');
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
console.error(chalk.red('Error: ') + `unknown input${report.unknown.length > 1 ? 's' : ''} for ` + chalk.cyan(resolved.model) + ':');
|
|
45
|
+
for (const k of report.unknown) {
|
|
46
|
+
const hint = report.suggestions.get(k);
|
|
47
|
+
console.error(chalk.yellow(` ${k}` + (hint ? ` (did you mean \`${hint}\`?)` : '')));
|
|
48
|
+
}
|
|
49
|
+
console.error(chalk.gray(' Accepted inputs: ') + report.known.join(', '));
|
|
50
|
+
console.error(chalk.gray(' Bypass check: ') + chalk.cyan('--no-validate'));
|
|
51
|
+
}
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
33
55
|
// Pricing that depends on media (e.g. per-second video upscaling) is
|
|
34
56
|
// measured server-side from the URL, so a local path is useless to the
|
|
35
57
|
// pricing engine. Uploading is slow and quota-limited, so it is opt-in:
|
package/dist/commands/run.js
CHANGED
|
@@ -5,8 +5,8 @@ import fs from 'node:fs';
|
|
|
5
5
|
import { requireClient } from '../lib/client.js';
|
|
6
6
|
import { parseInputs, withInputSyntaxHint } from '../lib/inputs.js';
|
|
7
7
|
import { resolveLocalFiles } from '../lib/local-files.js';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { submitPrediction, waitForPrediction } from '../lib/api.js';
|
|
9
|
+
import { modelInputProps, unknownInputsFor } from '../lib/validate-inputs.js';
|
|
10
10
|
import { uploadWithCache } from '../lib/upload-cache.js';
|
|
11
11
|
import { downloadOutputs, printOutputs } from '../lib/output.js';
|
|
12
12
|
import { emitJson, emitJsonError, isJsonMode, log, setJsonMode } from '../lib/log.js';
|
|
@@ -106,41 +106,27 @@ export function registerRun(program) {
|
|
|
106
106
|
// avoid stale-cache false positives, re-fetches the catalog once
|
|
107
107
|
// before rejecting.
|
|
108
108
|
if (opts.validate !== false) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
109
|
+
const report = await unknownInputsFor(model, input);
|
|
110
|
+
if (report) {
|
|
111
|
+
const lines = report.unknown.map((k) => {
|
|
112
|
+
const hint = report.suggestions.get(k);
|
|
113
|
+
return ` ${k}` + (hint ? ` (did you mean \`${hint}\`?)` : '');
|
|
114
|
+
});
|
|
115
|
+
const msg = `Model ${model} does not accept: ${report.unknown.join(', ')}. ` +
|
|
116
|
+
`The API silently drops unknown inputs, so this would bill a run that ignores them.`;
|
|
117
|
+
if (isJsonMode()) {
|
|
118
|
+
emitJsonError(msg, { unknown_inputs: report.unknown, accepted_inputs: report.known });
|
|
117
119
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
emitJsonError(msg, { unknown_inputs: report.unknown, accepted_inputs: report.known });
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
console.error(chalk.red('Error: ') + `unknown input${report.unknown.length > 1 ? 's' : ''} for ` + chalk.cyan(model) + ':');
|
|
130
|
-
for (const l of lines)
|
|
131
|
-
console.error(chalk.yellow(l));
|
|
132
|
-
console.error(chalk.gray(' The API silently drops unknown inputs — the run would be billed with them ignored.'));
|
|
133
|
-
console.error(chalk.gray(' Accepted inputs: ') + report.known.join(', '));
|
|
134
|
-
console.error(chalk.gray(' Full schema: ') + chalk.cyan(`wavespeed schema ${model}`));
|
|
135
|
-
console.error(chalk.gray(' Bypass check: ') + chalk.cyan('--no-validate'));
|
|
136
|
-
}
|
|
137
|
-
process.exit(1);
|
|
120
|
+
else {
|
|
121
|
+
console.error(chalk.red('Error: ') + `unknown input${report.unknown.length > 1 ? 's' : ''} for ` + chalk.cyan(model) + ':');
|
|
122
|
+
for (const l of lines)
|
|
123
|
+
console.error(chalk.yellow(l));
|
|
124
|
+
console.error(chalk.gray(' The API silently drops unknown inputs — the run would be billed with them ignored.'));
|
|
125
|
+
console.error(chalk.gray(' Accepted inputs: ') + report.known.join(', '));
|
|
126
|
+
console.error(chalk.gray(' Full schema: ') + chalk.cyan(`wavespeed schema ${model}`));
|
|
127
|
+
console.error(chalk.gray(' Bypass check: ') + chalk.cyan('--no-validate'));
|
|
138
128
|
}
|
|
139
|
-
|
|
140
|
-
catch {
|
|
141
|
-
// Catalog unreachable — validation is best-effort, never a blocker.
|
|
142
|
-
// (The unknown-input rejection above exits the process directly and
|
|
143
|
-
// is not routed through here.)
|
|
129
|
+
process.exit(1);
|
|
144
130
|
}
|
|
145
131
|
}
|
|
146
132
|
// `@path` inputs become hosted URLs before submission. Only the
|
|
@@ -109,3 +109,22 @@ export function findUnknownInputs(input, model) {
|
|
|
109
109
|
}
|
|
110
110
|
return { unknown, known, suggestions };
|
|
111
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Inputs the model's schema does not declare, checked against the live catalog. The API silently drops unknown
|
|
114
|
+
* keys, so a typo bills a run (or quotes a price) that ignores it. Fails open: null when the catalog has no schema
|
|
115
|
+
* or cannot be reached; re-fetches once before reporting, so a stale cache cannot block a valid input.
|
|
116
|
+
*/
|
|
117
|
+
export async function unknownInputsFor(model, input) {
|
|
118
|
+
try {
|
|
119
|
+
let { models } = await fetchModels();
|
|
120
|
+
let report = findUnknownInputs(input, models.find((m) => m.model_id === model));
|
|
121
|
+
if (report) {
|
|
122
|
+
({ models } = await fetchModels({ refresh: true }));
|
|
123
|
+
report = findUnknownInputs(input, models.find((m) => m.model_id === model));
|
|
124
|
+
}
|
|
125
|
+
return report;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|