@vectoriox/iox-ui 5.61.0 → 5.62.1
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/fesm2022/vectoriox-iox-ui-generation-core.mjs +466 -274
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs.map +1 -1
- package/generation-core/index.d.ts +181 -99
- package/package.json +1 -1
- package/src/generation-core/ai-run.ts +40 -3
- package/src/generation-core/plan-edits.ts +128 -47
- package/src/generation-core/plan-wire.ts +150 -62
- package/src/generation-core/strict-schema.ts +32 -7
|
@@ -10826,7 +10826,7 @@ const BUSINESS_KEYS = shapeKeys(SHAPE['business']);
|
|
|
10826
10826
|
const DESIGN_REQUIRED = requiredShapeKeys(SHAPE['design']);
|
|
10827
10827
|
const DESIGN_KEYS = shapeKeys(SHAPE['design']);
|
|
10828
10828
|
const CONTENT_TYPE_KEYS = shapeKeys(SHAPE['contentType']);
|
|
10829
|
-
const ROLE_KEYS = shapeKeys(SHAPE['roles']);
|
|
10829
|
+
const ROLE_KEYS$1 = shapeKeys(SHAPE['roles']);
|
|
10830
10830
|
const PAGE_SEO_KEYS = shapeKeys(SHAPE['pageSeo']);
|
|
10831
10831
|
const ARRAY_TYPE = 'array';
|
|
10832
10832
|
const FIELD_KEYS = shapeKeys(SHAPE['field']);
|
|
@@ -10984,8 +10984,8 @@ function validateRoles(c, roles, path, typeName, fields, fieldTypes) {
|
|
|
10984
10984
|
}
|
|
10985
10985
|
const out = {};
|
|
10986
10986
|
for (const [role, field] of Object.entries(roles)) {
|
|
10987
|
-
if (!ROLE_KEYS.includes(role)) {
|
|
10988
|
-
c.add(SitePlanIssueCode.UnknownRole, `${path}.${role}`, `"${role}" is not a role (${ROLE_KEYS.join(', ')})`);
|
|
10987
|
+
if (!ROLE_KEYS$1.includes(role)) {
|
|
10988
|
+
c.add(SitePlanIssueCode.UnknownRole, `${path}.${role}`, `"${role}" is not a role (${ROLE_KEYS$1.join(', ')})`);
|
|
10989
10989
|
continue;
|
|
10990
10990
|
}
|
|
10991
10991
|
if (typeof field !== 'string' || !fields.has(field)) {
|
|
@@ -13381,179 +13381,21 @@ function buildStepFacts(plan, actions = {}) {
|
|
|
13381
13381
|
}
|
|
13382
13382
|
|
|
13383
13383
|
/**
|
|
13384
|
-
* The
|
|
13385
|
-
*
|
|
13386
|
-
*
|
|
13387
|
-
*
|
|
13388
|
-
*
|
|
13389
|
-
*
|
|
13390
|
-
*
|
|
13391
|
-
|
|
13392
|
-
|
|
13393
|
-
(
|
|
13394
|
-
AiProviderId["Anthropic"] = "anthropic";
|
|
13395
|
-
AiProviderId["OpenRouter"] = "openrouter";
|
|
13396
|
-
})(AiProviderId || (AiProviderId = {}));
|
|
13397
|
-
/** Who pays: the platform's credits, or the studio's own provider key (metered for display, never charged). */
|
|
13398
|
-
var AiKeyMode;
|
|
13399
|
-
(function (AiKeyMode) {
|
|
13400
|
-
AiKeyMode["Platform"] = "platform";
|
|
13401
|
-
AiKeyMode["Own"] = "own";
|
|
13402
|
-
})(AiKeyMode || (AiKeyMode = {}));
|
|
13403
|
-
/** The steps a model works on (board 9's rows), in screen order. */
|
|
13404
|
-
var AiStep;
|
|
13405
|
-
(function (AiStep) {
|
|
13406
|
-
AiStep["ReadMessage"] = "analysis";
|
|
13407
|
-
AiStep["PlanSite"] = "plan";
|
|
13408
|
-
AiStep["ChangePlan"] = "planEdit";
|
|
13409
|
-
AiStep["MakePictures"] = "image";
|
|
13410
|
-
AiStep["NameStructures"] = "scanNaming";
|
|
13411
|
-
})(AiStep || (AiStep = {}));
|
|
13412
|
-
const AI_STEPS = Object.values(AiStep);
|
|
13413
|
-
/** Each step's name on the wire (the control plane's `label`, the console) — the screens translate their own. */
|
|
13414
|
-
const AI_STEP_LABELS = {
|
|
13415
|
-
[AiStep.ReadMessage]: 'Read your message',
|
|
13416
|
-
[AiStep.PlanSite]: 'Plan the site',
|
|
13417
|
-
[AiStep.ChangePlan]: 'Change the plan',
|
|
13418
|
-
[AiStep.MakePictures]: 'Make pictures',
|
|
13419
|
-
[AiStep.NameStructures]: 'Name repeated structures',
|
|
13420
|
-
};
|
|
13421
|
-
/** What a model must be able to do for a step to run on it. */
|
|
13422
|
-
var AiCapability;
|
|
13423
|
-
(function (AiCapability) {
|
|
13424
|
-
AiCapability["StructuredOutput"] = "structuredOutputs";
|
|
13425
|
-
AiCapability["ImageInput"] = "imageInput";
|
|
13426
|
-
AiCapability["ImageOutput"] = "imageOutput";
|
|
13427
|
-
})(AiCapability || (AiCapability = {}));
|
|
13428
|
-
const AI_STEP_NEEDS = {
|
|
13429
|
-
[AiStep.ReadMessage]: [AiCapability.StructuredOutput, AiCapability.ImageInput],
|
|
13430
|
-
[AiStep.PlanSite]: [AiCapability.StructuredOutput],
|
|
13431
|
-
[AiStep.ChangePlan]: [AiCapability.StructuredOutput],
|
|
13432
|
-
[AiStep.MakePictures]: [AiCapability.ImageOutput],
|
|
13433
|
-
[AiStep.NameStructures]: [AiCapability.StructuredOutput],
|
|
13434
|
-
};
|
|
13435
|
-
/** Tokens a typical use of a step reads and writes (plan §3d "Where credits go") — what a shown estimate is made of. */
|
|
13436
|
-
const AI_STEP_TYPICAL_TOKENS = {
|
|
13437
|
-
[AiStep.ReadMessage]: { input: 8_800, output: 1_500, cacheWrite: 0, cacheRead: 0 },
|
|
13438
|
-
[AiStep.PlanSite]: { input: 12_000, output: 7_000, cacheWrite: 0, cacheRead: 0 },
|
|
13439
|
-
[AiStep.ChangePlan]: { input: 6_000, output: 500, cacheWrite: 0, cacheRead: 0 },
|
|
13440
|
-
// a 1 K picture is 1,120 image tokens (Google's figure for Nano Banana 2) — I-D4
|
|
13441
|
-
[AiStep.MakePictures]: { input: 0, output: 0, cacheWrite: 0, cacheRead: 0, imageOutput: 1_120 },
|
|
13442
|
-
[AiStep.NameStructures]: { input: 80_000, output: 10_000, cacheWrite: 0, cacheRead: 0 },
|
|
13443
|
-
};
|
|
13444
|
-
/** 1 credit = 1 US cent of what the AI actually cost (decision D4). */
|
|
13445
|
-
const CREDITS_PER_USD = 100;
|
|
13446
|
-
const TOKENS_PER_PRICE_UNIT = 1_000_000;
|
|
13447
|
-
const CREDIT_PRECISION = 1e6;
|
|
13448
|
-
const NO_TOKENS = Object.freeze({ input: 0, output: 0, cacheWrite: 0, cacheRead: 0 });
|
|
13449
|
-
/** What `tokens` cost at `price`, in US dollars. */
|
|
13450
|
-
function costUsd(tokens, price) {
|
|
13451
|
-
const perToken = (perMillion, fallback) => (perMillion ?? fallback) / TOKENS_PER_PRICE_UNIT;
|
|
13452
|
-
return tokens.input * perToken(price.input, 0)
|
|
13453
|
-
+ tokens.output * perToken(price.output, 0)
|
|
13454
|
-
+ tokens.cacheWrite * perToken(price.cacheWrite, price.input)
|
|
13455
|
-
+ tokens.cacheRead * perToken(price.cacheRead, price.input)
|
|
13456
|
-
+ (tokens.imageOutput ?? 0) * perToken(price.imageOutput, price.output);
|
|
13457
|
-
}
|
|
13458
|
-
/** Dollars → credits, kept to a millionth of a credit (a short read costs a fraction of one). */
|
|
13459
|
-
function creditsOfUsd(usd) {
|
|
13460
|
-
return Math.round(usd * CREDITS_PER_USD * CREDIT_PRECISION) / CREDIT_PRECISION;
|
|
13461
|
-
}
|
|
13462
|
-
/** The credits a typical use of `step` costs on a model of `price` — never typed, always computed (§3d). */
|
|
13463
|
-
function estimateStepCredits(step, price, tokens = AI_STEP_TYPICAL_TOKENS[step]) {
|
|
13464
|
-
return creditsOfUsd(costUsd(tokens, price));
|
|
13465
|
-
}
|
|
13466
|
-
/** Can `model` do `step`? The step's needs against the model's capabilities. */
|
|
13467
|
-
function modelCanDo(model, step) {
|
|
13468
|
-
return AI_STEP_NEEDS[step].every(need => model.capabilities.includes(need));
|
|
13469
|
-
}
|
|
13470
|
-
/** Why AI did not run (board 11). The server refuses with these BEFORE any model call. */
|
|
13471
|
-
var AiRefusalCode;
|
|
13472
|
-
(function (AiRefusalCode) {
|
|
13473
|
-
AiRefusalCode["NotInPlan"] = "ai.notInPlan";
|
|
13474
|
-
AiRefusalCode["OutOfCredits"] = "ai.outOfCredits";
|
|
13475
|
-
AiRefusalCode["CapReached"] = "ai.capReached";
|
|
13476
|
-
AiRefusalCode["NotConnected"] = "ai.notConnected";
|
|
13477
|
-
AiRefusalCode["KeyInvalid"] = "ai.keyInvalid";
|
|
13478
|
-
AiRefusalCode["PlanUnknown"] = "ai.planUnknown";
|
|
13479
|
-
})(AiRefusalCode || (AiRefusalCode = {}));
|
|
13480
|
-
/** The HTTP status each refusal answers with (board 11). */
|
|
13481
|
-
const AI_REFUSAL_STATUS = {
|
|
13482
|
-
[AiRefusalCode.NotInPlan]: 403,
|
|
13483
|
-
[AiRefusalCode.OutOfCredits]: 402,
|
|
13484
|
-
[AiRefusalCode.CapReached]: 402,
|
|
13485
|
-
[AiRefusalCode.NotConnected]: 409,
|
|
13486
|
-
[AiRefusalCode.KeyInvalid]: 409,
|
|
13487
|
-
[AiRefusalCode.PlanUnknown]: 503,
|
|
13488
|
-
};
|
|
13489
|
-
/** What a refusal offers next. */
|
|
13490
|
-
var AiNextKind;
|
|
13491
|
-
(function (AiNextKind) {
|
|
13492
|
-
/** Ask IOX (a prefilled email to the operator, until a plan can be bought — D2). */
|
|
13493
|
-
AiNextKind["Contact"] = "contact";
|
|
13494
|
-
/** Open Settings ▸ AI. */
|
|
13495
|
-
AiNextKind["Settings"] = "settings";
|
|
13496
|
-
/** Try again later, or with less. */
|
|
13497
|
-
AiNextKind["Retry"] = "retry";
|
|
13498
|
-
})(AiNextKind || (AiNextKind = {}));
|
|
13499
|
-
const AI_REFUSAL_NEXT = {
|
|
13500
|
-
[AiRefusalCode.NotInPlan]: AiNextKind.Contact,
|
|
13501
|
-
[AiRefusalCode.OutOfCredits]: AiNextKind.Contact,
|
|
13502
|
-
[AiRefusalCode.CapReached]: AiNextKind.Retry,
|
|
13503
|
-
[AiRefusalCode.NotConnected]: AiNextKind.Settings,
|
|
13504
|
-
[AiRefusalCode.KeyInvalid]: AiNextKind.Settings,
|
|
13505
|
-
[AiRefusalCode.PlanUnknown]: AiNextKind.Retry,
|
|
13506
|
-
};
|
|
13507
|
-
/** The plan entitlements AI reads (the control plane's `features` / `limits` keys) — ONE spelling. */
|
|
13508
|
-
var AiFeature;
|
|
13509
|
-
(function (AiFeature) {
|
|
13510
|
-
AiFeature["SiteGeneration"] = "aiSiteGeneration";
|
|
13511
|
-
})(AiFeature || (AiFeature = {}));
|
|
13512
|
-
var AiLimit;
|
|
13513
|
-
(function (AiLimit) {
|
|
13514
|
-
AiLimit["CreditsPerMonth"] = "aiCreditsPerMonth";
|
|
13515
|
-
AiLimit["CreditsPerGeneration"] = "aiCreditsPerGeneration";
|
|
13516
|
-
})(AiLimit || (AiLimit = {}));
|
|
13517
|
-
/** A generation's stages (§3d). The runner works while `task` is set; otherwise the owner has the next move. */
|
|
13518
|
-
var AiGenerationStage;
|
|
13519
|
-
(function (AiGenerationStage) {
|
|
13520
|
-
AiGenerationStage["Reading"] = "reading";
|
|
13521
|
-
AiGenerationStage["Analysis"] = "analysis";
|
|
13522
|
-
AiGenerationStage["Design"] = "design";
|
|
13523
|
-
AiGenerationStage["Plan"] = "plan";
|
|
13524
|
-
AiGenerationStage["Build"] = "build";
|
|
13525
|
-
AiGenerationStage["Done"] = "done";
|
|
13526
|
-
AiGenerationStage["Failed"] = "failed";
|
|
13527
|
-
AiGenerationStage["Cancelled"] = "cancelled";
|
|
13528
|
-
})(AiGenerationStage || (AiGenerationStage = {}));
|
|
13529
|
-
/** The stages nothing advances any more. */
|
|
13530
|
-
const FINAL_GENERATION_STAGES = [AiGenerationStage.Done, AiGenerationStage.Failed, AiGenerationStage.Cancelled];
|
|
13531
|
-
/** How one model call ended — a ledger row's status. */
|
|
13532
|
-
var AiCallStatus;
|
|
13533
|
-
(function (AiCallStatus) {
|
|
13534
|
-
/** Written before the call: a reservation at the estimate until the call settles. */
|
|
13535
|
-
AiCallStatus["Started"] = "started";
|
|
13536
|
-
AiCallStatus["Ok"] = "ok";
|
|
13537
|
-
/** The answer came back and the shared validator refused it (with a path). */
|
|
13538
|
-
AiCallStatus["Invalid"] = "invalid";
|
|
13539
|
-
/** The model declined (a provider refusal) — charged what the provider reports. */
|
|
13540
|
-
AiCallStatus["Refused"] = "refused";
|
|
13541
|
-
AiCallStatus["Error"] = "error";
|
|
13542
|
-
/** The process died during the call: charged at the estimate, since what it cost was never reported. */
|
|
13543
|
-
AiCallStatus["Interrupted"] = "interrupted";
|
|
13544
|
-
})(AiCallStatus || (AiCallStatus = {}));
|
|
13545
|
-
|
|
13546
|
-
/**
|
|
13547
|
-
* The plan WIRE (AI plan M1-3, vectoriox/iox-cms#45): what a model writes when it plans a site — a compact format inside
|
|
13548
|
-
* the Claude API's structured-output limits (≤ 24 optional parameters and ≤ 16 unions per request; `strict-schema.ts`).
|
|
13549
|
-
* The Site Plan itself (`SPEC_SITE_PLAN_SHAPE`) has 213 optional keys once every pattern is a branch, so the model never
|
|
13550
|
-
* writes it: it writes this, and ONE pure `expandPlanWire` turns it into the Site Plan that `validateSitePlan` and the
|
|
13551
|
-
* stage's gate judge exactly as before.
|
|
13384
|
+
* The plan WIRE (AI plan M1-3, vectoriox/iox-cms#45, #47): what a model writes when it plans a site — a compact format
|
|
13385
|
+
* inside the Claude API's structured-output limits (≤ 24 optional parameters and ≤ 16 unions per request, and a compiled
|
|
13386
|
+
* grammar under the size Claude refuses — measured as ≤ 60 properties; `strict-schema.ts`). The Site Plan itself
|
|
13387
|
+
* (`SPEC_SITE_PLAN_SHAPE`) has 213 optional keys once every pattern is a branch, so the model never writes it: it writes
|
|
13388
|
+
* this, and ONE pure `expandPlanWire` turns it into the Site Plan that `validateSitePlan` and the stage's gate judge
|
|
13389
|
+
* exactly as before.
|
|
13390
|
+
*
|
|
13391
|
+
* - **Written in two PARTS, one request each** (#47 — the whole plan in one format was 64 properties): the **structure** (the business,
|
|
13392
|
+
* the content types, the data sources) and then the **pages** (the pages, the header and footer), written knowing the
|
|
13393
|
+
* structure. Every top-level key belongs to exactly one part (`part` below); `expandPlanWire` puts the parts together.
|
|
13552
13394
|
*
|
|
13553
13395
|
* - **Every key required.** A plan key that may be absent is written with its stated "none" — `''`, `0`, `false`, `[]`.
|
|
13554
13396
|
* - **Closed lists as enums** (patterns, page types, needs, slots, parts, forms, entrances, header/footer), from the
|
|
13555
13397
|
* contract — never per-pattern branches: the gate refuses a slot or a part the chosen pattern does not have, with its path.
|
|
13556
|
-
* - **Maps as lists:** slots `[{ slot, text }]`, links `[{ slot, route }]`, seed values `[{ field, value }]`, roles
|
|
13398
|
+
* - **Maps as lists:** slots `[{ slot, text }]`, links `[{ slot, route }]`, seed values `[{ field, value }]`, roles `[{ role, field }]`.
|
|
13557
13399
|
* - **One data source object** for both kinds; `type` says which keys are read (the other kind's hold their none).
|
|
13558
13400
|
* - **What the model does not decide is not on the wire** (`PLAN_WIRE_DERIVED`): the look (the owner's pick), the run,
|
|
13559
13401
|
* the language (the reading's), the facts (the owner's own, verbatim), the version, the home page (the page at `/`).
|
|
@@ -13563,13 +13405,24 @@ var AiCallStatus;
|
|
|
13563
13405
|
* every plan key is either carried or derived (`planWireGaps()`); a key the plan shape gains breaks the builder until it
|
|
13564
13406
|
* is placed. The expanders translate, never repair: a value the model got wrong stays wrong, for the gate to refuse.
|
|
13565
13407
|
*/
|
|
13566
|
-
|
|
13408
|
+
/** The plan's parts, in the order they are written: the pages bind to the structure's sources and types. */
|
|
13409
|
+
var PlanPart;
|
|
13410
|
+
(function (PlanPart) {
|
|
13411
|
+
PlanPart["Structure"] = "structure";
|
|
13412
|
+
PlanPart["Pages"] = "pages";
|
|
13413
|
+
})(PlanPart || (PlanPart = {}));
|
|
13414
|
+
const PLAN_PARTS = [PlanPart.Structure, PlanPart.Pages];
|
|
13415
|
+
/** Each part's format name (a request names the format it asks for). */
|
|
13416
|
+
const PLAN_WIRE_PART_SCHEMA_NAMES = {
|
|
13417
|
+
[PlanPart.Structure]: 'site_plan_structure',
|
|
13418
|
+
[PlanPart.Pages]: 'site_plan_pages',
|
|
13419
|
+
};
|
|
13567
13420
|
var PlanWireEntry;
|
|
13568
13421
|
(function (PlanWireEntry) {
|
|
13569
13422
|
PlanWireEntry["Plan"] = "plan";
|
|
13570
13423
|
PlanWireEntry["Business"] = "business";
|
|
13571
13424
|
PlanWireEntry["ContentType"] = "contentType";
|
|
13572
|
-
PlanWireEntry["
|
|
13425
|
+
PlanWireEntry["RoleField"] = "roleField";
|
|
13573
13426
|
PlanWireEntry["Field"] = "field";
|
|
13574
13427
|
PlanWireEntry["Seed"] = "seed";
|
|
13575
13428
|
PlanWireEntry["SeedValue"] = "seedValue";
|
|
@@ -13583,13 +13436,17 @@ var PlanWireEntry;
|
|
|
13583
13436
|
})(PlanWireEntry || (PlanWireEntry = {}));
|
|
13584
13437
|
const PLAN_SHAPE = SPEC_SITE_PLAN_SHAPE;
|
|
13585
13438
|
const key = (ref, none) => ({ refs: [ref], ...(none !== undefined ? { none } : {}) });
|
|
13439
|
+
const inPart = (part, spec) => ({ ...spec, part });
|
|
13440
|
+
/** The roles a content type's fields play (`roles` of the plan shape), in the plan shape's order. */
|
|
13441
|
+
const ROLE_KEYS = shapeKeys(PLAN_SHAPE['roles']);
|
|
13442
|
+
const roleWords = ROLE_KEYS.map(role => `${role}: ${describeShapeKey(PLAN_SHAPE['roles'], role).replace(/^string — /, '')}`).join('; ');
|
|
13586
13443
|
const WIRE_KEYS = {
|
|
13587
13444
|
plan: {
|
|
13588
|
-
business: key('plan.business'),
|
|
13589
|
-
contentTypes: key('plan.contentTypes'),
|
|
13590
|
-
dataSources: { refs: ['plan.dataSources'], text: 'dataSource[] — merged by alias into the org library' },
|
|
13591
|
-
pages: key('plan.pages'),
|
|
13592
|
-
globals: key('plan.globals'),
|
|
13445
|
+
business: inPart(PlanPart.Structure, key('plan.business')),
|
|
13446
|
+
contentTypes: inPart(PlanPart.Structure, key('plan.contentTypes')),
|
|
13447
|
+
dataSources: inPart(PlanPart.Structure, { refs: ['plan.dataSources'], text: 'dataSource[] — merged by alias into the org library' }),
|
|
13448
|
+
pages: inPart(PlanPart.Pages, key('plan.pages')),
|
|
13449
|
+
globals: inPart(PlanPart.Pages, key('plan.globals')),
|
|
13593
13450
|
},
|
|
13594
13451
|
business: {
|
|
13595
13452
|
name: key('business.name'),
|
|
@@ -13598,17 +13455,13 @@ const WIRE_KEYS = {
|
|
|
13598
13455
|
contentType: {
|
|
13599
13456
|
name: key('contentType.name'),
|
|
13600
13457
|
label: key('contentType.label'),
|
|
13601
|
-
roles:
|
|
13458
|
+
roles: { refs: ['contentType.roles'], text: `roleField[] — ${describeShapeKey(PLAN_SHAPE['contentType'], 'roles').replace(/^roles — /, '')}; a role no field plays is left out` },
|
|
13602
13459
|
fields: key('contentType.fields'),
|
|
13603
13460
|
seed: { refs: ['contentType.seed'], text: 'seed — the sample items', none: 'count 0 and no values' },
|
|
13604
13461
|
},
|
|
13605
|
-
|
|
13606
|
-
|
|
13607
|
-
|
|
13608
|
-
image: key('roles.image', "''"),
|
|
13609
|
-
meta: key('roles.meta', "''"),
|
|
13610
|
-
date: key('roles.date', "''"),
|
|
13611
|
-
gallery: key('roles.gallery', "''"),
|
|
13462
|
+
roleField: {
|
|
13463
|
+
role: { refs: ROLE_KEYS.map(role => `roles.${role}`), text: `${ROLE_KEYS.map(r => `'${r}'`).join(' | ')} — ${roleWords}` },
|
|
13464
|
+
field: { refs: ['roles.title'], text: 'string — the field of the type that plays it' },
|
|
13612
13465
|
},
|
|
13613
13466
|
field: {
|
|
13614
13467
|
name: key('field.name'),
|
|
@@ -13697,6 +13550,19 @@ const wireText = (spec) => {
|
|
|
13697
13550
|
};
|
|
13698
13551
|
/** The wire's shape table, in the plan table's words — what the model reads, and what the format is built from. */
|
|
13699
13552
|
const PLAN_WIRE_SHAPE = Object.fromEntries(Object.entries(WIRE_KEYS).map(([entry, keys]) => [entry, Object.fromEntries(Object.entries(keys).map(([k, spec]) => [k, wireText(spec)]))]));
|
|
13553
|
+
/** Each part's top-level keys, from the ONE wire table. */
|
|
13554
|
+
const PLAN_PART_KEYS = Object.fromEntries(PLAN_PARTS.map(part => [part, Object.entries(WIRE_KEYS.plan).filter(([, spec]) => spec.part === part).map(([k]) => k)]));
|
|
13555
|
+
/** The keys of a shape entry, but these. */
|
|
13556
|
+
const shapeWithout = (entry, keys) => Object.fromEntries(Object.entries(entry).filter(([k]) => !keys.includes(k.replace(/\?$/, ''))));
|
|
13557
|
+
/**
|
|
13558
|
+
* The part a plan path is the answer of (`contentTypes[1].seed.title` → structure, `pages[0].sections[2]` → pages). A
|
|
13559
|
+
* path no part writes — the root (the composer), or a key the generation decides — is the LAST part's: it is written
|
|
13560
|
+
* knowing everything before it.
|
|
13561
|
+
*/
|
|
13562
|
+
function planPartOfPath(path) {
|
|
13563
|
+
const top = path.split(/[.[]/, 1)[0];
|
|
13564
|
+
return PLAN_PARTS.find(part => PLAN_PART_KEYS[part].includes(top)) ?? PLAN_PARTS[PLAN_PARTS.length - 1];
|
|
13565
|
+
}
|
|
13700
13566
|
/** Plan keys neither carried by a wire key nor derived — always empty (a spec and the builder hold it). */
|
|
13701
13567
|
function planWireGaps() {
|
|
13702
13568
|
const carried = new Set(Object.values(WIRE_KEYS).flatMap(keys => Object.values(keys).flatMap(spec => spec.refs)));
|
|
@@ -13729,13 +13595,23 @@ const globalIds = (position) => Object.entries(SITE_PATTERNS).filter(([, p]) =>
|
|
|
13729
13595
|
function wireFieldJsonSchema() {
|
|
13730
13596
|
return shaped(PlanWireEntry.Field, { name: jsonText(), type: jsonText(), controller: jsonText(), required: jsonBoolean(), note: jsonText() });
|
|
13731
13597
|
}
|
|
13732
|
-
|
|
13733
|
-
|
|
13598
|
+
/** The keys an entry leaves out under `options`. */
|
|
13599
|
+
const CHILD_KEYS = { contentType: 'fields', page: 'sections' };
|
|
13600
|
+
const childless = (entry, options) => (options.children === false && CHILD_KEYS[entry] ? [CHILD_KEYS[entry]] : []);
|
|
13601
|
+
/** The shape an entry is written in, under `options`. */
|
|
13602
|
+
function wireEntryShape(entry, options = {}) {
|
|
13603
|
+
return shapeWithout(PLAN_WIRE_SHAPE[entry], childless(entry, options));
|
|
13604
|
+
}
|
|
13605
|
+
const shapedAs = (entry, options, properties) => {
|
|
13606
|
+
const left = childless(entry, options);
|
|
13607
|
+
return jsonShapedObject(wireEntryShape(entry, options), Object.fromEntries(Object.entries(properties).filter(([k]) => !left.includes(k))), `wire ${entry}`);
|
|
13608
|
+
};
|
|
13609
|
+
function wireContentTypeJsonSchema(options = {}) {
|
|
13734
13610
|
const value = shaped(PlanWireEntry.SeedValue, { field: jsonText(), value: jsonAnyOf([jsonText(), jsonListOf(jsonText())]) });
|
|
13735
|
-
return
|
|
13611
|
+
return shapedAs(PlanWireEntry.ContentType, options, {
|
|
13736
13612
|
name: jsonText(),
|
|
13737
13613
|
label: jsonText(),
|
|
13738
|
-
roles: shaped(PlanWireEntry.
|
|
13614
|
+
roles: jsonListOf(shaped(PlanWireEntry.RoleField, { role: jsonOneOf(ROLE_KEYS), field: jsonText() })),
|
|
13739
13615
|
fields: jsonListOf(wireFieldJsonSchema()),
|
|
13740
13616
|
seed: shaped(PlanWireEntry.Seed, { count: jsonInteger(), sample: jsonBoolean(), isPublished: jsonBoolean(), values: jsonListOf(value) }),
|
|
13741
13617
|
});
|
|
@@ -13766,8 +13642,8 @@ function wireSectionJsonSchema() {
|
|
|
13766
13642
|
enter: orNone(ANIMATE_IDS),
|
|
13767
13643
|
});
|
|
13768
13644
|
}
|
|
13769
|
-
function wirePageJsonSchema() {
|
|
13770
|
-
return
|
|
13645
|
+
function wirePageJsonSchema(options = {}) {
|
|
13646
|
+
return shapedAs(PlanWireEntry.Page, options, {
|
|
13771
13647
|
key: jsonText(),
|
|
13772
13648
|
name: jsonText(),
|
|
13773
13649
|
route: jsonText(),
|
|
@@ -13780,21 +13656,32 @@ function wirePageJsonSchema() {
|
|
|
13780
13656
|
sections: jsonListOf(wireSectionJsonSchema()),
|
|
13781
13657
|
});
|
|
13782
13658
|
}
|
|
13783
|
-
|
|
13784
|
-
|
|
13659
|
+
const PLAN_TOP_SCHEMAS = {
|
|
13660
|
+
business: () => shaped(PlanWireEntry.Business, { name: jsonText(), tagline: jsonText() }),
|
|
13661
|
+
contentTypes: () => jsonListOf(wireContentTypeJsonSchema()),
|
|
13662
|
+
dataSources: () => jsonListOf(wireDataSourceJsonSchema()),
|
|
13663
|
+
pages: () => jsonListOf(wirePageJsonSchema()),
|
|
13664
|
+
globals: () => shaped(PlanWireEntry.Globals, {
|
|
13665
|
+
[GlobalSlot.Header]: jsonOneOf(globalIds(PatternGlobalPosition.Before)),
|
|
13666
|
+
[GlobalSlot.Footer]: jsonOneOf(globalIds(PatternGlobalPosition.After)),
|
|
13667
|
+
}),
|
|
13668
|
+
};
|
|
13669
|
+
/** The plan's top-level shape for one part (its keys only) — what that part's request shows and is built from. */
|
|
13670
|
+
function planWirePartShape(part) {
|
|
13671
|
+
return Object.fromEntries(Object.entries(PLAN_WIRE_SHAPE.plan).filter(([k]) => PLAN_PART_KEYS[part].includes(k)));
|
|
13672
|
+
}
|
|
13673
|
+
/**
|
|
13674
|
+
* The format a model writes one part of a plan in — one request each (vectoriox/iox-cms#47). The same for every studio
|
|
13675
|
+
* and run: nothing in it is pinned, so the provider compiles each part's grammar once.
|
|
13676
|
+
*/
|
|
13677
|
+
function planWirePartJsonSchema(part) {
|
|
13785
13678
|
const gaps = planWireGaps();
|
|
13786
13679
|
if (gaps.length)
|
|
13787
13680
|
throw new PlanWireShapeError(`the plan wire neither carries nor derives ${gaps.join(', ')}`);
|
|
13788
|
-
|
|
13789
|
-
|
|
13790
|
-
|
|
13791
|
-
|
|
13792
|
-
pages: jsonListOf(wirePageJsonSchema()),
|
|
13793
|
-
globals: shaped(PlanWireEntry.Globals, {
|
|
13794
|
-
[GlobalSlot.Header]: jsonOneOf(globalIds(PatternGlobalPosition.Before)),
|
|
13795
|
-
[GlobalSlot.Footer]: jsonOneOf(globalIds(PatternGlobalPosition.After)),
|
|
13796
|
-
}),
|
|
13797
|
-
});
|
|
13681
|
+
const missing = Object.keys(WIRE_KEYS.plan).filter(k => !PLAN_TOP_SCHEMAS[k] || !WIRE_KEYS.plan[k].part);
|
|
13682
|
+
if (missing.length)
|
|
13683
|
+
throw new PlanWireShapeError(`the plan wire's ${missing.join(', ')} has no part or no format`);
|
|
13684
|
+
return jsonShapedObject(planWirePartShape(part), Object.fromEntries(PLAN_PART_KEYS[part].map(k => [k, PLAN_TOP_SCHEMAS[k]()])), `wire plan ${part}`);
|
|
13798
13685
|
}
|
|
13799
13686
|
/** A value standing for "absent": `''` (or nothing at all). */
|
|
13800
13687
|
const isNone = (v) => v === undefined || v === NONE;
|
|
@@ -13820,11 +13707,12 @@ function expandWireField(wire) {
|
|
|
13820
13707
|
const { required, note, ...rest } = wire;
|
|
13821
13708
|
return { ...rest, ...optional('required', required, v => v === undefined || v === false), ...optional('note', note) };
|
|
13822
13709
|
}
|
|
13823
|
-
function expandWireContentType(wire) {
|
|
13710
|
+
function expandWireContentType(wire, options = {}) {
|
|
13824
13711
|
if (!isPlainObject(wire))
|
|
13825
13712
|
return wire;
|
|
13826
|
-
const {
|
|
13827
|
-
const
|
|
13713
|
+
const written = options.children === false ? { fields: [], ...wire } : wire;
|
|
13714
|
+
const { roles, fields, seed, ...rest } = written;
|
|
13715
|
+
const out = { ...rest, roles: listToMap(roles, 'role', 'field'), fields: each(fields, expandWireField) };
|
|
13828
13716
|
if (!isPlainObject(seed))
|
|
13829
13717
|
return seed === undefined ? out : { ...out, seed };
|
|
13830
13718
|
const { values, ...fixed } = seed;
|
|
@@ -13859,10 +13747,11 @@ function expandWireSection(wire) {
|
|
|
13859
13747
|
...optional('enter', enter),
|
|
13860
13748
|
};
|
|
13861
13749
|
}
|
|
13862
|
-
function expandWirePage(wire) {
|
|
13750
|
+
function expandWirePage(wire, options = {}) {
|
|
13863
13751
|
if (!isPlainObject(wire))
|
|
13864
13752
|
return wire;
|
|
13865
|
-
const
|
|
13753
|
+
const written = options.children === false ? { sections: [], ...wire } : wire;
|
|
13754
|
+
const { archetype, need, template, routeSource, seo, sections, ...rest } = written;
|
|
13866
13755
|
const words = isPlainObject(seo) ? { ...optional('title', seo['title']), ...optional('description', seo['description']) } : seo;
|
|
13867
13756
|
return {
|
|
13868
13757
|
...rest,
|
|
@@ -13882,12 +13771,17 @@ function expandWireBusiness(wire, facts) {
|
|
|
13882
13771
|
return { ...rest, ...optional('tagline', tagline), ...(facts && Object.keys(facts).length ? { facts: { ...facts } } : {}) };
|
|
13883
13772
|
}
|
|
13884
13773
|
/**
|
|
13885
|
-
* A model's plan (the wire) + what the generation decides (the look, the run, the language, the owner's
|
|
13886
|
-
* Site Plan. Pure. Translates, never repairs: whatever
|
|
13774
|
+
* A model's plan (its parts, on the wire) + what the generation decides (the look, the run, the language, the owner's
|
|
13775
|
+
* facts) → the Site Plan. Pure. Translates, never repairs: whatever an answer got wrong is still wrong, for
|
|
13776
|
+
* `validateSitePlan` — a part that is not an object is the result itself, a part not written yet leaves its keys out
|
|
13777
|
+
* (the validator then names them, at their own paths).
|
|
13887
13778
|
*/
|
|
13888
|
-
function expandPlanWire(
|
|
13889
|
-
|
|
13890
|
-
|
|
13779
|
+
function expandPlanWire(parts, context) {
|
|
13780
|
+
const written = PLAN_PARTS.filter(part => parts[part] !== undefined).map(part => parts[part]);
|
|
13781
|
+
const odd = written.find(value => !isPlainObject(value));
|
|
13782
|
+
if (odd !== undefined)
|
|
13783
|
+
return odd;
|
|
13784
|
+
const wire = Object.assign({}, ...written);
|
|
13891
13785
|
const { business, contentTypes, dataSources, pages, ...rest } = wire;
|
|
13892
13786
|
return {
|
|
13893
13787
|
...rest,
|
|
@@ -13905,11 +13799,12 @@ function expandPlanWire(wire, context) {
|
|
|
13905
13799
|
function fieldToWire(field) {
|
|
13906
13800
|
return { name: field.name, type: field.type, controller: field.controller, required: field.required ?? false, note: field.note ?? NONE };
|
|
13907
13801
|
}
|
|
13908
|
-
function contentTypeToWire(type) {
|
|
13909
|
-
const
|
|
13802
|
+
function contentTypeToWire(type, options = {}) {
|
|
13803
|
+
const played = type.roles;
|
|
13804
|
+
const roles = ROLE_KEYS.filter(role => played[role]).map(role => ({ role, field: played[role] }));
|
|
13910
13805
|
const { count = 0, sample = false, isPublished = false, ...values } = type.seed ?? {};
|
|
13911
13806
|
return {
|
|
13912
|
-
name: type.name, label: type.label, roles, fields: type.fields.map(fieldToWire),
|
|
13807
|
+
name: type.name, label: type.label, roles, ...(options.children === false ? {} : { fields: type.fields.map(fieldToWire) }),
|
|
13913
13808
|
seed: { count, sample, isPublished, values: mapToList(values, 'field', 'value') },
|
|
13914
13809
|
};
|
|
13915
13810
|
}
|
|
@@ -13928,24 +13823,224 @@ function sectionToWire(section) {
|
|
|
13928
13823
|
binding: section.binding ?? NONE, without: [...(section.without ?? [])], form: section.form ?? NONE, enter: section.enter ?? NONE,
|
|
13929
13824
|
};
|
|
13930
13825
|
}
|
|
13931
|
-
function pageToWire(page) {
|
|
13826
|
+
function pageToWire(page, options = {}) {
|
|
13932
13827
|
return {
|
|
13933
13828
|
key: page.key, name: page.name, route: page.route, inNav: page.inNav ?? false, archetype: page.archetype ?? NONE, need: page.need ?? NONE,
|
|
13934
13829
|
template: page.template ?? NONE, routeSource: page.routeSource ?? NONE,
|
|
13935
|
-
seo: { title: page.seo?.title ?? NONE, description: page.seo?.description ?? NONE },
|
|
13830
|
+
seo: { title: page.seo?.title ?? NONE, description: page.seo?.description ?? NONE },
|
|
13831
|
+
...(options.children === false ? {} : { sections: page.sections.map(sectionToWire) }),
|
|
13936
13832
|
};
|
|
13937
13833
|
}
|
|
13938
|
-
|
|
13834
|
+
const PLAN_TO_WIRE = {
|
|
13835
|
+
business: plan => ({ name: plan.business.name, tagline: plan.business.tagline ?? NONE }),
|
|
13836
|
+
contentTypes: plan => plan.contentTypes.map(type => contentTypeToWire(type)),
|
|
13837
|
+
dataSources: plan => plan.dataSources.map(dataSourceToWire),
|
|
13838
|
+
pages: plan => plan.pages.map(page => pageToWire(page)),
|
|
13839
|
+
globals: plan => ({ ...plan.globals }),
|
|
13840
|
+
};
|
|
13841
|
+
/** One part of the plan on the wire: what that part's request would have answered. */
|
|
13842
|
+
function planPartToWire(plan, part) {
|
|
13843
|
+
return Object.fromEntries(PLAN_PART_KEYS[part].map(k => [k, PLAN_TO_WIRE[k](plan)]));
|
|
13844
|
+
}
|
|
13845
|
+
/** The plan → the wire, by part: what a model would have written (the change prompt shows it; fakes and evals write it). */
|
|
13939
13846
|
function planToWire(plan) {
|
|
13940
|
-
return
|
|
13941
|
-
business: { name: plan.business.name, tagline: plan.business.tagline ?? NONE },
|
|
13942
|
-
contentTypes: plan.contentTypes.map(contentTypeToWire),
|
|
13943
|
-
dataSources: plan.dataSources.map(dataSourceToWire),
|
|
13944
|
-
pages: plan.pages.map(pageToWire),
|
|
13945
|
-
globals: { ...plan.globals },
|
|
13946
|
-
};
|
|
13847
|
+
return Object.fromEntries(PLAN_PARTS.map(part => [part, planPartToWire(plan, part)]));
|
|
13947
13848
|
}
|
|
13948
13849
|
|
|
13850
|
+
/**
|
|
13851
|
+
* The AI pipeline's WIRE vocabulary (AI plan §3c, §3n, M0): what iox-cms's `ai` API answers and what every client
|
|
13852
|
+
* of it reads — the steps a model works on, who pays (IOX credits or the studio's own key), a model of the vetted
|
|
13853
|
+
* list with its price per token class, the one cost → credits rule (1 credit = 1 US cent of what the AI cost, D4),
|
|
13854
|
+
* the refusals (board 11) and a generation's stages. ONE definition: iox-cms meters and refuses with it, the
|
|
13855
|
+
* Settings ▸ AI screen and the wizard show it. Pure types, enums and helpers; no transport, no prices typed here.
|
|
13856
|
+
* The step and capability spellings are the control plane's (`GET /instances/me/entitlements`, AI plan "M0 control
|
|
13857
|
+
* plane — the instance-route contract") — the platform has ONE spelling of each.
|
|
13858
|
+
*/
|
|
13859
|
+
var AiProviderId;
|
|
13860
|
+
(function (AiProviderId) {
|
|
13861
|
+
AiProviderId["Anthropic"] = "anthropic";
|
|
13862
|
+
AiProviderId["OpenRouter"] = "openrouter";
|
|
13863
|
+
})(AiProviderId || (AiProviderId = {}));
|
|
13864
|
+
/** Who pays: the platform's credits, or the studio's own provider key (metered for display, never charged). */
|
|
13865
|
+
var AiKeyMode;
|
|
13866
|
+
(function (AiKeyMode) {
|
|
13867
|
+
AiKeyMode["Platform"] = "platform";
|
|
13868
|
+
AiKeyMode["Own"] = "own";
|
|
13869
|
+
})(AiKeyMode || (AiKeyMode = {}));
|
|
13870
|
+
/** The steps a model works on (board 9's rows), in screen order. */
|
|
13871
|
+
var AiStep;
|
|
13872
|
+
(function (AiStep) {
|
|
13873
|
+
AiStep["ReadMessage"] = "analysis";
|
|
13874
|
+
AiStep["PlanSite"] = "plan";
|
|
13875
|
+
AiStep["ChangePlan"] = "planEdit";
|
|
13876
|
+
AiStep["MakePictures"] = "image";
|
|
13877
|
+
AiStep["NameStructures"] = "scanNaming";
|
|
13878
|
+
})(AiStep || (AiStep = {}));
|
|
13879
|
+
const AI_STEPS = Object.values(AiStep);
|
|
13880
|
+
/** Each step's name on the wire (the control plane's `label`, the console) — the screens translate their own. */
|
|
13881
|
+
const AI_STEP_LABELS = {
|
|
13882
|
+
[AiStep.ReadMessage]: 'Read your message',
|
|
13883
|
+
[AiStep.PlanSite]: 'Plan the site',
|
|
13884
|
+
[AiStep.ChangePlan]: 'Change the plan',
|
|
13885
|
+
[AiStep.MakePictures]: 'Make pictures',
|
|
13886
|
+
[AiStep.NameStructures]: 'Name repeated structures',
|
|
13887
|
+
};
|
|
13888
|
+
/** What a model must be able to do for a step to run on it. */
|
|
13889
|
+
var AiCapability;
|
|
13890
|
+
(function (AiCapability) {
|
|
13891
|
+
AiCapability["StructuredOutput"] = "structuredOutputs";
|
|
13892
|
+
AiCapability["ImageInput"] = "imageInput";
|
|
13893
|
+
AiCapability["ImageOutput"] = "imageOutput";
|
|
13894
|
+
})(AiCapability || (AiCapability = {}));
|
|
13895
|
+
const AI_STEP_NEEDS = {
|
|
13896
|
+
[AiStep.ReadMessage]: [AiCapability.StructuredOutput, AiCapability.ImageInput],
|
|
13897
|
+
[AiStep.PlanSite]: [AiCapability.StructuredOutput],
|
|
13898
|
+
[AiStep.ChangePlan]: [AiCapability.StructuredOutput],
|
|
13899
|
+
[AiStep.MakePictures]: [AiCapability.ImageOutput],
|
|
13900
|
+
[AiStep.NameStructures]: [AiCapability.StructuredOutput],
|
|
13901
|
+
};
|
|
13902
|
+
/**
|
|
13903
|
+
* The steps a model answers in the plan's parts, one request per part (vectoriox/iox-cms#47 — the whole plan's format
|
|
13904
|
+
* compiles to a grammar Claude refuses), and the tokens a typical request of each part reads and writes. Each part reads
|
|
13905
|
+
* the same instructions; the pages read the structure too and write most of the words.
|
|
13906
|
+
*/
|
|
13907
|
+
const AI_STEP_PART_TOKENS = {
|
|
13908
|
+
[AiStep.PlanSite]: {
|
|
13909
|
+
[PlanPart.Structure]: { input: 10_000, output: 1_500, cacheWrite: 0, cacheRead: 0 },
|
|
13910
|
+
[PlanPart.Pages]: { input: 12_000, output: 5_500, cacheWrite: 0, cacheRead: 0 },
|
|
13911
|
+
},
|
|
13912
|
+
[AiStep.ChangePlan]: {
|
|
13913
|
+
[PlanPart.Structure]: { input: 6_000, output: 300, cacheWrite: 0, cacheRead: 0 },
|
|
13914
|
+
[PlanPart.Pages]: { input: 6_500, output: 400, cacheWrite: 0, cacheRead: 0 },
|
|
13915
|
+
},
|
|
13916
|
+
};
|
|
13917
|
+
/** The parts a step is asked in, in order — none for a step asked in one request. */
|
|
13918
|
+
function aiStepParts(step) {
|
|
13919
|
+
return AI_STEP_PART_TOKENS[step] ? PLAN_PARTS : [];
|
|
13920
|
+
}
|
|
13921
|
+
const addTokens = (a, b) => ({
|
|
13922
|
+
input: a.input + b.input, output: a.output + b.output, cacheWrite: a.cacheWrite + b.cacheWrite, cacheRead: a.cacheRead + b.cacheRead,
|
|
13923
|
+
...(a.imageOutput !== undefined || b.imageOutput !== undefined ? { imageOutput: (a.imageOutput ?? 0) + (b.imageOutput ?? 0) } : {}),
|
|
13924
|
+
});
|
|
13925
|
+
const partsTotal = (step) => PLAN_PARTS.map(part => AI_STEP_PART_TOKENS[step][part]).reduce(addTokens);
|
|
13926
|
+
/**
|
|
13927
|
+
* Tokens a typical use of a step reads and writes (plan §3d "Where credits go") — what a shown estimate is made of. A
|
|
13928
|
+
* step asked in parts is every part's request together, so the estimate (and a generation's start gate) counts them all.
|
|
13929
|
+
*/
|
|
13930
|
+
const AI_STEP_TYPICAL_TOKENS = {
|
|
13931
|
+
[AiStep.ReadMessage]: { input: 8_800, output: 1_500, cacheWrite: 0, cacheRead: 0 },
|
|
13932
|
+
[AiStep.PlanSite]: partsTotal(AiStep.PlanSite),
|
|
13933
|
+
[AiStep.ChangePlan]: partsTotal(AiStep.ChangePlan),
|
|
13934
|
+
// a 1 K picture is 1,120 image tokens (Google's figure for Nano Banana 2) — I-D4
|
|
13935
|
+
[AiStep.MakePictures]: { input: 0, output: 0, cacheWrite: 0, cacheRead: 0, imageOutput: 1_120 },
|
|
13936
|
+
[AiStep.NameStructures]: { input: 80_000, output: 10_000, cacheWrite: 0, cacheRead: 0 },
|
|
13937
|
+
};
|
|
13938
|
+
/** 1 credit = 1 US cent of what the AI actually cost (decision D4). */
|
|
13939
|
+
const CREDITS_PER_USD = 100;
|
|
13940
|
+
const TOKENS_PER_PRICE_UNIT = 1_000_000;
|
|
13941
|
+
const CREDIT_PRECISION = 1e6;
|
|
13942
|
+
const NO_TOKENS = Object.freeze({ input: 0, output: 0, cacheWrite: 0, cacheRead: 0 });
|
|
13943
|
+
/** What `tokens` cost at `price`, in US dollars. */
|
|
13944
|
+
function costUsd(tokens, price) {
|
|
13945
|
+
const perToken = (perMillion, fallback) => (perMillion ?? fallback) / TOKENS_PER_PRICE_UNIT;
|
|
13946
|
+
return tokens.input * perToken(price.input, 0)
|
|
13947
|
+
+ tokens.output * perToken(price.output, 0)
|
|
13948
|
+
+ tokens.cacheWrite * perToken(price.cacheWrite, price.input)
|
|
13949
|
+
+ tokens.cacheRead * perToken(price.cacheRead, price.input)
|
|
13950
|
+
+ (tokens.imageOutput ?? 0) * perToken(price.imageOutput, price.output);
|
|
13951
|
+
}
|
|
13952
|
+
/** Dollars → credits, kept to a millionth of a credit (a short read costs a fraction of one). */
|
|
13953
|
+
function creditsOfUsd(usd) {
|
|
13954
|
+
return Math.round(usd * CREDITS_PER_USD * CREDIT_PRECISION) / CREDIT_PRECISION;
|
|
13955
|
+
}
|
|
13956
|
+
/** What a typical request of `step` reads and writes — one part's when the step is asked in parts, else the whole step's. */
|
|
13957
|
+
function typicalStepTokens(step, part) {
|
|
13958
|
+
return (part && AI_STEP_PART_TOKENS[step]?.[part]) || AI_STEP_TYPICAL_TOKENS[step];
|
|
13959
|
+
}
|
|
13960
|
+
/** The credits a typical use of `step` costs on a model of `price` — never typed, always computed (§3d). */
|
|
13961
|
+
function estimateStepCredits(step, price, tokens = AI_STEP_TYPICAL_TOKENS[step]) {
|
|
13962
|
+
return creditsOfUsd(costUsd(tokens, price));
|
|
13963
|
+
}
|
|
13964
|
+
/** Can `model` do `step`? The step's needs against the model's capabilities. */
|
|
13965
|
+
function modelCanDo(model, step) {
|
|
13966
|
+
return AI_STEP_NEEDS[step].every(need => model.capabilities.includes(need));
|
|
13967
|
+
}
|
|
13968
|
+
/** Why AI did not run (board 11). The server refuses with these BEFORE any model call. */
|
|
13969
|
+
var AiRefusalCode;
|
|
13970
|
+
(function (AiRefusalCode) {
|
|
13971
|
+
AiRefusalCode["NotInPlan"] = "ai.notInPlan";
|
|
13972
|
+
AiRefusalCode["OutOfCredits"] = "ai.outOfCredits";
|
|
13973
|
+
AiRefusalCode["CapReached"] = "ai.capReached";
|
|
13974
|
+
AiRefusalCode["NotConnected"] = "ai.notConnected";
|
|
13975
|
+
AiRefusalCode["KeyInvalid"] = "ai.keyInvalid";
|
|
13976
|
+
AiRefusalCode["PlanUnknown"] = "ai.planUnknown";
|
|
13977
|
+
})(AiRefusalCode || (AiRefusalCode = {}));
|
|
13978
|
+
/** The HTTP status each refusal answers with (board 11). */
|
|
13979
|
+
const AI_REFUSAL_STATUS = {
|
|
13980
|
+
[AiRefusalCode.NotInPlan]: 403,
|
|
13981
|
+
[AiRefusalCode.OutOfCredits]: 402,
|
|
13982
|
+
[AiRefusalCode.CapReached]: 402,
|
|
13983
|
+
[AiRefusalCode.NotConnected]: 409,
|
|
13984
|
+
[AiRefusalCode.KeyInvalid]: 409,
|
|
13985
|
+
[AiRefusalCode.PlanUnknown]: 503,
|
|
13986
|
+
};
|
|
13987
|
+
/** What a refusal offers next. */
|
|
13988
|
+
var AiNextKind;
|
|
13989
|
+
(function (AiNextKind) {
|
|
13990
|
+
/** Ask IOX (a prefilled email to the operator, until a plan can be bought — D2). */
|
|
13991
|
+
AiNextKind["Contact"] = "contact";
|
|
13992
|
+
/** Open Settings ▸ AI. */
|
|
13993
|
+
AiNextKind["Settings"] = "settings";
|
|
13994
|
+
/** Try again later, or with less. */
|
|
13995
|
+
AiNextKind["Retry"] = "retry";
|
|
13996
|
+
})(AiNextKind || (AiNextKind = {}));
|
|
13997
|
+
const AI_REFUSAL_NEXT = {
|
|
13998
|
+
[AiRefusalCode.NotInPlan]: AiNextKind.Contact,
|
|
13999
|
+
[AiRefusalCode.OutOfCredits]: AiNextKind.Contact,
|
|
14000
|
+
[AiRefusalCode.CapReached]: AiNextKind.Retry,
|
|
14001
|
+
[AiRefusalCode.NotConnected]: AiNextKind.Settings,
|
|
14002
|
+
[AiRefusalCode.KeyInvalid]: AiNextKind.Settings,
|
|
14003
|
+
[AiRefusalCode.PlanUnknown]: AiNextKind.Retry,
|
|
14004
|
+
};
|
|
14005
|
+
/** The plan entitlements AI reads (the control plane's `features` / `limits` keys) — ONE spelling. */
|
|
14006
|
+
var AiFeature;
|
|
14007
|
+
(function (AiFeature) {
|
|
14008
|
+
AiFeature["SiteGeneration"] = "aiSiteGeneration";
|
|
14009
|
+
})(AiFeature || (AiFeature = {}));
|
|
14010
|
+
var AiLimit;
|
|
14011
|
+
(function (AiLimit) {
|
|
14012
|
+
AiLimit["CreditsPerMonth"] = "aiCreditsPerMonth";
|
|
14013
|
+
AiLimit["CreditsPerGeneration"] = "aiCreditsPerGeneration";
|
|
14014
|
+
})(AiLimit || (AiLimit = {}));
|
|
14015
|
+
/** A generation's stages (§3d). The runner works while `task` is set; otherwise the owner has the next move. */
|
|
14016
|
+
var AiGenerationStage;
|
|
14017
|
+
(function (AiGenerationStage) {
|
|
14018
|
+
AiGenerationStage["Reading"] = "reading";
|
|
14019
|
+
AiGenerationStage["Analysis"] = "analysis";
|
|
14020
|
+
AiGenerationStage["Design"] = "design";
|
|
14021
|
+
AiGenerationStage["Plan"] = "plan";
|
|
14022
|
+
AiGenerationStage["Build"] = "build";
|
|
14023
|
+
AiGenerationStage["Done"] = "done";
|
|
14024
|
+
AiGenerationStage["Failed"] = "failed";
|
|
14025
|
+
AiGenerationStage["Cancelled"] = "cancelled";
|
|
14026
|
+
})(AiGenerationStage || (AiGenerationStage = {}));
|
|
14027
|
+
/** The stages nothing advances any more. */
|
|
14028
|
+
const FINAL_GENERATION_STAGES = [AiGenerationStage.Done, AiGenerationStage.Failed, AiGenerationStage.Cancelled];
|
|
14029
|
+
/** How one model call ended — a ledger row's status. */
|
|
14030
|
+
var AiCallStatus;
|
|
14031
|
+
(function (AiCallStatus) {
|
|
14032
|
+
/** Written before the call: a reservation at the estimate until the call settles. */
|
|
14033
|
+
AiCallStatus["Started"] = "started";
|
|
14034
|
+
AiCallStatus["Ok"] = "ok";
|
|
14035
|
+
/** The answer came back and the shared validator refused it (with a path). */
|
|
14036
|
+
AiCallStatus["Invalid"] = "invalid";
|
|
14037
|
+
/** The model declined (a provider refusal) — charged what the provider reports. */
|
|
14038
|
+
AiCallStatus["Refused"] = "refused";
|
|
14039
|
+
AiCallStatus["Error"] = "error";
|
|
14040
|
+
/** The process died during the call: charged at the estimate, since what it cost was never reported. */
|
|
14041
|
+
AiCallStatus["Interrupted"] = "interrupted";
|
|
14042
|
+
})(AiCallStatus || (AiCallStatus = {}));
|
|
14043
|
+
|
|
13949
14044
|
/**
|
|
13950
14045
|
* What a structured-output request costs the provider's grammar compiler, counted the way the Claude API counts it
|
|
13951
14046
|
* (platform.claude.com/docs/en/build-with-claude/structured-outputs, "Schema complexity limits", read 2026-10-27):
|
|
@@ -13955,10 +14050,24 @@ function planToWire(plan) {
|
|
|
13955
14050
|
* The limits are per REQUEST — every strict schema in it together. The ONE counter: the specs hold every schema iox-cms
|
|
13956
14051
|
* sends to it, and the fake Claude API refuses with it exactly as Claude does — the plan stage once sent 213 optional
|
|
13957
14052
|
* parameters and every suite stayed green because the fake accepted anything (vectoriox/iox-cms#45).
|
|
14053
|
+
*
|
|
14054
|
+
* Beyond those, Claude refuses a grammar it compiles too large — a limit the docs name ("additional internal limits on
|
|
14055
|
+
* the compiled grammar size") without a number. MEASURED against the API on 2026-10-27 (vectoriox/iox-cms#47): the
|
|
14056
|
+
* total number of properties decides it, not enums or descriptions — 64 and 61 properties refused, 53 and fewer
|
|
14057
|
+
* accepted (53 already took ~9 s to compile). So 60 is the line we refuse above, and 40 is what we send.
|
|
14058
|
+
*/
|
|
14059
|
+
const STRICT_SCHEMA_LIMITS = {
|
|
14060
|
+
strictTools: 20,
|
|
14061
|
+
optional: 24,
|
|
14062
|
+
unions: 16,
|
|
14063
|
+
/** MEASURED, not documented (2026-10-27): the smallest refused request held 61 properties, the largest accepted 53. */
|
|
14064
|
+
properties: 60,
|
|
14065
|
+
};
|
|
14066
|
+
/**
|
|
14067
|
+
* What one request iox-cms sends may use: half of each documented limit, so a step can gain a field (or a strict tool)
|
|
14068
|
+
* without a 400 — and 40 properties, a third under the measured grammar line, where compiling stays a few seconds.
|
|
13958
14069
|
*/
|
|
13959
|
-
const
|
|
13960
|
-
/** What a schema iox-cms sends may use — half of each limit, so a step can gain a field (or a strict tool) without a 400. */
|
|
13961
|
-
const STRICT_SCHEMA_BUDGET = { optional: 12, unions: 8 };
|
|
14070
|
+
const STRICT_SCHEMA_BUDGET = { optional: 12, unions: 8, properties: 40 };
|
|
13962
14071
|
/** The keywords the docs list as not supported ("Numerical constraints", "String constraints", "Array constraints beyond `minItems` of 0 or 1"). */
|
|
13963
14072
|
const UNSUPPORTED_KEYWORDS = [
|
|
13964
14073
|
'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf', 'minLength', 'maxLength', 'maxItems', 'uniqueItems', 'contains',
|
|
@@ -13977,6 +14086,9 @@ function count(node, path, out) {
|
|
|
13977
14086
|
if (node['type'] === 'object' || properties) {
|
|
13978
14087
|
const required = new Set(Array.isArray(node['required']) ? node['required'] : []);
|
|
13979
14088
|
out.optional += Object.keys(properties ?? {}).filter(key => !required.has(key)).length;
|
|
14089
|
+
out.properties += Object.keys(properties ?? {}).length;
|
|
14090
|
+
if (properties)
|
|
14091
|
+
out.objects++;
|
|
13980
14092
|
if (node['additionalProperties'] !== false)
|
|
13981
14093
|
out.unsupported.push(`${at('additionalProperties')}: must be false`);
|
|
13982
14094
|
}
|
|
@@ -14005,14 +14117,17 @@ function count(node, path, out) {
|
|
|
14005
14117
|
}
|
|
14006
14118
|
/** The cost of ONE request's strict schemas together: its JSON output format and its strict tools' input schemas. */
|
|
14007
14119
|
function strictSchemaComplexity(schemas, strictTools = 0) {
|
|
14008
|
-
const out = { optional: 0, unions: 0, strictTools, unsupported: [] };
|
|
14120
|
+
const out = { optional: 0, unions: 0, strictTools, properties: 0, objects: 0, unsupported: [] };
|
|
14009
14121
|
schemas.forEach((schema, i) => count(schema, schemas.length > 1 ? `[${i}]` : '', out));
|
|
14010
14122
|
return out;
|
|
14011
14123
|
}
|
|
14124
|
+
/** Claude's words for a grammar it compiles too large (2026-10-27, the plan stage — vectoriox/iox-cms#47), verbatim. */
|
|
14125
|
+
const GRAMMAR_TOO_LARGE_MESSAGE = 'The compiled grammar is too large, which would cause performance issues. Simplify your tool schemas or reduce the number of strict tools.';
|
|
14012
14126
|
/**
|
|
14013
|
-
* The 400 the Claude API answers a request over its limits with, or null. The optional-parameter
|
|
14014
|
-
* Claude answered on 2026-10-27, verbatim; the union, strict-tool and unsupported messages follow
|
|
14015
|
-
* captured).
|
|
14127
|
+
* The 400 the Claude API answers a request over its limits with, or null. The optional-parameter and the grammar-size
|
|
14128
|
+
* messages are the ones Claude answered on 2026-10-27, verbatim; the union, strict-tool and unsupported messages follow
|
|
14129
|
+
* their shape (not yet captured). The documented limits are checked first: a request over one of them never reaches the
|
|
14130
|
+
* grammar compiler.
|
|
14016
14131
|
*/
|
|
14017
14132
|
function strictSchemaRefusal(complexity) {
|
|
14018
14133
|
const refuse = (message) => ({ status: 400, type: 'invalid_request_error', message });
|
|
@@ -14027,6 +14142,8 @@ function strictSchemaRefusal(complexity) {
|
|
|
14027
14142
|
if (complexity.unions > STRICT_SCHEMA_LIMITS.unions) {
|
|
14028
14143
|
return refuse(`Schemas contains too many parameters with union types (${complexity.unions}), which would make grammar compilation inefficient. Reduce the number of parameters with union types in your tool schemas (limit: ${STRICT_SCHEMA_LIMITS.unions}).`);
|
|
14029
14144
|
}
|
|
14145
|
+
if (complexity.properties > STRICT_SCHEMA_LIMITS.properties)
|
|
14146
|
+
return refuse(GRAMMAR_TOO_LARGE_MESSAGE);
|
|
14030
14147
|
return null;
|
|
14031
14148
|
}
|
|
14032
14149
|
|
|
@@ -14070,9 +14187,32 @@ const PLAN_EDIT_SHAPE = {
|
|
|
14070
14187
|
[PlanEditOp.SwapPattern]: { op: "'swapPattern'", page: 'string — a page key', index: 'integer — the section', pattern: 'string — the pattern it becomes (its slots kept where the new one has them)' },
|
|
14071
14188
|
[PlanEditOp.SetSlot]: { op: "'setSlot'", page: 'string — a page key', index: 'integer — the section', slot: "string — one of the pattern's slots", text: "string — the words, in the owner's language and voice" },
|
|
14072
14189
|
[PlanEditOp.AddContentType]: { op: "'addContentType'", contentType: 'contentType — a unique name', 'dataSources?': 'dataSource[] — the list / single sources its pages read' },
|
|
14073
|
-
[PlanEditOp.AddField]: { op: "'addField'", contentType: 'string — a content type of the plan', field: 'field', 'sample?': "string | string[] — the samples' value for it" },
|
|
14190
|
+
[PlanEditOp.AddField]: { op: "'addField'", contentType: 'string — a content type of the plan', field: 'field', 'sample?': "string | string[] — the samples' value for it; absent: what the type's seed already holds for it, else ''" },
|
|
14074
14191
|
[PlanEditOp.SetSectionData]: { op: "'setSectionData'", page: 'string — a page key', index: 'integer — the section', binding: 'string — the list source the section repeats' },
|
|
14075
14192
|
};
|
|
14193
|
+
/**
|
|
14194
|
+
* The plan part each op changes (vectoriox/iox-cms#47): a sentence is answered in the plan's two parts, one request each —
|
|
14195
|
+
* the structure's edits first, then the pages' written knowing them.
|
|
14196
|
+
*/
|
|
14197
|
+
const PLAN_EDIT_OP_PART = {
|
|
14198
|
+
[PlanEditOp.AddPage]: PlanPart.Pages,
|
|
14199
|
+
[PlanEditOp.RemovePage]: PlanPart.Pages,
|
|
14200
|
+
[PlanEditOp.RenamePage]: PlanPart.Pages,
|
|
14201
|
+
[PlanEditOp.MergePages]: PlanPart.Pages,
|
|
14202
|
+
[PlanEditOp.MovePage]: PlanPart.Pages,
|
|
14203
|
+
[PlanEditOp.AddSection]: PlanPart.Pages,
|
|
14204
|
+
[PlanEditOp.RemoveSection]: PlanPart.Pages,
|
|
14205
|
+
[PlanEditOp.MoveSection]: PlanPart.Pages,
|
|
14206
|
+
[PlanEditOp.SwapPattern]: PlanPart.Pages,
|
|
14207
|
+
[PlanEditOp.SetSlot]: PlanPart.Pages,
|
|
14208
|
+
[PlanEditOp.SetSectionData]: PlanPart.Pages,
|
|
14209
|
+
[PlanEditOp.AddContentType]: PlanPart.Structure,
|
|
14210
|
+
[PlanEditOp.AddField]: PlanPart.Structure,
|
|
14211
|
+
};
|
|
14212
|
+
/** The ops written in one part, in `PlanEditOp` order. */
|
|
14213
|
+
function planEditOps(part) {
|
|
14214
|
+
return Object.values(PlanEditOp).filter(op => PLAN_EDIT_OP_PART[op] === part);
|
|
14215
|
+
}
|
|
14076
14216
|
var PlanChangeKind;
|
|
14077
14217
|
(function (PlanChangeKind) {
|
|
14078
14218
|
PlanChangeKind["Page"] = "page";
|
|
@@ -14193,6 +14333,8 @@ function findSection(d, pageAt, index, c, path) {
|
|
|
14193
14333
|
return ok;
|
|
14194
14334
|
}
|
|
14195
14335
|
const setSections = (d, at, sections) => { d.pages[at] = { ...d.pages[at], sections }; };
|
|
14336
|
+
/** A child added to a page or type the same edits added is part of that addition — the map marks the parent only. */
|
|
14337
|
+
const addedHere = (changed, kind, at) => changed.some(ch => ch.kind === kind && ch.action === PlanChangeAction.Added && (kind === PlanChangeKind.Page ? ch.page === at.page : ch.name === at.name));
|
|
14196
14338
|
/** One handler per op — a data table, never an if-chain. */
|
|
14197
14339
|
const APPLY = {
|
|
14198
14340
|
[PlanEditOp.AddPage]: (d, e, c, path, changed) => {
|
|
@@ -14257,7 +14399,8 @@ const APPLY = {
|
|
|
14257
14399
|
const index = e.at === undefined ? sections.length : clamp(e.at, sections.length);
|
|
14258
14400
|
sections.splice(index, 0, e.section);
|
|
14259
14401
|
setSections(d, at, sections);
|
|
14260
|
-
changed
|
|
14402
|
+
if (!addedHere(changed, PlanChangeKind.Page, { page: d.pages[at].key }))
|
|
14403
|
+
changed.push({ kind: PlanChangeKind.Section, action: PlanChangeAction.Added, page: d.pages[at].key, index });
|
|
14261
14404
|
},
|
|
14262
14405
|
[PlanEditOp.RemoveSection]: (d, e, c, path, changed) => {
|
|
14263
14406
|
const at = findPage(d, e.page, c, `${path}.page`);
|
|
@@ -14329,9 +14472,10 @@ const APPLY = {
|
|
|
14329
14472
|
const type = d.contentTypes[at];
|
|
14330
14473
|
if (type.fields.some(f => f.name === e.field.name))
|
|
14331
14474
|
return c.add(PlanEditIssueCode.Duplicate, `${path}.field.name`, `"${type.name}" already has a field "${e.field.name}"`);
|
|
14332
|
-
const seed = type.seed ? { ...type.seed, [e.field.name]: e.sample ?? '' } : undefined;
|
|
14475
|
+
const seed = type.seed ? { ...type.seed, [e.field.name]: e.sample ?? type.seed[e.field.name] ?? '' } : undefined;
|
|
14333
14476
|
d.contentTypes[at] = { ...type, fields: [...type.fields, e.field], ...(seed ? { seed } : {}) };
|
|
14334
|
-
changed
|
|
14477
|
+
if (!addedHere(changed, PlanChangeKind.ContentType, { name: type.name }))
|
|
14478
|
+
changed.push({ kind: PlanChangeKind.Field, action: PlanChangeAction.Added, name: `${type.name}.${e.field.name}` });
|
|
14335
14479
|
},
|
|
14336
14480
|
[PlanEditOp.SetSectionData]: (d, e, c, path, changed) => {
|
|
14337
14481
|
const at = findPage(d, e.page, c, `${path}.page`);
|
|
@@ -14370,20 +14514,34 @@ function applyPlanEdits(plan, edits) {
|
|
|
14370
14514
|
return { plan: { ...plan, pages: draft.pages, contentTypes: draft.contentTypes, dataSources }, changed, issues: [] };
|
|
14371
14515
|
}
|
|
14372
14516
|
// ── The format a model writes edits in ─────────────────────────────────────────────────────────────────
|
|
14373
|
-
|
|
14517
|
+
/** Each part's answer format name. */
|
|
14518
|
+
const PLAN_EDITS_PART_SCHEMA_NAMES = {
|
|
14519
|
+
[PlanPart.Structure]: 'plan_structure_edits',
|
|
14520
|
+
[PlanPart.Pages]: 'plan_page_edits',
|
|
14521
|
+
};
|
|
14374
14522
|
/** A sentence's answer: the edits, or why the ops cannot say it (one line, in the owner's language). */
|
|
14375
14523
|
var PlanEditsOutcome;
|
|
14376
14524
|
(function (PlanEditsOutcome) {
|
|
14377
14525
|
PlanEditsOutcome["Edits"] = "edits";
|
|
14378
14526
|
PlanEditsOutcome["Cannot"] = "cannot";
|
|
14379
14527
|
})(PlanEditsOutcome || (PlanEditsOutcome = {}));
|
|
14380
|
-
/**
|
|
14381
|
-
|
|
14382
|
-
|
|
14383
|
-
|
|
14384
|
-
|
|
14528
|
+
/**
|
|
14529
|
+
* Each part's answer, explained. The structure is asked first, so it says whether the sentence is a plan change at all
|
|
14530
|
+
* (`outcome`); the pages are asked only when it is, and write only their edits.
|
|
14531
|
+
*/
|
|
14532
|
+
const PLAN_EDITS_ANSWER_SHAPES = {
|
|
14533
|
+
[PlanPart.Structure]: {
|
|
14534
|
+
outcome: "'edits' | 'cannot' — cannot: no operation of either part (below) can do what the sentence asks",
|
|
14535
|
+
edits: 'edit[] — the content types and fields the sentence adds, in order; empty when it adds none, or cannot',
|
|
14536
|
+
reason: "string — when cannot, ONE short line for the owner in their language (what to do instead); else ''",
|
|
14537
|
+
},
|
|
14538
|
+
[PlanPart.Pages]: {
|
|
14539
|
+
edits: 'edit[] — the page and section edits, in order, made after the structure edits (already applied); empty when none',
|
|
14540
|
+
},
|
|
14385
14541
|
};
|
|
14386
14542
|
const NO_INDEX = -1;
|
|
14543
|
+
/** A new page or content type on the wire: its children are edits of their own. */
|
|
14544
|
+
const CHILDLESS = { children: false };
|
|
14387
14545
|
/** A list holding exactly one object: none → absent, one → it (expanded), more → the list (the gate refuses it). */
|
|
14388
14546
|
const oneOf = (schema, expand, toWire, what) => ({
|
|
14389
14547
|
text: `${what}[] — exactly one when the op takes it`,
|
|
@@ -14401,17 +14559,22 @@ const EDIT_WIRE = {
|
|
|
14401
14559
|
after: textKey('addPage: the key of the page it follows in the menu'),
|
|
14402
14560
|
into: textKey('mergePages: the page that stays'),
|
|
14403
14561
|
from: textKey('mergePages: the page whose sections move into it, then removed'),
|
|
14404
|
-
index: indexKey('the section, 0-based on its page'),
|
|
14562
|
+
index: indexKey('the section, 0-based on its page (addSection: the place the new one goes)'),
|
|
14405
14563
|
to: indexKey('movePage / moveSection: the new 0-based place'),
|
|
14406
|
-
at: indexKey('addSection: its 0-based place on the page'),
|
|
14407
14564
|
pattern: { text: 'string — swapPattern: the section pattern it becomes', none: '', schema: () => jsonOneOf(['', ...sectionPatternIds()]) },
|
|
14408
14565
|
slot: textKey("setSlot: one of the pattern's slots"),
|
|
14409
14566
|
text: textKey("setSlot: the words, in the owner's language and voice"),
|
|
14410
14567
|
binding: textKey('setSectionData: the list source the section repeats'),
|
|
14411
14568
|
contentType: textKey('addField: a content type of the plan'),
|
|
14412
|
-
newPage:
|
|
14569
|
+
newPage: {
|
|
14570
|
+
...oneOf(() => wirePageJsonSchema(CHILDLESS), v => expandWirePage(v, CHILDLESS), p => pageToWire(p, CHILDLESS), 'page'),
|
|
14571
|
+
text: 'page[] — exactly one when the op takes it, WITHOUT sections: each is an addSection after it',
|
|
14572
|
+
},
|
|
14413
14573
|
newSection: oneOf(wireSectionJsonSchema, expandWireSection, sectionToWire, 'section'),
|
|
14414
|
-
newContentType:
|
|
14574
|
+
newContentType: {
|
|
14575
|
+
...oneOf(() => wireContentTypeJsonSchema(CHILDLESS), v => expandWireContentType(v, CHILDLESS), t => contentTypeToWire(t, CHILDLESS), 'contentType'),
|
|
14576
|
+
text: "contentType[] — exactly one when the op takes it, WITHOUT fields: each is an addField after it (the samples' values stay in its seed)",
|
|
14577
|
+
},
|
|
14415
14578
|
dataSources: {
|
|
14416
14579
|
text: 'dataSource[] — addContentType: the list / single sources its pages read', none: [],
|
|
14417
14580
|
schema: () => jsonListOf(wireDataSourceJsonSchema()),
|
|
@@ -14428,7 +14591,7 @@ const PLAN_EDIT_WIRE_KEYS = {
|
|
|
14428
14591
|
[PlanEditOp.RenamePage]: { page: 'page', name: 'name', route: 'route' },
|
|
14429
14592
|
[PlanEditOp.MergePages]: { into: 'into', from: 'from' },
|
|
14430
14593
|
[PlanEditOp.MovePage]: { page: 'page', to: 'to' },
|
|
14431
|
-
[PlanEditOp.AddSection]: { page: 'page', section: 'newSection', at: '
|
|
14594
|
+
[PlanEditOp.AddSection]: { page: 'page', section: 'newSection', at: 'index' },
|
|
14432
14595
|
[PlanEditOp.RemoveSection]: { page: 'page', index: 'index' },
|
|
14433
14596
|
[PlanEditOp.MoveSection]: { page: 'page', from: 'index', to: 'to' },
|
|
14434
14597
|
[PlanEditOp.SwapPattern]: { page: 'page', index: 'index', pattern: 'pattern' },
|
|
@@ -14437,19 +14600,27 @@ const PLAN_EDIT_WIRE_KEYS = {
|
|
|
14437
14600
|
[PlanEditOp.AddField]: { contentType: 'contentType', field: 'newField', sample: 'sample' },
|
|
14438
14601
|
[PlanEditOp.SetSectionData]: { page: 'page', index: 'index', binding: 'binding' },
|
|
14439
14602
|
};
|
|
14603
|
+
/** The wire keys a part's edit carries: every key its ops read, in `EDIT_WIRE` order. */
|
|
14604
|
+
function planEditWireKeys(part) {
|
|
14605
|
+
const read = new Set(planEditOps(part).flatMap(op => Object.values(PLAN_EDIT_WIRE_KEYS[op])));
|
|
14606
|
+
return Object.keys(EDIT_WIRE).filter(k => read.has(k));
|
|
14607
|
+
}
|
|
14440
14608
|
const isEditWireNone = (wireKey, v) => {
|
|
14441
14609
|
const none = EDIT_WIRE[wireKey].none;
|
|
14442
14610
|
return v === undefined || (Array.isArray(none) ? Array.isArray(v) && v.length === 0 : v === none) || (wireKey === 'sample' && Array.isArray(v) && v.length === 0);
|
|
14443
14611
|
};
|
|
14444
14612
|
const noneText = (none) => (Array.isArray(none) ? '[]' : typeof none === 'string' ? "''" : String(none));
|
|
14445
|
-
|
|
14446
|
-
|
|
14447
|
-
|
|
14448
|
-
|
|
14449
|
-
}
|
|
14450
|
-
|
|
14451
|
-
|
|
14452
|
-
|
|
14613
|
+
const opsText = (part) => planEditOps(part).map(op => `'${op}'`).join(' | ');
|
|
14614
|
+
/** A part's flat edit, as a shape table: every key required, each with its none. */
|
|
14615
|
+
function planEditWireShape(part) {
|
|
14616
|
+
return {
|
|
14617
|
+
op: `${opsText(part)} — which keys below are read`,
|
|
14618
|
+
...Object.fromEntries(planEditWireKeys(part).map(k => [k, `${EDIT_WIRE[k].text} — ${noneText(EDIT_WIRE[k].none)} when the op does not read it`])),
|
|
14619
|
+
};
|
|
14620
|
+
}
|
|
14621
|
+
/** Per op of a part, the wire keys it reads, in `PLAN_EDIT_SHAPE`'s words (`?` = may hold its none) — what the model reads. */
|
|
14622
|
+
function planEditWireOps(part) {
|
|
14623
|
+
return Object.fromEntries(planEditOps(part).map(op => {
|
|
14453
14624
|
const entry = PLAN_EDIT_SHAPE[op];
|
|
14454
14625
|
const keys = PLAN_EDIT_WIRE_KEYS[op];
|
|
14455
14626
|
return [op, Object.fromEntries(Object.entries(keys).map(([planKey, wireKey]) => {
|
|
@@ -14477,20 +14648,21 @@ function planEditWireGaps() {
|
|
|
14477
14648
|
}
|
|
14478
14649
|
return gaps;
|
|
14479
14650
|
}
|
|
14480
|
-
|
|
14481
|
-
|
|
14651
|
+
const ANSWER_SCHEMAS = {
|
|
14652
|
+
outcome: () => jsonOneOf(Object.values(PlanEditsOutcome)),
|
|
14653
|
+
reason: () => jsonText(),
|
|
14654
|
+
edits: part => jsonListOf(jsonShapedObject(planEditWireShape(part), {
|
|
14655
|
+
op: jsonOneOf(planEditOps(part)),
|
|
14656
|
+
...Object.fromEntries(planEditWireKeys(part).map(k => [k, EDIT_WIRE[k].schema()])),
|
|
14657
|
+
}, `edit ${part}`)),
|
|
14658
|
+
};
|
|
14659
|
+
/** The format one part of a sentence's answer is written in — one request each (vectoriox/iox-cms#47). */
|
|
14660
|
+
function planEditsJsonSchema(part) {
|
|
14482
14661
|
const gaps = planEditWireGaps();
|
|
14483
14662
|
if (gaps.length)
|
|
14484
14663
|
throw new PlanWireShapeError(`the plan edits wire does not carry ${gaps.join(', ')}`);
|
|
14485
|
-
const
|
|
14486
|
-
|
|
14487
|
-
...Object.fromEntries(Object.entries(EDIT_WIRE).map(([k, spec]) => [k, spec.schema()])),
|
|
14488
|
-
}, 'edit');
|
|
14489
|
-
return jsonShapedObject(PLAN_EDITS_ANSWER_SHAPE, {
|
|
14490
|
-
outcome: jsonOneOf(Object.values(PlanEditsOutcome)),
|
|
14491
|
-
edits: jsonListOf(edit),
|
|
14492
|
-
reason: jsonText(),
|
|
14493
|
-
}, 'planEdits');
|
|
14664
|
+
const shape = PLAN_EDITS_ANSWER_SHAPES[part];
|
|
14665
|
+
return jsonShapedObject(shape, Object.fromEntries(shapeKeys(shape).map(k => [k, ANSWER_SCHEMAS[k](part)])), `planEdits ${part}`);
|
|
14494
14666
|
}
|
|
14495
14667
|
/** One flat edit → the op's edit: the keys it reads, a none left out where the op may go without it. Never repaired. */
|
|
14496
14668
|
function expandWireEdit(wire) {
|
|
@@ -14511,9 +14683,10 @@ function expandWireEdit(wire) {
|
|
|
14511
14683
|
}
|
|
14512
14684
|
return out;
|
|
14513
14685
|
}
|
|
14514
|
-
/** An edit →
|
|
14515
|
-
function
|
|
14516
|
-
const
|
|
14686
|
+
/** An edit's own keys → its flat wire edit, in its part's keys. */
|
|
14687
|
+
function flatEdit(edit) {
|
|
14688
|
+
const part = PLAN_EDIT_OP_PART[edit.op];
|
|
14689
|
+
const out = { op: edit.op, ...Object.fromEntries(planEditWireKeys(part).map(k => [k, Array.isArray(EDIT_WIRE[k].none) ? [] : EDIT_WIRE[k].none])) };
|
|
14517
14690
|
for (const [planKey, wireKey] of Object.entries(PLAN_EDIT_WIRE_KEYS[edit.op])) {
|
|
14518
14691
|
const value = edit[planKey];
|
|
14519
14692
|
if (value === undefined)
|
|
@@ -14522,7 +14695,26 @@ function planEditToWire(edit) {
|
|
|
14522
14695
|
}
|
|
14523
14696
|
return out;
|
|
14524
14697
|
}
|
|
14525
|
-
/**
|
|
14698
|
+
/** The children an added page or content type travels without, as the edits that add them after it. */
|
|
14699
|
+
function childEdits(edit) {
|
|
14700
|
+
if (edit.op === PlanEditOp.AddPage)
|
|
14701
|
+
return edit.page.sections.map(section => ({ op: PlanEditOp.AddSection, page: edit.page.key, section }));
|
|
14702
|
+
if (edit.op === PlanEditOp.AddContentType)
|
|
14703
|
+
return edit.contentType.fields.map(field => ({ op: PlanEditOp.AddField, contentType: edit.contentType.name, field }));
|
|
14704
|
+
return [];
|
|
14705
|
+
}
|
|
14706
|
+
/** An edit → the flat wire edits a model would write for it: itself, then its children (fakes and specs write them). */
|
|
14707
|
+
function planEditToWire(edit) {
|
|
14708
|
+
return [flatEdit(edit), ...childEdits(edit).map(flatEdit)];
|
|
14709
|
+
}
|
|
14710
|
+
/** Edits → each part's flat wire edits, in order (what each part's answer would hold). */
|
|
14711
|
+
function planEditsToWire(edits) {
|
|
14712
|
+
const out = Object.fromEntries(PLAN_PARTS.map(part => [part, []]));
|
|
14713
|
+
for (const edit of edits)
|
|
14714
|
+
out[PLAN_EDIT_OP_PART[edit.op]].push(...planEditToWire(edit));
|
|
14715
|
+
return out;
|
|
14716
|
+
}
|
|
14717
|
+
/** A model's answer (either part's) → edits (each flat edit expanded); never repaired. */
|
|
14526
14718
|
function planEditsFromWire(answer) {
|
|
14527
14719
|
if (!isPlainObject(answer) || !Array.isArray(answer['edits']))
|
|
14528
14720
|
return answer;
|
|
@@ -15105,5 +15297,5 @@ function addStockCredits(footer, credits) {
|
|
|
15105
15297
|
* Generated bundle index. Do not edit.
|
|
15106
15298
|
*/
|
|
15107
15299
|
|
|
15108
|
-
export { ACTIVE_BUILD_STATUSES, AI_REFUSAL_NEXT, AI_REFUSAL_STATUS, AI_STEPS, AI_STEP_LABELS, AI_STEP_NEEDS, AI_STEP_TYPICAL_TOKENS, AiCallStatus, AiCapability, AiFeature, AiGenerationStage, AiKeyMode, AiLimit, AiNextKind, AiProviderId, AiRefusalCode, AiStep, BAR_ITEMS_MAX, BAR_ITEMS_MIN, BASELINE_ROLES, BUILD_STEPS, BaselineRole, BindScope, BuildNoticeCode, BuildStep, CONTENT_ROLE_MEANINGS, CREDITS_PER_USD, CardBindTarget, CheckSeverity, ComposeError, ComposeIssueCode, ContentRole, ContentTypeAction, CreatedKind, DANGLING_LINK_CODES, DEFAULT_PHONE_MENU_BREAKPOINT, DEFAULT_SITE_LOCALE, ENTRANCE_EASING, ENTRANCE_ORIGIN_LOOK, ENTRANCE_TRIGGER, EntrancePart, EntranceSide, FACT_KEYS, FINAL_GENERATION_STAGES, FINDING_SEVERITY, GlobalSlot, HEBREW_PREFIXES, JSON_SCHEMA_BANNED_KEYWORDS, JsonSchemaShapeError, KeptReason, NAV_PART_LABELS, NAV_SHAPE_COPY, NEUTRAL_STYLE_PROPS, NODE_BASELINES, NO_TOKENS, NavPagesSource, NavRole, NavShape, NavShapeError, PAGE_ARCHETYPES,
|
|
15300
|
+
export { ACTIVE_BUILD_STATUSES, AI_REFUSAL_NEXT, AI_REFUSAL_STATUS, AI_STEPS, AI_STEP_LABELS, AI_STEP_NEEDS, AI_STEP_PART_TOKENS, AI_STEP_TYPICAL_TOKENS, AiCallStatus, AiCapability, AiFeature, AiGenerationStage, AiKeyMode, AiLimit, AiNextKind, AiProviderId, AiRefusalCode, AiStep, BAR_ITEMS_MAX, BAR_ITEMS_MIN, BASELINE_ROLES, BUILD_STEPS, BaselineRole, BindScope, BuildNoticeCode, BuildStep, CONTENT_ROLE_MEANINGS, CREDITS_PER_USD, CardBindTarget, CheckSeverity, ComposeError, ComposeIssueCode, ContentRole, ContentTypeAction, CreatedKind, DANGLING_LINK_CODES, DEFAULT_PHONE_MENU_BREAKPOINT, DEFAULT_SITE_LOCALE, ENTRANCE_EASING, ENTRANCE_ORIGIN_LOOK, ENTRANCE_TRIGGER, EntrancePart, EntranceSide, FACT_KEYS, FINAL_GENERATION_STAGES, FINDING_SEVERITY, GRAMMAR_TOO_LARGE_MESSAGE, GlobalSlot, HEBREW_PREFIXES, JSON_SCHEMA_BANNED_KEYWORDS, JsonSchemaShapeError, KeptReason, NAV_PART_LABELS, NAV_SHAPE_COPY, NEUTRAL_STYLE_PROPS, NODE_BASELINES, NO_TOKENS, NavPagesSource, NavRole, NavShape, NavShapeError, PAGE_ARCHETYPES, PLAN_EDITS_ANSWER_SHAPES, PLAN_EDITS_PART_SCHEMA_NAMES, PLAN_EDIT_OP_PART, PLAN_EDIT_SHAPE, PLAN_EDIT_WIRE_KEYS, PLAN_PARTS, PLAN_PART_KEYS, PLAN_WIRE_DERIVED, PLAN_WIRE_PART_SCHEMA_NAMES, PLAN_WIRE_SHAPE, PRICING_PATTERN, PUBLISH_BLOCKING_PLACEHOLDERS, PageDataKind, PathIssueCollector, PatternDirective, PatternGlobalPosition, PatternOpens, PatternPlaceholder, PatternRecipe, PhoneMenuBreakpoint, PlaceholderKind, PlanChangeAction, PlanChangeKind, PlanDataSourceMode, PlanDataSourceType, PlanEditIssueCode, PlanEditOp, PlanEditsOutcome, PlanPart, PlanWireEntry, PlanWireShapeError, PlanWordIssueCode, PreflightRefusalCode, PublishRefusalCode, REQUIRED_CONTENT_ROLES, RelativeOp, SAMPLE_IMAGE_REF, SITE_CHECKS, SITE_FACT_LABELS, SITE_HEADER_PATTERN, SITE_LIBRARY, SITE_LOCALES, SITE_NEED_IDS, SITE_PATTERNS, SITE_PLAN_VERSION, SITE_TEMPLATES, SPEC_SITE_ARCHETYPES, SPEC_SITE_COPY_LENGTHS, SPEC_SITE_DENSITY_COPY, SPEC_SITE_FACTS, SPEC_SITE_LIBRARY, SPEC_SITE_LOCALES, SPEC_SITE_NEED_IDS, SPEC_SITE_PATTERNS, SPEC_SITE_PLAN_SHAPE, SPEC_SITE_REQUIRED_ROLES, SPEC_SITE_ROLES, SPEC_SITE_TEMPLATES, STOCK_FAMILIES, STOCK_PROVIDER_TERMS, STRICT_SCHEMA_BUDGET, STRICT_SCHEMA_LIMITS, STUDIO_TEMPLATE, SiteBuildKind, SiteBuildStatus, SiteCheckId, SiteFindingCode, SiteLocale, SitePlanError, SitePlanIssueCode, SitePlanSourceKind, SiteTemplateError, SiteTemplateId, StepState, StockProviderId, addBarItem, addStockCredits, aiStepParts, applyPlanEdits, applyStockPhotos, archetypeIssues, assertStyleSlots, assertValidSitePlan, auditSite, barIconForRoute, blocksPublish, buildKeys, buildStepFacts, builtHomePageId, composeNavHeader, composeSection, composeSite, contentTypePreviewFields, contentTypeToWire, copyLengthOf, costUsd, createSerializedNode, creditsOfUsd, dataSourceToWire, describePlanData, describeShapeKey, entranceAction, entranceInteraction, estimateStepCredits, expandPlanWire, expandWireContentType, expandWireDataSource, expandWireEdit, expandWireField, expandWirePage, expandWireSection, failedBuildStep, fieldToWire, findNavRoot, isActiveBuild, isNonEmptyString, isPlainObject, isRtlLocale, jsonAnyOf, jsonBoolean, jsonClosedObject, jsonInteger, jsonListOf, jsonOneOf, jsonSchemaKeywords, jsonSchemaObjects, jsonShapedObject, jsonText, modelCanDo, moveBarItem, navPages, navShapeBreakpoint, navShapeOf, nextSourceOf, nodeBindPaths, pageToWire, parsePatternPath, parseSampleImageRef, pathAt, patternById, patternHasForm, patternLinkSlots, patternManifest, patternNeedsBinding, patternNodes, patternOptionalParts, patternRoleNeeds, patternScopes, patternSlotBudgets, patternSlots, patternTakesBinding, phoneMenuPages, planEditOps, planEditToWire, planEditWireGaps, planEditWireKeys, planEditWireOps, planEditWireShape, planEditsFromWire, planEditsJsonSchema, planEditsToWire, planEntrances, planFromTemplate, planNavShape, planPartOfPath, planPartToWire, planStockPhotos, planToWire, planWireGaps, planWirePartJsonSchema, planWirePartShape, planWordIssues, publishRefusal, publishVerdict, releasedLayoutDefaults, removeBarItem, requiredAcknowledgements, requiredShapeKeys, roleField, routeMatches, routeParams, sampleImageRef, sectionEntrances, sectionPatternIds, sectionToWire, setStyleProps, shapeKeys, showcaseGlobals, showcasePages, showcasePlans, siteNavPages, slotBudget, stockCopyKey, stockCreditLine, stockCreditLines, stockCreditNode, stockCreditNodeId, stockSlots, strictSchemaComplexity, strictSchemaRefusal, typicalStepTokens, validatePlanEdits, validateSitePlan, walkSerialized, wireContentTypeJsonSchema, wireDataSourceJsonSchema, wireFieldJsonSchema, wirePageJsonSchema, wireSectionJsonSchema, wordPattern, writtenTexts };
|
|
15109
15301
|
//# sourceMappingURL=vectoriox-iox-ui-generation-core.mjs.map
|