@vectoriox/iox-ui 5.52.0 → 5.54.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.
@@ -3025,5 +3025,153 @@ interface BuildStepFacts {
3025
3025
  }
3026
3026
  declare function buildStepFacts(plan: SitePlan, actions?: Readonly<Record<string, `${ContentTypeAction}`>>): BuildStepFacts;
3027
3027
 
3028
- export { ACTIVE_BUILD_STATUSES, BAR_ITEMS_MAX, BAR_ITEMS_MIN, BASELINE_ROLES, BUILD_STEPS, BaselineRole, BindScope, BuildNoticeCode, BuildStep, CardBindTarget, ComposeError, ComposeIssueCode, ContentTypeAction, CreatedKind, DANGLING_LINK_CODES, DEFAULT_PHONE_MENU_BREAKPOINT, ENTRANCE_EASING, ENTRANCE_ORIGIN_LOOK, ENTRANCE_TRIGGER, EntrancePart, FACT_KEYS, GlobalSlot, KeptReason, NAV_PART_LABELS, NAV_SHAPE_COPY, NEUTRAL_STYLE_PROPS, NODE_BASELINES, NavPagesSource, NavRole, NavShape, NavShapeError, PRICING_PATTERN, PUBLISH_BLOCKING_PLACEHOLDERS, PageDataKind, PathIssueCollector, PatternDirective, PatternGlobalPosition, PatternOpens, PatternPlaceholder, PatternRecipe, PhoneMenuBreakpoint, PlaceholderKind, PlanDataSourceMode, PlanDataSourceType, PreflightRefusalCode, RelativeOp, SAMPLE_IMAGE_REF, SITE_FACT_LABELS, SITE_HEADER_PATTERN, SITE_PATTERNS, SITE_PLAN_VERSION, SITE_TEMPLATES, SPEC_SITE_FACTS, SPEC_SITE_LIBRARY, SPEC_SITE_PATTERNS, SPEC_SITE_PLAN_SHAPE, SPEC_SITE_TEMPLATES, STUDIO_TEMPLATE, SiteBuildKind, SiteBuildStatus, SitePlanError, SitePlanIssueCode, SitePlanSourceKind, SiteTemplateError, SiteTemplateId, StepState, addBarItem, altSourceScopes, assertStyleSlots, assertValidSitePlan, barIconForRoute, blocksPublish, buildKeys, buildStepFacts, builtHomePageId, composeNavHeader, composeSection, composeSite, createSerializedNode, describePlanData, entranceAction, entranceInteraction, failedBuildStep, findNavRoot, isActiveBuild, isNonEmptyString, isPlainObject, moveBarItem, navPages, navShapeBreakpoint, navShapeOf, nextSourceOf, nodeBindPaths, parseSampleImageRef, pathAt, patternById, patternNeedsBinding, patternNodes, patternOptionalParts, patternScopes, patternSlots, patternTakesBinding, phoneMenuPages, planFromTemplate, planNavShape, releasedLayoutDefaults, removeBarItem, requiredShapeKeys, routeParams, sampleImageRef, sectionEntrances, setStyleProps, shapeKeys, siteNavPages, staggerDelay, validateSitePlan, walkSerialized };
3029
- export type { AddBarItemResult, BarItemsEditResult, BuildNotice, BuildStepError, BuildStepFacts, BuildStepRecord, CmsDataSourcePlan, ComposeContext, ComposeIssue, ComposedDataSource, ComposedItem, ComposedNavHeader, ComposedPage, ComposedPlaceholder, ComposedRoute, ComposedSection, ComposedSite, ContentFieldPlan, ContentSeedPlan, ContentTypePlan, CreateNodeOptions, CreatedRecord, DataSourcePlan, EntranceAction, EntranceInteraction, EntranceSpec, FactKey, NavHeaderInput, NavHeaderOptions, NavPage, NavShapeOptions, NavShapeResult, NodeBaseline, PageData, PageDataLine, PagePlan, PageSectionPlan, PathIssue, PatternNode, PlainObject, PreflightRefusal, RelativeDataSourcePlan, SectionEntrances, SectionInput, SectionOptions, SerializedBinding, SerializedNode, SerializedResponsive, ShapeEntry, ShapeIssueCodes, SiteBuildRun, SiteBusiness, SitePageRef, SitePattern, SitePlan, SitePlanIssue, SitePlanSource, SitePlanValidation, SiteTemplate, TemplatePlanInput, UndoKept, UndoPreview, UndoRecord };
3028
+ /**
3029
+ * The AI pipeline's WIRE vocabulary (AI plan §3c, §3n, M0): what iox-cms's `ai` API answers and what every client
3030
+ * of it reads — the steps a model works on, who pays (IOX credits or the studio's own key), a model of the vetted
3031
+ * list with its price per token class, the one cost → credits rule (1 credit = 1 US cent of what the AI cost, D4),
3032
+ * the refusals (board 11) and a generation's stages. ONE definition: iox-cms meters and refuses with it, the
3033
+ * Settings ▸ AI screen and the wizard show it. Pure types, enums and helpers; no transport, no prices typed here.
3034
+ * The step and capability spellings are the control plane's (`GET /instances/me/entitlements`, AI plan "M0 control
3035
+ * plane — the instance-route contract") — the platform has ONE spelling of each.
3036
+ */
3037
+ declare enum AiProviderId {
3038
+ Anthropic = "anthropic",
3039
+ OpenRouter = "openrouter"
3040
+ }
3041
+ /** Who pays: the platform's credits, or the studio's own provider key (metered for display, never charged). */
3042
+ declare enum AiKeyMode {
3043
+ Platform = "platform",
3044
+ Own = "own"
3045
+ }
3046
+ /** The steps a model works on (board 9's rows), in screen order. */
3047
+ declare enum AiStep {
3048
+ ReadMessage = "analysis",
3049
+ PlanSite = "plan",
3050
+ ChangePlan = "planEdit",
3051
+ MakePictures = "image",
3052
+ NameStructures = "scanNaming"
3053
+ }
3054
+ declare const AI_STEPS: ReadonlyArray<AiStep>;
3055
+ /** What a model must be able to do for a step to run on it. */
3056
+ declare enum AiCapability {
3057
+ StructuredOutput = "structuredOutputs",
3058
+ ImageInput = "imageInput",
3059
+ ImageOutput = "imageOutput"
3060
+ }
3061
+ declare const AI_STEP_NEEDS: Readonly<Record<AiStep, ReadonlyArray<AiCapability>>>;
3062
+ /** Tokens a typical use of a step reads and writes (plan §3d "Where credits go") — what a shown estimate is made of. */
3063
+ declare const AI_STEP_TYPICAL_TOKENS: Readonly<Record<AiStep, AiTokenCounts>>;
3064
+ /** Tokens by the class a provider prices them in. */
3065
+ interface AiTokenCounts {
3066
+ input: number;
3067
+ output: number;
3068
+ /** Written to the prompt cache (priced above input). */
3069
+ cacheWrite: number;
3070
+ /** Read from the prompt cache (priced well below input). */
3071
+ cacheRead: number;
3072
+ }
3073
+ /** US dollars per million tokens, per class. A class without a price is priced as input. */
3074
+ interface AiTokenPrice {
3075
+ input: number;
3076
+ output: number;
3077
+ cacheWrite?: number;
3078
+ cacheRead?: number;
3079
+ }
3080
+ /** A model of the vetted list (served by the control plane with the entitlements, §3n) — its price travels with it. */
3081
+ interface AiModelEntry {
3082
+ id: string;
3083
+ provider: `${AiProviderId}`;
3084
+ label: string;
3085
+ /** The steps it may be chosen for. */
3086
+ steps: ReadonlyArray<`${AiStep}`>;
3087
+ /** The steps it is the default for (board 9's "recommended"). */
3088
+ recommendedFor?: ReadonlyArray<`${AiStep}`>;
3089
+ /** The steps it runs when the default is not available (the control plane's `fallbackModel`). */
3090
+ fallbackFor?: ReadonlyArray<`${AiStep}`>;
3091
+ capabilities: ReadonlyArray<`${AiCapability}`>;
3092
+ /** Null when the provider reports each call's cost itself and the list carries no price. */
3093
+ price: AiTokenPrice | null;
3094
+ /** Kept so old ledger rows still name it; never offered, and a studio's choice of it falls back to the default. */
3095
+ retired?: boolean;
3096
+ }
3097
+ /** 1 credit = 1 US cent of what the AI actually cost (decision D4). */
3098
+ declare const CREDITS_PER_USD = 100;
3099
+ declare const NO_TOKENS: Readonly<AiTokenCounts>;
3100
+ /** What `tokens` cost at `price`, in US dollars. */
3101
+ declare function costUsd(tokens: AiTokenCounts, price: AiTokenPrice): number;
3102
+ /** Dollars → credits, kept to a millionth of a credit (a short read costs a fraction of one). */
3103
+ declare function creditsOfUsd(usd: number): number;
3104
+ /** The credits a typical use of `step` costs on a model of `price` — never typed, always computed (§3d). */
3105
+ declare function estimateStepCredits(step: AiStep, price: AiTokenPrice, tokens?: AiTokenCounts): number;
3106
+ /** Can `model` do `step`? The step's needs against the model's capabilities. */
3107
+ declare function modelCanDo(model: Pick<AiModelEntry, 'capabilities'>, step: AiStep): boolean;
3108
+ /** Why AI did not run (board 11). The server refuses with these BEFORE any model call. */
3109
+ declare enum AiRefusalCode {
3110
+ NotInPlan = "ai.notInPlan",
3111
+ OutOfCredits = "ai.outOfCredits",
3112
+ CapReached = "ai.capReached",
3113
+ NotConnected = "ai.notConnected",
3114
+ KeyInvalid = "ai.keyInvalid",
3115
+ PlanUnknown = "ai.planUnknown"
3116
+ }
3117
+ /** The HTTP status each refusal answers with (board 11). */
3118
+ declare const AI_REFUSAL_STATUS: Readonly<Record<AiRefusalCode, number>>;
3119
+ /** What a refusal offers next. */
3120
+ declare enum AiNextKind {
3121
+ /** Ask IOX (a prefilled email to the operator, until a plan can be bought — D2). */
3122
+ Contact = "contact",
3123
+ /** Open Settings ▸ AI. */
3124
+ Settings = "settings",
3125
+ /** Try again later, or with less. */
3126
+ Retry = "retry"
3127
+ }
3128
+ declare const AI_REFUSAL_NEXT: Readonly<Record<AiRefusalCode, AiNextKind>>;
3129
+ interface AiRefusal {
3130
+ code: AiRefusalCode;
3131
+ planName?: string;
3132
+ /** Credits left this period (platform credits only). */
3133
+ remaining?: number;
3134
+ /** What the refused call would have cost, about. */
3135
+ estimate?: number;
3136
+ next: {
3137
+ kind: AiNextKind;
3138
+ href?: string;
3139
+ };
3140
+ }
3141
+ /** The studio's AI credits this period (the connector's meter, E4). `allowance: null` = unlimited. */
3142
+ interface AiMeter {
3143
+ used: number;
3144
+ allowance: number | null;
3145
+ remaining: number | null;
3146
+ periodStart: string;
3147
+ periodEnd: string;
3148
+ }
3149
+ /** A generation's stages (§3d). The runner works while `task` is set; otherwise the owner has the next move. */
3150
+ declare enum AiGenerationStage {
3151
+ Reading = "reading",
3152
+ Analysis = "analysis",
3153
+ Design = "design",
3154
+ Plan = "plan",
3155
+ Build = "build",
3156
+ Done = "done",
3157
+ Failed = "failed",
3158
+ Cancelled = "cancelled"
3159
+ }
3160
+ /** The stages nothing advances any more. */
3161
+ declare const FINAL_GENERATION_STAGES: ReadonlyArray<AiGenerationStage>;
3162
+ /** How one model call ended — a ledger row's status. */
3163
+ declare enum AiCallStatus {
3164
+ /** Written before the call: a reservation at the estimate until the call settles. */
3165
+ Started = "started",
3166
+ Ok = "ok",
3167
+ /** The answer came back and the shared validator refused it (with a path). */
3168
+ Invalid = "invalid",
3169
+ /** The model declined (a provider refusal) — charged what the provider reports. */
3170
+ Refused = "refused",
3171
+ Error = "error",
3172
+ /** The process died during the call: charged at the estimate, since what it cost was never reported. */
3173
+ Interrupted = "interrupted"
3174
+ }
3175
+
3176
+ export { ACTIVE_BUILD_STATUSES, AI_REFUSAL_NEXT, AI_REFUSAL_STATUS, AI_STEPS, AI_STEP_NEEDS, AI_STEP_TYPICAL_TOKENS, AiCallStatus, AiCapability, AiGenerationStage, AiKeyMode, AiNextKind, AiProviderId, AiRefusalCode, AiStep, BAR_ITEMS_MAX, BAR_ITEMS_MIN, BASELINE_ROLES, BUILD_STEPS, BaselineRole, BindScope, BuildNoticeCode, BuildStep, CREDITS_PER_USD, CardBindTarget, ComposeError, ComposeIssueCode, ContentTypeAction, CreatedKind, DANGLING_LINK_CODES, DEFAULT_PHONE_MENU_BREAKPOINT, ENTRANCE_EASING, ENTRANCE_ORIGIN_LOOK, ENTRANCE_TRIGGER, EntrancePart, FACT_KEYS, FINAL_GENERATION_STAGES, GlobalSlot, KeptReason, NAV_PART_LABELS, NAV_SHAPE_COPY, NEUTRAL_STYLE_PROPS, NODE_BASELINES, NO_TOKENS, NavPagesSource, NavRole, NavShape, NavShapeError, PRICING_PATTERN, PUBLISH_BLOCKING_PLACEHOLDERS, PageDataKind, PathIssueCollector, PatternDirective, PatternGlobalPosition, PatternOpens, PatternPlaceholder, PatternRecipe, PhoneMenuBreakpoint, PlaceholderKind, PlanDataSourceMode, PlanDataSourceType, PreflightRefusalCode, RelativeOp, SAMPLE_IMAGE_REF, SITE_FACT_LABELS, SITE_HEADER_PATTERN, SITE_PATTERNS, SITE_PLAN_VERSION, SITE_TEMPLATES, SPEC_SITE_FACTS, SPEC_SITE_LIBRARY, SPEC_SITE_PATTERNS, SPEC_SITE_PLAN_SHAPE, SPEC_SITE_TEMPLATES, STUDIO_TEMPLATE, SiteBuildKind, SiteBuildStatus, SitePlanError, SitePlanIssueCode, SitePlanSourceKind, SiteTemplateError, SiteTemplateId, StepState, addBarItem, altSourceScopes, assertStyleSlots, assertValidSitePlan, barIconForRoute, blocksPublish, buildKeys, buildStepFacts, builtHomePageId, composeNavHeader, composeSection, composeSite, costUsd, createSerializedNode, creditsOfUsd, describePlanData, entranceAction, entranceInteraction, estimateStepCredits, failedBuildStep, findNavRoot, isActiveBuild, isNonEmptyString, isPlainObject, modelCanDo, moveBarItem, navPages, navShapeBreakpoint, navShapeOf, nextSourceOf, nodeBindPaths, parseSampleImageRef, pathAt, patternById, patternNeedsBinding, patternNodes, patternOptionalParts, patternScopes, patternSlots, patternTakesBinding, phoneMenuPages, planFromTemplate, planNavShape, releasedLayoutDefaults, removeBarItem, requiredShapeKeys, routeParams, sampleImageRef, sectionEntrances, setStyleProps, shapeKeys, siteNavPages, staggerDelay, validateSitePlan, walkSerialized };
3177
+ export type { AddBarItemResult, AiMeter, AiModelEntry, AiRefusal, AiTokenCounts, AiTokenPrice, BarItemsEditResult, BuildNotice, BuildStepError, BuildStepFacts, BuildStepRecord, CmsDataSourcePlan, ComposeContext, ComposeIssue, ComposedDataSource, ComposedItem, ComposedNavHeader, ComposedPage, ComposedPlaceholder, ComposedRoute, ComposedSection, ComposedSite, ContentFieldPlan, ContentSeedPlan, ContentTypePlan, CreateNodeOptions, CreatedRecord, DataSourcePlan, EntranceAction, EntranceInteraction, EntranceSpec, FactKey, NavHeaderInput, NavHeaderOptions, NavPage, NavShapeOptions, NavShapeResult, NodeBaseline, PageData, PageDataLine, PagePlan, PageSectionPlan, PathIssue, PatternNode, PlainObject, PreflightRefusal, RelativeDataSourcePlan, SectionEntrances, SectionInput, SectionOptions, SerializedBinding, SerializedNode, SerializedResponsive, ShapeEntry, ShapeIssueCodes, SiteBuildRun, SiteBusiness, SitePageRef, SitePattern, SitePlan, SitePlanIssue, SitePlanSource, SitePlanValidation, SiteTemplate, TemplatePlanInput, UndoKept, UndoPreview, UndoRecord };
@@ -2572,6 +2572,21 @@ interface AnalysisContext {
2572
2572
  /** At most this many moods (the closed list a preset carries too). */
2573
2573
  declare const MAX_ANALYSIS_MOODS = 3;
2574
2574
  declare function validateSiteAnalysis(value: unknown, ctx: AnalysisContext): AnalysisValidation;
2575
+ /**
2576
+ * The structured-output schema a model is given to write a `SiteAnalysis` (AI plan §3c/§3d, M0-5): built from the
2577
+ * SAME shape table `validateSiteAnalysis` reads (keys, which are required, each key's explanation as its
2578
+ * description) and the same closed lists (30 business types, needs, facts, moods, avoids) — so the shape the model
2579
+ * is told about is the shape the gate refuses. Written to the strictest common subset of JSON Schema the providers'
2580
+ * native strict modes accept (Anthropic `output_config.format`, OpenRouter `json_schema` + `strict`): every object
2581
+ * closes with `additionalProperties: false`; no length, count or number bounds (the gate checks those). The
2582
+ * schema is a hint the provider enforces; `validateSiteAnalysis` stays the gate.
2583
+ */
2584
+ type JsonSchema = {
2585
+ [key: string]: unknown;
2586
+ };
2587
+ /** The name a provider shows the format under (`json_schema.name`). */
2588
+ declare const SITE_ANALYSIS_SCHEMA_NAME = "site_analysis";
2589
+ declare function siteAnalysisJsonSchema(): JsonSchema;
2575
2590
 
2576
2591
  /**
2577
2592
  * What the PLAN prompt is given (AI plan rev 3 §3f "Retrieval, not the whole knowledge base"): the one matched
@@ -2658,5 +2673,5 @@ interface StructureChoice {
2658
2673
  */
2659
2674
  declare function structureFor(typeId: string | null | undefined): StructureChoice;
2660
2675
 
2661
- export { ANIMATE_ADVICE_STEPS, AnalysisIssueCode, AnalysisLocale, AvoidCheck, BUSINESS_TYPES, BUSINESS_TYPE_IDS, BookingNeed, COPY_LENGTH_ORDER, CopyLength, HARNESS_SOURCE, LITTLE_MOVEMENT_AVOID, LITTLE_MOVEMENT_CAP, LintKind, MAX_ANALYSIS_MOODS, NEEDS, NEED_IDS, NeedSource, PresetFit, RANK_WEIGHTS, READ_ANIMATE, READ_WEIGHTS, RankReason, SPEC_ANALYSIS_SHAPE, SPEC_BUSINESS_TYPES, SPEC_HARNESS_EXAMPLES, SPEC_HARNESS_SOURCE, SPEC_NEEDS, SPEC_STRUCTURES, SPEC_USER_AVOIDS, STRUCTURES, StyleReadGround, StyleReadHeadings, StyleReadMotion, StyleReadSourceKind, USER_AVOID_IDS, VoiceFormality, VoicePerson, addressesIn, analysisPromptContext, businessTypeById, calmerChoice, inferNeeds, isLookRule, lintDesign, lookMatches, mapStyleRead, matchBusinessType, matchBusinessTypes, motionAdvice, motionFor, normalizeAddress, oklchHue, presetHasSerifHeadings, presetLook, presetReason, rankStylePresets, rankWithReferences, structureFor, userAvoidRule, validateSiteAnalysis, withoutAddress };
2662
- export type { AnalysisContext, AnalysisIssue, AnalysisValidation, AvoidRule, BusinessType, BusinessTypeMatch, CheckRule, FactRead, GoodFit, LintFinding, LintSite, LookCondition, LookDesign, LookRule, MotionBasis, MotionInput, MotionStart, NeedDef, NeedId, PromptContext, PromptContextOptions, PromptSection, RankInput, RankedPreset, SiteAnalysis, SiteNeed, StructureChoice, StructureId, StyleHints, StyleRead, UserAvoidId };
2676
+ export { ANIMATE_ADVICE_STEPS, AnalysisIssueCode, AnalysisLocale, AvoidCheck, BUSINESS_TYPES, BUSINESS_TYPE_IDS, BookingNeed, COPY_LENGTH_ORDER, CopyLength, HARNESS_SOURCE, LITTLE_MOVEMENT_AVOID, LITTLE_MOVEMENT_CAP, LintKind, MAX_ANALYSIS_MOODS, NEEDS, NEED_IDS, NeedSource, PresetFit, RANK_WEIGHTS, READ_ANIMATE, READ_WEIGHTS, RankReason, SITE_ANALYSIS_SCHEMA_NAME, SPEC_ANALYSIS_SHAPE, SPEC_BUSINESS_TYPES, SPEC_HARNESS_EXAMPLES, SPEC_HARNESS_SOURCE, SPEC_NEEDS, SPEC_STRUCTURES, SPEC_USER_AVOIDS, STRUCTURES, StyleReadGround, StyleReadHeadings, StyleReadMotion, StyleReadSourceKind, USER_AVOID_IDS, VoiceFormality, VoicePerson, addressesIn, analysisPromptContext, businessTypeById, calmerChoice, inferNeeds, isLookRule, lintDesign, lookMatches, mapStyleRead, matchBusinessType, matchBusinessTypes, motionAdvice, motionFor, normalizeAddress, oklchHue, presetHasSerifHeadings, presetLook, presetReason, rankStylePresets, rankWithReferences, siteAnalysisJsonSchema, structureFor, userAvoidRule, validateSiteAnalysis, withoutAddress };
2677
+ export type { AnalysisContext, AnalysisIssue, AnalysisValidation, AvoidRule, BusinessType, BusinessTypeMatch, CheckRule, FactRead, GoodFit, JsonSchema, LintFinding, LintSite, LookCondition, LookDesign, LookRule, MotionBasis, MotionInput, MotionStart, NeedDef, NeedId, PromptContext, PromptContextOptions, PromptSection, RankInput, RankedPreset, SiteAnalysis, SiteNeed, StructureChoice, StructureId, StyleHints, StyleRead, UserAvoidId };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectoriox/iox-ui",
3
- "version": "5.52.0",
3
+ "version": "5.54.0",
4
4
  "peerDependencies": {
5
5
  "@angular/common": ">=20.0.0",
6
6
  "@angular/core": ">=20.0.0",
@@ -0,0 +1,209 @@
1
+ /**
2
+ * The AI pipeline's WIRE vocabulary (AI plan §3c, §3n, M0): what iox-cms's `ai` API answers and what every client
3
+ * of it reads — the steps a model works on, who pays (IOX credits or the studio's own key), a model of the vetted
4
+ * list with its price per token class, the one cost → credits rule (1 credit = 1 US cent of what the AI cost, D4),
5
+ * the refusals (board 11) and a generation's stages. ONE definition: iox-cms meters and refuses with it, the
6
+ * Settings ▸ AI screen and the wizard show it. Pure types, enums and helpers; no transport, no prices typed here.
7
+ * The step and capability spellings are the control plane's (`GET /instances/me/entitlements`, AI plan "M0 control
8
+ * plane — the instance-route contract") — the platform has ONE spelling of each.
9
+ */
10
+
11
+ export enum AiProviderId {
12
+ Anthropic = 'anthropic',
13
+ OpenRouter = 'openrouter',
14
+ }
15
+
16
+ /** Who pays: the platform's credits, or the studio's own provider key (metered for display, never charged). */
17
+ export enum AiKeyMode {
18
+ Platform = 'platform',
19
+ Own = 'own',
20
+ }
21
+
22
+ /** The steps a model works on (board 9's rows), in screen order. */
23
+ export enum AiStep {
24
+ ReadMessage = 'analysis',
25
+ PlanSite = 'plan',
26
+ ChangePlan = 'planEdit',
27
+ MakePictures = 'image',
28
+ NameStructures = 'scanNaming',
29
+ }
30
+
31
+ export const AI_STEPS: ReadonlyArray<AiStep> = Object.values(AiStep);
32
+
33
+ /** What a model must be able to do for a step to run on it. */
34
+ export enum AiCapability {
35
+ StructuredOutput = 'structuredOutputs',
36
+ ImageInput = 'imageInput',
37
+ ImageOutput = 'imageOutput',
38
+ }
39
+
40
+ export const AI_STEP_NEEDS: Readonly<Record<AiStep, ReadonlyArray<AiCapability>>> = {
41
+ [AiStep.ReadMessage]: [AiCapability.StructuredOutput, AiCapability.ImageInput],
42
+ [AiStep.PlanSite]: [AiCapability.StructuredOutput],
43
+ [AiStep.ChangePlan]: [AiCapability.StructuredOutput],
44
+ [AiStep.MakePictures]: [AiCapability.ImageOutput],
45
+ [AiStep.NameStructures]: [AiCapability.StructuredOutput],
46
+ };
47
+
48
+ /** Tokens a typical use of a step reads and writes (plan §3d "Where credits go") — what a shown estimate is made of. */
49
+ export const AI_STEP_TYPICAL_TOKENS: Readonly<Record<AiStep, AiTokenCounts>> = {
50
+ [AiStep.ReadMessage]: { input: 8_800, output: 1_500, cacheWrite: 0, cacheRead: 0 },
51
+ [AiStep.PlanSite]: { input: 12_000, output: 7_000, cacheWrite: 0, cacheRead: 0 },
52
+ [AiStep.ChangePlan]: { input: 6_000, output: 500, cacheWrite: 0, cacheRead: 0 },
53
+ [AiStep.MakePictures]: { input: 1_120, output: 0, cacheWrite: 0, cacheRead: 0 },
54
+ [AiStep.NameStructures]: { input: 80_000, output: 10_000, cacheWrite: 0, cacheRead: 0 },
55
+ };
56
+
57
+ /** Tokens by the class a provider prices them in. */
58
+ export interface AiTokenCounts {
59
+ input: number;
60
+ output: number;
61
+ /** Written to the prompt cache (priced above input). */
62
+ cacheWrite: number;
63
+ /** Read from the prompt cache (priced well below input). */
64
+ cacheRead: number;
65
+ }
66
+
67
+ /** US dollars per million tokens, per class. A class without a price is priced as input. */
68
+ export interface AiTokenPrice {
69
+ input: number;
70
+ output: number;
71
+ cacheWrite?: number;
72
+ cacheRead?: number;
73
+ }
74
+
75
+ /** A model of the vetted list (served by the control plane with the entitlements, §3n) — its price travels with it. */
76
+ export interface AiModelEntry {
77
+ id: string;
78
+ provider: `${AiProviderId}`;
79
+ label: string;
80
+ /** The steps it may be chosen for. */
81
+ steps: ReadonlyArray<`${AiStep}`>;
82
+ /** The steps it is the default for (board 9's "recommended"). */
83
+ recommendedFor?: ReadonlyArray<`${AiStep}`>;
84
+ /** The steps it runs when the default is not available (the control plane's `fallbackModel`). */
85
+ fallbackFor?: ReadonlyArray<`${AiStep}`>;
86
+ capabilities: ReadonlyArray<`${AiCapability}`>;
87
+ /** Null when the provider reports each call's cost itself and the list carries no price. */
88
+ price: AiTokenPrice | null;
89
+ /** Kept so old ledger rows still name it; never offered, and a studio's choice of it falls back to the default. */
90
+ retired?: boolean;
91
+ }
92
+
93
+ /** 1 credit = 1 US cent of what the AI actually cost (decision D4). */
94
+ export const CREDITS_PER_USD = 100;
95
+ const TOKENS_PER_PRICE_UNIT = 1_000_000;
96
+ const CREDIT_PRECISION = 1e6;
97
+
98
+ export const NO_TOKENS: Readonly<AiTokenCounts> = Object.freeze({ input: 0, output: 0, cacheWrite: 0, cacheRead: 0 });
99
+
100
+ /** What `tokens` cost at `price`, in US dollars. */
101
+ export function costUsd(tokens: AiTokenCounts, price: AiTokenPrice): number {
102
+ const perToken = (perMillion: number | undefined, fallback: number) => (perMillion ?? fallback) / TOKENS_PER_PRICE_UNIT;
103
+ return tokens.input * perToken(price.input, 0)
104
+ + tokens.output * perToken(price.output, 0)
105
+ + tokens.cacheWrite * perToken(price.cacheWrite, price.input)
106
+ + tokens.cacheRead * perToken(price.cacheRead, price.input);
107
+ }
108
+
109
+ /** Dollars → credits, kept to a millionth of a credit (a short read costs a fraction of one). */
110
+ export function creditsOfUsd(usd: number): number {
111
+ return Math.round(usd * CREDITS_PER_USD * CREDIT_PRECISION) / CREDIT_PRECISION;
112
+ }
113
+
114
+ /** The credits a typical use of `step` costs on a model of `price` — never typed, always computed (§3d). */
115
+ export function estimateStepCredits(step: AiStep, price: AiTokenPrice, tokens: AiTokenCounts = AI_STEP_TYPICAL_TOKENS[step]): number {
116
+ return creditsOfUsd(costUsd(tokens, price));
117
+ }
118
+
119
+ /** Can `model` do `step`? The step's needs against the model's capabilities. */
120
+ export function modelCanDo(model: Pick<AiModelEntry, 'capabilities'>, step: AiStep): boolean {
121
+ return AI_STEP_NEEDS[step].every(need => model.capabilities.includes(need));
122
+ }
123
+
124
+ /** Why AI did not run (board 11). The server refuses with these BEFORE any model call. */
125
+ export enum AiRefusalCode {
126
+ NotInPlan = 'ai.notInPlan',
127
+ OutOfCredits = 'ai.outOfCredits',
128
+ CapReached = 'ai.capReached',
129
+ NotConnected = 'ai.notConnected',
130
+ KeyInvalid = 'ai.keyInvalid',
131
+ PlanUnknown = 'ai.planUnknown',
132
+ }
133
+
134
+ /** The HTTP status each refusal answers with (board 11). */
135
+ export const AI_REFUSAL_STATUS: Readonly<Record<AiRefusalCode, number>> = {
136
+ [AiRefusalCode.NotInPlan]: 403,
137
+ [AiRefusalCode.OutOfCredits]: 402,
138
+ [AiRefusalCode.CapReached]: 402,
139
+ [AiRefusalCode.NotConnected]: 409,
140
+ [AiRefusalCode.KeyInvalid]: 409,
141
+ [AiRefusalCode.PlanUnknown]: 503,
142
+ };
143
+
144
+ /** What a refusal offers next. */
145
+ export enum AiNextKind {
146
+ /** Ask IOX (a prefilled email to the operator, until a plan can be bought — D2). */
147
+ Contact = 'contact',
148
+ /** Open Settings ▸ AI. */
149
+ Settings = 'settings',
150
+ /** Try again later, or with less. */
151
+ Retry = 'retry',
152
+ }
153
+
154
+ export const AI_REFUSAL_NEXT: Readonly<Record<AiRefusalCode, AiNextKind>> = {
155
+ [AiRefusalCode.NotInPlan]: AiNextKind.Contact,
156
+ [AiRefusalCode.OutOfCredits]: AiNextKind.Contact,
157
+ [AiRefusalCode.CapReached]: AiNextKind.Retry,
158
+ [AiRefusalCode.NotConnected]: AiNextKind.Settings,
159
+ [AiRefusalCode.KeyInvalid]: AiNextKind.Settings,
160
+ [AiRefusalCode.PlanUnknown]: AiNextKind.Retry,
161
+ };
162
+
163
+ export interface AiRefusal {
164
+ code: AiRefusalCode;
165
+ planName?: string;
166
+ /** Credits left this period (platform credits only). */
167
+ remaining?: number;
168
+ /** What the refused call would have cost, about. */
169
+ estimate?: number;
170
+ next: { kind: AiNextKind; href?: string };
171
+ }
172
+
173
+ /** The studio's AI credits this period (the connector's meter, E4). `allowance: null` = unlimited. */
174
+ export interface AiMeter {
175
+ used: number;
176
+ allowance: number | null;
177
+ remaining: number | null;
178
+ periodStart: string;
179
+ periodEnd: string;
180
+ }
181
+
182
+ /** A generation's stages (§3d). The runner works while `task` is set; otherwise the owner has the next move. */
183
+ export enum AiGenerationStage {
184
+ Reading = 'reading',
185
+ Analysis = 'analysis',
186
+ Design = 'design',
187
+ Plan = 'plan',
188
+ Build = 'build',
189
+ Done = 'done',
190
+ Failed = 'failed',
191
+ Cancelled = 'cancelled',
192
+ }
193
+
194
+ /** The stages nothing advances any more. */
195
+ export const FINAL_GENERATION_STAGES: ReadonlyArray<AiGenerationStage> = [AiGenerationStage.Done, AiGenerationStage.Failed, AiGenerationStage.Cancelled];
196
+
197
+ /** How one model call ended — a ledger row's status. */
198
+ export enum AiCallStatus {
199
+ /** Written before the call: a reservation at the estimate until the call settles. */
200
+ Started = 'started',
201
+ Ok = 'ok',
202
+ /** The answer came back and the shared validator refused it (with a path). */
203
+ Invalid = 'invalid',
204
+ /** The model declined (a provider refusal) — charged what the provider reports. */
205
+ Refused = 'refused',
206
+ Error = 'error',
207
+ /** The process died during the call: charged at the estimate, since what it cost was never reported. */
208
+ Interrupted = 'interrupted',
209
+ }
@@ -24,3 +24,4 @@ export * from './nav-shape';
24
24
  export * from './entrances';
25
25
  export * from './plan-data';
26
26
  export * from './site-build-run';
27
+ export * from './ai-run';
@@ -178,3 +178,73 @@ export function validateSiteAnalysis(value: unknown, ctx: AnalysisContext): Anal
178
178
 
179
179
  return { valid: c.issues.length === 0, issues: c.issues };
180
180
  }
181
+
182
+ /**
183
+ * The structured-output schema a model is given to write a `SiteAnalysis` (AI plan §3c/§3d, M0-5): built from the
184
+ * SAME shape table `validateSiteAnalysis` reads (keys, which are required, each key's explanation as its
185
+ * description) and the same closed lists (30 business types, needs, facts, moods, avoids) — so the shape the model
186
+ * is told about is the shape the gate refuses. Written to the strictest common subset of JSON Schema the providers'
187
+ * native strict modes accept (Anthropic `output_config.format`, OpenRouter `json_schema` + `strict`): every object
188
+ * closes with `additionalProperties: false`; no length, count or number bounds (the gate checks those). The
189
+ * schema is a hint the provider enforces; `validateSiteAnalysis` stays the gate.
190
+ */
191
+
192
+ export type JsonSchema = { [key: string]: unknown };
193
+
194
+ /** The name a provider shows the format under (`json_schema.name`). */
195
+ export const SITE_ANALYSIS_SCHEMA_NAME = 'site_analysis';
196
+
197
+ const OPTIONAL_MARK = '?';
198
+
199
+ const text = (): JsonSchema => ({ type: 'string' });
200
+ const oneOf = (values: ReadonlyArray<string>): JsonSchema => ({ type: 'string', enum: [...values] });
201
+ const listOf = (items: JsonSchema): JsonSchema => ({ type: 'array', items });
202
+
203
+ /** The shape table's own words for a key — the description the model reads. */
204
+ function describe(shape: string, key: string): string {
205
+ const entry = SHAPE[shape];
206
+ return String(entry[key] ?? entry[`${key}${OPTIONAL_MARK}`] ?? '');
207
+ }
208
+
209
+ /** An object of `shape`: exactly its keys, its required ones required, each described by the table. */
210
+ function shaped(shape: string, properties: Record<string, JsonSchema>): JsonSchema {
211
+ return {
212
+ type: 'object',
213
+ properties: Object.fromEntries(Object.entries(properties).map(([key, schema]) => [key, { ...schema, description: describe(shape, key) }])),
214
+ required: requiredShapeKeys(SHAPE[shape]),
215
+ additionalProperties: false,
216
+ };
217
+ }
218
+
219
+ /** `{ [fact]: factRead }`: the closed fact keys, each optional — a fact the prompt does not state is left out. */
220
+ function factsSchema(factRead: JsonSchema): JsonSchema {
221
+ return {
222
+ type: 'object',
223
+ properties: Object.fromEntries(FACT_KEYS.map(key => [key, factRead])),
224
+ required: [],
225
+ additionalProperties: false,
226
+ description: describe('analysis', 'facts'),
227
+ };
228
+ }
229
+
230
+ export function siteAnalysisJsonSchema(): JsonSchema {
231
+ const factRead = shaped('factRead', { value: text(), quote: text() });
232
+ return shaped('analysis', {
233
+ businessType: oneOf(BUSINESS_TYPE_IDS),
234
+ alternatives: listOf(oneOf(BUSINESS_TYPE_IDS)),
235
+ business: shaped('business', { name: text(), audience: text() }),
236
+ needs: listOf(shaped('need', { id: oneOf(NEED_IDS), from: oneOf(Object.values(NeedSource)), name: text() })),
237
+ facts: factsSchema(factRead),
238
+ missingFacts: listOf(oneOf(FACT_KEYS)),
239
+ voice: shaped('voice', {
240
+ tone: text(),
241
+ person: oneOf(Object.values(VoicePerson)),
242
+ formality: oneOf(Object.values(VoiceFormality)),
243
+ avoidWords: listOf(text()),
244
+ }),
245
+ moods: listOf(oneOf(MOOD_IDS)),
246
+ avoid: listOf(oneOf(USER_AVOID_IDS)),
247
+ hero: shaped('hero', { headline: text(), lead: text() }),
248
+ locale: oneOf(Object.values(AnalysisLocale)),
249
+ });
250
+ }