luxin-cli 0.2.4 → 0.2.6
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 +40 -0
- package/bin/luxin.mjs +210 -32
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,46 @@ provenance; this file is the human- and agent-readable release map.
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## 0.2.6 - 2026-08-09
|
|
10
|
+
|
|
11
|
+
- Release (trust): `create --guide` now says when it cannot price the request it
|
|
12
|
+
is planning, instead of presenting the model's default-shape quote as the
|
|
13
|
+
planned debit. `selection.planned_debit_source` reports which of the two you
|
|
14
|
+
got, and the unpriceable case carries a warning pointing at the dry-run.
|
|
15
|
+
- This closes a gap that was invisible rather than wrong: the hosted API
|
|
16
|
+
published per-aspect-ratio create debits only when they differed from the
|
|
17
|
+
quote, so an absent field could mean either "the quote already is the debit"
|
|
18
|
+
or "this server is older than your CLI". The npm package and the hosted API
|
|
19
|
+
deploy independently, so that second case is permanent, not a one-off, and it
|
|
20
|
+
under-quoted silently during any deploy gap or rollback. The API now publishes
|
|
21
|
+
the debit for every create-capable model (107 of 107 live rows), so absence
|
|
22
|
+
means exactly one thing, and the CLI now treats it that way.
|
|
23
|
+
- Carries the correction to the 0.2.5 note below: the "known issue" that shipped
|
|
24
|
+
in the 0.2.5 tarball describes behaviour that does not exist.
|
|
25
|
+
|
|
26
|
+
## 0.2.5 - 2026-08-09
|
|
27
|
+
|
|
28
|
+
- Release (correctness): `edit --guide` selects a model again. It gated on
|
|
29
|
+
`media.input.images.required`, which the hosted `/v1/models` summary does not
|
|
30
|
+
publish, so on 0.2.4 every edit brief returned `no_executable_model` and the
|
|
31
|
+
blocker named `create`. The predicate now reads `supports`, which the summary
|
|
32
|
+
does publish; measured on the live 220-model catalog the two express the same
|
|
33
|
+
79-model set, so this drops a redundant conjunct rather than a guarantee. The
|
|
34
|
+
blocker also names the operation it actually failed on.
|
|
35
|
+
- Release (correctness): a plain video brief reports its aspect ratio again.
|
|
36
|
+
`selection.suggested_aspect_ratio` read the same unpublished block and
|
|
37
|
+
returned null on every real video call unless the brief happened to name a
|
|
38
|
+
ratio.
|
|
39
|
+
- Correction (added after 0.2.5 shipped): the 0.2.5 tarball carries a "known
|
|
40
|
+
issue" note claiming `create --guide` can quote a different credit figure than
|
|
41
|
+
the `--dry-run` it hands you. **That note is wrong and the behaviour it
|
|
42
|
+
describes does not exist.** It was written from a measurement taken during a
|
|
43
|
+
deploy-skew window, minutes after the fix merged and before the hosted API had
|
|
44
|
+
deployed its half. Measured after both halves landed, the guide and the dry-run
|
|
45
|
+
agree: 5 and 5 with no ratio stated, 3 and 3 at 16:9. #2259 is fixed and
|
|
46
|
+
closed. Budgeting from the dry-run figure remains sound advice, but it is not a
|
|
47
|
+
workaround for anything.
|
|
48
|
+
|
|
9
49
|
## 0.2.4 - 2026-08-09
|
|
10
50
|
|
|
11
51
|
- Release (correctness): the guide honors an aspect ratio stated in the brief
|
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.6";
|
|
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";
|
|
@@ -1933,7 +1933,16 @@ async function createGuide(args, options = {}) {
|
|
|
1933
1933
|
requestedAspectRatio: briefAspectRatio,
|
|
1934
1934
|
});
|
|
1935
1935
|
let selection = selectModel(listedModels);
|
|
1936
|
-
|
|
1936
|
+
// The compact hosted list omits `media.input.aspect_ratios` entirely, so the
|
|
1937
|
+
// ratio capability can only come from the detail endpoint. Probe when the
|
|
1938
|
+
// brief named a ratio (selection itself has to be capability-aware) and also
|
|
1939
|
+
// when the pick is a video model: `selection.suggested_aspect_ratio` is
|
|
1940
|
+
// video-only, and without the probe it reported null on every real video
|
|
1941
|
+
// brief that did not state a ratio -- the same
|
|
1942
|
+
// published-payload-omits-the-field defect as #2265, one field over.
|
|
1943
|
+
const needsAspectRatioCapabilities =
|
|
1944
|
+
briefAspectRatio !== null || selection?.model?.modality === "video";
|
|
1945
|
+
if (needsAspectRatioCapabilities && listedModels !== null) {
|
|
1937
1946
|
// Two-pass: the first pass names the models worth asking about, then one
|
|
1938
1947
|
// bounded round of detail lookups makes the second pass actually
|
|
1939
1948
|
// capability-aware instead of guessing.
|
|
@@ -1943,6 +1952,7 @@ async function createGuide(args, options = {}) {
|
|
|
1943
1952
|
models: listedModels,
|
|
1944
1953
|
selected: selection?.model ?? null,
|
|
1945
1954
|
intent: requestedIntent,
|
|
1955
|
+
routing: briefAspectRatio !== null,
|
|
1946
1956
|
}),
|
|
1947
1957
|
apiBaseUrl,
|
|
1948
1958
|
});
|
|
@@ -1983,22 +1993,30 @@ async function createGuide(args, options = {}) {
|
|
|
1983
1993
|
modelParameters: modelParameters.value ?? {},
|
|
1984
1994
|
maxEstimatedUsdPerImage,
|
|
1985
1995
|
});
|
|
1986
|
-
|
|
1996
|
+
// Price the request the guide is about to emit, at the ratio it will emit it
|
|
1997
|
+
// with -- not the registry's default-shape quote, which describes a request
|
|
1998
|
+
// `create` never makes (#2259). Models priced from input-image dimensions stay
|
|
1999
|
+
// on the quote: the guide uploads nothing, so there are no dimensions to price
|
|
2000
|
+
// from. `tests/hosted-create-estimate-parity.test.ts` pins this against the
|
|
2001
|
+
// source dry-run so the two implementations cannot drift apart again.
|
|
2002
|
+
const pricing =
|
|
2003
|
+
selected === null || createGuideSelectedModelRequiresInputImage(selected)
|
|
2004
|
+
? createGuideModelCreditPricing(selected)
|
|
2005
|
+
: createGuidePricingForModel(
|
|
2006
|
+
selected,
|
|
2007
|
+
defaultedModelParameters.modelParameters,
|
|
2008
|
+
pricingContext,
|
|
2009
|
+
);
|
|
2010
|
+
// The quote is not the debit, and against a server that does not publish
|
|
2011
|
+
// debits this CLI cannot compute one -- it has no pricing engine, by design.
|
|
2012
|
+
// Say that plainly instead of letting the caller budget against a number the
|
|
2013
|
+
// product does not charge (#2259, #2272).
|
|
2014
|
+
const plannedDebitUnavailable =
|
|
1987
2015
|
selected !== null &&
|
|
2016
|
+
createGuideModelSupportsCreate(selected) &&
|
|
1988
2017
|
!createGuideSelectedModelRequiresInputImage(selected) &&
|
|
1989
|
-
createGuideCanPriceModelParameters(selected) &&
|
|
1990
|
-
(
|
|
1991
|
-
Object.keys(modelParameters.value ?? {}).length > 0);
|
|
1992
|
-
const pricing =
|
|
1993
|
-
selected === null
|
|
1994
|
-
? null
|
|
1995
|
-
: shouldPriceModelParameters
|
|
1996
|
-
? createGuidePricingForModel(
|
|
1997
|
-
selected,
|
|
1998
|
-
defaultedModelParameters.modelParameters,
|
|
1999
|
-
pricingContext,
|
|
2000
|
-
)
|
|
2001
|
-
: createGuideModelCreditPricing(selected);
|
|
2018
|
+
!createGuideCanPriceModelParameters(selected) &&
|
|
2019
|
+
!createGuideRegistryPublishesCreditDebit(selected);
|
|
2002
2020
|
const estimatedCredits = pricing?.credits_required ?? null;
|
|
2003
2021
|
// The guide publishes what the caller will be charged, never our provider
|
|
2004
2022
|
// cost. `economics.estimated_usd_per_image` is the hosted charge per image.
|
|
@@ -2057,6 +2075,7 @@ async function createGuide(args, options = {}) {
|
|
|
2057
2075
|
guideCommandPrefix,
|
|
2058
2076
|
);
|
|
2059
2077
|
const blocker = createGuideBlocker(stage, {
|
|
2078
|
+
operation: guideOperation,
|
|
2060
2079
|
requestedModelId,
|
|
2061
2080
|
quota,
|
|
2062
2081
|
estimatedCredits,
|
|
@@ -2397,6 +2416,9 @@ async function createGuide(args, options = {}) {
|
|
|
2397
2416
|
pricing_source: pricing?.pricing_source ?? null,
|
|
2398
2417
|
model_parameter_defaults_applied:
|
|
2399
2418
|
defaultedModelParameters.defaultsApplied,
|
|
2419
|
+
planned_debit_source: plannedDebitUnavailable
|
|
2420
|
+
? "create_credit_debit_unavailable"
|
|
2421
|
+
: "registry_create_credit_debit",
|
|
2400
2422
|
},
|
|
2401
2423
|
blocker,
|
|
2402
2424
|
guide_warning: guideWarning,
|
|
@@ -2434,11 +2456,20 @@ async function createGuide(args, options = {}) {
|
|
|
2434
2456
|
for (const field of compactOmittedFields) {
|
|
2435
2457
|
delete guideData[field];
|
|
2436
2458
|
}
|
|
2437
|
-
return createGuideSuccess(
|
|
2459
|
+
return createGuideSuccess(
|
|
2460
|
+
command,
|
|
2461
|
+
quota?.envelope.actor ?? null,
|
|
2462
|
+
guideData,
|
|
2463
|
+
plannedDebitUnavailable
|
|
2464
|
+
? [
|
|
2465
|
+
"create_credit_debit_unavailable: this hosted API does not publish per-aspect-ratio create debits, so the credits above are the model's default-shape quote and may not be what create debits. Run the dry_run command in escape_hatches for the exact figure before budgeting.",
|
|
2466
|
+
]
|
|
2467
|
+
: [],
|
|
2468
|
+
);
|
|
2438
2469
|
}
|
|
2439
2470
|
|
|
2440
|
-
function createGuideSuccess(command, actor, data) {
|
|
2441
|
-
const result = success(command, data);
|
|
2471
|
+
function createGuideSuccess(command, actor, data, warnings = []) {
|
|
2472
|
+
const result = success(command, data, warnings);
|
|
2442
2473
|
result.envelope.actor = actor;
|
|
2443
2474
|
return result;
|
|
2444
2475
|
}
|
|
@@ -2467,8 +2498,6 @@ function selectCreateGuideModel(
|
|
|
2467
2498
|
const isExecutableInputImageEdit = (model) =>
|
|
2468
2499
|
model?.status === "available" &&
|
|
2469
2500
|
guideModelExecutionStatus(model) === "executable" &&
|
|
2470
|
-
Array.isArray(model?.supports) &&
|
|
2471
|
-
(model.supports.includes("edit") || model.supports.includes("variation")) &&
|
|
2472
2501
|
createGuideSelectedModelRequiresInputImage(model);
|
|
2473
2502
|
const isExecutableGuideModel = (model) =>
|
|
2474
2503
|
operation === "edit"
|
|
@@ -2585,16 +2614,21 @@ function selectCreateGuideModel(
|
|
|
2585
2614
|
// capability UNKNOWN, which never disqualifies a model and never triggers the
|
|
2586
2615
|
// unsupported-ratio warning.
|
|
2587
2616
|
|
|
2588
|
-
function guideAspectRatioProbeModelIds({ models, selected, intent }) {
|
|
2617
|
+
function guideAspectRatioProbeModelIds({ models, selected, intent, routing }) {
|
|
2589
2618
|
const ids = [];
|
|
2590
2619
|
if (typeof selected?.id === "string") {
|
|
2591
2620
|
ids.push(selected.id);
|
|
2592
2621
|
}
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2622
|
+
// Only a stated ratio can make the guide hop between models. Without one the
|
|
2623
|
+
// sole open question is what the current pick can express, so asking about
|
|
2624
|
+
// the alternatives would be a round trip spent on an answer nothing reads.
|
|
2625
|
+
if (routing) {
|
|
2626
|
+
for (const modelId of preferredCreateGuideModelIds(
|
|
2627
|
+
createGuideIntentClass(intent),
|
|
2628
|
+
)) {
|
|
2629
|
+
if (models.some((model) => model?.id === modelId)) {
|
|
2630
|
+
ids.push(modelId);
|
|
2631
|
+
}
|
|
2598
2632
|
}
|
|
2599
2633
|
}
|
|
2600
2634
|
return [...new Set(ids)].slice(0, GUIDE_ASPECT_RATIO_PROBE_LIMIT);
|
|
@@ -2939,7 +2973,69 @@ function createGuidePricingForModel(model, modelParameters, context = {}) {
|
|
|
2939
2973
|
if (createGuideCanPriceModelParameters(model)) {
|
|
2940
2974
|
return createGuideXaiImagePricing(model, modelParameters, context);
|
|
2941
2975
|
}
|
|
2942
|
-
return
|
|
2976
|
+
return (
|
|
2977
|
+
createGuideAspectRatioCreditPricing(model, context) ??
|
|
2978
|
+
createGuideModelCreditPricing(model)
|
|
2979
|
+
);
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2982
|
+
// The debit `create` will actually make at the ratio the guide is about to
|
|
2983
|
+
// emit, read from the model registry (#2259).
|
|
2984
|
+
//
|
|
2985
|
+
// `economics.credit_pricing` is the quote at the model's own default shape --
|
|
2986
|
+
// no aspect ratio, no policy model-parameter defaults. `create` never sends
|
|
2987
|
+
// that request: it always sends a concrete ratio (`1:1` unless the brief asked
|
|
2988
|
+
// otherwise) plus the hosted defaults. For any model priced from its output
|
|
2989
|
+
// shape or from a defaulted parameter the quote is therefore not the debit, and
|
|
2990
|
+
// this CLI has no pricing engine to recompute it -- so the registry publishes
|
|
2991
|
+
// the answer and this reads it. Absent field means the quote already is the
|
|
2992
|
+
// debit.
|
|
2993
|
+
// Does this registry response publish create debits at all?
|
|
2994
|
+
//
|
|
2995
|
+
// The hosted API and this CLI ship independently, so a client can always be
|
|
2996
|
+
// talking to a server older than itself. `create_credit_debit_by_aspect_ratio`
|
|
2997
|
+
// is published for EVERY create-capable model precisely so that absence has one
|
|
2998
|
+
// meaning -- "this server cannot tell me the debit" -- rather than being
|
|
2999
|
+
// confusable with "the debit happens to equal the quote". When it is absent the
|
|
3000
|
+
// honest answer is to say so and point at the dry-run, not to present the
|
|
3001
|
+
// model's default-shape quote as the planned debit (#2259, #2272).
|
|
3002
|
+
function createGuideRegistryPublishesCreditDebit(model) {
|
|
3003
|
+
return (
|
|
3004
|
+
isRecord(model?.economics?.create_credit_debit_by_aspect_ratio) ||
|
|
3005
|
+
isRecord(model?.create_credit_debit_by_aspect_ratio)
|
|
3006
|
+
);
|
|
3007
|
+
}
|
|
3008
|
+
|
|
3009
|
+
function createGuideModelSupportsCreate(model) {
|
|
3010
|
+
return Array.isArray(model?.supports) && model.supports.includes("create");
|
|
3011
|
+
}
|
|
3012
|
+
|
|
3013
|
+
function createGuideAspectRatioCreditPricing(model, context) {
|
|
3014
|
+
// Both shapes: `GET /v1/models` serves the COMPACT summary, which flattens
|
|
3015
|
+
// economics onto the row, and only `?details=true` (or a fixture built from
|
|
3016
|
+
// the full catalog) nests it. Reading only the nested one is how a fix passes
|
|
3017
|
+
// its own test against a payload production never sends.
|
|
3018
|
+
const byAspectRatio = isRecord(
|
|
3019
|
+
model?.economics?.create_credit_debit_by_aspect_ratio,
|
|
3020
|
+
)
|
|
3021
|
+
? model.economics.create_credit_debit_by_aspect_ratio
|
|
3022
|
+
: model?.create_credit_debit_by_aspect_ratio;
|
|
3023
|
+
const aspectRatio = context?.aspectRatio;
|
|
3024
|
+
if (!isRecord(byAspectRatio) || typeof aspectRatio !== "string") {
|
|
3025
|
+
return null;
|
|
3026
|
+
}
|
|
3027
|
+
const credits = byAspectRatio[aspectRatio];
|
|
3028
|
+
if (!Number.isFinite(credits)) {
|
|
3029
|
+
return null;
|
|
3030
|
+
}
|
|
3031
|
+
const quoted = createGuideModelCreditPricing(model);
|
|
3032
|
+
return {
|
|
3033
|
+
...(quoted ?? {}),
|
|
3034
|
+
credits_required: credits,
|
|
3035
|
+
credit_unit_usd: CREDIT_UNIT_USD,
|
|
3036
|
+
estimated_charge_usd: roundUsd(credits * CREDIT_UNIT_USD),
|
|
3037
|
+
pricing_confidence: quoted?.pricing_confidence ?? "known",
|
|
3038
|
+
};
|
|
2943
3039
|
}
|
|
2944
3040
|
|
|
2945
3041
|
function createGuideCanPriceModelParameters(model) {
|
|
@@ -3017,6 +3113,43 @@ function roundUsdMicro(value) {
|
|
|
3017
3113
|
return Math.round(value * 1_000_000) / 1_000_000;
|
|
3018
3114
|
}
|
|
3019
3115
|
|
|
3116
|
+
// ---------------------------------------------------------------------------
|
|
3117
|
+
// How the guide reads a model field. Read this before adding an accessor.
|
|
3118
|
+
//
|
|
3119
|
+
// There is NO normalizer. `createGuide` hands the rows from `GET /v1/models`
|
|
3120
|
+
// to these accessors verbatim -- `withModelSummary` is the `models list`
|
|
3121
|
+
// command's compactor and never runs on this path. So an accessor here has to
|
|
3122
|
+
// cope with BOTH published shapes itself:
|
|
3123
|
+
//
|
|
3124
|
+
// GET /v1/models (compact list) flat: model_execution_status,
|
|
3125
|
+
// credits_required, estimated_usd_per_image,
|
|
3126
|
+
// max_outputs_per_request, artifact_storage.
|
|
3127
|
+
// NO `media`, `execution`, or `economics`.
|
|
3128
|
+
// GET /v1/models/:id (detail) nested under execution/economics/media.
|
|
3129
|
+
//
|
|
3130
|
+
// The convention is therefore NESTED-OR-FLAT: try the detail shape, fall back
|
|
3131
|
+
// to the flat field the list actually publishes. That convention is the whole
|
|
3132
|
+
// safety property, and #2265 was one accessor quietly breaking it.
|
|
3133
|
+
// `createGuideSelectedModelRequiresInputImage` read
|
|
3134
|
+
// `media.input.images.required` with a "fallback" of `accepts_input_images` --
|
|
3135
|
+
// a field the API has never published -- so against a real list row it had no
|
|
3136
|
+
// fallback at all, and it failed closed.
|
|
3137
|
+
//
|
|
3138
|
+
// guideModelExecutionStatus execution.model_execution_status -> flat
|
|
3139
|
+
// createGuideModelCreditPricing economics.credit_pricing -> flat
|
|
3140
|
+
// guideBudgetUsdForModel economics.estimated_usd_per_image -> flat
|
|
3141
|
+
// guideModelAspectRatioValues media.input.aspect_ratios.values -> none;
|
|
3142
|
+
// fails open and is hydrated from the detail
|
|
3143
|
+
// endpoint on demand (see the note there)
|
|
3144
|
+
// ...SelectedModelRequiresInputImage reads `supports`, published on the list
|
|
3145
|
+
//
|
|
3146
|
+
// `max_outputs_per_request` and `artifact_storage` are published flat as well,
|
|
3147
|
+
// but nothing on the guide path reads them. Their only nested reads live in
|
|
3148
|
+
// `modelSummaryRow`/`modelSummaryTaskTags`, which `withModelSummary` skips
|
|
3149
|
+
// outright for a payload already marked `compact_model_summary` -- which the
|
|
3150
|
+
// hosted list is. Those eight `media.*` reads therefore never see a list row.
|
|
3151
|
+
// ---------------------------------------------------------------------------
|
|
3152
|
+
|
|
3020
3153
|
function guideModelExecutionStatus(model) {
|
|
3021
3154
|
if (
|
|
3022
3155
|
isRecord(model?.execution) &&
|
|
@@ -3133,6 +3266,26 @@ function requestedAspectRatioFromBrief(input) {
|
|
|
3133
3266
|
// say. The hosted `/v1/models` list is a compact summary and omits the `media`
|
|
3134
3267
|
// block entirely, so "not published" is the common case here and must be kept
|
|
3135
3268
|
// distinct from "published and does not include this ratio".
|
|
3269
|
+
//
|
|
3270
|
+
// #2265, worth stating because the next reader will assume otherwise: this is
|
|
3271
|
+
// ONE OF EXACTLY TWO `media.*` reads that run over the LIST payload, and the
|
|
3272
|
+
// two behave OPPOSITELY. `GET /v1/models/:id` does publish `media`; the compact
|
|
3273
|
+
// list does not. So a `media.*` read is only safe here if it either fails open
|
|
3274
|
+
// or arranges to be hydrated from the detail endpoint first.
|
|
3275
|
+
//
|
|
3276
|
+
// - This one FAILS OPEN. Unknown never disqualifies a model, so an unhydrated
|
|
3277
|
+
// read costs a hint, not a decision. That is why capability routing kept
|
|
3278
|
+
// working in production while `edit --guide` was dead.
|
|
3279
|
+
// - `createGuideSelectedModelRequiresInputImage` used to FAIL CLOSED: no
|
|
3280
|
+
// `media` meant no model could prove it accepts an input image, so `edit
|
|
3281
|
+
// --guide` selected nothing on every real call. It no longer reads `media`
|
|
3282
|
+
// at all -- see the note there.
|
|
3283
|
+
//
|
|
3284
|
+
// Failing open is not the same as being correct. `guideModelSuggestedAspectRatio`
|
|
3285
|
+
// consumes this to REPORT a value rather than to gate one, so before the probe
|
|
3286
|
+
// was extended to video picks it published `suggested_aspect_ratio: null` on
|
|
3287
|
+
// every video brief that did not state a ratio -- silently, since no blocker
|
|
3288
|
+
// fires. Fail-open degrades quietly; that is its failure mode, not its absence.
|
|
3136
3289
|
function guideModelAspectRatioValues(model) {
|
|
3137
3290
|
const values = Array.isArray(model?.media?.input?.aspect_ratios?.values)
|
|
3138
3291
|
? model.media.input.aspect_ratios.values
|
|
@@ -3197,10 +3350,31 @@ function createGuideSuggestedAspectRatio(input) {
|
|
|
3197
3350
|
return values.includes("16:9") ? "16:9" : (values[0] ?? null);
|
|
3198
3351
|
}
|
|
3199
3352
|
|
|
3353
|
+
// #2265: this must decide from what the hosted payload actually publishes.
|
|
3354
|
+
// `GET /v1/models` returns the compact summary, which omits the `media` block
|
|
3355
|
+
// entirely (and has never published `accepts_input_images`), so gating on
|
|
3356
|
+
// `media.input.images.required` disqualified every model on every real call and
|
|
3357
|
+
// `edit --guide` could not select one, ever.
|
|
3358
|
+
//
|
|
3359
|
+
// This read FAILED CLOSED, which is why it was catastrophic rather than merely
|
|
3360
|
+
// lossy: an absent field could not prove any model accepts an input image, so
|
|
3361
|
+
// the guide selected nothing and reported `no_executable_model`. The other
|
|
3362
|
+
// list-payload `media.*` read (`guideModelAspectRatioValues`) fails open and
|
|
3363
|
+
// only loses a hint. Do not assume the two are interchangeable -- see the note
|
|
3364
|
+
// there before adding a third.
|
|
3365
|
+
//
|
|
3366
|
+
// `supports` is published and carries the same fact: it is derived from the
|
|
3367
|
+
// capability's operations (`image.edit` -> "edit", `image.variation` ->
|
|
3368
|
+
// "variation"), and both are input-image operations by construction. On the
|
|
3369
|
+
// live 220-model catalog the old conjunction and this predicate select the
|
|
3370
|
+
// identical 79 models, so the dropped conjunct was doing no work -- while
|
|
3371
|
+
// costing the mirror the ability to decide at all.
|
|
3372
|
+
//
|
|
3373
|
+
// Keep this reading only summary fields. `src/` holds strictly more model data
|
|
3374
|
+
// than the mirror can ever see; every extra field the guide reads there is a
|
|
3375
|
+
// fresh chance for the two surfaces to decide differently.
|
|
3200
3376
|
function createGuideSelectedModelRequiresInputImage(model) {
|
|
3201
3377
|
return (
|
|
3202
|
-
(model?.media?.input?.images?.required === true ||
|
|
3203
|
-
model?.accepts_input_images === true) &&
|
|
3204
3378
|
Array.isArray(model?.supports) &&
|
|
3205
3379
|
(model.supports.includes("edit") || model.supports.includes("variation"))
|
|
3206
3380
|
);
|
|
@@ -3891,12 +4065,16 @@ function createGuideBlocker(stage, input) {
|
|
|
3891
4065
|
};
|
|
3892
4066
|
}
|
|
3893
4067
|
if (stage === "no_executable_model") {
|
|
4068
|
+
// #2265: name the operation that actually failed. Saying "create" when the
|
|
4069
|
+
// caller ran `edit --guide` made the one diagnostic an agent gets point at
|
|
4070
|
+
// the wrong half of the product.
|
|
4071
|
+
const operation = input.operation === "edit" ? "edit" : "create";
|
|
3894
4072
|
return {
|
|
3895
4073
|
code: "no_executable_model",
|
|
3896
4074
|
message:
|
|
3897
4075
|
input.requestedModelId === null
|
|
3898
|
-
?
|
|
3899
|
-
: `Requested model is not currently an available executable
|
|
4076
|
+
? `No available executable ${operation} model was found in the public registry.`
|
|
4077
|
+
: `Requested model is not currently an available executable ${operation} model: ${input.requestedModelId}`,
|
|
3900
4078
|
};
|
|
3901
4079
|
}
|
|
3902
4080
|
if (stage === "auth_required") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "luxin-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Image generation, image editing, video, audio, and image-to-3D for agents, with no API key to obtain first. The agent signs itself up, starts on free preview credits, and gets hosted media URLs, recoverable jobs, and cost receipts.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|