@vectoriox/iox-ui 5.61.0 → 5.62.0
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 +464 -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 +148 -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,11 @@ 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 { roles, fields, seed, ...rest } = wire;
|
|
13827
|
-
const out = { ...rest, roles:
|
|
13713
|
+
const { roles, fields, seed, ...rest } = options.children === false ? { fields: [], ...wire } : wire;
|
|
13714
|
+
const out = { ...rest, roles: listToMap(roles, 'role', 'field'), fields: each(fields, expandWireField) };
|
|
13828
13715
|
if (!isPlainObject(seed))
|
|
13829
13716
|
return seed === undefined ? out : { ...out, seed };
|
|
13830
13717
|
const { values, ...fixed } = seed;
|
|
@@ -13859,10 +13746,10 @@ function expandWireSection(wire) {
|
|
|
13859
13746
|
...optional('enter', enter),
|
|
13860
13747
|
};
|
|
13861
13748
|
}
|
|
13862
|
-
function expandWirePage(wire) {
|
|
13749
|
+
function expandWirePage(wire, options = {}) {
|
|
13863
13750
|
if (!isPlainObject(wire))
|
|
13864
13751
|
return wire;
|
|
13865
|
-
const { archetype, need, template, routeSource, seo, sections, ...rest } = wire;
|
|
13752
|
+
const { archetype, need, template, routeSource, seo, sections, ...rest } = options.children === false ? { sections: [], ...wire } : wire;
|
|
13866
13753
|
const words = isPlainObject(seo) ? { ...optional('title', seo['title']), ...optional('description', seo['description']) } : seo;
|
|
13867
13754
|
return {
|
|
13868
13755
|
...rest,
|
|
@@ -13882,12 +13769,17 @@ function expandWireBusiness(wire, facts) {
|
|
|
13882
13769
|
return { ...rest, ...optional('tagline', tagline), ...(facts && Object.keys(facts).length ? { facts: { ...facts } } : {}) };
|
|
13883
13770
|
}
|
|
13884
13771
|
/**
|
|
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
|
|
13772
|
+
* A model's plan (its parts, on the wire) + what the generation decides (the look, the run, the language, the owner's
|
|
13773
|
+
* facts) → the Site Plan. Pure. Translates, never repairs: whatever an answer got wrong is still wrong, for
|
|
13774
|
+
* `validateSitePlan` — a part that is not an object is the result itself, a part not written yet leaves its keys out
|
|
13775
|
+
* (the validator then names them, at their own paths).
|
|
13887
13776
|
*/
|
|
13888
|
-
function expandPlanWire(
|
|
13889
|
-
|
|
13890
|
-
|
|
13777
|
+
function expandPlanWire(parts, context) {
|
|
13778
|
+
const written = PLAN_PARTS.filter(part => parts[part] !== undefined).map(part => parts[part]);
|
|
13779
|
+
const odd = written.find(value => !isPlainObject(value));
|
|
13780
|
+
if (odd !== undefined)
|
|
13781
|
+
return odd;
|
|
13782
|
+
const wire = Object.assign({}, ...written);
|
|
13891
13783
|
const { business, contentTypes, dataSources, pages, ...rest } = wire;
|
|
13892
13784
|
return {
|
|
13893
13785
|
...rest,
|
|
@@ -13905,11 +13797,12 @@ function expandPlanWire(wire, context) {
|
|
|
13905
13797
|
function fieldToWire(field) {
|
|
13906
13798
|
return { name: field.name, type: field.type, controller: field.controller, required: field.required ?? false, note: field.note ?? NONE };
|
|
13907
13799
|
}
|
|
13908
|
-
function contentTypeToWire(type) {
|
|
13909
|
-
const
|
|
13800
|
+
function contentTypeToWire(type, options = {}) {
|
|
13801
|
+
const played = type.roles;
|
|
13802
|
+
const roles = ROLE_KEYS.filter(role => played[role]).map(role => ({ role, field: played[role] }));
|
|
13910
13803
|
const { count = 0, sample = false, isPublished = false, ...values } = type.seed ?? {};
|
|
13911
13804
|
return {
|
|
13912
|
-
name: type.name, label: type.label, roles, fields: type.fields.map(fieldToWire),
|
|
13805
|
+
name: type.name, label: type.label, roles, ...(options.children === false ? {} : { fields: type.fields.map(fieldToWire) }),
|
|
13913
13806
|
seed: { count, sample, isPublished, values: mapToList(values, 'field', 'value') },
|
|
13914
13807
|
};
|
|
13915
13808
|
}
|
|
@@ -13928,24 +13821,224 @@ function sectionToWire(section) {
|
|
|
13928
13821
|
binding: section.binding ?? NONE, without: [...(section.without ?? [])], form: section.form ?? NONE, enter: section.enter ?? NONE,
|
|
13929
13822
|
};
|
|
13930
13823
|
}
|
|
13931
|
-
function pageToWire(page) {
|
|
13824
|
+
function pageToWire(page, options = {}) {
|
|
13932
13825
|
return {
|
|
13933
13826
|
key: page.key, name: page.name, route: page.route, inNav: page.inNav ?? false, archetype: page.archetype ?? NONE, need: page.need ?? NONE,
|
|
13934
13827
|
template: page.template ?? NONE, routeSource: page.routeSource ?? NONE,
|
|
13935
|
-
seo: { title: page.seo?.title ?? NONE, description: page.seo?.description ?? NONE },
|
|
13828
|
+
seo: { title: page.seo?.title ?? NONE, description: page.seo?.description ?? NONE },
|
|
13829
|
+
...(options.children === false ? {} : { sections: page.sections.map(sectionToWire) }),
|
|
13936
13830
|
};
|
|
13937
13831
|
}
|
|
13938
|
-
|
|
13832
|
+
const PLAN_TO_WIRE = {
|
|
13833
|
+
business: plan => ({ name: plan.business.name, tagline: plan.business.tagline ?? NONE }),
|
|
13834
|
+
contentTypes: plan => plan.contentTypes.map(type => contentTypeToWire(type)),
|
|
13835
|
+
dataSources: plan => plan.dataSources.map(dataSourceToWire),
|
|
13836
|
+
pages: plan => plan.pages.map(page => pageToWire(page)),
|
|
13837
|
+
globals: plan => ({ ...plan.globals }),
|
|
13838
|
+
};
|
|
13839
|
+
/** One part of the plan on the wire: what that part's request would have answered. */
|
|
13840
|
+
function planPartToWire(plan, part) {
|
|
13841
|
+
return Object.fromEntries(PLAN_PART_KEYS[part].map(k => [k, PLAN_TO_WIRE[k](plan)]));
|
|
13842
|
+
}
|
|
13843
|
+
/** The plan → the wire, by part: what a model would have written (the change prompt shows it; fakes and evals write it). */
|
|
13939
13844
|
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
|
-
};
|
|
13845
|
+
return Object.fromEntries(PLAN_PARTS.map(part => [part, planPartToWire(plan, part)]));
|
|
13947
13846
|
}
|
|
13948
13847
|
|
|
13848
|
+
/**
|
|
13849
|
+
* The AI pipeline's WIRE vocabulary (AI plan §3c, §3n, M0): what iox-cms's `ai` API answers and what every client
|
|
13850
|
+
* of it reads — the steps a model works on, who pays (IOX credits or the studio's own key), a model of the vetted
|
|
13851
|
+
* list with its price per token class, the one cost → credits rule (1 credit = 1 US cent of what the AI cost, D4),
|
|
13852
|
+
* the refusals (board 11) and a generation's stages. ONE definition: iox-cms meters and refuses with it, the
|
|
13853
|
+
* Settings ▸ AI screen and the wizard show it. Pure types, enums and helpers; no transport, no prices typed here.
|
|
13854
|
+
* The step and capability spellings are the control plane's (`GET /instances/me/entitlements`, AI plan "M0 control
|
|
13855
|
+
* plane — the instance-route contract") — the platform has ONE spelling of each.
|
|
13856
|
+
*/
|
|
13857
|
+
var AiProviderId;
|
|
13858
|
+
(function (AiProviderId) {
|
|
13859
|
+
AiProviderId["Anthropic"] = "anthropic";
|
|
13860
|
+
AiProviderId["OpenRouter"] = "openrouter";
|
|
13861
|
+
})(AiProviderId || (AiProviderId = {}));
|
|
13862
|
+
/** Who pays: the platform's credits, or the studio's own provider key (metered for display, never charged). */
|
|
13863
|
+
var AiKeyMode;
|
|
13864
|
+
(function (AiKeyMode) {
|
|
13865
|
+
AiKeyMode["Platform"] = "platform";
|
|
13866
|
+
AiKeyMode["Own"] = "own";
|
|
13867
|
+
})(AiKeyMode || (AiKeyMode = {}));
|
|
13868
|
+
/** The steps a model works on (board 9's rows), in screen order. */
|
|
13869
|
+
var AiStep;
|
|
13870
|
+
(function (AiStep) {
|
|
13871
|
+
AiStep["ReadMessage"] = "analysis";
|
|
13872
|
+
AiStep["PlanSite"] = "plan";
|
|
13873
|
+
AiStep["ChangePlan"] = "planEdit";
|
|
13874
|
+
AiStep["MakePictures"] = "image";
|
|
13875
|
+
AiStep["NameStructures"] = "scanNaming";
|
|
13876
|
+
})(AiStep || (AiStep = {}));
|
|
13877
|
+
const AI_STEPS = Object.values(AiStep);
|
|
13878
|
+
/** Each step's name on the wire (the control plane's `label`, the console) — the screens translate their own. */
|
|
13879
|
+
const AI_STEP_LABELS = {
|
|
13880
|
+
[AiStep.ReadMessage]: 'Read your message',
|
|
13881
|
+
[AiStep.PlanSite]: 'Plan the site',
|
|
13882
|
+
[AiStep.ChangePlan]: 'Change the plan',
|
|
13883
|
+
[AiStep.MakePictures]: 'Make pictures',
|
|
13884
|
+
[AiStep.NameStructures]: 'Name repeated structures',
|
|
13885
|
+
};
|
|
13886
|
+
/** What a model must be able to do for a step to run on it. */
|
|
13887
|
+
var AiCapability;
|
|
13888
|
+
(function (AiCapability) {
|
|
13889
|
+
AiCapability["StructuredOutput"] = "structuredOutputs";
|
|
13890
|
+
AiCapability["ImageInput"] = "imageInput";
|
|
13891
|
+
AiCapability["ImageOutput"] = "imageOutput";
|
|
13892
|
+
})(AiCapability || (AiCapability = {}));
|
|
13893
|
+
const AI_STEP_NEEDS = {
|
|
13894
|
+
[AiStep.ReadMessage]: [AiCapability.StructuredOutput, AiCapability.ImageInput],
|
|
13895
|
+
[AiStep.PlanSite]: [AiCapability.StructuredOutput],
|
|
13896
|
+
[AiStep.ChangePlan]: [AiCapability.StructuredOutput],
|
|
13897
|
+
[AiStep.MakePictures]: [AiCapability.ImageOutput],
|
|
13898
|
+
[AiStep.NameStructures]: [AiCapability.StructuredOutput],
|
|
13899
|
+
};
|
|
13900
|
+
/**
|
|
13901
|
+
* The steps a model answers in the plan's parts, one request per part (vectoriox/iox-cms#47 — the whole plan's format
|
|
13902
|
+
* compiles to a grammar Claude refuses), and the tokens a typical request of each part reads and writes. Each part reads
|
|
13903
|
+
* the same instructions; the pages read the structure too and write most of the words.
|
|
13904
|
+
*/
|
|
13905
|
+
const AI_STEP_PART_TOKENS = {
|
|
13906
|
+
[AiStep.PlanSite]: {
|
|
13907
|
+
[PlanPart.Structure]: { input: 10_000, output: 1_500, cacheWrite: 0, cacheRead: 0 },
|
|
13908
|
+
[PlanPart.Pages]: { input: 12_000, output: 5_500, cacheWrite: 0, cacheRead: 0 },
|
|
13909
|
+
},
|
|
13910
|
+
[AiStep.ChangePlan]: {
|
|
13911
|
+
[PlanPart.Structure]: { input: 6_000, output: 300, cacheWrite: 0, cacheRead: 0 },
|
|
13912
|
+
[PlanPart.Pages]: { input: 6_500, output: 400, cacheWrite: 0, cacheRead: 0 },
|
|
13913
|
+
},
|
|
13914
|
+
};
|
|
13915
|
+
/** The parts a step is asked in, in order — none for a step asked in one request. */
|
|
13916
|
+
function aiStepParts(step) {
|
|
13917
|
+
return AI_STEP_PART_TOKENS[step] ? PLAN_PARTS : [];
|
|
13918
|
+
}
|
|
13919
|
+
const addTokens = (a, b) => ({
|
|
13920
|
+
input: a.input + b.input, output: a.output + b.output, cacheWrite: a.cacheWrite + b.cacheWrite, cacheRead: a.cacheRead + b.cacheRead,
|
|
13921
|
+
...(a.imageOutput !== undefined || b.imageOutput !== undefined ? { imageOutput: (a.imageOutput ?? 0) + (b.imageOutput ?? 0) } : {}),
|
|
13922
|
+
});
|
|
13923
|
+
const partsTotal = (step) => PLAN_PARTS.map(part => AI_STEP_PART_TOKENS[step][part]).reduce(addTokens);
|
|
13924
|
+
/**
|
|
13925
|
+
* Tokens a typical use of a step reads and writes (plan §3d "Where credits go") — what a shown estimate is made of. A
|
|
13926
|
+
* step asked in parts is every part's request together, so the estimate (and a generation's start gate) counts them all.
|
|
13927
|
+
*/
|
|
13928
|
+
const AI_STEP_TYPICAL_TOKENS = {
|
|
13929
|
+
[AiStep.ReadMessage]: { input: 8_800, output: 1_500, cacheWrite: 0, cacheRead: 0 },
|
|
13930
|
+
[AiStep.PlanSite]: partsTotal(AiStep.PlanSite),
|
|
13931
|
+
[AiStep.ChangePlan]: partsTotal(AiStep.ChangePlan),
|
|
13932
|
+
// a 1 K picture is 1,120 image tokens (Google's figure for Nano Banana 2) — I-D4
|
|
13933
|
+
[AiStep.MakePictures]: { input: 0, output: 0, cacheWrite: 0, cacheRead: 0, imageOutput: 1_120 },
|
|
13934
|
+
[AiStep.NameStructures]: { input: 80_000, output: 10_000, cacheWrite: 0, cacheRead: 0 },
|
|
13935
|
+
};
|
|
13936
|
+
/** 1 credit = 1 US cent of what the AI actually cost (decision D4). */
|
|
13937
|
+
const CREDITS_PER_USD = 100;
|
|
13938
|
+
const TOKENS_PER_PRICE_UNIT = 1_000_000;
|
|
13939
|
+
const CREDIT_PRECISION = 1e6;
|
|
13940
|
+
const NO_TOKENS = Object.freeze({ input: 0, output: 0, cacheWrite: 0, cacheRead: 0 });
|
|
13941
|
+
/** What `tokens` cost at `price`, in US dollars. */
|
|
13942
|
+
function costUsd(tokens, price) {
|
|
13943
|
+
const perToken = (perMillion, fallback) => (perMillion ?? fallback) / TOKENS_PER_PRICE_UNIT;
|
|
13944
|
+
return tokens.input * perToken(price.input, 0)
|
|
13945
|
+
+ tokens.output * perToken(price.output, 0)
|
|
13946
|
+
+ tokens.cacheWrite * perToken(price.cacheWrite, price.input)
|
|
13947
|
+
+ tokens.cacheRead * perToken(price.cacheRead, price.input)
|
|
13948
|
+
+ (tokens.imageOutput ?? 0) * perToken(price.imageOutput, price.output);
|
|
13949
|
+
}
|
|
13950
|
+
/** Dollars → credits, kept to a millionth of a credit (a short read costs a fraction of one). */
|
|
13951
|
+
function creditsOfUsd(usd) {
|
|
13952
|
+
return Math.round(usd * CREDITS_PER_USD * CREDIT_PRECISION) / CREDIT_PRECISION;
|
|
13953
|
+
}
|
|
13954
|
+
/** What a typical request of `step` reads and writes — one part's when the step is asked in parts, else the whole step's. */
|
|
13955
|
+
function typicalStepTokens(step, part) {
|
|
13956
|
+
return (part && AI_STEP_PART_TOKENS[step]?.[part]) || AI_STEP_TYPICAL_TOKENS[step];
|
|
13957
|
+
}
|
|
13958
|
+
/** The credits a typical use of `step` costs on a model of `price` — never typed, always computed (§3d). */
|
|
13959
|
+
function estimateStepCredits(step, price, tokens = AI_STEP_TYPICAL_TOKENS[step]) {
|
|
13960
|
+
return creditsOfUsd(costUsd(tokens, price));
|
|
13961
|
+
}
|
|
13962
|
+
/** Can `model` do `step`? The step's needs against the model's capabilities. */
|
|
13963
|
+
function modelCanDo(model, step) {
|
|
13964
|
+
return AI_STEP_NEEDS[step].every(need => model.capabilities.includes(need));
|
|
13965
|
+
}
|
|
13966
|
+
/** Why AI did not run (board 11). The server refuses with these BEFORE any model call. */
|
|
13967
|
+
var AiRefusalCode;
|
|
13968
|
+
(function (AiRefusalCode) {
|
|
13969
|
+
AiRefusalCode["NotInPlan"] = "ai.notInPlan";
|
|
13970
|
+
AiRefusalCode["OutOfCredits"] = "ai.outOfCredits";
|
|
13971
|
+
AiRefusalCode["CapReached"] = "ai.capReached";
|
|
13972
|
+
AiRefusalCode["NotConnected"] = "ai.notConnected";
|
|
13973
|
+
AiRefusalCode["KeyInvalid"] = "ai.keyInvalid";
|
|
13974
|
+
AiRefusalCode["PlanUnknown"] = "ai.planUnknown";
|
|
13975
|
+
})(AiRefusalCode || (AiRefusalCode = {}));
|
|
13976
|
+
/** The HTTP status each refusal answers with (board 11). */
|
|
13977
|
+
const AI_REFUSAL_STATUS = {
|
|
13978
|
+
[AiRefusalCode.NotInPlan]: 403,
|
|
13979
|
+
[AiRefusalCode.OutOfCredits]: 402,
|
|
13980
|
+
[AiRefusalCode.CapReached]: 402,
|
|
13981
|
+
[AiRefusalCode.NotConnected]: 409,
|
|
13982
|
+
[AiRefusalCode.KeyInvalid]: 409,
|
|
13983
|
+
[AiRefusalCode.PlanUnknown]: 503,
|
|
13984
|
+
};
|
|
13985
|
+
/** What a refusal offers next. */
|
|
13986
|
+
var AiNextKind;
|
|
13987
|
+
(function (AiNextKind) {
|
|
13988
|
+
/** Ask IOX (a prefilled email to the operator, until a plan can be bought — D2). */
|
|
13989
|
+
AiNextKind["Contact"] = "contact";
|
|
13990
|
+
/** Open Settings ▸ AI. */
|
|
13991
|
+
AiNextKind["Settings"] = "settings";
|
|
13992
|
+
/** Try again later, or with less. */
|
|
13993
|
+
AiNextKind["Retry"] = "retry";
|
|
13994
|
+
})(AiNextKind || (AiNextKind = {}));
|
|
13995
|
+
const AI_REFUSAL_NEXT = {
|
|
13996
|
+
[AiRefusalCode.NotInPlan]: AiNextKind.Contact,
|
|
13997
|
+
[AiRefusalCode.OutOfCredits]: AiNextKind.Contact,
|
|
13998
|
+
[AiRefusalCode.CapReached]: AiNextKind.Retry,
|
|
13999
|
+
[AiRefusalCode.NotConnected]: AiNextKind.Settings,
|
|
14000
|
+
[AiRefusalCode.KeyInvalid]: AiNextKind.Settings,
|
|
14001
|
+
[AiRefusalCode.PlanUnknown]: AiNextKind.Retry,
|
|
14002
|
+
};
|
|
14003
|
+
/** The plan entitlements AI reads (the control plane's `features` / `limits` keys) — ONE spelling. */
|
|
14004
|
+
var AiFeature;
|
|
14005
|
+
(function (AiFeature) {
|
|
14006
|
+
AiFeature["SiteGeneration"] = "aiSiteGeneration";
|
|
14007
|
+
})(AiFeature || (AiFeature = {}));
|
|
14008
|
+
var AiLimit;
|
|
14009
|
+
(function (AiLimit) {
|
|
14010
|
+
AiLimit["CreditsPerMonth"] = "aiCreditsPerMonth";
|
|
14011
|
+
AiLimit["CreditsPerGeneration"] = "aiCreditsPerGeneration";
|
|
14012
|
+
})(AiLimit || (AiLimit = {}));
|
|
14013
|
+
/** A generation's stages (§3d). The runner works while `task` is set; otherwise the owner has the next move. */
|
|
14014
|
+
var AiGenerationStage;
|
|
14015
|
+
(function (AiGenerationStage) {
|
|
14016
|
+
AiGenerationStage["Reading"] = "reading";
|
|
14017
|
+
AiGenerationStage["Analysis"] = "analysis";
|
|
14018
|
+
AiGenerationStage["Design"] = "design";
|
|
14019
|
+
AiGenerationStage["Plan"] = "plan";
|
|
14020
|
+
AiGenerationStage["Build"] = "build";
|
|
14021
|
+
AiGenerationStage["Done"] = "done";
|
|
14022
|
+
AiGenerationStage["Failed"] = "failed";
|
|
14023
|
+
AiGenerationStage["Cancelled"] = "cancelled";
|
|
14024
|
+
})(AiGenerationStage || (AiGenerationStage = {}));
|
|
14025
|
+
/** The stages nothing advances any more. */
|
|
14026
|
+
const FINAL_GENERATION_STAGES = [AiGenerationStage.Done, AiGenerationStage.Failed, AiGenerationStage.Cancelled];
|
|
14027
|
+
/** How one model call ended — a ledger row's status. */
|
|
14028
|
+
var AiCallStatus;
|
|
14029
|
+
(function (AiCallStatus) {
|
|
14030
|
+
/** Written before the call: a reservation at the estimate until the call settles. */
|
|
14031
|
+
AiCallStatus["Started"] = "started";
|
|
14032
|
+
AiCallStatus["Ok"] = "ok";
|
|
14033
|
+
/** The answer came back and the shared validator refused it (with a path). */
|
|
14034
|
+
AiCallStatus["Invalid"] = "invalid";
|
|
14035
|
+
/** The model declined (a provider refusal) — charged what the provider reports. */
|
|
14036
|
+
AiCallStatus["Refused"] = "refused";
|
|
14037
|
+
AiCallStatus["Error"] = "error";
|
|
14038
|
+
/** The process died during the call: charged at the estimate, since what it cost was never reported. */
|
|
14039
|
+
AiCallStatus["Interrupted"] = "interrupted";
|
|
14040
|
+
})(AiCallStatus || (AiCallStatus = {}));
|
|
14041
|
+
|
|
13949
14042
|
/**
|
|
13950
14043
|
* What a structured-output request costs the provider's grammar compiler, counted the way the Claude API counts it
|
|
13951
14044
|
* (platform.claude.com/docs/en/build-with-claude/structured-outputs, "Schema complexity limits", read 2026-10-27):
|
|
@@ -13955,10 +14048,24 @@ function planToWire(plan) {
|
|
|
13955
14048
|
* The limits are per REQUEST — every strict schema in it together. The ONE counter: the specs hold every schema iox-cms
|
|
13956
14049
|
* sends to it, and the fake Claude API refuses with it exactly as Claude does — the plan stage once sent 213 optional
|
|
13957
14050
|
* parameters and every suite stayed green because the fake accepted anything (vectoriox/iox-cms#45).
|
|
14051
|
+
*
|
|
14052
|
+
* Beyond those, Claude refuses a grammar it compiles too large — a limit the docs name ("additional internal limits on
|
|
14053
|
+
* the compiled grammar size") without a number. MEASURED against the API on 2026-10-27 (vectoriox/iox-cms#47): the
|
|
14054
|
+
* total number of properties decides it, not enums or descriptions — 64 and 61 properties refused, 53 and fewer
|
|
14055
|
+
* accepted (53 already took ~9 s to compile). So 60 is the line we refuse above, and 40 is what we send.
|
|
14056
|
+
*/
|
|
14057
|
+
const STRICT_SCHEMA_LIMITS = {
|
|
14058
|
+
strictTools: 20,
|
|
14059
|
+
optional: 24,
|
|
14060
|
+
unions: 16,
|
|
14061
|
+
/** MEASURED, not documented (2026-10-27): the smallest refused request held 61 properties, the largest accepted 53. */
|
|
14062
|
+
properties: 60,
|
|
14063
|
+
};
|
|
14064
|
+
/**
|
|
14065
|
+
* What one request iox-cms sends may use: half of each documented limit, so a step can gain a field (or a strict tool)
|
|
14066
|
+
* without a 400 — and 40 properties, a third under the measured grammar line, where compiling stays a few seconds.
|
|
13958
14067
|
*/
|
|
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 };
|
|
14068
|
+
const STRICT_SCHEMA_BUDGET = { optional: 12, unions: 8, properties: 40 };
|
|
13962
14069
|
/** The keywords the docs list as not supported ("Numerical constraints", "String constraints", "Array constraints beyond `minItems` of 0 or 1"). */
|
|
13963
14070
|
const UNSUPPORTED_KEYWORDS = [
|
|
13964
14071
|
'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf', 'minLength', 'maxLength', 'maxItems', 'uniqueItems', 'contains',
|
|
@@ -13977,6 +14084,9 @@ function count(node, path, out) {
|
|
|
13977
14084
|
if (node['type'] === 'object' || properties) {
|
|
13978
14085
|
const required = new Set(Array.isArray(node['required']) ? node['required'] : []);
|
|
13979
14086
|
out.optional += Object.keys(properties ?? {}).filter(key => !required.has(key)).length;
|
|
14087
|
+
out.properties += Object.keys(properties ?? {}).length;
|
|
14088
|
+
if (properties)
|
|
14089
|
+
out.objects++;
|
|
13980
14090
|
if (node['additionalProperties'] !== false)
|
|
13981
14091
|
out.unsupported.push(`${at('additionalProperties')}: must be false`);
|
|
13982
14092
|
}
|
|
@@ -14005,14 +14115,17 @@ function count(node, path, out) {
|
|
|
14005
14115
|
}
|
|
14006
14116
|
/** The cost of ONE request's strict schemas together: its JSON output format and its strict tools' input schemas. */
|
|
14007
14117
|
function strictSchemaComplexity(schemas, strictTools = 0) {
|
|
14008
|
-
const out = { optional: 0, unions: 0, strictTools, unsupported: [] };
|
|
14118
|
+
const out = { optional: 0, unions: 0, strictTools, properties: 0, objects: 0, unsupported: [] };
|
|
14009
14119
|
schemas.forEach((schema, i) => count(schema, schemas.length > 1 ? `[${i}]` : '', out));
|
|
14010
14120
|
return out;
|
|
14011
14121
|
}
|
|
14122
|
+
/** Claude's words for a grammar it compiles too large (2026-10-27, the plan stage — vectoriox/iox-cms#47), verbatim. */
|
|
14123
|
+
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
14124
|
/**
|
|
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).
|
|
14125
|
+
* The 400 the Claude API answers a request over its limits with, or null. The optional-parameter and the grammar-size
|
|
14126
|
+
* messages are the ones Claude answered on 2026-10-27, verbatim; the union, strict-tool and unsupported messages follow
|
|
14127
|
+
* their shape (not yet captured). The documented limits are checked first: a request over one of them never reaches the
|
|
14128
|
+
* grammar compiler.
|
|
14016
14129
|
*/
|
|
14017
14130
|
function strictSchemaRefusal(complexity) {
|
|
14018
14131
|
const refuse = (message) => ({ status: 400, type: 'invalid_request_error', message });
|
|
@@ -14027,6 +14140,8 @@ function strictSchemaRefusal(complexity) {
|
|
|
14027
14140
|
if (complexity.unions > STRICT_SCHEMA_LIMITS.unions) {
|
|
14028
14141
|
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
14142
|
}
|
|
14143
|
+
if (complexity.properties > STRICT_SCHEMA_LIMITS.properties)
|
|
14144
|
+
return refuse(GRAMMAR_TOO_LARGE_MESSAGE);
|
|
14030
14145
|
return null;
|
|
14031
14146
|
}
|
|
14032
14147
|
|
|
@@ -14070,9 +14185,32 @@ const PLAN_EDIT_SHAPE = {
|
|
|
14070
14185
|
[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
14186
|
[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
14187
|
[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" },
|
|
14188
|
+
[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
14189
|
[PlanEditOp.SetSectionData]: { op: "'setSectionData'", page: 'string — a page key', index: 'integer — the section', binding: 'string — the list source the section repeats' },
|
|
14075
14190
|
};
|
|
14191
|
+
/**
|
|
14192
|
+
* The plan part each op changes (vectoriox/iox-cms#47): a sentence is answered in the plan's two parts, one request each —
|
|
14193
|
+
* the structure's edits first, then the pages' written knowing them.
|
|
14194
|
+
*/
|
|
14195
|
+
const PLAN_EDIT_OP_PART = {
|
|
14196
|
+
[PlanEditOp.AddPage]: PlanPart.Pages,
|
|
14197
|
+
[PlanEditOp.RemovePage]: PlanPart.Pages,
|
|
14198
|
+
[PlanEditOp.RenamePage]: PlanPart.Pages,
|
|
14199
|
+
[PlanEditOp.MergePages]: PlanPart.Pages,
|
|
14200
|
+
[PlanEditOp.MovePage]: PlanPart.Pages,
|
|
14201
|
+
[PlanEditOp.AddSection]: PlanPart.Pages,
|
|
14202
|
+
[PlanEditOp.RemoveSection]: PlanPart.Pages,
|
|
14203
|
+
[PlanEditOp.MoveSection]: PlanPart.Pages,
|
|
14204
|
+
[PlanEditOp.SwapPattern]: PlanPart.Pages,
|
|
14205
|
+
[PlanEditOp.SetSlot]: PlanPart.Pages,
|
|
14206
|
+
[PlanEditOp.SetSectionData]: PlanPart.Pages,
|
|
14207
|
+
[PlanEditOp.AddContentType]: PlanPart.Structure,
|
|
14208
|
+
[PlanEditOp.AddField]: PlanPart.Structure,
|
|
14209
|
+
};
|
|
14210
|
+
/** The ops written in one part, in `PlanEditOp` order. */
|
|
14211
|
+
function planEditOps(part) {
|
|
14212
|
+
return Object.values(PlanEditOp).filter(op => PLAN_EDIT_OP_PART[op] === part);
|
|
14213
|
+
}
|
|
14076
14214
|
var PlanChangeKind;
|
|
14077
14215
|
(function (PlanChangeKind) {
|
|
14078
14216
|
PlanChangeKind["Page"] = "page";
|
|
@@ -14193,6 +14331,8 @@ function findSection(d, pageAt, index, c, path) {
|
|
|
14193
14331
|
return ok;
|
|
14194
14332
|
}
|
|
14195
14333
|
const setSections = (d, at, sections) => { d.pages[at] = { ...d.pages[at], sections }; };
|
|
14334
|
+
/** A child added to a page or type the same edits added is part of that addition — the map marks the parent only. */
|
|
14335
|
+
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
14336
|
/** One handler per op — a data table, never an if-chain. */
|
|
14197
14337
|
const APPLY = {
|
|
14198
14338
|
[PlanEditOp.AddPage]: (d, e, c, path, changed) => {
|
|
@@ -14257,7 +14397,8 @@ const APPLY = {
|
|
|
14257
14397
|
const index = e.at === undefined ? sections.length : clamp(e.at, sections.length);
|
|
14258
14398
|
sections.splice(index, 0, e.section);
|
|
14259
14399
|
setSections(d, at, sections);
|
|
14260
|
-
changed
|
|
14400
|
+
if (!addedHere(changed, PlanChangeKind.Page, { page: d.pages[at].key }))
|
|
14401
|
+
changed.push({ kind: PlanChangeKind.Section, action: PlanChangeAction.Added, page: d.pages[at].key, index });
|
|
14261
14402
|
},
|
|
14262
14403
|
[PlanEditOp.RemoveSection]: (d, e, c, path, changed) => {
|
|
14263
14404
|
const at = findPage(d, e.page, c, `${path}.page`);
|
|
@@ -14329,9 +14470,10 @@ const APPLY = {
|
|
|
14329
14470
|
const type = d.contentTypes[at];
|
|
14330
14471
|
if (type.fields.some(f => f.name === e.field.name))
|
|
14331
14472
|
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;
|
|
14473
|
+
const seed = type.seed ? { ...type.seed, [e.field.name]: e.sample ?? type.seed[e.field.name] ?? '' } : undefined;
|
|
14333
14474
|
d.contentTypes[at] = { ...type, fields: [...type.fields, e.field], ...(seed ? { seed } : {}) };
|
|
14334
|
-
changed
|
|
14475
|
+
if (!addedHere(changed, PlanChangeKind.ContentType, { name: type.name }))
|
|
14476
|
+
changed.push({ kind: PlanChangeKind.Field, action: PlanChangeAction.Added, name: `${type.name}.${e.field.name}` });
|
|
14335
14477
|
},
|
|
14336
14478
|
[PlanEditOp.SetSectionData]: (d, e, c, path, changed) => {
|
|
14337
14479
|
const at = findPage(d, e.page, c, `${path}.page`);
|
|
@@ -14370,20 +14512,34 @@ function applyPlanEdits(plan, edits) {
|
|
|
14370
14512
|
return { plan: { ...plan, pages: draft.pages, contentTypes: draft.contentTypes, dataSources }, changed, issues: [] };
|
|
14371
14513
|
}
|
|
14372
14514
|
// ── The format a model writes edits in ─────────────────────────────────────────────────────────────────
|
|
14373
|
-
|
|
14515
|
+
/** Each part's answer format name. */
|
|
14516
|
+
const PLAN_EDITS_PART_SCHEMA_NAMES = {
|
|
14517
|
+
[PlanPart.Structure]: 'plan_structure_edits',
|
|
14518
|
+
[PlanPart.Pages]: 'plan_page_edits',
|
|
14519
|
+
};
|
|
14374
14520
|
/** A sentence's answer: the edits, or why the ops cannot say it (one line, in the owner's language). */
|
|
14375
14521
|
var PlanEditsOutcome;
|
|
14376
14522
|
(function (PlanEditsOutcome) {
|
|
14377
14523
|
PlanEditsOutcome["Edits"] = "edits";
|
|
14378
14524
|
PlanEditsOutcome["Cannot"] = "cannot";
|
|
14379
14525
|
})(PlanEditsOutcome || (PlanEditsOutcome = {}));
|
|
14380
|
-
/**
|
|
14381
|
-
|
|
14382
|
-
|
|
14383
|
-
|
|
14384
|
-
|
|
14526
|
+
/**
|
|
14527
|
+
* Each part's answer, explained. The structure is asked first, so it says whether the sentence is a plan change at all
|
|
14528
|
+
* (`outcome`); the pages are asked only when it is, and write only their edits.
|
|
14529
|
+
*/
|
|
14530
|
+
const PLAN_EDITS_ANSWER_SHAPES = {
|
|
14531
|
+
[PlanPart.Structure]: {
|
|
14532
|
+
outcome: "'edits' | 'cannot' — cannot: no operation of either part (below) can do what the sentence asks",
|
|
14533
|
+
edits: 'edit[] — the content types and fields the sentence adds, in order; empty when it adds none, or cannot',
|
|
14534
|
+
reason: "string — when cannot, ONE short line for the owner in their language (what to do instead); else ''",
|
|
14535
|
+
},
|
|
14536
|
+
[PlanPart.Pages]: {
|
|
14537
|
+
edits: 'edit[] — the page and section edits, in order, made after the structure edits (already applied); empty when none',
|
|
14538
|
+
},
|
|
14385
14539
|
};
|
|
14386
14540
|
const NO_INDEX = -1;
|
|
14541
|
+
/** A new page or content type on the wire: its children are edits of their own. */
|
|
14542
|
+
const CHILDLESS = { children: false };
|
|
14387
14543
|
/** A list holding exactly one object: none → absent, one → it (expanded), more → the list (the gate refuses it). */
|
|
14388
14544
|
const oneOf = (schema, expand, toWire, what) => ({
|
|
14389
14545
|
text: `${what}[] — exactly one when the op takes it`,
|
|
@@ -14401,17 +14557,22 @@ const EDIT_WIRE = {
|
|
|
14401
14557
|
after: textKey('addPage: the key of the page it follows in the menu'),
|
|
14402
14558
|
into: textKey('mergePages: the page that stays'),
|
|
14403
14559
|
from: textKey('mergePages: the page whose sections move into it, then removed'),
|
|
14404
|
-
index: indexKey('the section, 0-based on its page'),
|
|
14560
|
+
index: indexKey('the section, 0-based on its page (addSection: the place the new one goes)'),
|
|
14405
14561
|
to: indexKey('movePage / moveSection: the new 0-based place'),
|
|
14406
|
-
at: indexKey('addSection: its 0-based place on the page'),
|
|
14407
14562
|
pattern: { text: 'string — swapPattern: the section pattern it becomes', none: '', schema: () => jsonOneOf(['', ...sectionPatternIds()]) },
|
|
14408
14563
|
slot: textKey("setSlot: one of the pattern's slots"),
|
|
14409
14564
|
text: textKey("setSlot: the words, in the owner's language and voice"),
|
|
14410
14565
|
binding: textKey('setSectionData: the list source the section repeats'),
|
|
14411
14566
|
contentType: textKey('addField: a content type of the plan'),
|
|
14412
|
-
newPage:
|
|
14567
|
+
newPage: {
|
|
14568
|
+
...oneOf(() => wirePageJsonSchema(CHILDLESS), v => expandWirePage(v, CHILDLESS), p => pageToWire(p, CHILDLESS), 'page'),
|
|
14569
|
+
text: 'page[] — exactly one when the op takes it, WITHOUT sections: each is an addSection after it',
|
|
14570
|
+
},
|
|
14413
14571
|
newSection: oneOf(wireSectionJsonSchema, expandWireSection, sectionToWire, 'section'),
|
|
14414
|
-
newContentType:
|
|
14572
|
+
newContentType: {
|
|
14573
|
+
...oneOf(() => wireContentTypeJsonSchema(CHILDLESS), v => expandWireContentType(v, CHILDLESS), t => contentTypeToWire(t, CHILDLESS), 'contentType'),
|
|
14574
|
+
text: "contentType[] — exactly one when the op takes it, WITHOUT fields: each is an addField after it (the samples' values stay in its seed)",
|
|
14575
|
+
},
|
|
14415
14576
|
dataSources: {
|
|
14416
14577
|
text: 'dataSource[] — addContentType: the list / single sources its pages read', none: [],
|
|
14417
14578
|
schema: () => jsonListOf(wireDataSourceJsonSchema()),
|
|
@@ -14428,7 +14589,7 @@ const PLAN_EDIT_WIRE_KEYS = {
|
|
|
14428
14589
|
[PlanEditOp.RenamePage]: { page: 'page', name: 'name', route: 'route' },
|
|
14429
14590
|
[PlanEditOp.MergePages]: { into: 'into', from: 'from' },
|
|
14430
14591
|
[PlanEditOp.MovePage]: { page: 'page', to: 'to' },
|
|
14431
|
-
[PlanEditOp.AddSection]: { page: 'page', section: 'newSection', at: '
|
|
14592
|
+
[PlanEditOp.AddSection]: { page: 'page', section: 'newSection', at: 'index' },
|
|
14432
14593
|
[PlanEditOp.RemoveSection]: { page: 'page', index: 'index' },
|
|
14433
14594
|
[PlanEditOp.MoveSection]: { page: 'page', from: 'index', to: 'to' },
|
|
14434
14595
|
[PlanEditOp.SwapPattern]: { page: 'page', index: 'index', pattern: 'pattern' },
|
|
@@ -14437,19 +14598,27 @@ const PLAN_EDIT_WIRE_KEYS = {
|
|
|
14437
14598
|
[PlanEditOp.AddField]: { contentType: 'contentType', field: 'newField', sample: 'sample' },
|
|
14438
14599
|
[PlanEditOp.SetSectionData]: { page: 'page', index: 'index', binding: 'binding' },
|
|
14439
14600
|
};
|
|
14601
|
+
/** The wire keys a part's edit carries: every key its ops read, in `EDIT_WIRE` order. */
|
|
14602
|
+
function planEditWireKeys(part) {
|
|
14603
|
+
const read = new Set(planEditOps(part).flatMap(op => Object.values(PLAN_EDIT_WIRE_KEYS[op])));
|
|
14604
|
+
return Object.keys(EDIT_WIRE).filter(k => read.has(k));
|
|
14605
|
+
}
|
|
14440
14606
|
const isEditWireNone = (wireKey, v) => {
|
|
14441
14607
|
const none = EDIT_WIRE[wireKey].none;
|
|
14442
14608
|
return v === undefined || (Array.isArray(none) ? Array.isArray(v) && v.length === 0 : v === none) || (wireKey === 'sample' && Array.isArray(v) && v.length === 0);
|
|
14443
14609
|
};
|
|
14444
14610
|
const noneText = (none) => (Array.isArray(none) ? '[]' : typeof none === 'string' ? "''" : String(none));
|
|
14445
|
-
|
|
14446
|
-
|
|
14447
|
-
|
|
14448
|
-
|
|
14449
|
-
}
|
|
14450
|
-
|
|
14451
|
-
|
|
14452
|
-
|
|
14611
|
+
const opsText = (part) => planEditOps(part).map(op => `'${op}'`).join(' | ');
|
|
14612
|
+
/** A part's flat edit, as a shape table: every key required, each with its none. */
|
|
14613
|
+
function planEditWireShape(part) {
|
|
14614
|
+
return {
|
|
14615
|
+
op: `${opsText(part)} — which keys below are read`,
|
|
14616
|
+
...Object.fromEntries(planEditWireKeys(part).map(k => [k, `${EDIT_WIRE[k].text} — ${noneText(EDIT_WIRE[k].none)} when the op does not read it`])),
|
|
14617
|
+
};
|
|
14618
|
+
}
|
|
14619
|
+
/** Per op of a part, the wire keys it reads, in `PLAN_EDIT_SHAPE`'s words (`?` = may hold its none) — what the model reads. */
|
|
14620
|
+
function planEditWireOps(part) {
|
|
14621
|
+
return Object.fromEntries(planEditOps(part).map(op => {
|
|
14453
14622
|
const entry = PLAN_EDIT_SHAPE[op];
|
|
14454
14623
|
const keys = PLAN_EDIT_WIRE_KEYS[op];
|
|
14455
14624
|
return [op, Object.fromEntries(Object.entries(keys).map(([planKey, wireKey]) => {
|
|
@@ -14477,20 +14646,21 @@ function planEditWireGaps() {
|
|
|
14477
14646
|
}
|
|
14478
14647
|
return gaps;
|
|
14479
14648
|
}
|
|
14480
|
-
|
|
14481
|
-
|
|
14649
|
+
const ANSWER_SCHEMAS = {
|
|
14650
|
+
outcome: () => jsonOneOf(Object.values(PlanEditsOutcome)),
|
|
14651
|
+
reason: () => jsonText(),
|
|
14652
|
+
edits: part => jsonListOf(jsonShapedObject(planEditWireShape(part), {
|
|
14653
|
+
op: jsonOneOf(planEditOps(part)),
|
|
14654
|
+
...Object.fromEntries(planEditWireKeys(part).map(k => [k, EDIT_WIRE[k].schema()])),
|
|
14655
|
+
}, `edit ${part}`)),
|
|
14656
|
+
};
|
|
14657
|
+
/** The format one part of a sentence's answer is written in — one request each (vectoriox/iox-cms#47). */
|
|
14658
|
+
function planEditsJsonSchema(part) {
|
|
14482
14659
|
const gaps = planEditWireGaps();
|
|
14483
14660
|
if (gaps.length)
|
|
14484
14661
|
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');
|
|
14662
|
+
const shape = PLAN_EDITS_ANSWER_SHAPES[part];
|
|
14663
|
+
return jsonShapedObject(shape, Object.fromEntries(shapeKeys(shape).map(k => [k, ANSWER_SCHEMAS[k](part)])), `planEdits ${part}`);
|
|
14494
14664
|
}
|
|
14495
14665
|
/** 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
14666
|
function expandWireEdit(wire) {
|
|
@@ -14511,9 +14681,10 @@ function expandWireEdit(wire) {
|
|
|
14511
14681
|
}
|
|
14512
14682
|
return out;
|
|
14513
14683
|
}
|
|
14514
|
-
/** An edit →
|
|
14515
|
-
function
|
|
14516
|
-
const
|
|
14684
|
+
/** An edit's own keys → its flat wire edit, in its part's keys. */
|
|
14685
|
+
function flatEdit(edit) {
|
|
14686
|
+
const part = PLAN_EDIT_OP_PART[edit.op];
|
|
14687
|
+
const out = { op: edit.op, ...Object.fromEntries(planEditWireKeys(part).map(k => [k, Array.isArray(EDIT_WIRE[k].none) ? [] : EDIT_WIRE[k].none])) };
|
|
14517
14688
|
for (const [planKey, wireKey] of Object.entries(PLAN_EDIT_WIRE_KEYS[edit.op])) {
|
|
14518
14689
|
const value = edit[planKey];
|
|
14519
14690
|
if (value === undefined)
|
|
@@ -14522,7 +14693,26 @@ function planEditToWire(edit) {
|
|
|
14522
14693
|
}
|
|
14523
14694
|
return out;
|
|
14524
14695
|
}
|
|
14525
|
-
/**
|
|
14696
|
+
/** The children an added page or content type travels without, as the edits that add them after it. */
|
|
14697
|
+
function childEdits(edit) {
|
|
14698
|
+
if (edit.op === PlanEditOp.AddPage)
|
|
14699
|
+
return edit.page.sections.map(section => ({ op: PlanEditOp.AddSection, page: edit.page.key, section }));
|
|
14700
|
+
if (edit.op === PlanEditOp.AddContentType)
|
|
14701
|
+
return edit.contentType.fields.map(field => ({ op: PlanEditOp.AddField, contentType: edit.contentType.name, field }));
|
|
14702
|
+
return [];
|
|
14703
|
+
}
|
|
14704
|
+
/** An edit → the flat wire edits a model would write for it: itself, then its children (fakes and specs write them). */
|
|
14705
|
+
function planEditToWire(edit) {
|
|
14706
|
+
return [flatEdit(edit), ...childEdits(edit).map(flatEdit)];
|
|
14707
|
+
}
|
|
14708
|
+
/** Edits → each part's flat wire edits, in order (what each part's answer would hold). */
|
|
14709
|
+
function planEditsToWire(edits) {
|
|
14710
|
+
const out = Object.fromEntries(PLAN_PARTS.map(part => [part, []]));
|
|
14711
|
+
for (const edit of edits)
|
|
14712
|
+
out[PLAN_EDIT_OP_PART[edit.op]].push(...planEditToWire(edit));
|
|
14713
|
+
return out;
|
|
14714
|
+
}
|
|
14715
|
+
/** A model's answer (either part's) → edits (each flat edit expanded); never repaired. */
|
|
14526
14716
|
function planEditsFromWire(answer) {
|
|
14527
14717
|
if (!isPlainObject(answer) || !Array.isArray(answer['edits']))
|
|
14528
14718
|
return answer;
|
|
@@ -15105,5 +15295,5 @@ function addStockCredits(footer, credits) {
|
|
|
15105
15295
|
* Generated bundle index. Do not edit.
|
|
15106
15296
|
*/
|
|
15107
15297
|
|
|
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,
|
|
15298
|
+
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
15299
|
//# sourceMappingURL=vectoriox-iox-ui-generation-core.mjs.map
|