luxin-cli 0.2.3 → 0.2.4
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/CHANGELOG.md +41 -0
- package/bin/luxin.mjs +678 -190
- package/cli.md +142 -52
- package/commands.json +1 -0
- package/llms.txt +6 -6
- package/package.json +2 -3
- package/skill.md +14 -10
- package/skills/agent-image-generation/SKILL.md +1 -1
- package/skills/ai-audio-generation/SKILL.md +1 -1
- package/skills/ai-image-generation/SKILL.md +1 -1
- package/skills/ai-video-generation/SKILL.md +1 -1
- package/skills/creative-media/SKILL.md +1 -1
- package/skills/image-edit/SKILL.md +1 -1
- package/skills/image-generation/SKILL.md +2 -2
- package/skills/image-skill/SKILL.md +78 -0
- package/skills/image-to-3d/SKILL.md +2 -2
- package/skills/luxin/SKILL.md +14 -10
- package/skills/luxin/references/cli.md +142 -52
- package/skills/luxin/references/commands.json +1 -0
- package/skills/luxin/references/llms.txt +6 -6
- package/SKILL.md +0 -436
package/bin/luxin.mjs
CHANGED
|
@@ -15,7 +15,7 @@ import { Readable } from "node:stream";
|
|
|
15
15
|
import { pipeline } from "node:stream/promises";
|
|
16
16
|
import os from "node:os";
|
|
17
17
|
|
|
18
|
-
const VERSION = "0.2.
|
|
18
|
+
const VERSION = "0.2.4";
|
|
19
19
|
const PACKAGE_NAME = "luxin";
|
|
20
20
|
const DEFAULT_API_BASE_URL = "https://api.luxin.sh";
|
|
21
21
|
const DEFAULT_DOCS_BASE_URL = "https://luxin.sh";
|
|
@@ -32,6 +32,39 @@ const PROMPTLESS_EDIT_MODEL_IDS = new Set([
|
|
|
32
32
|
// provider rejects any prompt, so the public CLI must not require/send one.
|
|
33
33
|
"fal.trellis-image-to-3d",
|
|
34
34
|
]);
|
|
35
|
+
// #2204-#2216: the guide fields the `ready_compact` output mode drops. Kept as
|
|
36
|
+
// one list so the delete and `data.output_mode.omitted_fields` cannot drift.
|
|
37
|
+
const COMPACT_OMITTABLE_GUIDE_FIELDS = [
|
|
38
|
+
"auth_handoff",
|
|
39
|
+
"auth_ready",
|
|
40
|
+
"escape_hatches",
|
|
41
|
+
"guide_recovery",
|
|
42
|
+
"no_spend_evaluation",
|
|
43
|
+
"recommended_no_spend_command",
|
|
44
|
+
"recommended_no_spend_command_effect",
|
|
45
|
+
"recommended_no_spend_command_label",
|
|
46
|
+
"self_fund_handoff",
|
|
47
|
+
"self_fund_next_command",
|
|
48
|
+
"self_fund_next_command_label",
|
|
49
|
+
"self_fund_preparation",
|
|
50
|
+
];
|
|
51
|
+
// Kept whenever the pre-wall top-up is recommended: dropping the funding nudge
|
|
52
|
+
// at ready_to_create would regress the self-fund wall work.
|
|
53
|
+
const COMPACT_SELF_FUND_GUIDE_FIELDS = new Set([
|
|
54
|
+
"self_fund_handoff",
|
|
55
|
+
"self_fund_next_command",
|
|
56
|
+
"self_fund_next_command_label",
|
|
57
|
+
"self_fund_preparation",
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
// #2194 + #2204-#2216: in `ready_compact` mode `escape_hatches` is omitted, so
|
|
61
|
+
// the no-spend discovery note points at the literal command instead of a field
|
|
62
|
+
// that is not in the response.
|
|
63
|
+
function noSpendDiscoveryNote(readyCompact) {
|
|
64
|
+
return readyCompact
|
|
65
|
+
? "No-spend output mode omits live self-fund and payment command templates. Re-run this guide without --no-spend to surface them; self_fund_discovery.inspect_methods_command stays available for read-only, no-spend payment-method inspection. provider_call, credit_debit, and media_write remain false (see data.mutation). data.output_mode lists the fields this ready_to_create response also omits."
|
|
66
|
+
: "No-spend output mode omits live self-fund and payment command templates. Re-run this guide without --no-spend to surface them; data.escape_hatches.payment_methods stays available for read-only, no-spend payment-method inspection. provider_call, credit_debit, and media_write remain false (see data.mutation).";
|
|
67
|
+
}
|
|
35
68
|
const DEFAULT_CONFIG_PATH = join(
|
|
36
69
|
process.env.XDG_CONFIG_HOME ?? join(os.homedir(), ".config"),
|
|
37
70
|
"luxin",
|
|
@@ -52,9 +85,28 @@ const AGENT_SKILL_URL = "https://luxin.sh/skill.md";
|
|
|
52
85
|
const AGENT_LLMS_URL = "https://luxin.sh/llms.txt";
|
|
53
86
|
const AGENT_CLI_DOCS_URL = "https://luxin.sh/cli.md";
|
|
54
87
|
const CREDIT_UNIT_USD = 0.01;
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
|
|
88
|
+
const CANONICAL_PUBLIC_MEDIA_HOST = "media.luxin.sh";
|
|
89
|
+
const LEGACY_PUBLIC_MEDIA_HOST = "media.image-skill.com";
|
|
90
|
+
// media.luxin.sh is the canonical media host. media.image-skill.com is the
|
|
91
|
+
// pre-rename host; it still serves the identical objects, so asset URLs handed
|
|
92
|
+
// out under it stay valid inputs forever.
|
|
93
|
+
const ACCEPTED_PUBLIC_MEDIA_HOSTS = [
|
|
94
|
+
CANONICAL_PUBLIC_MEDIA_HOST,
|
|
95
|
+
LEGACY_PUBLIC_MEDIA_HOST,
|
|
96
|
+
];
|
|
97
|
+
// Credit prices for the xAI Grok Imagine image variants, in credits
|
|
98
|
+
// (1 credit = CREDIT_UNIT_USD). The guide prices model parameters locally so it
|
|
99
|
+
// can pick a resolution without a second round trip; the hosted API remains the
|
|
100
|
+
// authority. Exact fractions keep the local estimate identical to the hosted
|
|
101
|
+
// price. These are prices, not costs: the CLI never computes or publishes
|
|
102
|
+
// provider cost or margin.
|
|
103
|
+
const XAI_IMAGE_CREDIT_PRICES = {
|
|
104
|
+
source_image: { standard: 1 / 3, quality: 5 / 3 },
|
|
105
|
+
output_image: {
|
|
106
|
+
standard: 10 / 3,
|
|
107
|
+
quality: { "1k": 25 / 3, "2k": 35 / 3 },
|
|
108
|
+
},
|
|
109
|
+
};
|
|
58
110
|
const MODALITY_COMMAND_ALIASES = new Map([
|
|
59
111
|
["image", { command: "create", intent: null }],
|
|
60
112
|
["video", { command: "create", intent: "video" }],
|
|
@@ -156,6 +208,111 @@ const GUIDE_NEXT_COMMAND_PLACEHOLDERS = [
|
|
|
156
208
|
},
|
|
157
209
|
];
|
|
158
210
|
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
// Explicit aspect-ratio intent (#2213/#2203/#2209, hardened by #2243/#2252).
|
|
213
|
+
//
|
|
214
|
+
// An agent that briefs "a 16:9 cinematic widescreen poster" is stating a
|
|
215
|
+
// REQUIRED output shape, not decorating the prompt. This CLI used to ignore it
|
|
216
|
+
// entirely: `createGuideSuggestedAspectRatio` only ever suggested a ratio for
|
|
217
|
+
// video models, so `selection.suggested_aspect_ratio` was null for every image
|
|
218
|
+
// create and the emitted `next_command` carried no `--aspect-ratio`. The agent
|
|
219
|
+
// then got the model default with no signal that the ratio it asked for had
|
|
220
|
+
// been dropped.
|
|
221
|
+
//
|
|
222
|
+
// Aspect ratio is a TOP-LEVEL NORMALIZED control (`--aspect-ratio`, validated
|
|
223
|
+
// against `media.input.aspect_ratios.values`), NOT a `model_parameters` field.
|
|
224
|
+
//
|
|
225
|
+
// Conservative on purpose: a false negative degrades to the old behavior (no
|
|
226
|
+
// suggested ratio), while a false positive silently overrides the shape an
|
|
227
|
+
// agent actually wanted. So this fires ONLY on an explicit ratio token from a
|
|
228
|
+
// known allowlist, or on an orientation word BOUND to an orientation noun.
|
|
229
|
+
// Bare "portrait" and bare "landscape" are deliberately excluded -- "a portrait
|
|
230
|
+
// of a woman" and "a landscape at dusk" are subject/genre words.
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
// Ratio tokens accepted verbatim from a brief. Deliberately limited to the
|
|
234
|
+
// unambiguous photographic/display ratios. `2:1` and `1:2` are NOT here: they
|
|
235
|
+
// collide with odds and mixing ratios far more often than they express a shape.
|
|
236
|
+
const EXPLICIT_ASPECT_RATIOS = [
|
|
237
|
+
"1:1",
|
|
238
|
+
"4:3",
|
|
239
|
+
"3:4",
|
|
240
|
+
"3:2",
|
|
241
|
+
"2:3",
|
|
242
|
+
"16:9",
|
|
243
|
+
"9:16",
|
|
244
|
+
"21:9",
|
|
245
|
+
"9:21",
|
|
246
|
+
];
|
|
247
|
+
|
|
248
|
+
// A `W:H` token that is not part of a longer numeric run (so "16:9:30" and
|
|
249
|
+
// "1920:1080:2" do not match) and is not a clock time ("9:16 am").
|
|
250
|
+
const ASPECT_RATIO_TOKEN_RE =
|
|
251
|
+
/(?<![\d.:])(\d{1,2}):(\d{1,2})(?![\d.:])(?!\s*[ap]\.?m\.?\b)/gi;
|
|
252
|
+
|
|
253
|
+
// A `W:H` token can appear in a brief without being a shape request:
|
|
254
|
+
//
|
|
255
|
+
// "the ratio of sugar to flour is 3:2 in this recipe" -> must not fire
|
|
256
|
+
// "a train departing at 16:9" -> must not fire
|
|
257
|
+
//
|
|
258
|
+
// Deliberately narrow. These match the immediate context of the token only;
|
|
259
|
+
// they are not an attempt at natural-language understanding, and #2252 had to
|
|
260
|
+
// walk back a first cut that over-matched.
|
|
261
|
+
const RATIO_SUPPRESSING_CONTEXT = [
|
|
262
|
+
// Quantity ratios and mixes: "ratio of sugar to flour is 3:2", "a 2:3 mix",
|
|
263
|
+
// "dilute 1:1", "3:2 by weight/volume".
|
|
264
|
+
//
|
|
265
|
+
// Requires the "ratio of X TO Y" quantities construction, and exempts the
|
|
266
|
+
// shape vocabulary. A first cut matched any "ratio of ... N:M" and silently
|
|
267
|
+
// killed "an image with an aspect ratio of 16:9" -- the single most standard
|
|
268
|
+
// way to state a ratio in English, and a far worse failure than the recipe
|
|
269
|
+
// case it was written for (#2252).
|
|
270
|
+
/\bratios?\s+of\b(?![^.!?]*?\b(?:width|height|aspect)\b)[^.!?]*?\bto\b[^.!?]*?\d{1,2}:\d{1,2}/i,
|
|
271
|
+
/\d{1,2}:\d{1,2}\s*(?:dilution|mix(?:ture)?|blend|ratio\s+by|by\s+(?:weight|volume)|odds)\b/i,
|
|
272
|
+
/\b(?:dilut\w*|mix(?:ed|ing)?|blend\w*|steep\w*)\s+(?:it\s+|them\s+)?(?:at\s+|to\s+)?\d{1,2}:\d{1,2}/i,
|
|
273
|
+
// Clock times written without a leading zero: "a train departing at 16:9".
|
|
274
|
+
// `ASPECT_RATIO_TOKEN_RE` already rejects the "9:16 am" form; this covers the
|
|
275
|
+
// bare 24-hour reading.
|
|
276
|
+
//
|
|
277
|
+
// Keyed on a SCHEDULE VERB, not on a bare preposition. "at" alone is
|
|
278
|
+
// ambiguous and suppressing it broke a real request -- "render at 4:3 for the
|
|
279
|
+
// retro TV bumper" is a shape request and must keep firing.
|
|
280
|
+
//
|
|
281
|
+
// `leav` and `board` are deliberately NOT in this list: they collide with
|
|
282
|
+
// ordinary image nouns (autumn leaves, boardwalk, surfboard, keyboard,
|
|
283
|
+
// billboard), so "a photo of autumn leaves at 16:9" would lose its ratio.
|
|
284
|
+
/\b(?:depart|arriv|due|scheduled|timetabled)\w*\s+(?:at|by|around)?\s*\d{1,2}:\d{1,2}\b/i,
|
|
285
|
+
];
|
|
286
|
+
|
|
287
|
+
// Orientation nouns that turn an ambiguous orientation adjective into an
|
|
288
|
+
// unambiguous shape request. "portrait orientation" / "vertical video" /
|
|
289
|
+
// "square canvas" are shape requests; "portrait of a woman" is not.
|
|
290
|
+
const ORIENTATION_NOUN =
|
|
291
|
+
"orientation|format|mode|aspect(?:\\s+ratio)?|ratio|crop|canvas|image|photo|picture|poster|banner|video|frame|composition|layout|thumbnail|cover";
|
|
292
|
+
|
|
293
|
+
function boundOrientationRe(adjective) {
|
|
294
|
+
// "<adj> <noun>" (e.g. "vertical video") or "in <adj>" / "<adj> orientation".
|
|
295
|
+
return new RegExp(
|
|
296
|
+
`\\b(?:in\\s+${adjective}\\b|${adjective}[\\s-]+(?:${ORIENTATION_NOUN})\\b)`,
|
|
297
|
+
"i",
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const ORIENTATION_RULES = [
|
|
302
|
+
// "widescreen" and "ultrawide" are unambiguous on their own -- they have no
|
|
303
|
+
// competing subject/genre meaning.
|
|
304
|
+
{ re: /\bultra[\s-]?wide(?:screen)?\b/i, ratio: "21:9" },
|
|
305
|
+
{ re: /\bwide[\s-]?screen\b/i, ratio: "16:9" },
|
|
306
|
+
// These four need an orientation noun to bind to.
|
|
307
|
+
{ re: boundOrientationRe("portrait"), ratio: "9:16" },
|
|
308
|
+
{ re: boundOrientationRe("vertical"), ratio: "9:16" },
|
|
309
|
+
{ re: boundOrientationRe("landscape"), ratio: "16:9" },
|
|
310
|
+
{ re: boundOrientationRe("horizontal"), ratio: "16:9" },
|
|
311
|
+
{ re: boundOrientationRe("square"), ratio: "1:1" },
|
|
312
|
+
];
|
|
313
|
+
|
|
314
|
+
const GUIDE_ASPECT_RATIO_PROBE_LIMIT = 8;
|
|
315
|
+
|
|
159
316
|
function agentSkillHint(commandPrefix = "luxin") {
|
|
160
317
|
return {
|
|
161
318
|
url: AGENT_SKILL_URL,
|
|
@@ -553,6 +710,7 @@ function commandHelpByKey(key) {
|
|
|
553
710
|
optional_flags: [
|
|
554
711
|
"--guide",
|
|
555
712
|
"--no-spend",
|
|
713
|
+
"--explain",
|
|
556
714
|
"--dry-run",
|
|
557
715
|
"--model",
|
|
558
716
|
"--aspect-ratio",
|
|
@@ -1753,16 +1911,61 @@ async function createGuide(args, options = {}) {
|
|
|
1753
1911
|
return token.result;
|
|
1754
1912
|
}
|
|
1755
1913
|
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1914
|
+
// Explicit aspect ratio stated in the brief, treated as a required capability
|
|
1915
|
+
// during model selection and propagated into the emitted commands
|
|
1916
|
+
// (#2213/#2203/#2209, guards corrected by #2252).
|
|
1917
|
+
const briefAspectRatio = requestedAspectRatioFromBrief({
|
|
1918
|
+
prompt: trimmedPrompt,
|
|
1919
|
+
intent: requestedIntentFlag,
|
|
1920
|
+
});
|
|
1921
|
+
const listedModels =
|
|
1922
|
+
models.envelope.ok && Array.isArray(models.envelope.data?.models)
|
|
1923
|
+
? models.envelope.data.models
|
|
1924
|
+
: null;
|
|
1925
|
+
const selectModel = (candidateModels) =>
|
|
1926
|
+
candidateModels === null
|
|
1927
|
+
? null
|
|
1928
|
+
: selectCreateGuideModel(candidateModels, requestedModelId, {
|
|
1759
1929
|
operation: guideOperation,
|
|
1760
1930
|
prompt: trimmedPrompt,
|
|
1761
1931
|
intent: requestedIntent,
|
|
1762
1932
|
maxEstimatedUsdPerImage,
|
|
1933
|
+
requestedAspectRatio: briefAspectRatio,
|
|
1934
|
+
});
|
|
1935
|
+
let selection = selectModel(listedModels);
|
|
1936
|
+
if (briefAspectRatio !== null && listedModels !== null) {
|
|
1937
|
+
// Two-pass: the first pass names the models worth asking about, then one
|
|
1938
|
+
// bounded round of detail lookups makes the second pass actually
|
|
1939
|
+
// capability-aware instead of guessing.
|
|
1940
|
+
const capabilityModels = await guideModelsWithAspectRatioCapabilities({
|
|
1941
|
+
models: listedModels,
|
|
1942
|
+
modelIds: guideAspectRatioProbeModelIds({
|
|
1943
|
+
models: listedModels,
|
|
1944
|
+
selected: selection?.model ?? null,
|
|
1945
|
+
intent: requestedIntent,
|
|
1946
|
+
}),
|
|
1947
|
+
apiBaseUrl,
|
|
1948
|
+
});
|
|
1949
|
+
if (capabilityModels !== listedModels) {
|
|
1950
|
+
selection = selectModel(capabilityModels);
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
const selected = selection?.model ?? null;
|
|
1954
|
+
const selectedAspectRatio = createGuideSuggestedAspectRatio({
|
|
1955
|
+
model: selected,
|
|
1956
|
+
requestedAspectRatio: briefAspectRatio,
|
|
1957
|
+
});
|
|
1958
|
+
// The brief asked for a ratio and the selected model cannot express it, so no
|
|
1959
|
+
// model could. Say so instead of dropping it silently.
|
|
1960
|
+
const unsupportedAspectRatioWarning =
|
|
1961
|
+
briefAspectRatio !== null &&
|
|
1962
|
+
selectedAspectRatio === null &&
|
|
1963
|
+
selected !== null
|
|
1964
|
+
? aspectRatioUnsupportedWarning({
|
|
1965
|
+
aspectRatio: briefAspectRatio,
|
|
1966
|
+
modelId: selected.id,
|
|
1763
1967
|
})
|
|
1764
1968
|
: null;
|
|
1765
|
-
const selectedAspectRatio = createGuideSuggestedAspectRatio(selected);
|
|
1766
1969
|
const pricingContext = {
|
|
1767
1970
|
aspectRatio: selectedAspectRatio ?? "1:1",
|
|
1768
1971
|
outputCount: 1,
|
|
@@ -1796,25 +1999,16 @@ async function createGuide(args, options = {}) {
|
|
|
1796
1999
|
pricingContext,
|
|
1797
2000
|
)
|
|
1798
2001
|
: createGuideModelCreditPricing(selected);
|
|
1799
|
-
const providerCostEstimate =
|
|
1800
|
-
selected === null || !shouldPriceModelParameters
|
|
1801
|
-
? null
|
|
1802
|
-
: createGuideProviderCostEstimateForModel(
|
|
1803
|
-
selected,
|
|
1804
|
-
defaultedModelParameters.modelParameters,
|
|
1805
|
-
pricingContext,
|
|
1806
|
-
);
|
|
1807
2002
|
const estimatedCredits = pricing?.credits_required ?? null;
|
|
1808
|
-
|
|
1809
|
-
|
|
2003
|
+
// The guide publishes what the caller will be charged, never our provider
|
|
2004
|
+
// cost. `economics.estimated_usd_per_image` is the hosted charge per image.
|
|
2005
|
+
const estimatedDebitUsdPerImage =
|
|
2006
|
+
pricing?.estimated_charge_usd ??
|
|
1810
2007
|
selected?.economics?.estimated_usd_per_image ??
|
|
1811
|
-
pricing?.fallback_provider_cost_usd ??
|
|
1812
2008
|
(typeof selected?.estimated_usd_per_image === "number"
|
|
1813
2009
|
? selected.estimated_usd_per_image
|
|
1814
2010
|
: null) ??
|
|
1815
2011
|
null;
|
|
1816
|
-
const estimatedDebitUsdPerImage =
|
|
1817
|
-
pricing?.estimated_revenue_usd ?? estimatedProviderUsdPerImage;
|
|
1818
2012
|
const budgetGuard =
|
|
1819
2013
|
maxEstimatedUsdPerImage ??
|
|
1820
2014
|
estimatedDebitUsdPerImage ??
|
|
@@ -1867,6 +2061,14 @@ async function createGuide(args, options = {}) {
|
|
|
1867
2061
|
quota,
|
|
1868
2062
|
estimatedCredits,
|
|
1869
2063
|
});
|
|
2064
|
+
// #2204-#2216: `--explain` opts back into the complete payload in every
|
|
2065
|
+
// stage; the compact default is for the agent that just wants the decision.
|
|
2066
|
+
// The payment/self-fund/recovery apparatus is correct when the guide is
|
|
2067
|
+
// blocked and noise when it is not, so shape on that state rather than on a
|
|
2068
|
+
// flag the caller has to know about.
|
|
2069
|
+
const explainOutputMode = flagBool(args, "explain");
|
|
2070
|
+
const readyCompact =
|
|
2071
|
+
stage === "ready_to_create" && blocker === null && !explainOutputMode;
|
|
1870
2072
|
const nextCommand = createGuideNextCommand(stage, {
|
|
1871
2073
|
prompt: trimmedPrompt,
|
|
1872
2074
|
selected,
|
|
@@ -1925,11 +2127,21 @@ async function createGuide(args, options = {}) {
|
|
|
1925
2127
|
noSpendNextCommandLabel,
|
|
1926
2128
|
noSpendNextCommandEffect,
|
|
1927
2129
|
});
|
|
1928
|
-
const
|
|
2130
|
+
const baseGuideWarning = createGuideWarning(stage, {
|
|
1929
2131
|
nextCommandEffect,
|
|
1930
2132
|
paymentSummary,
|
|
1931
2133
|
nextCommandCopyRunnable,
|
|
2134
|
+
readyCompact,
|
|
1932
2135
|
});
|
|
2136
|
+
// Append, never replace: the stage warning carries the spend-safety contract
|
|
2137
|
+
// agents rely on, and the ratio note is additive context.
|
|
2138
|
+
const guideWarning =
|
|
2139
|
+
unsupportedAspectRatioWarning === null
|
|
2140
|
+
? baseGuideWarning
|
|
2141
|
+
: {
|
|
2142
|
+
...baseGuideWarning,
|
|
2143
|
+
warning: `${baseGuideWarning.warning} ${unsupportedAspectRatioWarning}`,
|
|
2144
|
+
};
|
|
1933
2145
|
const quotaTopUp = createGuideQuotaTopUp(
|
|
1934
2146
|
quota?.envelope.data?.top_up ?? null,
|
|
1935
2147
|
);
|
|
@@ -1991,9 +2203,16 @@ async function createGuide(args, options = {}) {
|
|
|
1991
2203
|
// dry-run command and the mutation proof, and keep the self-fund path
|
|
1992
2204
|
// discoverable via self_fund_discovery instead of dominating the response.
|
|
1993
2205
|
const noSpendOutputMode = flagBool(args, "no-spend");
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
2206
|
+
// Keep the pre-wall self-fund nudge whenever it is actually recommended.
|
|
2207
|
+
const selfFundRecommended = selfFundPreparation?.recommended === true;
|
|
2208
|
+
const paymentSummaryOutput =
|
|
2209
|
+
noSpendOutputMode || readyCompact
|
|
2210
|
+
? {
|
|
2211
|
+
...paymentSummary,
|
|
2212
|
+
suggested_commands: [],
|
|
2213
|
+
...(readyCompact ? { preferred_method_summary: null } : {}),
|
|
2214
|
+
}
|
|
2215
|
+
: paymentSummary;
|
|
1997
2216
|
const selfFundHandoffOutput = noSpendOutputMode ? null : selfFundHandoff;
|
|
1998
2217
|
const selfFundPreparationOutput = noSpendOutputMode
|
|
1999
2218
|
? null
|
|
@@ -2030,7 +2249,7 @@ async function createGuide(args, options = {}) {
|
|
|
2030
2249
|
},
|
|
2031
2250
|
),
|
|
2032
2251
|
inspect_methods_command: escapeHatches.payment_methods,
|
|
2033
|
-
note:
|
|
2252
|
+
note: noSpendDiscoveryNote(readyCompact),
|
|
2034
2253
|
},
|
|
2035
2254
|
}
|
|
2036
2255
|
: {
|
|
@@ -2046,7 +2265,49 @@ async function createGuide(args, options = {}) {
|
|
|
2046
2265
|
escapeHatches,
|
|
2047
2266
|
selfFundNextCommand: selfFundNextCommandOutput,
|
|
2048
2267
|
});
|
|
2049
|
-
|
|
2268
|
+
// #2204-#2216: fields the compact ready payload drops. `self_fund_*` only
|
|
2269
|
+
// joins the list when the pre-wall top-up is not recommended.
|
|
2270
|
+
const compactOmittedFields = readyCompact
|
|
2271
|
+
? COMPACT_OMITTABLE_GUIDE_FIELDS.filter(
|
|
2272
|
+
(field) =>
|
|
2273
|
+
selfFundRecommended === false ||
|
|
2274
|
+
!COMPACT_SELF_FUND_GUIDE_FIELDS.has(field),
|
|
2275
|
+
)
|
|
2276
|
+
: [];
|
|
2277
|
+
const compactCompactedFields = readyCompact
|
|
2278
|
+
? [
|
|
2279
|
+
"checks.payments.preferred_method_summary",
|
|
2280
|
+
"checks.payments.suggested_commands",
|
|
2281
|
+
"checks.quota.top_up",
|
|
2282
|
+
]
|
|
2283
|
+
: [];
|
|
2284
|
+
const outputModeData = {
|
|
2285
|
+
schema: "image-skill.guide-output-mode.v1",
|
|
2286
|
+
mode: readyCompact ? "ready_compact" : "full",
|
|
2287
|
+
reason: readyCompact
|
|
2288
|
+
? "stage is ready_to_create with no blocker, so the guide returns the decision, the cost, and the no-spend proof. Payment, self-fund, auth-handoff, and recovery detail belong to blocked stages and are omitted here."
|
|
2289
|
+
: explainOutputMode
|
|
2290
|
+
? "--explain returns every guide field in every stage."
|
|
2291
|
+
: "the guide is not at an unblocked ready_to_create stage, so every field is returned.",
|
|
2292
|
+
omitted_fields: compactOmittedFields,
|
|
2293
|
+
compacted_fields: compactCompactedFields,
|
|
2294
|
+
full_output_command: renderGuideCommand(
|
|
2295
|
+
trimmedPrompt.length > 0 ? trimmedPrompt : "PROMPT",
|
|
2296
|
+
explicitApiBaseUrl(args),
|
|
2297
|
+
guideCommandPrefix,
|
|
2298
|
+
{
|
|
2299
|
+
operation: guideOperation,
|
|
2300
|
+
inputReference: options.inputReference,
|
|
2301
|
+
modelId: requestedModelId,
|
|
2302
|
+
providerId: requestedProviderId,
|
|
2303
|
+
intent: requestedIntentFlag,
|
|
2304
|
+
maxEstimatedUsdPerImage,
|
|
2305
|
+
modelParametersJson: requestedModelParametersJson,
|
|
2306
|
+
explain: true,
|
|
2307
|
+
},
|
|
2308
|
+
),
|
|
2309
|
+
};
|
|
2310
|
+
const guideData = {
|
|
2050
2311
|
schema:
|
|
2051
2312
|
guideOperation === "edit"
|
|
2052
2313
|
? "image-skill.edit-guide.v1"
|
|
@@ -2086,7 +2347,7 @@ async function createGuide(args, options = {}) {
|
|
|
2086
2347
|
required_credits: estimatedCredits,
|
|
2087
2348
|
daily_jobs_remaining:
|
|
2088
2349
|
quota?.envelope.data?.daily_jobs?.remaining ?? null,
|
|
2089
|
-
top_up: quotaTopUp,
|
|
2350
|
+
top_up: readyCompact ? null : quotaTopUp,
|
|
2090
2351
|
reason:
|
|
2091
2352
|
quota === null
|
|
2092
2353
|
? "auth_required"
|
|
@@ -2109,20 +2370,28 @@ async function createGuide(args, options = {}) {
|
|
|
2109
2370
|
modality: selected.modality ?? null,
|
|
2110
2371
|
suggested_aspect_ratio: selectedAspectRatio,
|
|
2111
2372
|
reason:
|
|
2112
|
-
requestedModelId
|
|
2113
|
-
?
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2373
|
+
requestedModelId !== null
|
|
2374
|
+
? createGuideRequestedSelectionReason(selected)
|
|
2375
|
+
: selection?.aspectRatioRoutedFrom != null &&
|
|
2376
|
+
briefAspectRatio !== null
|
|
2377
|
+
? aspectRatioRoutedCreateSelectionReason({
|
|
2378
|
+
aspectRatio: briefAspectRatio,
|
|
2379
|
+
skippedModelId: selection.aspectRatioRoutedFrom,
|
|
2380
|
+
})
|
|
2381
|
+
: selection?.aspectRatioCapableFallback === true &&
|
|
2382
|
+
briefAspectRatio !== null
|
|
2383
|
+
? `guide selected an available model that supports the requested ${briefAspectRatio} aspect ratio`
|
|
2384
|
+
: createGuideSelectionReason(
|
|
2385
|
+
selected,
|
|
2386
|
+
trimmedPrompt,
|
|
2387
|
+
requestedIntent,
|
|
2388
|
+
guideOperation,
|
|
2389
|
+
),
|
|
2120
2390
|
},
|
|
2121
2391
|
cost: {
|
|
2122
2392
|
estimated_credits: estimatedCredits,
|
|
2123
2393
|
estimated_usd_per_image: estimatedDebitUsdPerImage,
|
|
2124
2394
|
estimated_debit_usd_per_image: estimatedDebitUsdPerImage,
|
|
2125
|
-
estimated_provider_usd_per_image: estimatedProviderUsdPerImage,
|
|
2126
2395
|
credit_unit_usd: pricing?.credit_unit_usd ?? CREDIT_UNIT_USD,
|
|
2127
2396
|
pricing_confidence: pricing?.pricing_confidence ?? null,
|
|
2128
2397
|
pricing_source: pricing?.pricing_source ?? null,
|
|
@@ -2152,6 +2421,7 @@ async function createGuide(args, options = {}) {
|
|
|
2152
2421
|
auth_handoff: authHandoff,
|
|
2153
2422
|
escape_hatches: escapeHatches,
|
|
2154
2423
|
no_spend_output_mode: noSpendOutputModeData,
|
|
2424
|
+
output_mode: outputModeData,
|
|
2155
2425
|
mutation: {
|
|
2156
2426
|
provider_call: false,
|
|
2157
2427
|
hosted_create: false,
|
|
@@ -2160,7 +2430,11 @@ async function createGuide(args, options = {}) {
|
|
|
2160
2430
|
credit_debit: false,
|
|
2161
2431
|
media_write: false,
|
|
2162
2432
|
},
|
|
2163
|
-
}
|
|
2433
|
+
};
|
|
2434
|
+
for (const field of compactOmittedFields) {
|
|
2435
|
+
delete guideData[field];
|
|
2436
|
+
}
|
|
2437
|
+
return createGuideSuccess(command, quota?.envelope.actor ?? null, guideData);
|
|
2164
2438
|
}
|
|
2165
2439
|
|
|
2166
2440
|
function createGuideSuccess(command, actor, data) {
|
|
@@ -2177,6 +2451,12 @@ function selectCreateGuideModel(
|
|
|
2177
2451
|
prompt = "",
|
|
2178
2452
|
intent = undefined,
|
|
2179
2453
|
maxEstimatedUsdPerImage = null,
|
|
2454
|
+
/**
|
|
2455
|
+
* Aspect ratio explicitly requested by the brief, treated as a REQUIRED
|
|
2456
|
+
* capability during selection (#2213/#2203/#2209) the same way a
|
|
2457
|
+
* reproducibility need is. Null when the brief states no ratio.
|
|
2458
|
+
*/
|
|
2459
|
+
requestedAspectRatio = null,
|
|
2180
2460
|
} = {},
|
|
2181
2461
|
) {
|
|
2182
2462
|
const isExecutableCreate = (model) =>
|
|
@@ -2194,10 +2474,18 @@ function selectCreateGuideModel(
|
|
|
2194
2474
|
operation === "edit"
|
|
2195
2475
|
? isExecutableInputImageEdit(model)
|
|
2196
2476
|
: isExecutableCreate(model) || isExecutableInputImageEdit(model);
|
|
2477
|
+
const requestedRatio = requestedAspectRatio ?? null;
|
|
2478
|
+
const chosen = (
|
|
2479
|
+
model,
|
|
2480
|
+
{ aspectRatioRoutedFrom = null, aspectRatioCapableFallback = false } = {},
|
|
2481
|
+
) =>
|
|
2482
|
+
model === undefined || model === null
|
|
2483
|
+
? null
|
|
2484
|
+
: { model, aspectRatioRoutedFrom, aspectRatioCapableFallback };
|
|
2197
2485
|
if (requestedModelId !== null) {
|
|
2198
2486
|
const requested = resolveModelLookup(requestedModelId, models);
|
|
2199
2487
|
return requested !== undefined && isExecutableGuideModel(requested)
|
|
2200
|
-
? requested
|
|
2488
|
+
? chosen(requested)
|
|
2201
2489
|
: null;
|
|
2202
2490
|
}
|
|
2203
2491
|
const candidates =
|
|
@@ -2214,7 +2502,7 @@ function selectCreateGuideModel(
|
|
|
2214
2502
|
});
|
|
2215
2503
|
const threeDimensional = eligible3d[0];
|
|
2216
2504
|
if (threeDimensional !== undefined) {
|
|
2217
|
-
return threeDimensional;
|
|
2505
|
+
return chosen(threeDimensional);
|
|
2218
2506
|
}
|
|
2219
2507
|
}
|
|
2220
2508
|
const eligible = guideCandidatesWithinBudget({
|
|
@@ -2224,23 +2512,151 @@ function selectCreateGuideModel(
|
|
|
2224
2512
|
if (createGuideImpliesAudio({ prompt, intent })) {
|
|
2225
2513
|
const audio = eligible.find((model) => model?.modality === "audio");
|
|
2226
2514
|
if (audio !== undefined) {
|
|
2227
|
-
return audio;
|
|
2515
|
+
return chosen(audio);
|
|
2228
2516
|
}
|
|
2229
2517
|
}
|
|
2230
2518
|
if (createGuideImpliesVideo({ prompt, intent })) {
|
|
2231
|
-
const
|
|
2519
|
+
const videoCandidates = eligible.filter(
|
|
2520
|
+
(model) => model?.modality === "video",
|
|
2521
|
+
);
|
|
2522
|
+
const video =
|
|
2523
|
+
videoCandidates.find(
|
|
2524
|
+
(model) => !modelCannotExpressAspectRatio(model, requestedRatio),
|
|
2525
|
+
) ?? videoCandidates[0];
|
|
2232
2526
|
if (video !== undefined) {
|
|
2233
|
-
return video;
|
|
2527
|
+
return chosen(video);
|
|
2234
2528
|
}
|
|
2235
2529
|
}
|
|
2236
2530
|
const intentClass = createGuideIntentClass(intent);
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2531
|
+
const preferredCandidates = preferredCreateGuideModelIds(intentClass)
|
|
2532
|
+
.map((modelId) => eligible.find((model) => model?.id === modelId))
|
|
2533
|
+
.filter((model) => model !== undefined);
|
|
2534
|
+
const [topPreferred] = preferredCandidates;
|
|
2535
|
+
|
|
2536
|
+
// An explicitly requested aspect ratio is a REQUIRED capability, so walk the
|
|
2537
|
+
// curated preference list in strength order but skip models that are KNOWN
|
|
2538
|
+
// not to express the ratio through the normalized `--aspect-ratio` control
|
|
2539
|
+
// (#2213/#2203/#2209). Unknown capability is not a denial: see
|
|
2540
|
+
// `modelAspectRatioSupport`.
|
|
2541
|
+
const preferredSupportingRatio = preferredCandidates.find(
|
|
2542
|
+
(model) => !modelCannotExpressAspectRatio(model, requestedRatio),
|
|
2543
|
+
);
|
|
2544
|
+
if (preferredSupportingRatio !== undefined) {
|
|
2545
|
+
const routedForAspectRatio =
|
|
2546
|
+
requestedRatio !== null &&
|
|
2547
|
+
topPreferred !== undefined &&
|
|
2548
|
+
topPreferred.id !== preferredSupportingRatio.id;
|
|
2549
|
+
return chosen(preferredSupportingRatio, {
|
|
2550
|
+
aspectRatioRoutedFrom: routedForAspectRatio ? topPreferred.id : null,
|
|
2551
|
+
});
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
// No curated model can express the ratio: fall out to the strongest
|
|
2555
|
+
// remaining executable candidate that provably can, before degrading to the
|
|
2556
|
+
// ratio-blind fallback.
|
|
2557
|
+
if (requestedRatio !== null) {
|
|
2558
|
+
const capable = eligible.find(
|
|
2559
|
+
(model) => modelAspectRatioSupport(model, requestedRatio) === true,
|
|
2560
|
+
);
|
|
2561
|
+
if (capable !== undefined) {
|
|
2562
|
+
return chosen(capable, {
|
|
2563
|
+
aspectRatioRoutedFrom: topPreferred?.id ?? null,
|
|
2564
|
+
aspectRatioCapableFallback: topPreferred === undefined,
|
|
2565
|
+
});
|
|
2241
2566
|
}
|
|
2242
2567
|
}
|
|
2243
|
-
|
|
2568
|
+
|
|
2569
|
+
// Nothing can express the requested ratio (or none was requested): keep the
|
|
2570
|
+
// ordinary selection exactly. The caller emits an honest guide_warning rather
|
|
2571
|
+
// than letting the ratio disappear silently.
|
|
2572
|
+
if (topPreferred !== undefined) {
|
|
2573
|
+
return chosen(topPreferred);
|
|
2574
|
+
}
|
|
2575
|
+
return chosen(eligible[0]);
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
// The hosted `/v1/models` list is a compact summary and does not publish
|
|
2579
|
+
// `media.input.aspect_ratios.values`, so a stated ratio cannot be checked
|
|
2580
|
+
// against it. Probe the detail endpoint for the handful of models the guide
|
|
2581
|
+
// would actually route between -- never the whole catalog, whose
|
|
2582
|
+
// `?details=true` payload is multiple megabytes.
|
|
2583
|
+
//
|
|
2584
|
+
// Fail-open: an unreachable, errored, or malformed detail response leaves the
|
|
2585
|
+
// capability UNKNOWN, which never disqualifies a model and never triggers the
|
|
2586
|
+
// unsupported-ratio warning.
|
|
2587
|
+
|
|
2588
|
+
function guideAspectRatioProbeModelIds({ models, selected, intent }) {
|
|
2589
|
+
const ids = [];
|
|
2590
|
+
if (typeof selected?.id === "string") {
|
|
2591
|
+
ids.push(selected.id);
|
|
2592
|
+
}
|
|
2593
|
+
for (const modelId of preferredCreateGuideModelIds(
|
|
2594
|
+
createGuideIntentClass(intent),
|
|
2595
|
+
)) {
|
|
2596
|
+
if (models.some((model) => model?.id === modelId)) {
|
|
2597
|
+
ids.push(modelId);
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
return [...new Set(ids)].slice(0, GUIDE_ASPECT_RATIO_PROBE_LIMIT);
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
async function guideModelsWithAspectRatioCapabilities({
|
|
2604
|
+
models,
|
|
2605
|
+
modelIds,
|
|
2606
|
+
apiBaseUrl,
|
|
2607
|
+
}) {
|
|
2608
|
+
const missing = modelIds.filter((modelId) => {
|
|
2609
|
+
const model = models.find((candidate) => candidate?.id === modelId);
|
|
2610
|
+
return model !== undefined && guideModelAspectRatioValues(model) === null;
|
|
2611
|
+
});
|
|
2612
|
+
if (missing.length === 0) {
|
|
2613
|
+
return models;
|
|
2614
|
+
}
|
|
2615
|
+
const probes = await Promise.all(
|
|
2616
|
+
missing.map(async (modelId) => {
|
|
2617
|
+
try {
|
|
2618
|
+
const detail = await apiRequest({
|
|
2619
|
+
command: "luxin create --guide",
|
|
2620
|
+
method: "GET",
|
|
2621
|
+
apiBaseUrl,
|
|
2622
|
+
path: `/v1/models/${encodeURIComponent(modelId)}`,
|
|
2623
|
+
});
|
|
2624
|
+
const values = guideModelAspectRatioValues(
|
|
2625
|
+
detail?.envelope?.data?.model ?? null,
|
|
2626
|
+
);
|
|
2627
|
+
return values === null ? null : { modelId, values };
|
|
2628
|
+
} catch {
|
|
2629
|
+
return null;
|
|
2630
|
+
}
|
|
2631
|
+
}),
|
|
2632
|
+
);
|
|
2633
|
+
const resolved = new Map(
|
|
2634
|
+
probes
|
|
2635
|
+
.filter((probe) => probe !== null)
|
|
2636
|
+
.map((probe) => [probe.modelId, probe.values]),
|
|
2637
|
+
);
|
|
2638
|
+
if (resolved.size === 0) {
|
|
2639
|
+
return models;
|
|
2640
|
+
}
|
|
2641
|
+
return models.map((model) => {
|
|
2642
|
+
const values = resolved.get(model?.id);
|
|
2643
|
+
if (values === undefined) {
|
|
2644
|
+
return model;
|
|
2645
|
+
}
|
|
2646
|
+
return {
|
|
2647
|
+
...model,
|
|
2648
|
+
media: {
|
|
2649
|
+
...model?.media,
|
|
2650
|
+
input: {
|
|
2651
|
+
...model?.media?.input,
|
|
2652
|
+
aspect_ratios: {
|
|
2653
|
+
...model?.media?.input?.aspect_ratios,
|
|
2654
|
+
values,
|
|
2655
|
+
},
|
|
2656
|
+
},
|
|
2657
|
+
},
|
|
2658
|
+
};
|
|
2659
|
+
});
|
|
2244
2660
|
}
|
|
2245
2661
|
|
|
2246
2662
|
const MODEL_ALIAS_RULES = [
|
|
@@ -2412,29 +2828,47 @@ function createGuideIntentClass(intent) {
|
|
|
2412
2828
|
return "general";
|
|
2413
2829
|
}
|
|
2414
2830
|
|
|
2831
|
+
// Mirrors src/hosted-create-policy.ts (#2231). budget_draft is a superset of
|
|
2832
|
+
// general and both are ordered ascending by charged credits, so a
|
|
2833
|
+
// cost-minimizing intent can never select a model priced above the general
|
|
2834
|
+
// default. final keeps the quality-premium lead.
|
|
2415
2835
|
function preferredCreateGuideModelIds(intentClass) {
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2836
|
+
if (intentClass === "budget_draft") {
|
|
2837
|
+
return [
|
|
2838
|
+
"fal.flux-1-schnell",
|
|
2839
|
+
"fal.flux-1-dev",
|
|
2840
|
+
"xai.grok-imagine-image",
|
|
2841
|
+
"fal.bria-text-to-image-fast",
|
|
2842
|
+
"xai.grok-imagine-image-quality",
|
|
2843
|
+
"openai.gpt-image-2",
|
|
2844
|
+
];
|
|
2845
|
+
}
|
|
2846
|
+
if (intentClass === "final") {
|
|
2847
|
+
return [
|
|
2848
|
+
"xai.grok-imagine-image-quality",
|
|
2849
|
+
"openai.gpt-image-2",
|
|
2850
|
+
"fal.flux-1-dev",
|
|
2851
|
+
"xai.grok-imagine-image",
|
|
2852
|
+
"fal.flux-1-schnell",
|
|
2853
|
+
"fal.bria-text-to-image-fast",
|
|
2854
|
+
];
|
|
2855
|
+
}
|
|
2856
|
+
return [
|
|
2857
|
+
"fal.flux-1-dev",
|
|
2858
|
+
"xai.grok-imagine-image",
|
|
2859
|
+
"xai.grok-imagine-image-quality",
|
|
2860
|
+
"openai.gpt-image-2",
|
|
2861
|
+
"fal.flux-1-schnell",
|
|
2862
|
+
"fal.bria-text-to-image-fast",
|
|
2863
|
+
];
|
|
2429
2864
|
}
|
|
2430
2865
|
|
|
2431
2866
|
function guideBudgetUsdForModel(model) {
|
|
2867
|
+
// Budget caps compare the credit debit -- what the caller pays.
|
|
2432
2868
|
const pricing = createGuideModelCreditPricing(model);
|
|
2433
2869
|
return (
|
|
2434
|
-
pricing?.
|
|
2870
|
+
pricing?.estimated_charge_usd ??
|
|
2435
2871
|
model?.economics?.estimated_usd_per_image ??
|
|
2436
|
-
pricing?.estimated_provider_cost_usd ??
|
|
2437
|
-
pricing?.fallback_provider_cost_usd ??
|
|
2438
2872
|
(typeof model?.estimated_usd_per_image === "number"
|
|
2439
2873
|
? model.estimated_usd_per_image
|
|
2440
2874
|
: null) ??
|
|
@@ -2450,7 +2884,7 @@ function createGuideDefaultModelParameters(input) {
|
|
|
2450
2884
|
input.model?.id === "xai.grok-imagine-image-quality" &&
|
|
2451
2885
|
modelParameters.resolution === undefined
|
|
2452
2886
|
) {
|
|
2453
|
-
// Budget defaults must compare the credit debit (
|
|
2887
|
+
// Budget defaults must compare the credit debit (estimated_charge_usd),
|
|
2454
2888
|
// the same basis the hosted budget guard enforces, not the provider cost.
|
|
2455
2889
|
const twoKPricing = createGuidePricingForModel(
|
|
2456
2890
|
input.model,
|
|
@@ -2459,8 +2893,8 @@ function createGuideDefaultModelParameters(input) {
|
|
|
2459
2893
|
);
|
|
2460
2894
|
const twoKAllowedByBudget =
|
|
2461
2895
|
input.maxEstimatedUsdPerImage === null ||
|
|
2462
|
-
typeof twoKPricing?.
|
|
2463
|
-
twoKPricing.
|
|
2896
|
+
typeof twoKPricing?.estimated_charge_usd !== "number" ||
|
|
2897
|
+
twoKPricing.estimated_charge_usd <= input.maxEstimatedUsdPerImage;
|
|
2464
2898
|
const intentClass = createGuideIntentClass(input.intent);
|
|
2465
2899
|
const resolution =
|
|
2466
2900
|
intentClass !== "budget_draft" && twoKAllowedByBudget ? "2k" : "1k";
|
|
@@ -2490,8 +2924,8 @@ function createGuideDefaultModelParameters(input) {
|
|
|
2490
2924
|
);
|
|
2491
2925
|
const mediumAllowedByBudget =
|
|
2492
2926
|
input.maxEstimatedUsdPerImage === null ||
|
|
2493
|
-
typeof mediumPricing?.
|
|
2494
|
-
mediumPricing.
|
|
2927
|
+
typeof mediumPricing?.estimated_charge_usd !== "number" ||
|
|
2928
|
+
mediumPricing.estimated_charge_usd <= input.maxEstimatedUsdPerImage;
|
|
2495
2929
|
if (mediumAllowedByBudget) {
|
|
2496
2930
|
modelParameters.quality = "medium";
|
|
2497
2931
|
defaultsApplied.push("quality=medium");
|
|
@@ -2502,45 +2936,17 @@ function createGuideDefaultModelParameters(input) {
|
|
|
2502
2936
|
}
|
|
2503
2937
|
|
|
2504
2938
|
function createGuidePricingForModel(model, modelParameters, context = {}) {
|
|
2505
|
-
|
|
2506
|
-
model,
|
|
2507
|
-
modelParameters,
|
|
2508
|
-
context,
|
|
2509
|
-
);
|
|
2510
|
-
if (estimate.estimated_provider_cost_usd === null) {
|
|
2511
|
-
return createGuideModelCreditPricing(model);
|
|
2939
|
+
if (createGuideCanPriceModelParameters(model)) {
|
|
2940
|
+
return createGuideXaiImagePricing(model, modelParameters, context);
|
|
2512
2941
|
}
|
|
2513
|
-
return
|
|
2514
|
-
providerCostUsd: estimate.estimated_provider_cost_usd,
|
|
2515
|
-
pricingConfidence: estimate.pricing_confidence,
|
|
2516
|
-
pricingSource: estimate.pricing_source,
|
|
2517
|
-
});
|
|
2942
|
+
return createGuideModelCreditPricing(model);
|
|
2518
2943
|
}
|
|
2519
2944
|
|
|
2520
2945
|
function createGuideCanPriceModelParameters(model) {
|
|
2521
2946
|
return String(model?.id ?? "").startsWith("xai.grok-imagine-image");
|
|
2522
2947
|
}
|
|
2523
2948
|
|
|
2524
|
-
function
|
|
2525
|
-
model,
|
|
2526
|
-
modelParameters = {},
|
|
2527
|
-
context = {},
|
|
2528
|
-
) {
|
|
2529
|
-
if (String(model?.id ?? "").startsWith("xai.grok-imagine-image")) {
|
|
2530
|
-
return createGuideXaiImageCostEstimate(model, modelParameters, context);
|
|
2531
|
-
}
|
|
2532
|
-
return {
|
|
2533
|
-
estimated_provider_cost_usd:
|
|
2534
|
-
typeof model?.economics?.estimated_usd_per_image === "number"
|
|
2535
|
-
? model.economics.estimated_usd_per_image
|
|
2536
|
-
: (createGuideModelCreditPricing(model)?.estimated_provider_cost_usd ??
|
|
2537
|
-
null),
|
|
2538
|
-
pricing_source: "model_registry",
|
|
2539
|
-
pricing_confidence: "known",
|
|
2540
|
-
};
|
|
2541
|
-
}
|
|
2542
|
-
|
|
2543
|
-
function createGuideXaiImageCostEstimate(model, modelParameters, context) {
|
|
2949
|
+
function createGuideXaiImagePricing(model, modelParameters, context) {
|
|
2544
2950
|
const modelId = String(model?.id ?? "");
|
|
2545
2951
|
const quality = modelId.includes("-quality");
|
|
2546
2952
|
const edit = modelId.endsWith("-edit");
|
|
@@ -2555,11 +2961,20 @@ function createGuideXaiImageCostEstimate(model, modelParameters, context) {
|
|
|
2555
2961
|
? context.referenceAssetCount
|
|
2556
2962
|
: 0;
|
|
2557
2963
|
const sourceImageCount = edit ? 1 + referenceAssetCount : 0;
|
|
2558
|
-
const
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2964
|
+
const sourceCredits = quality
|
|
2965
|
+
? XAI_IMAGE_CREDIT_PRICES.source_image.quality
|
|
2966
|
+
: XAI_IMAGE_CREDIT_PRICES.source_image.standard;
|
|
2967
|
+
const outputCredits = quality
|
|
2968
|
+
? XAI_IMAGE_CREDIT_PRICES.output_image.quality[resolution]
|
|
2969
|
+
: XAI_IMAGE_CREDIT_PRICES.output_image.standard;
|
|
2970
|
+
const creditsRequired = Math.max(
|
|
2971
|
+
1,
|
|
2972
|
+
Math.ceil(
|
|
2973
|
+
roundUsdMicro(
|
|
2974
|
+
sourceCredits * sourceImageCount + outputCredits * outputImageCount,
|
|
2975
|
+
),
|
|
2976
|
+
),
|
|
2977
|
+
);
|
|
2563
2978
|
const defaultResolution =
|
|
2564
2979
|
modelParameters?.resolution === undefined ||
|
|
2565
2980
|
modelParameters?.resolution === null ||
|
|
@@ -2568,67 +2983,12 @@ function createGuideXaiImageCostEstimate(model, modelParameters, context) {
|
|
|
2568
2983
|
defaultResolution &&
|
|
2569
2984
|
outputImageCount === 1 &&
|
|
2570
2985
|
sourceImageCount === (edit ? 1 : 0);
|
|
2571
|
-
return {
|
|
2572
|
-
estimated_provider_cost_usd: roundUsdMicro(
|
|
2573
|
-
inputUsdPerImage * sourceImageCount +
|
|
2574
|
-
outputUsdPerImage * outputImageCount,
|
|
2575
|
-
),
|
|
2576
|
-
pricing_source: defaultShape ? "model_registry" : "model_parameters",
|
|
2577
|
-
pricing_confidence: "known",
|
|
2578
|
-
};
|
|
2579
|
-
}
|
|
2580
|
-
|
|
2581
|
-
function createGuideCreditPricingForProviderCost(input) {
|
|
2582
|
-
const providerCostUsd = roundUsdMicro(input.providerCostUsd);
|
|
2583
|
-
const creditsRequired = Math.max(
|
|
2584
|
-
1,
|
|
2585
|
-
Math.ceil(
|
|
2586
|
-
roundUsdMicro(
|
|
2587
|
-
providerCostUsd / (1 - TARGET_GROSS_MARGIN) / CREDIT_UNIT_USD,
|
|
2588
|
-
),
|
|
2589
|
-
),
|
|
2590
|
-
);
|
|
2591
|
-
const estimatedRevenueUsd = roundUsd(creditsRequired * CREDIT_UNIT_USD);
|
|
2592
|
-
const estimatedPaymentFeeUsd = roundUsdMicro(
|
|
2593
|
-
estimatedRevenueUsd * PAYMENT_BACKED_CREDIT_PAYMENT_FEE_RATE,
|
|
2594
|
-
);
|
|
2595
|
-
const estimatedNetRevenueUsd = roundUsdMicro(
|
|
2596
|
-
estimatedRevenueUsd - estimatedPaymentFeeUsd,
|
|
2597
|
-
);
|
|
2598
|
-
const estimatedGrossMargin =
|
|
2599
|
-
estimatedRevenueUsd > 0
|
|
2600
|
-
? roundRatio(
|
|
2601
|
-
(estimatedRevenueUsd - providerCostUsd) / estimatedRevenueUsd,
|
|
2602
|
-
)
|
|
2603
|
-
: null;
|
|
2604
|
-
const estimatedFeeAdjustedMargin =
|
|
2605
|
-
estimatedRevenueUsd > 0
|
|
2606
|
-
? roundRatio(
|
|
2607
|
-
(estimatedNetRevenueUsd - providerCostUsd) / estimatedRevenueUsd,
|
|
2608
|
-
)
|
|
2609
|
-
: null;
|
|
2610
|
-
const selfFundBlockReason =
|
|
2611
|
-
estimatedNetRevenueUsd + 1e-9 < providerCostUsd
|
|
2612
|
-
? "payment_fee_margin_negative"
|
|
2613
|
-
: null;
|
|
2614
2986
|
return {
|
|
2615
2987
|
credits_required: creditsRequired,
|
|
2616
2988
|
credit_unit_usd: CREDIT_UNIT_USD,
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
estimated_gross_margin: estimatedGrossMargin,
|
|
2621
|
-
payment_fee_rate: PAYMENT_BACKED_CREDIT_PAYMENT_FEE_RATE,
|
|
2622
|
-
payment_fee_model: PAYMENT_BACKED_CREDIT_PAYMENT_FEE_MODEL,
|
|
2623
|
-
estimated_payment_fee_usd: estimatedPaymentFeeUsd,
|
|
2624
|
-
estimated_net_revenue_usd: estimatedNetRevenueUsd,
|
|
2625
|
-
estimated_fee_adjusted_margin: estimatedFeeAdjustedMargin,
|
|
2626
|
-
self_fundable: selfFundBlockReason === null,
|
|
2627
|
-
self_fund_block_reason: selfFundBlockReason,
|
|
2628
|
-
target_gross_margin: TARGET_GROSS_MARGIN,
|
|
2629
|
-
pricing_confidence: input.pricingConfidence,
|
|
2630
|
-
pricing_source: input.pricingSource,
|
|
2631
|
-
margin_model: "provider_cost_plus_margin",
|
|
2989
|
+
estimated_charge_usd: roundUsd(creditsRequired * CREDIT_UNIT_USD),
|
|
2990
|
+
pricing_confidence: "known",
|
|
2991
|
+
pricing_source: defaultShape ? "model_registry" : "model_parameters",
|
|
2632
2992
|
};
|
|
2633
2993
|
}
|
|
2634
2994
|
|
|
@@ -2653,10 +3013,6 @@ function roundUsd(value) {
|
|
|
2653
3013
|
return Math.round(value * 100) / 100;
|
|
2654
3014
|
}
|
|
2655
3015
|
|
|
2656
|
-
function roundRatio(value) {
|
|
2657
|
-
return Math.round(value * 1000) / 1000;
|
|
2658
|
-
}
|
|
2659
|
-
|
|
2660
3016
|
function roundUsdMicro(value) {
|
|
2661
3017
|
return Math.round(value * 1_000_000) / 1_000_000;
|
|
2662
3018
|
}
|
|
@@ -2682,16 +3038,8 @@ function createGuideModelCreditPricing(model) {
|
|
|
2682
3038
|
}
|
|
2683
3039
|
return {
|
|
2684
3040
|
credits_required: model.credits_required,
|
|
2685
|
-
estimated_revenue_usd:
|
|
2686
|
-
typeof model.credits_required === "number"
|
|
2687
|
-
? model.credits_required * CREDIT_UNIT_USD
|
|
2688
|
-
: null,
|
|
2689
|
-
estimated_provider_cost_usd:
|
|
2690
|
-
typeof model?.estimated_usd_per_image === "number"
|
|
2691
|
-
? model.estimated_usd_per_image
|
|
2692
|
-
: null,
|
|
2693
|
-
fallback_provider_cost_usd: null,
|
|
2694
3041
|
credit_unit_usd: CREDIT_UNIT_USD,
|
|
3042
|
+
estimated_charge_usd: roundUsd(model.credits_required * CREDIT_UNIT_USD),
|
|
2695
3043
|
pricing_confidence:
|
|
2696
3044
|
typeof model?.pricing_confidence === "string"
|
|
2697
3045
|
? model.pricing_confidence
|
|
@@ -2726,14 +3074,124 @@ function createGuideImpliesVideo(input) {
|
|
|
2726
3074
|
);
|
|
2727
3075
|
}
|
|
2728
3076
|
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
3077
|
+
// Extract an explicitly requested aspect ratio from a create/edit brief.
|
|
3078
|
+
//
|
|
3079
|
+
// Precedence:
|
|
3080
|
+
// 1. An explicit `W:H` token from EXPLICIT_ASPECT_RATIOS wins over any
|
|
3081
|
+
// orientation word -- it is the most specific statement of intent.
|
|
3082
|
+
// 2. Otherwise a bound orientation phrase is used.
|
|
3083
|
+
//
|
|
3084
|
+
// Conflicts resolve to null (no request) rather than to a guess: two different
|
|
3085
|
+
// explicit ratios, or two orientation phrases implying different shapes, mean
|
|
3086
|
+
// the brief is ambiguous and the guide should not pick a side.
|
|
3087
|
+
function classifyRequestedAspectRatio(input) {
|
|
3088
|
+
const text = `${input?.intent ?? ""} ${input?.prompt ?? ""}`.trim();
|
|
3089
|
+
if (text.length === 0) {
|
|
3090
|
+
return { aspectRatio: null, signal: null };
|
|
3091
|
+
}
|
|
3092
|
+
|
|
3093
|
+
const ratioTokenIsSuppressed = RATIO_SUPPRESSING_CONTEXT.some((re) =>
|
|
3094
|
+
re.test(text),
|
|
3095
|
+
);
|
|
3096
|
+
|
|
3097
|
+
const explicit = new Set();
|
|
3098
|
+
if (!ratioTokenIsSuppressed) {
|
|
3099
|
+
for (const match of text.matchAll(ASPECT_RATIO_TOKEN_RE)) {
|
|
3100
|
+
const token = `${match[1]}:${match[2]}`;
|
|
3101
|
+
if (EXPLICIT_ASPECT_RATIOS.includes(token)) {
|
|
3102
|
+
explicit.add(token);
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
if (explicit.size === 1) {
|
|
3107
|
+
const [only] = [...explicit];
|
|
3108
|
+
return { aspectRatio: only ?? null, signal: "explicit_ratio_token" };
|
|
3109
|
+
}
|
|
3110
|
+
if (explicit.size > 1) {
|
|
3111
|
+
// Contradictory explicit ratios: refuse to guess.
|
|
3112
|
+
return { aspectRatio: null, signal: null };
|
|
2732
3113
|
}
|
|
3114
|
+
|
|
3115
|
+
const implied = new Set();
|
|
3116
|
+
for (const rule of ORIENTATION_RULES) {
|
|
3117
|
+
if (rule.re.test(text)) {
|
|
3118
|
+
implied.add(rule.ratio);
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
if (implied.size === 1) {
|
|
3122
|
+
const [only] = [...implied];
|
|
3123
|
+
return { aspectRatio: only ?? null, signal: "orientation_phrase" };
|
|
3124
|
+
}
|
|
3125
|
+
return { aspectRatio: null, signal: null };
|
|
3126
|
+
}
|
|
3127
|
+
|
|
3128
|
+
function requestedAspectRatioFromBrief(input) {
|
|
3129
|
+
return classifyRequestedAspectRatio(input).aspectRatio;
|
|
3130
|
+
}
|
|
3131
|
+
|
|
3132
|
+
// The normalized ratios a model publishes, or null when the payload does not
|
|
3133
|
+
// say. The hosted `/v1/models` list is a compact summary and omits the `media`
|
|
3134
|
+
// block entirely, so "not published" is the common case here and must be kept
|
|
3135
|
+
// distinct from "published and does not include this ratio".
|
|
3136
|
+
function guideModelAspectRatioValues(model) {
|
|
2733
3137
|
const values = Array.isArray(model?.media?.input?.aspect_ratios?.values)
|
|
2734
3138
|
? model.media.input.aspect_ratios.values
|
|
2735
3139
|
: model?.aspect_ratios;
|
|
2736
|
-
|
|
3140
|
+
return Array.isArray(values) ? values : null;
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
// Tri-state on purpose: true (published and supported), false (published and
|
|
3144
|
+
// NOT supported), null (capability unknown). Unknown must never be read as a
|
|
3145
|
+
// denial -- doing so would make every ratio request in production claim "no
|
|
3146
|
+
// available model supports this", which is worse than the bug being fixed.
|
|
3147
|
+
function modelAspectRatioSupport(model, aspectRatio) {
|
|
3148
|
+
if (aspectRatio === null || aspectRatio === undefined) {
|
|
3149
|
+
return true;
|
|
3150
|
+
}
|
|
3151
|
+
const values = guideModelAspectRatioValues(model);
|
|
3152
|
+
return values === null ? null : values.includes(aspectRatio);
|
|
3153
|
+
}
|
|
3154
|
+
|
|
3155
|
+
function modelCannotExpressAspectRatio(model, aspectRatio) {
|
|
3156
|
+
return modelAspectRatioSupport(model, aspectRatio) === false;
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
// Honest capability note for the case where an explicitly requested aspect
|
|
3160
|
+
// ratio made the guide skip its normal top pick. Name the capability that
|
|
3161
|
+
// forced the hop so agents learn which model could not express the shape.
|
|
3162
|
+
function aspectRatioRoutedCreateSelectionReason(input) {
|
|
3163
|
+
return `guide selected a create model that supports the requested ${input.aspectRatio} aspect ratio because ${input.skippedModelId} does not expose ${input.aspectRatio}`;
|
|
3164
|
+
}
|
|
3165
|
+
|
|
3166
|
+
// Fail-honestly note for the case where NO available model can express the
|
|
3167
|
+
// requested ratio. The guide keeps its normal selection and says so rather than
|
|
3168
|
+
// silently dropping the ratio, which is the exact failure this fixes.
|
|
3169
|
+
function aspectRatioUnsupportedWarning(input) {
|
|
3170
|
+
return `The requested ${input.aspectRatio} aspect ratio is not supported by any available model, so data.next_command omits --aspect-ratio and ${input.modelId} will use its own default shape. Run the model_inspection_command to see the ratios it does support.`;
|
|
3171
|
+
}
|
|
3172
|
+
|
|
3173
|
+
function createGuideSuggestedAspectRatio(input) {
|
|
3174
|
+
const model = input?.model ?? null;
|
|
3175
|
+
const requestedAspectRatio = input?.requestedAspectRatio ?? null;
|
|
3176
|
+
if (model === null) {
|
|
3177
|
+
return null;
|
|
3178
|
+
}
|
|
3179
|
+
// An explicit request wins for every modality, but not when the selected
|
|
3180
|
+
// model is KNOWN not to express it. In that case return null so
|
|
3181
|
+
// `next_command` stays runnable (`--aspect-ratio` would fail model
|
|
3182
|
+
// validation) and let the caller warn instead of silently pretending the
|
|
3183
|
+
// ratio was honored.
|
|
3184
|
+
if (requestedAspectRatio !== null) {
|
|
3185
|
+
return modelCannotExpressAspectRatio(model, requestedAspectRatio)
|
|
3186
|
+
? null
|
|
3187
|
+
: requestedAspectRatio;
|
|
3188
|
+
}
|
|
3189
|
+
// No explicit request: preserve the existing video-only default.
|
|
3190
|
+
if (model?.modality !== "video") {
|
|
3191
|
+
return null;
|
|
3192
|
+
}
|
|
3193
|
+
const values = guideModelAspectRatioValues(model);
|
|
3194
|
+
if (values === null) {
|
|
2737
3195
|
return null;
|
|
2738
3196
|
}
|
|
2739
3197
|
return values.includes("16:9") ? "16:9" : (values[0] ?? null);
|
|
@@ -2787,9 +3245,14 @@ function createGuideSelectionReason(
|
|
|
2787
3245
|
model?.id,
|
|
2788
3246
|
)
|
|
2789
3247
|
) {
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
3248
|
+
const intentClass = createGuideIntentClass(intent);
|
|
3249
|
+
if (intentClass === "budget_draft") {
|
|
3250
|
+
return "guide selected the lowest-credit available create model for a draft/budget intent";
|
|
3251
|
+
}
|
|
3252
|
+
if (intentClass === "general") {
|
|
3253
|
+
return "guide selected a cost-efficient general-purpose create model for iteration; pass --intent final for the quality-first model";
|
|
3254
|
+
}
|
|
3255
|
+
return "guide selected the strongest currently available quality-first create model for this intent";
|
|
2793
3256
|
}
|
|
2794
3257
|
return operation === "edit"
|
|
2795
3258
|
? "guide selected the first available executable input-image edit model"
|
|
@@ -4072,14 +4535,22 @@ function createGuideWarning(stage, input) {
|
|
|
4072
4535
|
warning,
|
|
4073
4536
|
};
|
|
4074
4537
|
}
|
|
4538
|
+
// #2204-#2216: name the field this response actually carries. The compact
|
|
4539
|
+
// ready payload drops the `recommended_no_spend_command` alias, so it points
|
|
4540
|
+
// at the base field instead; blocked and `--explain` payloads keep the alias
|
|
4541
|
+
// and keep naming it. Either way
|
|
4542
|
+
// `data[data.guide_warning.recommended_command_field]` resolves.
|
|
4543
|
+
const noSpendField =
|
|
4544
|
+
input.readyCompact === true
|
|
4545
|
+
? "no_spend_next_command"
|
|
4546
|
+
: "recommended_no_spend_command";
|
|
4075
4547
|
return {
|
|
4076
4548
|
...base,
|
|
4077
4549
|
next_command_safety: "live_media_create_credit_debit",
|
|
4078
4550
|
no_spend_safe: false,
|
|
4079
4551
|
spend_required: true,
|
|
4080
|
-
recommended_command_field:
|
|
4081
|
-
warning:
|
|
4082
|
-
"data.next_command is a live media create that can call a provider, debit credits, and create media. Run it only when media spend is allowed; otherwise run data.recommended_no_spend_command.",
|
|
4552
|
+
recommended_command_field: noSpendField,
|
|
4553
|
+
warning: `data.next_command is a live media create that can call a provider, debit credits, and create media. Run it only when media spend is allowed; otherwise run data.${noSpendField}.`,
|
|
4083
4554
|
};
|
|
4084
4555
|
}
|
|
4085
4556
|
|
|
@@ -4169,6 +4640,7 @@ function createGuideNextCommand(stage, input) {
|
|
|
4169
4640
|
prompt: input.prompt,
|
|
4170
4641
|
inputReference: input.inputReference,
|
|
4171
4642
|
budgetGuard: input.budgetGuard,
|
|
4643
|
+
aspectRatio: input.aspectRatio,
|
|
4172
4644
|
modelParametersJson: input.modelParametersJson,
|
|
4173
4645
|
dryRun: false,
|
|
4174
4646
|
idempotencyKey: `edit-guide-${Date.now()}-${randomBytes(4).toString("hex")}`,
|
|
@@ -4228,6 +4700,7 @@ function createGuideEscapeHatches(input) {
|
|
|
4228
4700
|
prompt: input.prompt,
|
|
4229
4701
|
inputReference: input.inputReference,
|
|
4230
4702
|
budgetGuard: input.budgetGuard,
|
|
4703
|
+
aspectRatio: input.aspectRatio,
|
|
4231
4704
|
modelParametersJson: input.modelParametersJson,
|
|
4232
4705
|
dryRun: true,
|
|
4233
4706
|
apiBaseUrl: input.apiBaseUrl,
|
|
@@ -4257,7 +4730,7 @@ function renderGuideCommand(
|
|
|
4257
4730
|
const operation = options.operation ?? "create";
|
|
4258
4731
|
return [
|
|
4259
4732
|
commandPrefix,
|
|
4260
|
-
`${operation} --guide --prompt`,
|
|
4733
|
+
`${operation} --guide${options.explain === true ? " --explain" : ""} --prompt`,
|
|
4261
4734
|
shellQuote(prompt),
|
|
4262
4735
|
...(operation === "edit" &&
|
|
4263
4736
|
typeof options.inputReference === "string" &&
|
|
@@ -4363,6 +4836,9 @@ function renderInputImageGuideCommand(input) {
|
|
|
4363
4836
|
"--model",
|
|
4364
4837
|
shellQuote(input.modelId),
|
|
4365
4838
|
...(promptless ? [] : ["--prompt", shellQuote(input.prompt)]),
|
|
4839
|
+
...(input.aspectRatio === null || input.aspectRatio === undefined
|
|
4840
|
+
? []
|
|
4841
|
+
: ["--aspect-ratio", shellQuote(input.aspectRatio)]),
|
|
4366
4842
|
"--max-estimated-usd-per-image",
|
|
4367
4843
|
shellQuote(formatUsd(input.budgetGuard)),
|
|
4368
4844
|
...(input.modelParametersJson === null ||
|
|
@@ -4517,6 +4993,12 @@ async function create(argv) {
|
|
|
4517
4993
|
"create --no-spend is a guide output mode; combine it with --guide, or use create --dry-run for a no-spend planned job",
|
|
4518
4994
|
);
|
|
4519
4995
|
}
|
|
4996
|
+
if (flagBool(args, "explain")) {
|
|
4997
|
+
return invalid(
|
|
4998
|
+
"luxin create",
|
|
4999
|
+
"create --explain is a guide output mode; combine it with --guide to return every guide field",
|
|
5000
|
+
);
|
|
5001
|
+
}
|
|
4520
5002
|
const prompt = await promptValue(args);
|
|
4521
5003
|
if (!prompt.ok) {
|
|
4522
5004
|
return prompt.result;
|
|
@@ -4670,6 +5152,12 @@ async function edit(argv) {
|
|
|
4670
5152
|
"edit --no-spend is a guide output mode; combine it with --guide, or use edit --dry-run for a no-spend planned job",
|
|
4671
5153
|
);
|
|
4672
5154
|
}
|
|
5155
|
+
if (flagBool(args, "explain")) {
|
|
5156
|
+
return invalid(
|
|
5157
|
+
"luxin edit",
|
|
5158
|
+
"edit --explain is a guide output mode; combine it with --guide to return every guide field",
|
|
5159
|
+
);
|
|
5160
|
+
}
|
|
4673
5161
|
if (input === undefined) {
|
|
4674
5162
|
return invalid(
|
|
4675
5163
|
"luxin edit",
|
|
@@ -4789,7 +5277,7 @@ async function assets(argv) {
|
|
|
4789
5277
|
if (assetId === null) {
|
|
4790
5278
|
return invalid(
|
|
4791
5279
|
"luxin assets",
|
|
4792
|
-
|
|
5280
|
+
`assets currently supports Luxin asset ids and ${ACCEPTED_PUBLIC_MEDIA_HOSTS.join(" or ")} URLs`,
|
|
4793
5281
|
);
|
|
4794
5282
|
}
|
|
4795
5283
|
const token = await resolveToken(args);
|
|
@@ -7224,7 +7712,7 @@ function assetIdFromReference(reference) {
|
|
|
7224
7712
|
const url = new URL(reference);
|
|
7225
7713
|
if (
|
|
7226
7714
|
url.protocol !== "https:" ||
|
|
7227
|
-
url.hostname
|
|
7715
|
+
!ACCEPTED_PUBLIC_MEDIA_HOSTS.includes(url.hostname.toLowerCase()) ||
|
|
7228
7716
|
!url.pathname.startsWith("/a/")
|
|
7229
7717
|
) {
|
|
7230
7718
|
return null;
|