@aglyn/plugins-ai 1.0.0-beta.231 → 1.0.0-beta.233
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/package.json +17 -17
- package/src/lib/activity/ai-activity-actions.js +8 -4
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-job-follow.component.js +1 -0
- package/src/lib/components/ai-job-follow.component.js.map +1 -1
- package/src/lib/components/ai-job-links.js +13 -0
- package/src/lib/components/ai-job-links.js.map +1 -1
- package/src/lib/components/ai-jobs-list-page.component.js +1 -0
- package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
- package/src/lib/components/ai-media-create.component.d.ts +20 -7
- package/src/lib/components/ai-media-create.component.js +107 -55
- package/src/lib/components/ai-media-create.component.js.map +1 -1
- package/src/lib/components/ai-seo-fields-card.component.js +1 -0
- package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
- package/src/lib/components/ai-site-memory-card.component.d.ts +32 -0
- package/src/lib/components/ai-site-memory-card.component.js +187 -0
- package/src/lib/components/ai-site-memory-card.component.js.map +1 -0
- package/src/lib/components/ai-site-start-card.component.js +80 -77
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-theme-proposal-card.component.js +4 -0
- package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
- package/src/lib/components/ai-usage-strip.component.js +1 -0
- package/src/lib/components/ai-usage-strip.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.d.ts +10 -0
- package/src/lib/components/assist-panel.component.js +119 -9
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/assist-signals-page.component.js +2 -0
- package/src/lib/components/assist-signals-page.component.js.map +1 -1
- package/src/lib/components/billing-ai-allotments.component.js +3 -0
- package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
- package/src/lib/components/billing-ai-top-users.component.js +1 -0
- package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
- package/src/lib/components/billing-assist-overage-card.component.js +2 -0
- package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
- package/src/lib/components/member-ai-usage-card.component.js +10 -0
- package/src/lib/components/member-ai-usage-card.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-card.component.js +1 -0
- package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-conversations.component.d.ts +23 -0
- package/src/lib/components/staff-org-ai-conversations.component.js +346 -0
- package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -0
- package/src/lib/declarations.server.js +7 -4
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/jobs/ai-build-capabilities.d.ts +13 -1
- package/src/lib/jobs/ai-build-capabilities.js +109 -9
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
- package/src/lib/jobs/ai-business-profile-prefill.d.ts +29 -0
- package/src/lib/jobs/ai-business-profile-prefill.js +85 -0
- package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -0
- package/src/lib/jobs/ai-job-build-step.js +3 -1
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-edit-step.d.ts +225 -0
- package/src/lib/jobs/ai-job-edit-step.js +631 -0
- package/src/lib/jobs/ai-job-edit-step.js.map +1 -0
- package/src/lib/jobs/ai-job-insight-step.js +21 -4
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +8 -3
- package/src/lib/jobs/ai-job-layout-language.js +8 -2
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +7 -6
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-logic-step.d.ts +20 -0
- package/src/lib/jobs/ai-job-logic-step.js +87 -6
- package/src/lib/jobs/ai-job-logic-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +52 -7
- package/src/lib/jobs/ai-job-page-language.js +72 -9
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +12 -2
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +24 -5
- package/src/lib/jobs/ai-job-plan-step.js +68 -21
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +163 -0
- package/src/lib/jobs/ai-job-site-content.js +443 -0
- package/src/lib/jobs/ai-job-site-content.js.map +1 -0
- package/src/lib/jobs/ai-job-site-look.d.ts +112 -0
- package/src/lib/jobs/ai-job-site-look.js +322 -0
- package/src/lib/jobs/ai-job-site-look.js.map +1 -0
- package/src/lib/jobs/ai-job-site-step.d.ts +26 -5
- package/src/lib/jobs/ai-job-site-step.js +163 -24
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs-notify.d.ts +16 -1
- package/src/lib/jobs/ai-jobs-notify.js +81 -3
- package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +14 -0
- package/src/lib/jobs/ai-jobs.js +28 -10
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/layout-language/ai-layout-compiler.d.ts +9 -0
- package/src/lib/layout-language/ai-layout-compiler.js +120 -33
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame.js +5 -4
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-language.d.ts +12 -5
- package/src/lib/layout-language/ai-layout-language.js +92 -9
- package/src/lib/layout-language/ai-layout-language.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +144 -0
- package/src/lib/layout-language/ai-layout-pictures.js +214 -0
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -0
- package/src/lib/layout-language/ai-layout-store.d.ts +2 -0
- package/src/lib/layout-language/ai-layout-store.js +7 -1
- package/src/lib/layout-language/ai-layout-store.js.map +1 -1
- package/src/lib/model/ai-build-progress.js +4 -1
- package/src/lib/model/ai-build-progress.js.map +1 -1
- package/src/lib/model/ai-edit-job.d.ts +109 -0
- package/src/lib/model/ai-edit-job.js +157 -0
- package/src/lib/model/ai-edit-job.js.map +1 -0
- package/src/lib/model/ai-job-activity.js +1 -0
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +3 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-logic-job.d.ts +18 -1
- package/src/lib/model/ai-logic-job.js +24 -3
- package/src/lib/model/ai-logic-job.js.map +1 -1
- package/src/lib/model/ai-media-credits.d.ts +8 -4
- package/src/lib/model/ai-media-credits.js +11 -4
- package/src/lib/model/ai-media-credits.js.map +1 -1
- package/src/lib/model/ai-media-kinds.d.ts +68 -0
- package/src/lib/model/ai-media-kinds.js +98 -0
- package/src/lib/model/ai-media-kinds.js.map +1 -0
- package/src/lib/model/ai-plan-capabilities.d.ts +16 -0
- package/src/lib/model/ai-plan-capabilities.js +5 -0
- package/src/lib/model/ai-plan-capabilities.js.map +1 -1
- package/src/lib/model/ai-site-build-progress.d.ts +4 -0
- package/src/lib/model/ai-site-build-progress.js +28 -11
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-site-context.d.ts +111 -0
- package/src/lib/model/ai-site-context.js +222 -0
- package/src/lib/model/ai-site-context.js.map +1 -0
- package/src/lib/model/ai-site-job.d.ts +10 -2
- package/src/lib/model/ai-site-job.js +12 -5
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.d.ts +164 -0
- package/src/lib/model/ai-site-kinds.js +1942 -0
- package/src/lib/model/ai-site-kinds.js.map +1 -0
- package/src/lib/model/ai-site-look.d.ts +150 -0
- package/src/lib/model/ai-site-look.js +1236 -0
- package/src/lib/model/ai-site-look.js.map +1 -0
- package/src/lib/model/ai-site-memory.d.ts +80 -0
- package/src/lib/model/ai-site-memory.js +171 -0
- package/src/lib/model/ai-site-memory.js.map +1 -0
- package/src/lib/model/ai-site-start.d.ts +10 -1
- package/src/lib/model/ai-site-start.js +16 -2
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/model/assist-follow-up.d.ts +92 -0
- package/src/lib/model/assist-follow-up.js +128 -0
- package/src/lib/model/assist-follow-up.js.map +1 -0
- package/src/lib/operator-alerts.d.ts +18 -0
- package/src/lib/operator-alerts.js +42 -0
- package/src/lib/operator-alerts.js.map +1 -1
- package/src/lib/plugin-config.js +2 -0
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +20 -1
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/anthropic.d.ts +9 -1
- package/src/lib/providers/anthropic.js +143 -9
- package/src/lib/providers/anthropic.js.map +1 -1
- package/src/lib/providers/catalog.d.ts +37 -7
- package/src/lib/providers/catalog.js +103 -11
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/contract.d.ts +29 -0
- package/src/lib/providers/contract.js.map +1 -1
- package/src/lib/providers/image-contract.d.ts +22 -1
- package/src/lib/providers/image-contract.js +40 -1
- package/src/lib/providers/image-contract.js.map +1 -1
- package/src/lib/providers/image-endpoints.d.ts +24 -0
- package/src/lib/providers/image-endpoints.js +28 -0
- package/src/lib/providers/image-endpoints.js.map +1 -0
- package/src/lib/providers/model-choice.js +23 -2
- package/src/lib/providers/model-choice.js.map +1 -1
- package/src/lib/providers/routing.js +18 -0
- package/src/lib/providers/routing.js.map +1 -1
- package/src/lib/providers/vertex-image-endpoint.d.ts +48 -0
- package/src/lib/providers/vertex-image-endpoint.js +80 -0
- package/src/lib/providers/vertex-image-endpoint.js.map +1 -0
- package/src/lib/providers/vertex-image.d.ts +13 -27
- package/src/lib/providers/vertex-image.js +15 -42
- package/src/lib/providers/vertex-image.js.map +1 -1
- package/src/lib/runtime/ai-blog-post-generation.d.ts +62 -0
- package/src/lib/runtime/ai-blog-post-generation.js +89 -0
- package/src/lib/runtime/ai-blog-post-generation.js.map +1 -0
- package/src/lib/runtime/ai-dev-env.d.ts +53 -0
- package/src/lib/runtime/ai-dev-env.js +72 -0
- package/src/lib/runtime/ai-dev-env.js.map +1 -0
- package/src/lib/runtime/ai-dev-replay.d.ts +116 -0
- package/src/lib/runtime/ai-dev-replay.js +313 -0
- package/src/lib/runtime/ai-dev-replay.js.map +1 -0
- package/src/lib/runtime/ai-doctrine-validators.d.ts +7 -0
- package/src/lib/runtime/ai-doctrine-validators.js +9 -2
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +5 -1
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.js +4 -0
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-eval.d.ts +13 -1
- package/src/lib/runtime/ai-eval.js +84 -1
- package/src/lib/runtime/ai-eval.js.map +1 -1
- package/src/lib/runtime/ai-live-batch.d.ts +45 -0
- package/src/lib/runtime/ai-live-batch.js +89 -0
- package/src/lib/runtime/ai-live-batch.js.map +1 -0
- package/src/lib/runtime/ai-runtime.js +46 -2
- package/src/lib/runtime/ai-runtime.js.map +1 -1
- package/src/lib/runtime/site-context.d.ts +58 -0
- package/src/lib/runtime/site-context.js +127 -0
- package/src/lib/runtime/site-context.js.map +1 -0
- package/src/lib/server/ai-admin-conversations.d.ts +22 -0
- package/src/lib/server/ai-admin-conversations.js +197 -0
- package/src/lib/server/ai-admin-conversations.js.map +1 -0
- package/src/lib/server/ai-media-image.d.ts +5 -5
- package/src/lib/server/ai-media-image.js +25 -11
- package/src/lib/server/ai-media-image.js.map +1 -1
- package/src/lib/server/ai-media-raster-prompt.d.ts +34 -0
- package/src/lib/server/ai-media-raster-prompt.js +53 -0
- package/src/lib/server/ai-media-raster-prompt.js.map +1 -0
- package/src/lib/server/assist-chat.d.ts +11 -0
- package/src/lib/server/assist-chat.js +57 -18
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-edit-applied.js +16 -3
- package/src/lib/server/assist-edit-applied.js.map +1 -1
- package/src/lib/server/assist-view-context.d.ts +19 -1
- package/src/lib/server/assist-view-context.js +38 -1
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server.js +10 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/subprocessors.js +17 -1
- package/src/lib/subprocessors.js.map +1 -1
- package/src/lib/tools/ai-blog-post-tool.d.ts +60 -0
- package/src/lib/tools/ai-blog-post-tool.js +175 -0
- package/src/lib/tools/ai-blog-post-tool.js.map +1 -0
- package/src/lib/usage/assist-usage.d.ts +3 -0
- package/src/lib/usage/assist-usage.js +4 -2
- package/src/lib/usage/assist-usage.js.map +1 -1
- package/src/lib/usage/staff-org-ai-conversations.d.ts +103 -0
- package/src/lib/usage/staff-org-ai-conversations.js +152 -0
- package/src/lib/usage/staff-org-ai-conversations.js.map +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-logic-job.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n evaluateHostFunction,\n expressionSyntaxError,\n type FunctionComparator,\n type FunctionValueType,\n type HostFunction,\n functionReferencedNames,\n} from '@aglyn/aglyn/app-utils/functions'\nimport {\n functionGlobals,\n type HostVariable,\n type HostVariableType,\n VARIABLE_NAME_PATTERN,\n} from '@aglyn/aglyn/app-utils/variables'\nimport type { AiDoctrineViolation } from '../runtime/ai-doctrine-validators'\n\n/**\n * A `logic` job (AGL-3603): a site's Functions & Variables, by AI.\n *\n * - `function` proposes a function — parameters, locals, the numbered\n * if/then/otherwise operations and the value it returns — from a\n * description, or, naming a saved function, a changed or fixed copy of\n * it. Every expression is held to the evaluator's own grammar\n * (`expressionSyntaxError`), every name it reads to the function's own\n * names and the site's variables, and the whole definition is run once\n * with each parameter at its starting value before anyone sees it.\n * - `variable` proposes one site variable: a name, a type and a value in\n * that type's stored form.\n * - `explain` reads a saved function and answers in plain words what it\n * computes, and anything in it worth checking.\n *\n * Nothing is written: a proposal rides on the job's output, and the logic\n * editor is where a person saves it, or does not.\n */\n\nexport const AI_LOGIC_JOB_MODES = ['function', 'variable', 'explain'] as const\nexport type AiLogicJobMode = (typeof AI_LOGIC_JOB_MODES)[number]\n\nexport type AiLogicJobInputs =\n | { mode: 'function'; functionId: string | null }\n | { mode: 'variable' }\n | { mode: 'explain'; functionId: string }\n\nconst DOCUMENT_ID = /^[A-Za-z0-9_-]{1,128}$/\n\nexport const AI_LOGIC_PICK_FUNCTION_COPY = 'Pick the function to explain'\n\n/** A job's inputs read into its mode, or the sentence that says what is wrong with them. */\nexport function parseAiLogicJobInputs(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): AiLogicJobInputs | string {\n const mode = inputs?.['mode'] ?? 'function'\n if (!(AI_LOGIC_JOB_MODES as readonly unknown[]).includes(mode)) {\n return 'inputs.mode must be function, variable or explain'\n }\n if (mode === 'variable') return { mode }\n const functionId = inputs?.['functionId']\n const named = typeof functionId === 'string' && DOCUMENT_ID.test(functionId) ? functionId : null\n if (functionId != null && !named) return AI_LOGIC_PICK_FUNCTION_COPY\n if (mode === 'explain') return named ? { mode, functionId: named } : AI_LOGIC_PICK_FUNCTION_COPY\n return { mode: 'function', functionId: named }\n}\n\n// ── The bounds a proposal is held to ──────────────────────────────────────\n\nexport const AI_LOGIC_MAX_PARAMETERS = 12\nexport const AI_LOGIC_MAX_LOCALS = 20\nexport const AI_LOGIC_MAX_OPERATIONS = 24\nexport const AI_LOGIC_MAX_SETS = 12\nexport const AI_LOGIC_EXPRESSION_MAX_CHARS = 400\nexport const AI_LOGIC_LABEL_MAX_CHARS = 80\nexport const AI_LOGIC_VARIABLE_VALUE_MAX_CHARS = 2_000\n\nexport const AI_LOGIC_VALUE_TYPES: readonly FunctionValueType[] = ['number', 'text', 'boolean']\nexport const AI_LOGIC_COMPARATORS: readonly FunctionComparator[] = ['==', '!=', '<', '<=', '>', '>=']\n\n/** The variable types a proposal may use: every type the Variables editor offers. */\nexport const AI_LOGIC_VARIABLE_TYPES: readonly HostVariableType[] = [\n 'text',\n 'number',\n 'boolean',\n 'date',\n 'time',\n 'dictionary',\n 'collection',\n]\n\n/** A site variable as a proposal is checked against: its name, type and value. */\nexport type AiLogicSiteVariable = Pick<HostVariable, 'name' | 'type' | 'value'>\n\n/** What a proposal's names are checked against: the site's variables and functions. */\nexport interface AiLogicContext {\n variables: readonly AiLogicSiteVariable[]\n /** The site's function names, which a new function's name must not repeat. */\n functions: readonly string[]\n /** The saved function a change starts from; its own name is free to keep. */\n editing?: string | null\n}\n\nexport interface AiLogicCheckResult<T> {\n value: T | null\n violations: AiDoctrineViolation[]\n}\n\nconst violation = (code: string, message: string): AiDoctrineViolation => ({ rule: null, code, message })\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\nconst MARKUP = /<\\s*\\/?\\s*(script|iframe|style|object|embed)\\b|javascript:/i\n\n/** A starting value a parameter's type can hold, for the run every proposal is given. */\nfunction sampleFor(type: FunctionValueType, defaultValue: string | undefined): string {\n if (defaultValue) return defaultValue\n return type === 'number' ? '1' : type === 'boolean' ? 'true' : 'sample'\n}\n\n/**\n * A `submit_function` answer read into a definition the function editor\n * opens, or the violations a re-ask names. Only what the evaluator can run\n * is kept: an expression it cannot parse, a name it cannot find, a SET on a\n * site variable, or a run that fails on starting values is refused.\n */\nexport function checkAiLogicFunction(\n answer: Record<string, unknown>,\n context: AiLogicContext,\n): AiLogicCheckResult<HostFunction> {\n const violations: AiDoctrineViolation[] = []\n const name = str(answer['name'])\n if (!VARIABLE_NAME_PATTERN.test(name)) {\n violations.push(\n violation('logic-name', 'The function name must start with a letter or _, then letters, digits or _; at most 40 characters.'),\n )\n } else if (name !== context.editing && context.functions.includes(name)) {\n violations.push(violation('logic-name-taken', `The site already has a function named ${name}. Choose another name.`))\n }\n\n const own = new Set<string>()\n const ownName = (raw: unknown, what: string): string => {\n const value = str(raw)\n if (!VARIABLE_NAME_PATTERN.test(value)) {\n violations.push(violation('logic-name', `A ${what} name must start with a letter or _, then letters, digits or _.`))\n } else if (own.has(value)) {\n violations.push(violation('logic-name-repeated', `The name ${value} is used twice. Each parameter and local needs its own name.`))\n }\n own.add(value)\n return value\n }\n const typeOf = (raw: unknown): FunctionValueType => {\n if ((AI_LOGIC_VALUE_TYPES as readonly unknown[]).includes(raw)) return raw as FunctionValueType\n violations.push(violation('logic-type', 'A type is number, text or boolean.'))\n return 'number'\n }\n\n const rawParameters = Array.isArray(answer['parameters']) ? answer['parameters'].filter(isRecord) : []\n const rawLocals = Array.isArray(answer['locals']) ? answer['locals'].filter(isRecord) : []\n const rawOperations = Array.isArray(answer['operations']) ? answer['operations'].filter(isRecord) : []\n if (rawParameters.length > AI_LOGIC_MAX_PARAMETERS) {\n violations.push(violation('logic-size', `At most ${AI_LOGIC_MAX_PARAMETERS} parameters.`))\n }\n if (rawLocals.length > AI_LOGIC_MAX_LOCALS) {\n violations.push(violation('logic-size', `At most ${AI_LOGIC_MAX_LOCALS} locals.`))\n }\n if (!rawOperations.length || rawOperations.length > AI_LOGIC_MAX_OPERATIONS) {\n violations.push(violation('logic-size', `Between 1 and ${AI_LOGIC_MAX_OPERATIONS} operations.`))\n }\n\n const parameters = rawParameters.slice(0, AI_LOGIC_MAX_PARAMETERS).map((raw) => {\n const parameter: HostFunction['parameters'][number] = {\n name: ownName(raw['name'], 'parameter'),\n type: typeOf(raw['type']),\n required: raw['required'] === true,\n }\n const label = str(raw['label']).slice(0, AI_LOGIC_LABEL_MAX_CHARS)\n if (label) parameter.label = label\n const start = str(raw['defaultValue']).slice(0, AI_LOGIC_LABEL_MAX_CHARS)\n if (start) parameter.defaultValue = start\n return parameter\n })\n const variables = rawLocals.slice(0, AI_LOGIC_MAX_LOCALS).map((raw) => ({\n name: ownName(raw['name'], 'local'),\n type: typeOf(raw['type']),\n }))\n\n const site = new Set(context.variables.map((variable) => variable.name))\n const expression = (raw: unknown, where: string): string => {\n const text = typeof raw === 'string' ? raw.trim() : ''\n if (!text) {\n violations.push(violation('logic-expression', `${where} is empty. Write a value, a name or an expression.`))\n return text\n }\n if (text.length > AI_LOGIC_EXPRESSION_MAX_CHARS || MARKUP.test(text)) {\n violations.push(violation('logic-expression', `${where} is not an expression the function can run.`))\n return text\n }\n const problem = expressionSyntaxError(text)\n if (problem) violations.push(violation('logic-syntax', `${where} cannot be read: ${problem}.`))\n return text\n }\n const sets = (raw: unknown, where: string) => {\n const list = Array.isArray(raw) ? raw.filter(isRecord) : []\n if (list.length > AI_LOGIC_MAX_SETS) {\n violations.push(violation('logic-size', `${where} has more than ${AI_LOGIC_MAX_SETS} assignments.`))\n }\n return list.slice(0, AI_LOGIC_MAX_SETS).map((set, index) => {\n const target = str(set['set'])\n if (!own.has(target)) {\n violations.push(\n violation(\n 'logic-set-target',\n site.has(target)\n ? `${where}, assignment ${index + 1}, sets the site variable ${target}. A function reads site variables and sets only its own parameters and locals.`\n : `${where}, assignment ${index + 1}, sets ${target || 'nothing'}, which is not one of the function's parameters or locals.`,\n ),\n )\n }\n return { set: target, expression: expression(set['expression'], `${where}, assignment ${index + 1}`) }\n })\n }\n const operations = rawOperations.slice(0, AI_LOGIC_MAX_OPERATIONS).map((raw, index) => {\n const where = `Operation ${index + 1}`\n const condition = isRecord(raw['if']) ? raw['if'] : {}\n const comparator = (AI_LOGIC_COMPARATORS as readonly unknown[]).includes(condition['comparator'])\n ? (condition['comparator'] as FunctionComparator)\n : null\n if (!comparator) violations.push(violation('logic-comparator', `${where}'s condition compares with one of == != < <= > >=.`))\n return {\n if: {\n left: expression(condition['left'], `${where}'s condition (left)`),\n comparator: comparator ?? '==',\n right: expression(condition['right'], `${where}'s condition (right)`),\n },\n then: sets(raw['then'], `${where} (then)`),\n otherwise: sets(raw['otherwise'], `${where} (otherwise)`),\n }\n })\n\n const returnValue = str(answer['returnValue'])\n if (!own.has(returnValue)) {\n violations.push(violation('logic-return', 'returnValue names one of the function’s own parameters or locals.'))\n }\n const definition: HostFunction = { name, parameters, variables, operations, returnValue }\n\n // Every name an expression reads that is not the function's own must be a site variable.\n const unknown = functionReferencedNames(definition).filter((read) => !site.has(read))\n if (unknown.length) {\n violations.push(\n violation(\n 'logic-unknown-name',\n `The function reads ${unknown.join(', ')}, which is neither one of its own names nor a site variable. Use only the names listed.`,\n ),\n )\n }\n\n if (!violations.length) {\n // One run with every parameter at a starting value: what the editor's\n // own test run would show first, so a definition that cannot run on its\n // first try is refused here rather than handed to a person.\n const variablesByName = Object.fromEntries(\n context.variables.map((variable) => [variable.name, variable as HostVariable]),\n )\n const args = Object.fromEntries(\n parameters.map((parameter) => [parameter.name, sampleFor(parameter.type, parameter.defaultValue)]),\n )\n const run = evaluateHostFunction(definition, args, { globals: functionGlobals(definition, variablesByName) })\n if (run.ok === false) violations.push(violation('logic-run', `The function fails when it runs: ${run.error}.`))\n }\n return { value: violations.length ? null : definition, violations }\n}\n\n/** A `submit_variable` answer read into the variable the Variables editor opens. */\nexport function checkAiLogicVariable(\n answer: Record<string, unknown>,\n context: Pick<AiLogicContext, 'variables'>,\n): AiLogicCheckResult<AiLogicSiteVariable> {\n const violations: AiDoctrineViolation[] = []\n const name = str(answer['name'])\n if (!VARIABLE_NAME_PATTERN.test(name)) {\n violations.push(violation('logic-name', 'The variable name must start with a letter or _, then letters, digits or _; at most 40 characters.'))\n } else if (context.variables.some((variable) => variable.name === name)) {\n violations.push(violation('logic-name-taken', `The site already has a variable named ${name}. Choose another name.`))\n }\n const type = answer['type']\n if (!(AI_LOGIC_VARIABLE_TYPES as readonly unknown[]).includes(type)) {\n violations.push(violation('logic-type', `A variable's type is one of ${AI_LOGIC_VARIABLE_TYPES.join(', ')}.`))\n }\n const value = typeof answer['value'] === 'string' ? answer['value'] : ''\n if (value.length > AI_LOGIC_VARIABLE_VALUE_MAX_CHARS || MARKUP.test(value)) {\n violations.push(violation('logic-value', 'The value is too long, or holds markup.'))\n } else if (type === 'number' && !Number.isFinite(Number(value))) {\n violations.push(violation('logic-value', 'A number variable’s value is a number, such as 49 or 0.5.'))\n } else if (type === 'boolean' && value !== 'true' && value !== 'false') {\n violations.push(violation('logic-value', 'A boolean variable’s value is true or false.'))\n } else if (type === 'date' && Number.isNaN(new Date(value).getTime())) {\n violations.push(violation('logic-value', 'A date variable’s value is a date such as 2026-10-01.'))\n } else if (type === 'dictionary' || type === 'collection') {\n let parsed: unknown\n try {\n parsed = JSON.parse(value)\n } catch {\n parsed = null\n }\n const fits = type === 'collection' ? Array.isArray(parsed) : isRecord(parsed)\n if (!fits) {\n violations.push(\n violation(\n 'logic-value',\n type === 'collection'\n ? 'A collection’s value is a JSON list, such as [\"red\",\"green\"].'\n : 'A dictionary’s value is a JSON object of names to values, such as {\"starter\":19,\"pro\":49}.',\n ),\n )\n }\n }\n return {\n value: violations.length ? null : { name, type: type as HostVariableType, value },\n violations,\n }\n}\n\n// ── What a job hands the logic editor ─────────────────────────────────────\n\n/** The output resource a logic proposal rides on. */\nexport const AI_LOGIC_RESOURCE = 'logic'\n\n/** A logic output's proposal: a function to open in the editor, or a variable. */\nexport type AiLogicProposal =\n | { kind: 'function'; functionId: string | null; definition: HostFunction }\n | { kind: 'variable'; variable: AiLogicSiteVariable }\n\n/**\n * A proposal read back from a job's output, in the browser, before the\n * editor is handed it; `null` for anything that is not one. The server held\n * the definition to the grammar; this only refuses a shape that is not one.\n */\nexport function readAiLogicProposal(value: unknown): AiLogicProposal | null {\n if (!isRecord(value)) return null\n if (value['kind'] === 'variable' && isRecord(value['variable'])) {\n const variable = value['variable']\n if (typeof variable['name'] !== 'string' || typeof variable['type'] !== 'string') return null\n return {\n kind: 'variable',\n variable: {\n name: variable['name'],\n type: variable['type'] as HostVariableType,\n value: typeof variable['value'] === 'string' ? variable['value'] : '',\n },\n }\n }\n if (value['kind'] !== 'function' || !isRecord(value['definition'])) return null\n const definition = value['definition']\n if (\n typeof definition['name'] !== 'string' ||\n !Array.isArray(definition['parameters']) ||\n !Array.isArray(definition['variables']) ||\n !Array.isArray(definition['operations'])\n ) {\n return null\n }\n return {\n kind: 'function',\n functionId: typeof value['functionId'] === 'string' ? value['functionId'] : null,\n definition: definition as unknown as HostFunction,\n }\n}\n\n// ── What a job tells a person ─────────────────────────────────────────────\n\nexport const AI_LOGIC_NO_SITE_COPY = 'This logic job names a site this workspace does not have.'\nexport const AI_LOGIC_UNAVAILABLE_COPY = 'Turn on Logic for this site before starting the job.'\nexport const AI_LOGIC_GONE_COPY = 'That function no longer exists.'\nexport const AI_LOGIC_NO_FUNCTION_COPY =\n 'The AI could not write a function the site can run from this description. Try describing what goes in, and what should come out.'\nexport const AI_LOGIC_NO_VARIABLE_COPY =\n 'The AI could not write a variable from this description. Try naming what it holds, and its value.'\nexport const AI_LOGIC_NO_EXPLANATION_COPY = 'The AI could not explain this function. Try again.'\n"],"names":["evaluateHostFunction","expressionSyntaxError","functionReferencedNames","functionGlobals","VARIABLE_NAME_PATTERN","AI_LOGIC_JOB_MODES","DOCUMENT_ID","AI_LOGIC_PICK_FUNCTION_COPY","parseAiLogicJobInputs","inputs","mode","includes","functionId","named","test","AI_LOGIC_MAX_PARAMETERS","AI_LOGIC_MAX_LOCALS","AI_LOGIC_MAX_OPERATIONS","AI_LOGIC_MAX_SETS","AI_LOGIC_EXPRESSION_MAX_CHARS","AI_LOGIC_LABEL_MAX_CHARS","AI_LOGIC_VARIABLE_VALUE_MAX_CHARS","AI_LOGIC_VALUE_TYPES","AI_LOGIC_COMPARATORS","AI_LOGIC_VARIABLE_TYPES","violation","code","message","rule","isRecord","value","Array","isArray","str","trim","MARKUP","sampleFor","type","defaultValue","checkAiLogicFunction","answer","context","violations","name","push","editing","functions","own","Set","ownName","raw","what","has","add","typeOf","rawParameters","filter","rawLocals","rawOperations","length","parameters","slice","map","parameter","required","label","start","variables","site","variable","expression","where","text","problem","sets","list","set","index","target","operations","condition","comparator","if","left","right","then","otherwise","returnValue","definition","unknown","read","join","variablesByName","Object","fromEntries","args","run","globals","ok","error","checkAiLogicVariable","some","Number","isFinite","isNaN","Date","getTime","parsed","JSON","parse","fits","AI_LOGIC_RESOURCE","readAiLogicProposal","kind","AI_LOGIC_NO_SITE_COPY","AI_LOGIC_UNAVAILABLE_COPY","AI_LOGIC_GONE_COPY","AI_LOGIC_NO_FUNCTION_COPY","AI_LOGIC_NO_VARIABLE_COPY","AI_LOGIC_NO_EXPLANATION_COPY"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,oBAAoB,EACpBC,qBAAqB,EAIrBC,uBAAuB,QAClB,mCAAkC;AACzC,SACEC,eAAe,EAGfC,qBAAqB,QAChB,mCAAkC;AAGzC;;;;;;;;;;;;;;;;;CAiBC,GAED,OAAO,MAAMC,qBAAqB;IAAC;IAAY;IAAY;CAAU,CAAS;AAQ9E,MAAMC,cAAc;AAEpB,OAAO,MAAMC,8BAA8B,+BAA8B;AAEzE,0FAA0F,GAC1F,OAAO,SAASC,sBACdC,MAA4D;;IAE5D,MAAMC,eAAOD,0BAAAA,MAAQ,CAAC,OAAO,mBAAI;IACjC,IAAI,CAAC,AAACJ,mBAA0CM,QAAQ,CAACD,OAAO;QAC9D,OAAO;IACT;IACA,IAAIA,SAAS,YAAY,OAAO;QAAEA;IAAK;IACvC,MAAME,aAAaH,0BAAAA,MAAQ,CAAC,aAAa;IACzC,MAAMI,QAAQ,OAAOD,eAAe,YAAYN,YAAYQ,IAAI,CAACF,cAAcA,aAAa;IAC5F,IAAIA,cAAc,QAAQ,CAACC,OAAO,OAAON;IACzC,IAAIG,SAAS,WAAW,OAAOG,QAAQ;QAAEH;QAAME,YAAYC;IAAM,IAAIN;IACrE,OAAO;QAAEG,MAAM;QAAYE,YAAYC;IAAM;AAC/C;AAEA,6EAA6E;AAE7E,OAAO,MAAME,0BAA0B,GAAE;AACzC,OAAO,MAAMC,sBAAsB,GAAE;AACrC,OAAO,MAAMC,0BAA0B,GAAE;AACzC,OAAO,MAAMC,oBAAoB,GAAE;AACnC,OAAO,MAAMC,gCAAgC,IAAG;AAChD,OAAO,MAAMC,2BAA2B,GAAE;AAC1C,OAAO,MAAMC,oCAAoC,KAAK;AAEtD,OAAO,MAAMC,uBAAqD;IAAC;IAAU;IAAQ;CAAU,CAAA;AAC/F,OAAO,MAAMC,uBAAsD;IAAC;IAAM;IAAM;IAAK;IAAM;IAAK;CAAK,CAAA;AAErG,mFAAmF,GACnF,OAAO,MAAMC,0BAAuD;IAClE;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAA;AAmBD,MAAMC,YAAY,CAACC,MAAcC,UAA0C,CAAA;QAAEC,MAAM;QAAMF;QAAMC;IAAQ,CAAA;AAEvG,MAAME,WAAW,CAACC,QAChB,OAAOA,UAAU,YAAYA,UAAU,QAAQ,CAACC,MAAMC,OAAO,CAACF;AAEhE,MAAMG,MAAM,CAACH,QAA4B,OAAOA,UAAU,WAAWA,MAAMI,IAAI,KAAK;AAEpF,MAAMC,SAAS;AAEf,uFAAuF,GACvF,SAASC,UAAUC,IAAuB,EAAEC,YAAgC;IAC1E,IAAIA,cAAc,OAAOA;IACzB,OAAOD,SAAS,WAAW,MAAMA,SAAS,YAAY,SAAS;AACjE;AAEA;;;;;CAKC,GACD,OAAO,SAASE,qBACdC,MAA+B,EAC/BC,OAAuB;IAEvB,MAAMC,aAAoC,EAAE;IAC5C,MAAMC,OAAOV,IAAIO,MAAM,CAAC,OAAO;IAC/B,IAAI,CAACpC,sBAAsBU,IAAI,CAAC6B,OAAO;QACrCD,WAAWE,IAAI,CACbnB,UAAU,cAAc;IAE5B,OAAO,IAAIkB,SAASF,QAAQI,OAAO,IAAIJ,QAAQK,SAAS,CAACnC,QAAQ,CAACgC,OAAO;QACvED,WAAWE,IAAI,CAACnB,UAAU,oBAAoB,CAAC,sCAAsC,EAAEkB,KAAK,sBAAsB,CAAC;IACrH;IAEA,MAAMI,MAAM,IAAIC;IAChB,MAAMC,UAAU,CAACC,KAAcC;QAC7B,MAAMrB,QAAQG,IAAIiB;QAClB,IAAI,CAAC9C,sBAAsBU,IAAI,CAACgB,QAAQ;YACtCY,WAAWE,IAAI,CAACnB,UAAU,cAAc,CAAC,EAAE,EAAE0B,KAAK,+DAA+D,CAAC;QACpH,OAAO,IAAIJ,IAAIK,GAAG,CAACtB,QAAQ;YACzBY,WAAWE,IAAI,CAACnB,UAAU,uBAAuB,CAAC,SAAS,EAAEK,MAAM,4DAA4D,CAAC;QAClI;QACAiB,IAAIM,GAAG,CAACvB;QACR,OAAOA;IACT;IACA,MAAMwB,SAAS,CAACJ;QACd,IAAI,AAAC5B,qBAA4CX,QAAQ,CAACuC,MAAM,OAAOA;QACvER,WAAWE,IAAI,CAACnB,UAAU,cAAc;QACxC,OAAO;IACT;IAEA,MAAM8B,gBAAgBxB,MAAMC,OAAO,CAACQ,MAAM,CAAC,aAAa,IAAIA,MAAM,CAAC,aAAa,CAACgB,MAAM,CAAC3B,YAAY,EAAE;IACtG,MAAM4B,YAAY1B,MAAMC,OAAO,CAACQ,MAAM,CAAC,SAAS,IAAIA,MAAM,CAAC,SAAS,CAACgB,MAAM,CAAC3B,YAAY,EAAE;IAC1F,MAAM6B,gBAAgB3B,MAAMC,OAAO,CAACQ,MAAM,CAAC,aAAa,IAAIA,MAAM,CAAC,aAAa,CAACgB,MAAM,CAAC3B,YAAY,EAAE;IACtG,IAAI0B,cAAcI,MAAM,GAAG5C,yBAAyB;QAClD2B,WAAWE,IAAI,CAACnB,UAAU,cAAc,CAAC,QAAQ,EAAEV,wBAAwB,YAAY,CAAC;IAC1F;IACA,IAAI0C,UAAUE,MAAM,GAAG3C,qBAAqB;QAC1C0B,WAAWE,IAAI,CAACnB,UAAU,cAAc,CAAC,QAAQ,EAAET,oBAAoB,QAAQ,CAAC;IAClF;IACA,IAAI,CAAC0C,cAAcC,MAAM,IAAID,cAAcC,MAAM,GAAG1C,yBAAyB;QAC3EyB,WAAWE,IAAI,CAACnB,UAAU,cAAc,CAAC,cAAc,EAAER,wBAAwB,YAAY,CAAC;IAChG;IAEA,MAAM2C,aAAaL,cAAcM,KAAK,CAAC,GAAG9C,yBAAyB+C,GAAG,CAAC,CAACZ;QACtE,MAAMa,YAAgD;YACpDpB,MAAMM,QAAQC,GAAG,CAAC,OAAO,EAAE;YAC3Bb,MAAMiB,OAAOJ,GAAG,CAAC,OAAO;YACxBc,UAAUd,GAAG,CAAC,WAAW,KAAK;QAChC;QACA,MAAMe,QAAQhC,IAAIiB,GAAG,CAAC,QAAQ,EAAEW,KAAK,CAAC,GAAGzC;QACzC,IAAI6C,OAAOF,UAAUE,KAAK,GAAGA;QAC7B,MAAMC,QAAQjC,IAAIiB,GAAG,CAAC,eAAe,EAAEW,KAAK,CAAC,GAAGzC;QAChD,IAAI8C,OAAOH,UAAUzB,YAAY,GAAG4B;QACpC,OAAOH;IACT;IACA,MAAMI,YAAYV,UAAUI,KAAK,CAAC,GAAG7C,qBAAqB8C,GAAG,CAAC,CAACZ,MAAS,CAAA;YACtEP,MAAMM,QAAQC,GAAG,CAAC,OAAO,EAAE;YAC3Bb,MAAMiB,OAAOJ,GAAG,CAAC,OAAO;QAC1B,CAAA;IAEA,MAAMkB,OAAO,IAAIpB,IAAIP,QAAQ0B,SAAS,CAACL,GAAG,CAAC,CAACO,WAAaA,SAAS1B,IAAI;IACtE,MAAM2B,aAAa,CAACpB,KAAcqB;QAChC,MAAMC,OAAO,OAAOtB,QAAQ,WAAWA,IAAIhB,IAAI,KAAK;QACpD,IAAI,CAACsC,MAAM;YACT9B,WAAWE,IAAI,CAACnB,UAAU,oBAAoB,GAAG8C,MAAM,kDAAkD,CAAC;YAC1G,OAAOC;QACT;QACA,IAAIA,KAAKb,MAAM,GAAGxC,iCAAiCgB,OAAOrB,IAAI,CAAC0D,OAAO;YACpE9B,WAAWE,IAAI,CAACnB,UAAU,oBAAoB,GAAG8C,MAAM,2CAA2C,CAAC;YACnG,OAAOC;QACT;QACA,MAAMC,UAAUxE,sBAAsBuE;QACtC,IAAIC,SAAS/B,WAAWE,IAAI,CAACnB,UAAU,gBAAgB,GAAG8C,MAAM,iBAAiB,EAAEE,QAAQ,CAAC,CAAC;QAC7F,OAAOD;IACT;IACA,MAAME,OAAO,CAACxB,KAAcqB;QAC1B,MAAMI,OAAO5C,MAAMC,OAAO,CAACkB,OAAOA,IAAIM,MAAM,CAAC3B,YAAY,EAAE;QAC3D,IAAI8C,KAAKhB,MAAM,GAAGzC,mBAAmB;YACnCwB,WAAWE,IAAI,CAACnB,UAAU,cAAc,GAAG8C,MAAM,eAAe,EAAErD,kBAAkB,aAAa,CAAC;QACpG;QACA,OAAOyD,KAAKd,KAAK,CAAC,GAAG3C,mBAAmB4C,GAAG,CAAC,CAACc,KAAKC;YAChD,MAAMC,SAAS7C,IAAI2C,GAAG,CAAC,MAAM;YAC7B,IAAI,CAAC7B,IAAIK,GAAG,CAAC0B,SAAS;gBACpBpC,WAAWE,IAAI,CACbnB,UACE,oBACA2C,KAAKhB,GAAG,CAAC0B,UACL,GAAGP,MAAM,aAAa,EAAEM,QAAQ,EAAE,yBAAyB,EAAEC,OAAO,8EAA8E,CAAC,GACnJ,GAAGP,MAAM,aAAa,EAAEM,QAAQ,EAAE,OAAO,EAAEC,UAAU,UAAU,0DAA0D,CAAC;YAGpI;YACA,OAAO;gBAAEF,KAAKE;gBAAQR,YAAYA,WAAWM,GAAG,CAAC,aAAa,EAAE,GAAGL,MAAM,aAAa,EAAEM,QAAQ,GAAG;YAAE;QACvG;IACF;IACA,MAAME,aAAarB,cAAcG,KAAK,CAAC,GAAG5C,yBAAyB6C,GAAG,CAAC,CAACZ,KAAK2B;QAC3E,MAAMN,QAAQ,CAAC,UAAU,EAAEM,QAAQ,GAAG;QACtC,MAAMG,YAAYnD,SAASqB,GAAG,CAAC,KAAK,IAAIA,GAAG,CAAC,KAAK,GAAG,CAAC;QACrD,MAAM+B,aAAa,AAAC1D,qBAA4CZ,QAAQ,CAACqE,SAAS,CAAC,aAAa,IAC3FA,SAAS,CAAC,aAAa,GACxB;QACJ,IAAI,CAACC,YAAYvC,WAAWE,IAAI,CAACnB,UAAU,oBAAoB,GAAG8C,MAAM,kDAAkD,CAAC;QAC3H,OAAO;YACLW,IAAI;gBACFC,MAAMb,WAAWU,SAAS,CAAC,OAAO,EAAE,GAAGT,MAAM,mBAAmB,CAAC;gBACjEU,UAAU,EAAEA,qBAAAA,aAAc;gBAC1BG,OAAOd,WAAWU,SAAS,CAAC,QAAQ,EAAE,GAAGT,MAAM,oBAAoB,CAAC;YACtE;YACAc,MAAMX,KAAKxB,GAAG,CAAC,OAAO,EAAE,GAAGqB,MAAM,OAAO,CAAC;YACzCe,WAAWZ,KAAKxB,GAAG,CAAC,YAAY,EAAE,GAAGqB,MAAM,YAAY,CAAC;QAC1D;IACF;IAEA,MAAMgB,cAActD,IAAIO,MAAM,CAAC,cAAc;IAC7C,IAAI,CAACO,IAAIK,GAAG,CAACmC,cAAc;QACzB7C,WAAWE,IAAI,CAACnB,UAAU,gBAAgB;IAC5C;IACA,MAAM+D,aAA2B;QAAE7C;QAAMiB;QAAYO;QAAWY;QAAYQ;IAAY;IAExF,yFAAyF;IACzF,MAAME,UAAUvF,wBAAwBsF,YAAYhC,MAAM,CAAC,CAACkC,OAAS,CAACtB,KAAKhB,GAAG,CAACsC;IAC/E,IAAID,QAAQ9B,MAAM,EAAE;QAClBjB,WAAWE,IAAI,CACbnB,UACE,sBACA,CAAC,mBAAmB,EAAEgE,QAAQE,IAAI,CAAC,MAAM,uFAAuF,CAAC;IAGvI;IAEA,IAAI,CAACjD,WAAWiB,MAAM,EAAE;QACtB,sEAAsE;QACtE,wEAAwE;QACxE,4DAA4D;QAC5D,MAAMiC,kBAAkBC,OAAOC,WAAW,CACxCrD,QAAQ0B,SAAS,CAACL,GAAG,CAAC,CAACO,WAAa;gBAACA,SAAS1B,IAAI;gBAAE0B;aAAyB;QAE/E,MAAM0B,OAAOF,OAAOC,WAAW,CAC7BlC,WAAWE,GAAG,CAAC,CAACC,YAAc;gBAACA,UAAUpB,IAAI;gBAAEP,UAAU2B,UAAU1B,IAAI,EAAE0B,UAAUzB,YAAY;aAAE;QAEnG,MAAM0D,MAAMhG,qBAAqBwF,YAAYO,MAAM;YAAEE,SAAS9F,gBAAgBqF,YAAYI;QAAiB;QAC3G,IAAII,IAAIE,EAAE,KAAK,OAAOxD,WAAWE,IAAI,CAACnB,UAAU,aAAa,CAAC,iCAAiC,EAAEuE,IAAIG,KAAK,CAAC,CAAC,CAAC;IAC/G;IACA,OAAO;QAAErE,OAAOY,WAAWiB,MAAM,GAAG,OAAO6B;QAAY9C;IAAW;AACpE;AAEA,kFAAkF,GAClF,OAAO,SAAS0D,qBACd5D,MAA+B,EAC/BC,OAA0C;IAE1C,MAAMC,aAAoC,EAAE;IAC5C,MAAMC,OAAOV,IAAIO,MAAM,CAAC,OAAO;IAC/B,IAAI,CAACpC,sBAAsBU,IAAI,CAAC6B,OAAO;QACrCD,WAAWE,IAAI,CAACnB,UAAU,cAAc;IAC1C,OAAO,IAAIgB,QAAQ0B,SAAS,CAACkC,IAAI,CAAC,CAAChC,WAAaA,SAAS1B,IAAI,KAAKA,OAAO;QACvED,WAAWE,IAAI,CAACnB,UAAU,oBAAoB,CAAC,sCAAsC,EAAEkB,KAAK,sBAAsB,CAAC;IACrH;IACA,MAAMN,OAAOG,MAAM,CAAC,OAAO;IAC3B,IAAI,CAAC,AAAChB,wBAA+Cb,QAAQ,CAAC0B,OAAO;QACnEK,WAAWE,IAAI,CAACnB,UAAU,cAAc,CAAC,4BAA4B,EAAED,wBAAwBmE,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9G;IACA,MAAM7D,QAAQ,OAAOU,MAAM,CAAC,QAAQ,KAAK,WAAWA,MAAM,CAAC,QAAQ,GAAG;IACtE,IAAIV,MAAM6B,MAAM,GAAGtC,qCAAqCc,OAAOrB,IAAI,CAACgB,QAAQ;QAC1EY,WAAWE,IAAI,CAACnB,UAAU,eAAe;IAC3C,OAAO,IAAIY,SAAS,YAAY,CAACiE,OAAOC,QAAQ,CAACD,OAAOxE,SAAS;QAC/DY,WAAWE,IAAI,CAACnB,UAAU,eAAe;IAC3C,OAAO,IAAIY,SAAS,aAAaP,UAAU,UAAUA,UAAU,SAAS;QACtEY,WAAWE,IAAI,CAACnB,UAAU,eAAe;IAC3C,OAAO,IAAIY,SAAS,UAAUiE,OAAOE,KAAK,CAAC,IAAIC,KAAK3E,OAAO4E,OAAO,KAAK;QACrEhE,WAAWE,IAAI,CAACnB,UAAU,eAAe;IAC3C,OAAO,IAAIY,SAAS,gBAAgBA,SAAS,cAAc;QACzD,IAAIsE;QACJ,IAAI;YACFA,SAASC,KAAKC,KAAK,CAAC/E;QACtB,EAAE,eAAM;YACN6E,SAAS;QACX;QACA,MAAMG,OAAOzE,SAAS,eAAeN,MAAMC,OAAO,CAAC2E,UAAU9E,SAAS8E;QACtE,IAAI,CAACG,MAAM;YACTpE,WAAWE,IAAI,CACbnB,UACE,eACAY,SAAS,eACL,kEACA;QAGV;IACF;IACA,OAAO;QACLP,OAAOY,WAAWiB,MAAM,GAAG,OAAO;YAAEhB;YAAMN,MAAMA;YAA0BP;QAAM;QAChFY;IACF;AACF;AAEA,6EAA6E;AAE7E,mDAAmD,GACnD,OAAO,MAAMqE,oBAAoB,QAAO;AAOxC;;;;CAIC,GACD,OAAO,SAASC,oBAAoBlF,KAAc;IAChD,IAAI,CAACD,SAASC,QAAQ,OAAO;IAC7B,IAAIA,KAAK,CAAC,OAAO,KAAK,cAAcD,SAASC,KAAK,CAAC,WAAW,GAAG;QAC/D,MAAMuC,WAAWvC,KAAK,CAAC,WAAW;QAClC,IAAI,OAAOuC,QAAQ,CAAC,OAAO,KAAK,YAAY,OAAOA,QAAQ,CAAC,OAAO,KAAK,UAAU,OAAO;QACzF,OAAO;YACL4C,MAAM;YACN5C,UAAU;gBACR1B,MAAM0B,QAAQ,CAAC,OAAO;gBACtBhC,MAAMgC,QAAQ,CAAC,OAAO;gBACtBvC,OAAO,OAAOuC,QAAQ,CAAC,QAAQ,KAAK,WAAWA,QAAQ,CAAC,QAAQ,GAAG;YACrE;QACF;IACF;IACA,IAAIvC,KAAK,CAAC,OAAO,KAAK,cAAc,CAACD,SAASC,KAAK,CAAC,aAAa,GAAG,OAAO;IAC3E,MAAM0D,aAAa1D,KAAK,CAAC,aAAa;IACtC,IACE,OAAO0D,UAAU,CAAC,OAAO,KAAK,YAC9B,CAACzD,MAAMC,OAAO,CAACwD,UAAU,CAAC,aAAa,KACvC,CAACzD,MAAMC,OAAO,CAACwD,UAAU,CAAC,YAAY,KACtC,CAACzD,MAAMC,OAAO,CAACwD,UAAU,CAAC,aAAa,GACvC;QACA,OAAO;IACT;IACA,OAAO;QACLyB,MAAM;QACNrG,YAAY,OAAOkB,KAAK,CAAC,aAAa,KAAK,WAAWA,KAAK,CAAC,aAAa,GAAG;QAC5E0D,YAAYA;IACd;AACF;AAEA,6EAA6E;AAE7E,OAAO,MAAM0B,wBAAwB,4DAA2D;AAChG,OAAO,MAAMC,4BAA4B,uDAAsD;AAC/F,OAAO,MAAMC,qBAAqB,kCAAiC;AACnE,OAAO,MAAMC,4BACX,mIAAkI;AACpI,OAAO,MAAMC,4BACX,oGAAmG;AACrG,OAAO,MAAMC,+BAA+B,qDAAoD"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-logic-job.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n evaluateHostFunction,\n expressionSyntaxError,\n type FunctionComparator,\n type FunctionValueType,\n type HostFunction,\n functionReferencedNames,\n} from '@aglyn/aglyn/app-utils/functions'\nimport {\n functionGlobals,\n type HostVariable,\n type HostVariableType,\n VARIABLE_NAME_PATTERN,\n} from '@aglyn/aglyn/app-utils/variables'\nimport type { AiDoctrineViolation } from '../runtime/ai-doctrine-validators'\n\n/**\n * A `logic` job (AGL-3603): a site's Functions & Variables, by AI.\n *\n * - `function` proposes a function — parameters, locals, the numbered\n * if/then/otherwise operations and the value it returns — from a\n * description, or, naming a saved function, a changed or fixed copy of\n * it. Every expression is held to the evaluator's own grammar\n * (`expressionSyntaxError`), every name it reads to the function's own\n * names and the site's variables, and the whole definition is run once\n * with each parameter at its starting value before anyone sees it.\n * - `variable` proposes one site variable: a name, a type and a value in\n * that type's stored form.\n * - `explain` reads a saved function and answers in plain words what it\n * computes, and anything in it worth checking.\n *\n * Nothing is written: a proposal rides on the job's output, and the logic\n * editor is where a person saves it, or does not. The one exception is a\n * NEW function asked for by a build (AGL-3616), which the person confirmed\n * as a plan: that unit's job writes the checked function through the logic\n * plugin's `function` draft writer, a record nothing on the site uses until\n * a page binds it.\n */\n\nexport const AI_LOGIC_JOB_MODES = ['function', 'variable', 'explain'] as const\nexport type AiLogicJobMode = (typeof AI_LOGIC_JOB_MODES)[number]\n\nexport type AiLogicJobInputs =\n | { mode: 'function'; functionId: string | null }\n | { mode: 'variable' }\n | { mode: 'explain'; functionId: string }\n\nconst DOCUMENT_ID = /^[A-Za-z0-9_-]{1,128}$/\n\nexport const AI_LOGIC_PICK_FUNCTION_COPY = 'Pick the function to explain'\n\n/** A job's inputs read into its mode, or the sentence that says what is wrong with them. */\nexport function parseAiLogicJobInputs(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): AiLogicJobInputs | string {\n const mode = inputs?.['mode'] ?? 'function'\n if (!(AI_LOGIC_JOB_MODES as readonly unknown[]).includes(mode)) {\n return 'inputs.mode must be function, variable or explain'\n }\n if (mode === 'variable') return { mode }\n const functionId = inputs?.['functionId']\n const named = typeof functionId === 'string' && DOCUMENT_ID.test(functionId) ? functionId : null\n if (functionId != null && !named) return AI_LOGIC_PICK_FUNCTION_COPY\n if (mode === 'explain') return named ? { mode, functionId: named } : AI_LOGIC_PICK_FUNCTION_COPY\n return { mode: 'function', functionId: named }\n}\n\n// ── The bounds a proposal is held to ──────────────────────────────────────\n\nexport const AI_LOGIC_MAX_PARAMETERS = 12\nexport const AI_LOGIC_MAX_LOCALS = 20\nexport const AI_LOGIC_MAX_OPERATIONS = 24\nexport const AI_LOGIC_MAX_SETS = 12\nexport const AI_LOGIC_EXPRESSION_MAX_CHARS = 400\nexport const AI_LOGIC_LABEL_MAX_CHARS = 80\nexport const AI_LOGIC_VARIABLE_VALUE_MAX_CHARS = 2_000\n\nexport const AI_LOGIC_VALUE_TYPES: readonly FunctionValueType[] = ['number', 'text', 'boolean']\nexport const AI_LOGIC_COMPARATORS: readonly FunctionComparator[] = ['==', '!=', '<', '<=', '>', '>=']\n\n/** The variable types a proposal may use: every type the Variables editor offers. */\nexport const AI_LOGIC_VARIABLE_TYPES: readonly HostVariableType[] = [\n 'text',\n 'number',\n 'boolean',\n 'date',\n 'time',\n 'dictionary',\n 'collection',\n]\n\n/** A site variable as a proposal is checked against: its name, type and value. */\nexport type AiLogicSiteVariable = Pick<HostVariable, 'name' | 'type' | 'value'>\n\n/** What a proposal's names are checked against: the site's variables and functions. */\nexport interface AiLogicContext {\n variables: readonly AiLogicSiteVariable[]\n /** The site's function names, which a new function's name must not repeat. */\n functions: readonly string[]\n /** The saved function a change starts from; its own name is free to keep. */\n editing?: string | null\n}\n\nexport interface AiLogicCheckResult<T> {\n value: T | null\n violations: AiDoctrineViolation[]\n}\n\nconst violation = (code: string, message: string): AiDoctrineViolation => ({ rule: null, code, message })\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\nconst MARKUP = /<\\s*\\/?\\s*(script|iframe|style|object|embed)\\b|javascript:/i\n\n/** A starting value a parameter's type can hold, for the run every proposal is given. */\nfunction sampleFor(type: FunctionValueType, defaultValue: string | undefined): string {\n if (defaultValue) return defaultValue\n return type === 'number' ? '1' : type === 'boolean' ? 'true' : 'sample'\n}\n\n/**\n * A `submit_function` answer read into a definition the function editor\n * opens, or the violations a re-ask names. Only what the evaluator can run\n * is kept: an expression it cannot parse, a name it cannot find, a SET on a\n * site variable, or a run that fails on starting values is refused.\n */\nexport function checkAiLogicFunction(\n answer: Record<string, unknown>,\n context: AiLogicContext,\n): AiLogicCheckResult<HostFunction> {\n const violations: AiDoctrineViolation[] = []\n const name = str(answer['name'])\n if (!VARIABLE_NAME_PATTERN.test(name)) {\n violations.push(\n violation('logic-name', 'The function name must start with a letter or _, then letters, digits or _; at most 40 characters.'),\n )\n } else if (\n name.toLowerCase() !== context.editing?.toLowerCase() &&\n context.functions.some((taken) => taken.toLowerCase() === name.toLowerCase())\n ) {\n // Case-insensitive, as the Functions card and the logic plugin's writer compare names.\n violations.push(violation('logic-name-taken', `The site already has a function named ${name}. Choose another name.`))\n }\n\n const own = new Set<string>()\n const ownName = (raw: unknown, what: string): string => {\n const value = str(raw)\n if (!VARIABLE_NAME_PATTERN.test(value)) {\n violations.push(violation('logic-name', `A ${what} name must start with a letter or _, then letters, digits or _.`))\n } else if (own.has(value)) {\n violations.push(violation('logic-name-repeated', `The name ${value} is used twice. Each parameter and local needs its own name.`))\n }\n own.add(value)\n return value\n }\n const typeOf = (raw: unknown): FunctionValueType => {\n if ((AI_LOGIC_VALUE_TYPES as readonly unknown[]).includes(raw)) return raw as FunctionValueType\n violations.push(violation('logic-type', 'A type is number, text or boolean.'))\n return 'number'\n }\n\n const rawParameters = Array.isArray(answer['parameters']) ? answer['parameters'].filter(isRecord) : []\n const rawLocals = Array.isArray(answer['locals']) ? answer['locals'].filter(isRecord) : []\n const rawOperations = Array.isArray(answer['operations']) ? answer['operations'].filter(isRecord) : []\n if (rawParameters.length > AI_LOGIC_MAX_PARAMETERS) {\n violations.push(violation('logic-size', `At most ${AI_LOGIC_MAX_PARAMETERS} parameters.`))\n }\n if (rawLocals.length > AI_LOGIC_MAX_LOCALS) {\n violations.push(violation('logic-size', `At most ${AI_LOGIC_MAX_LOCALS} locals.`))\n }\n if (!rawOperations.length || rawOperations.length > AI_LOGIC_MAX_OPERATIONS) {\n violations.push(violation('logic-size', `Between 1 and ${AI_LOGIC_MAX_OPERATIONS} operations.`))\n }\n\n const parameters = rawParameters.slice(0, AI_LOGIC_MAX_PARAMETERS).map((raw) => {\n const parameter: HostFunction['parameters'][number] = {\n name: ownName(raw['name'], 'parameter'),\n type: typeOf(raw['type']),\n required: raw['required'] === true,\n }\n const label = str(raw['label']).slice(0, AI_LOGIC_LABEL_MAX_CHARS)\n if (label) parameter.label = label\n const start = str(raw['defaultValue']).slice(0, AI_LOGIC_LABEL_MAX_CHARS)\n if (start) parameter.defaultValue = start\n return parameter\n })\n const variables = rawLocals.slice(0, AI_LOGIC_MAX_LOCALS).map((raw) => ({\n name: ownName(raw['name'], 'local'),\n type: typeOf(raw['type']),\n }))\n\n const site = new Set(context.variables.map((variable) => variable.name))\n const expression = (raw: unknown, where: string): string => {\n const text = typeof raw === 'string' ? raw.trim() : ''\n if (!text) {\n violations.push(violation('logic-expression', `${where} is empty. Write a value, a name or an expression.`))\n return text\n }\n if (text.length > AI_LOGIC_EXPRESSION_MAX_CHARS || MARKUP.test(text)) {\n violations.push(violation('logic-expression', `${where} is not an expression the function can run.`))\n return text\n }\n const problem = expressionSyntaxError(text)\n if (problem) violations.push(violation('logic-syntax', `${where} cannot be read: ${problem}.`))\n return text\n }\n const sets = (raw: unknown, where: string) => {\n const list = Array.isArray(raw) ? raw.filter(isRecord) : []\n if (list.length > AI_LOGIC_MAX_SETS) {\n violations.push(violation('logic-size', `${where} has more than ${AI_LOGIC_MAX_SETS} assignments.`))\n }\n return list.slice(0, AI_LOGIC_MAX_SETS).map((set, index) => {\n const target = str(set['set'])\n if (!own.has(target)) {\n violations.push(\n violation(\n 'logic-set-target',\n site.has(target)\n ? `${where}, assignment ${index + 1}, sets the site variable ${target}. A function reads site variables and sets only its own parameters and locals.`\n : `${where}, assignment ${index + 1}, sets ${target || 'nothing'}, which is not one of the function's parameters or locals.`,\n ),\n )\n }\n return { set: target, expression: expression(set['expression'], `${where}, assignment ${index + 1}`) }\n })\n }\n const operations = rawOperations.slice(0, AI_LOGIC_MAX_OPERATIONS).map((raw, index) => {\n const where = `Operation ${index + 1}`\n const condition = isRecord(raw['if']) ? raw['if'] : {}\n const comparator = (AI_LOGIC_COMPARATORS as readonly unknown[]).includes(condition['comparator'])\n ? (condition['comparator'] as FunctionComparator)\n : null\n if (!comparator) violations.push(violation('logic-comparator', `${where}'s condition compares with one of == != < <= > >=.`))\n return {\n if: {\n left: expression(condition['left'], `${where}'s condition (left)`),\n comparator: comparator ?? '==',\n right: expression(condition['right'], `${where}'s condition (right)`),\n },\n then: sets(raw['then'], `${where} (then)`),\n otherwise: sets(raw['otherwise'], `${where} (otherwise)`),\n }\n })\n\n const returnValue = str(answer['returnValue'])\n if (!own.has(returnValue)) {\n violations.push(violation('logic-return', 'returnValue names one of the function’s own parameters or locals.'))\n }\n const definition: HostFunction = { name, parameters, variables, operations, returnValue }\n\n // Every name an expression reads that is not the function's own must be a site variable.\n const unknown = functionReferencedNames(definition).filter((read) => !site.has(read))\n if (unknown.length) {\n violations.push(\n violation(\n 'logic-unknown-name',\n `The function reads ${unknown.join(', ')}, which is neither one of its own names nor a site variable. Use only the names listed.`,\n ),\n )\n }\n\n if (!violations.length) {\n // One run with every parameter at a starting value: what the editor's\n // own test run would show first, so a definition that cannot run on its\n // first try is refused here rather than handed to a person.\n const variablesByName = Object.fromEntries(\n context.variables.map((variable) => [variable.name, variable as HostVariable]),\n )\n const args = Object.fromEntries(\n parameters.map((parameter) => [parameter.name, sampleFor(parameter.type, parameter.defaultValue)]),\n )\n const run = evaluateHostFunction(definition, args, { globals: functionGlobals(definition, variablesByName) })\n if (run.ok === false) violations.push(violation('logic-run', `The function fails when it runs: ${run.error}.`))\n }\n return { value: violations.length ? null : definition, violations }\n}\n\n/** A `submit_variable` answer read into the variable the Variables editor opens. */\nexport function checkAiLogicVariable(\n answer: Record<string, unknown>,\n context: Pick<AiLogicContext, 'variables'>,\n): AiLogicCheckResult<AiLogicSiteVariable> {\n const violations: AiDoctrineViolation[] = []\n const name = str(answer['name'])\n if (!VARIABLE_NAME_PATTERN.test(name)) {\n violations.push(violation('logic-name', 'The variable name must start with a letter or _, then letters, digits or _; at most 40 characters.'))\n } else if (context.variables.some((variable) => variable.name.toLowerCase() === name.toLowerCase())) {\n // Case-insensitive, as the Variables card and the logic plugin's writer compare names.\n violations.push(violation('logic-name-taken', `The site already has a variable named ${name}. Choose another name.`))\n }\n const type = answer['type']\n if (!(AI_LOGIC_VARIABLE_TYPES as readonly unknown[]).includes(type)) {\n violations.push(violation('logic-type', `A variable's type is one of ${AI_LOGIC_VARIABLE_TYPES.join(', ')}.`))\n }\n const value = typeof answer['value'] === 'string' ? answer['value'] : ''\n if (value.length > AI_LOGIC_VARIABLE_VALUE_MAX_CHARS || MARKUP.test(value)) {\n violations.push(violation('logic-value', 'The value is too long, or holds markup.'))\n } else if (type === 'number' && !Number.isFinite(Number(value))) {\n violations.push(violation('logic-value', 'A number variable’s value is a number, such as 49 or 0.5.'))\n } else if (type === 'boolean' && value !== 'true' && value !== 'false') {\n violations.push(violation('logic-value', 'A boolean variable’s value is true or false.'))\n } else if (type === 'date' && Number.isNaN(new Date(value).getTime())) {\n violations.push(violation('logic-value', 'A date variable’s value is a date such as 2026-10-01.'))\n } else if (type === 'dictionary' || type === 'collection') {\n let parsed: unknown\n try {\n parsed = JSON.parse(value)\n } catch {\n parsed = null\n }\n const fits = type === 'collection' ? Array.isArray(parsed) : isRecord(parsed)\n if (!fits) {\n violations.push(\n violation(\n 'logic-value',\n type === 'collection'\n ? 'A collection’s value is a JSON list, such as [\"red\",\"green\"].'\n : 'A dictionary’s value is a JSON object of names to values, such as {\"starter\":19,\"pro\":49}.',\n ),\n )\n }\n }\n return {\n value: violations.length ? null : { name, type: type as HostVariableType, value },\n violations,\n }\n}\n\n// ── What a job hands the logic editor ─────────────────────────────────────\n\n/** The output resource a logic proposal rides on. */\nexport const AI_LOGIC_RESOURCE = 'logic'\n\n/** A logic output's proposal: a function to open in the editor, or a variable. */\nexport type AiLogicProposal =\n | { kind: 'function'; functionId: string | null; definition: HostFunction }\n | { kind: 'variable'; variable: AiLogicSiteVariable }\n\n/**\n * A proposal read back from a job's output, in the browser, before the\n * editor is handed it; `null` for anything that is not one. The server held\n * the definition to the grammar; this only refuses a shape that is not one.\n */\nexport function readAiLogicProposal(value: unknown): AiLogicProposal | null {\n if (!isRecord(value)) return null\n if (value['kind'] === 'variable' && isRecord(value['variable'])) {\n const variable = value['variable']\n if (typeof variable['name'] !== 'string' || typeof variable['type'] !== 'string') return null\n return {\n kind: 'variable',\n variable: {\n name: variable['name'],\n type: variable['type'] as HostVariableType,\n value: typeof variable['value'] === 'string' ? variable['value'] : '',\n },\n }\n }\n if (value['kind'] !== 'function' || !isRecord(value['definition'])) return null\n const definition = value['definition']\n if (\n typeof definition['name'] !== 'string' ||\n !Array.isArray(definition['parameters']) ||\n !Array.isArray(definition['variables']) ||\n !Array.isArray(definition['operations'])\n ) {\n return null\n }\n return {\n kind: 'function',\n functionId: typeof value['functionId'] === 'string' ? value['functionId'] : null,\n definition: definition as unknown as HostFunction,\n }\n}\n\n// ── What a job tells a person ─────────────────────────────────────────────\n\nexport const AI_LOGIC_NO_SITE_COPY = 'This logic job names a site this workspace does not have.'\nexport const AI_LOGIC_UNAVAILABLE_COPY = 'Turn on Logic for this site before starting the job.'\nexport const AI_LOGIC_GONE_COPY = 'That function no longer exists.'\nexport const AI_LOGIC_NO_FUNCTION_COPY =\n 'The AI could not write a function the site can run from this description. Try describing what goes in, and what should come out.'\nexport const AI_LOGIC_NO_VARIABLE_COPY =\n 'The AI could not write a variable from this description. Try naming what it holds, and its value.'\nexport const AI_LOGIC_NO_EXPLANATION_COPY = 'The AI could not explain this function. Try again.'\n\n// ── A function a build writes (AGL-3616) ──────────────────────────────────\n\n/**\n * The resource the logic plugin's `function` draft writer is registered\n * under. A `logic` job run as a unit of a build writes its function through\n * it; one a person started proposes it and writes nothing.\n */\nexport const AI_LOGIC_FUNCTION_RESOURCE = 'function'\n\n/** Whether a job is a build's unit rather than one a person started: a derived job carries its origin. */\nexport function aiLogicJobIsBuildUnit(job: { inputs?: Readonly<Record<string, unknown>> | null }): boolean {\n const origin = job.inputs?.['originJobId']\n return typeof origin === 'string' && origin.length > 0\n}\n\nexport const AI_LOGIC_FUNCTION_UNAVAILABLE_COPY = 'Functions are not available on this site.'\nexport const AI_LOGIC_FUNCTION_SAVE_FAILURE_COPY = 'The function was written but could not be saved. Try the job again.'\nexport const AI_LOGIC_FUNCTION_WRITTEN_NOTE =\n 'A new site function, checked against the expression grammar and run once. Nothing on the site uses it until a page binds it.'\n"],"names":["evaluateHostFunction","expressionSyntaxError","functionReferencedNames","functionGlobals","VARIABLE_NAME_PATTERN","AI_LOGIC_JOB_MODES","DOCUMENT_ID","AI_LOGIC_PICK_FUNCTION_COPY","parseAiLogicJobInputs","inputs","mode","includes","functionId","named","test","AI_LOGIC_MAX_PARAMETERS","AI_LOGIC_MAX_LOCALS","AI_LOGIC_MAX_OPERATIONS","AI_LOGIC_MAX_SETS","AI_LOGIC_EXPRESSION_MAX_CHARS","AI_LOGIC_LABEL_MAX_CHARS","AI_LOGIC_VARIABLE_VALUE_MAX_CHARS","AI_LOGIC_VALUE_TYPES","AI_LOGIC_COMPARATORS","AI_LOGIC_VARIABLE_TYPES","violation","code","message","rule","isRecord","value","Array","isArray","str","trim","MARKUP","sampleFor","type","defaultValue","checkAiLogicFunction","answer","context","violations","name","push","toLowerCase","editing","functions","some","taken","own","Set","ownName","raw","what","has","add","typeOf","rawParameters","filter","rawLocals","rawOperations","length","parameters","slice","map","parameter","required","label","start","variables","site","variable","expression","where","text","problem","sets","list","set","index","target","operations","condition","comparator","if","left","right","then","otherwise","returnValue","definition","unknown","read","join","variablesByName","Object","fromEntries","args","run","globals","ok","error","checkAiLogicVariable","Number","isFinite","isNaN","Date","getTime","parsed","JSON","parse","fits","AI_LOGIC_RESOURCE","readAiLogicProposal","kind","AI_LOGIC_NO_SITE_COPY","AI_LOGIC_UNAVAILABLE_COPY","AI_LOGIC_GONE_COPY","AI_LOGIC_NO_FUNCTION_COPY","AI_LOGIC_NO_VARIABLE_COPY","AI_LOGIC_NO_EXPLANATION_COPY","AI_LOGIC_FUNCTION_RESOURCE","aiLogicJobIsBuildUnit","job","origin","AI_LOGIC_FUNCTION_UNAVAILABLE_COPY","AI_LOGIC_FUNCTION_SAVE_FAILURE_COPY","AI_LOGIC_FUNCTION_WRITTEN_NOTE"],"mappings":"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"}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
17
|
import type { AiUsage } from '../providers/contract';
|
|
18
|
-
import type { AiImageMode } from '../providers/image-contract';
|
|
18
|
+
import type { AiImageMode, AiImageSize } from '../providers/image-contract';
|
|
19
19
|
/**
|
|
20
20
|
* What one picture in Media is expected to draw (AGL-3602), in credits: the
|
|
21
21
|
* estimate the dialog shows before anything is spent, and the figure the
|
|
@@ -23,7 +23,8 @@ import type { AiImageMode } from '../providers/image-contract';
|
|
|
23
23
|
*
|
|
24
24
|
* Each is an ESTIMATE at nominal usage, priced at the billed rates:
|
|
25
25
|
*
|
|
26
|
-
* - a photo is one picture at its per-picture rate
|
|
26
|
+
* - a photo is one picture at its per-picture rate for the size the plan
|
|
27
|
+
* makes (512 px on Free, 1K on a paid plan), plus a prompt and the
|
|
27
28
|
* thinking Google's model does before it draws;
|
|
28
29
|
* - an illustration is the text provider's answer — the instructions, the
|
|
29
30
|
* description, and a few thousand tokens of SVG.
|
|
@@ -48,5 +49,8 @@ export declare const AI_MEDIA_SVG_NOMINAL_USAGE: AiUsage;
|
|
|
48
49
|
* text provider serves illustrations on.
|
|
49
50
|
*/
|
|
50
51
|
export declare const AI_MEDIA_SVG_ESTIMATE_MODEL: string;
|
|
51
|
-
/**
|
|
52
|
-
|
|
52
|
+
/**
|
|
53
|
+
* The credits one picture is expected to draw in `mode`, on `model`, at
|
|
54
|
+
* `size` for a photo (1K when absent; an illustration has no size).
|
|
55
|
+
*/
|
|
56
|
+
export declare function aiMediaCreditsPerPicture(mode: AiImageMode, model?: string, size?: AiImageSize): number;
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
|
-
*/
|
|
16
|
+
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
17
|
+
var _ref;
|
|
17
18
|
var _AI_MODEL_CATALOG_find;
|
|
18
19
|
import { AI_IMAGE_DEFAULT_MODEL, AI_MODEL_CATALOG, estimateAiBilledUsd } from "../providers/catalog.js";
|
|
19
20
|
import { assistCreditsFromUsd } from "../usage/assist-credits.js";
|
|
@@ -24,7 +25,8 @@ import { assistCreditsFromUsd } from "../usage/assist-credits.js";
|
|
|
24
25
|
*
|
|
25
26
|
* Each is an ESTIMATE at nominal usage, priced at the billed rates:
|
|
26
27
|
*
|
|
27
|
-
* - a photo is one picture at its per-picture rate
|
|
28
|
+
* - a photo is one picture at its per-picture rate for the size the plan
|
|
29
|
+
* makes (512 px on Free, 1K on a paid plan), plus a prompt and the
|
|
28
30
|
* thinking Google's model does before it draws;
|
|
29
31
|
* - an illustration is the text provider's answer — the instructions, the
|
|
30
32
|
* description, and a few thousand tokens of SVG.
|
|
@@ -54,8 +56,13 @@ import { assistCreditsFromUsd } from "../usage/assist-credits.js";
|
|
|
54
56
|
* not known: the catalog's first fast-tier row, which is what the default
|
|
55
57
|
* text provider serves illustrations on.
|
|
56
58
|
*/ export const AI_MEDIA_SVG_ESTIMATE_MODEL = (_ref = (_AI_MODEL_CATALOG_find = AI_MODEL_CATALOG.find((entry)=>entry.tier === 'fast')) == null ? void 0 : _AI_MODEL_CATALOG_find.id) != null ? _ref : AI_MODEL_CATALOG[0].id;
|
|
57
|
-
/**
|
|
58
|
-
|
|
59
|
+
/**
|
|
60
|
+
* The credits one picture is expected to draw in `mode`, on `model`, at
|
|
61
|
+
* `size` for a photo (1K when absent; an illustration has no size).
|
|
62
|
+
*/ export function aiMediaCreditsPerPicture(mode, model, size) {
|
|
63
|
+
return mode === 'photo' ? assistCreditsFromUsd(estimateAiBilledUsd(_extends({}, AI_MEDIA_PHOTO_NOMINAL_USAGE, size ? {
|
|
64
|
+
imageSize: size
|
|
65
|
+
} : {}), model != null ? model : AI_IMAGE_DEFAULT_MODEL)) : assistCreditsFromUsd(estimateAiBilledUsd(AI_MEDIA_SVG_NOMINAL_USAGE, model != null ? model : AI_MEDIA_SVG_ESTIMATE_MODEL));
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
//# sourceMappingURL=ai-media-credits.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-media-credits.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AiUsage } from '../providers/contract'\nimport {\n AI_IMAGE_DEFAULT_MODEL,\n AI_MODEL_CATALOG,\n estimateAiBilledUsd,\n} from '../providers/catalog'\nimport type { AiImageMode } from '../providers/image-contract'\nimport { assistCreditsFromUsd } from '../usage/assist-credits'\n\n/**\n * What one picture in Media is expected to draw (AGL-3602), in credits: the\n * estimate the dialog shows before anything is spent, and the figure the\n * door holds a walled band to. Browser-safe, so both read the same number.\n *\n * Each is an ESTIMATE at nominal usage, priced at the billed rates:\n *\n * - a photo is one picture at its per-picture rate, plus a prompt and the\n * thinking Google's model does before it draws;\n * - an illustration is the text provider's answer — the instructions, the\n * description, and a few thousand tokens of SVG.\n *\n * What is actually charged is what was spent, measured: a picture held back\n * or not stored is never charged, and an illustration that needed its second\n * attempt costs about twice its estimate.\n */\n\n/** A photo's nominal tokens beside its picture. */\nexport const AI_MEDIA_PHOTO_NOMINAL_USAGE: AiUsage & { images: number } = {\n inputTokens: 600,\n outputTokens: 1_500,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n images: 1,\n}\n\n/**\n * An illustration's nominal tokens: about 2,100 of instructions — under the\n * fast tier's caching minimum, so read at full rate every time — the tool and\n * the description, and a few thousand of SVG.\n */\nexport const AI_MEDIA_SVG_NOMINAL_USAGE: AiUsage = {\n inputTokens: 2_500,\n outputTokens: 3_000,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n}\n\n/**\n * The model an illustration is estimated at where the server's choice is\n * not known: the catalog's first fast-tier row, which is what the default\n * text provider serves illustrations on.\n */\nexport const AI_MEDIA_SVG_ESTIMATE_MODEL =\n AI_MODEL_CATALOG.find((entry) => entry.tier === 'fast')?.id ?? AI_MODEL_CATALOG[0].id\n\n
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-media-credits.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AiUsage } from '../providers/contract'\nimport {\n AI_IMAGE_DEFAULT_MODEL,\n AI_MODEL_CATALOG,\n estimateAiBilledUsd,\n} from '../providers/catalog'\nimport type { AiImageMode, AiImageSize } from '../providers/image-contract'\nimport { assistCreditsFromUsd } from '../usage/assist-credits'\n\n/**\n * What one picture in Media is expected to draw (AGL-3602), in credits: the\n * estimate the dialog shows before anything is spent, and the figure the\n * door holds a walled band to. Browser-safe, so both read the same number.\n *\n * Each is an ESTIMATE at nominal usage, priced at the billed rates:\n *\n * - a photo is one picture at its per-picture rate for the size the plan\n * makes (512 px on Free, 1K on a paid plan), plus a prompt and the\n * thinking Google's model does before it draws;\n * - an illustration is the text provider's answer — the instructions, the\n * description, and a few thousand tokens of SVG.\n *\n * What is actually charged is what was spent, measured: a picture held back\n * or not stored is never charged, and an illustration that needed its second\n * attempt costs about twice its estimate.\n */\n\n/** A photo's nominal tokens beside its picture. */\nexport const AI_MEDIA_PHOTO_NOMINAL_USAGE: AiUsage & { images: number } = {\n inputTokens: 600,\n outputTokens: 1_500,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n images: 1,\n}\n\n/**\n * An illustration's nominal tokens: about 2,100 of instructions — under the\n * fast tier's caching minimum, so read at full rate every time — the tool and\n * the description, and a few thousand of SVG.\n */\nexport const AI_MEDIA_SVG_NOMINAL_USAGE: AiUsage = {\n inputTokens: 2_500,\n outputTokens: 3_000,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n}\n\n/**\n * The model an illustration is estimated at where the server's choice is\n * not known: the catalog's first fast-tier row, which is what the default\n * text provider serves illustrations on.\n */\nexport const AI_MEDIA_SVG_ESTIMATE_MODEL =\n AI_MODEL_CATALOG.find((entry) => entry.tier === 'fast')?.id ?? AI_MODEL_CATALOG[0].id\n\n/**\n * The credits one picture is expected to draw in `mode`, on `model`, at\n * `size` for a photo (1K when absent; an illustration has no size).\n */\nexport function aiMediaCreditsPerPicture(\n mode: AiImageMode,\n model?: string,\n size?: AiImageSize,\n): number {\n return mode === 'photo'\n ? assistCreditsFromUsd(\n estimateAiBilledUsd(\n { ...AI_MEDIA_PHOTO_NOMINAL_USAGE, ...(size ? { imageSize: size } : {}) },\n model ?? AI_IMAGE_DEFAULT_MODEL,\n ),\n )\n : assistCreditsFromUsd(estimateAiBilledUsd(AI_MEDIA_SVG_NOMINAL_USAGE, model ?? AI_MEDIA_SVG_ESTIMATE_MODEL))\n}\n"],"names":["AI_MODEL_CATALOG","AI_IMAGE_DEFAULT_MODEL","estimateAiBilledUsd","assistCreditsFromUsd","AI_MEDIA_PHOTO_NOMINAL_USAGE","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","images","AI_MEDIA_SVG_NOMINAL_USAGE","AI_MEDIA_SVG_ESTIMATE_MODEL","find","entry","tier","id","aiMediaCreditsPerPicture","mode","model","size","imageSize"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;;IAwDCA;AArDF,SACEC,sBAAsB,EACtBD,gBAAgB,EAChBE,mBAAmB,QACd,0BAAsB;AAE7B,SAASC,oBAAoB,QAAQ,6BAAyB;AAE9D;;;;;;;;;;;;;;;;CAgBC,GAED,iDAAiD,GACjD,OAAO,MAAMC,+BAA6D;IACxEC,aAAa;IACbC,cAAc;IACdC,iBAAiB;IACjBC,kBAAkB;IAClBC,QAAQ;AACV,EAAC;AAED;;;;CAIC,GACD,OAAO,MAAMC,6BAAsC;IACjDL,aAAa;IACbC,cAAc;IACdC,iBAAiB;IACjBC,kBAAkB;AACpB,EAAC;AAED;;;;CAIC,GACD,OAAO,MAAMG,uCACXX,yBAAAA,iBAAiBY,IAAI,CAAC,CAACC,QAAUA,MAAMC,IAAI,KAAK,4BAAhDd,uBAAyDe,EAAE,mBAAIf,gBAAgB,CAAC,EAAE,CAACe,EAAE,CAAA;AAEvF;;;CAGC,GACD,OAAO,SAASC,yBACdC,IAAiB,EACjBC,KAAc,EACdC,IAAkB;IAElB,OAAOF,SAAS,UACZd,qBACED,oBACE,aAAKE,8BAAkCe,OAAO;QAAEC,WAAWD;IAAK,IAAI,CAAC,IACrED,gBAAAA,QAASjB,2BAGbE,qBAAqBD,oBAAoBQ,4BAA4BQ,gBAAAA,QAASP;AACpF"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import { type AiImageAspectRatio, type AiImageMode, type AiRasterStyle, type AiSvgStyle } from '../providers/image-contract';
|
|
18
|
+
/**
|
|
19
|
+
* The kinds of picture Media's Create with AI offers (AGL-3602), as the Kind
|
|
20
|
+
* menu lists them: four drawn as SVG by the text provider, and seventeen made
|
|
21
|
+
* by the image provider — a photo whose description decides its style, six
|
|
22
|
+
* photographic genres, six art styles and four design assets.
|
|
23
|
+
*
|
|
24
|
+
* Browser-safe: the dialog reads this to draw the menu, pick a shape and
|
|
25
|
+
* decide which controls to show, with no request until Create. The style
|
|
26
|
+
* wording a raster kind adds to the description is the server's
|
|
27
|
+
* (`server/ai-media-raster-prompt.ts`), so the browser bundle does not carry
|
|
28
|
+
* it.
|
|
29
|
+
*/
|
|
30
|
+
/** The Kind menu's sections, in order. */
|
|
31
|
+
export declare const AI_MEDIA_KIND_GROUPS: readonly ["vector", "photo", "art", "design"];
|
|
32
|
+
export type AiMediaKindGroup = (typeof AI_MEDIA_KIND_GROUPS)[number];
|
|
33
|
+
/** Each section's heading in the Kind menu. */
|
|
34
|
+
export declare const AI_MEDIA_KIND_GROUP_LABELS: Readonly<Record<AiMediaKindGroup, string>>;
|
|
35
|
+
/** A kind's id: an SVG style, or an image-provider style. */
|
|
36
|
+
export type AiMediaKindId = AiSvgStyle | AiRasterStyle;
|
|
37
|
+
/**
|
|
38
|
+
* Which colors a kind takes. `palette` offers the site theme's colors or the
|
|
39
|
+
* person's own, sent with the request; `describe` sends none and suggests
|
|
40
|
+
* naming colors in the description; `none` is a photograph, whose colors
|
|
41
|
+
* are the scene's.
|
|
42
|
+
*/
|
|
43
|
+
export type AiMediaKindColors = 'palette' | 'describe' | 'none';
|
|
44
|
+
export interface AiMediaKind {
|
|
45
|
+
id: AiMediaKindId;
|
|
46
|
+
group: AiMediaKindGroup;
|
|
47
|
+
/** The provider mode the door runs it in. */
|
|
48
|
+
mode: AiImageMode;
|
|
49
|
+
label: string;
|
|
50
|
+
/** The line under the Kind menu once it is chosen. */
|
|
51
|
+
hint: string;
|
|
52
|
+
/** The shape the dialog switches to when the kind is chosen. */
|
|
53
|
+
aspectRatio: AiImageAspectRatio;
|
|
54
|
+
colors: AiMediaKindColors;
|
|
55
|
+
/** The description field's example. */
|
|
56
|
+
placeholder: string;
|
|
57
|
+
}
|
|
58
|
+
export declare const AI_MEDIA_KINDS: readonly AiMediaKind[];
|
|
59
|
+
/** A kind by id; `undefined` for an id no kind has. */
|
|
60
|
+
export declare function aiMediaKind(id: string): AiMediaKind | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* The kinds this deployment offers: every kind where the image provider is
|
|
63
|
+
* on, and only the SVG kinds where it is not, so nothing the door would
|
|
64
|
+
* refuse is ever listed.
|
|
65
|
+
*/
|
|
66
|
+
export declare function aiMediaKindsOffered(photos: boolean): readonly AiMediaKind[];
|
|
67
|
+
/** Every SVG and raster style has exactly one kind; the spec holds the list to it. */
|
|
68
|
+
export declare const AI_MEDIA_KIND_IDS: readonly AiMediaKindId[];
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { AI_RASTER_STYLES, AI_SVG_STYLES } from "../providers/image-contract.js";
|
|
17
|
+
/**
|
|
18
|
+
* The kinds of picture Media's Create with AI offers (AGL-3602), as the Kind
|
|
19
|
+
* menu lists them: four drawn as SVG by the text provider, and seventeen made
|
|
20
|
+
* by the image provider — a photo whose description decides its style, six
|
|
21
|
+
* photographic genres, six art styles and four design assets.
|
|
22
|
+
*
|
|
23
|
+
* Browser-safe: the dialog reads this to draw the menu, pick a shape and
|
|
24
|
+
* decide which controls to show, with no request until Create. The style
|
|
25
|
+
* wording a raster kind adds to the description is the server's
|
|
26
|
+
* (`server/ai-media-raster-prompt.ts`), so the browser bundle does not carry
|
|
27
|
+
* it.
|
|
28
|
+
*/ /** The Kind menu's sections, in order. */ export const AI_MEDIA_KIND_GROUPS = [
|
|
29
|
+
'vector',
|
|
30
|
+
'photo',
|
|
31
|
+
'art',
|
|
32
|
+
'design'
|
|
33
|
+
];
|
|
34
|
+
/** Each section's heading in the Kind menu. */ export const AI_MEDIA_KIND_GROUP_LABELS = {
|
|
35
|
+
vector: 'Vector',
|
|
36
|
+
photo: 'Photo',
|
|
37
|
+
art: 'Art',
|
|
38
|
+
design: 'Design'
|
|
39
|
+
};
|
|
40
|
+
const vector = (id, label, hint, placeholder)=>({
|
|
41
|
+
id,
|
|
42
|
+
group: 'vector',
|
|
43
|
+
mode: 'illustration',
|
|
44
|
+
label,
|
|
45
|
+
hint,
|
|
46
|
+
aspectRatio: '1:1',
|
|
47
|
+
colors: 'palette',
|
|
48
|
+
placeholder
|
|
49
|
+
});
|
|
50
|
+
const raster = (id, group, label, hint, aspectRatio, placeholder)=>({
|
|
51
|
+
id,
|
|
52
|
+
group,
|
|
53
|
+
mode: 'photo',
|
|
54
|
+
label,
|
|
55
|
+
hint,
|
|
56
|
+
aspectRatio,
|
|
57
|
+
colors: group === 'photo' ? 'none' : 'describe',
|
|
58
|
+
placeholder
|
|
59
|
+
});
|
|
60
|
+
export const AI_MEDIA_KINDS = [
|
|
61
|
+
vector('illustration', 'Illustration', 'A spot illustration or small scene, drawn as an SVG and sharp at any size.', 'A friendly delivery van with a parcel, simple and flat'),
|
|
62
|
+
vector('icon', 'Icon', 'A single icon in one color, or two tones, readable at small sizes.', 'A shopping bag with a heart'),
|
|
63
|
+
vector('pattern', 'Pattern', 'A seamless background that tiles.', 'Scattered coffee beans and leaves'),
|
|
64
|
+
vector('logo', 'Logo mark', 'A simple symbol, without lettering. Not a real brand’s logo.', 'A mountain peak inside a circle'),
|
|
65
|
+
raster('photo', 'photo', 'Photo', 'A realistic photo; your description decides the style.', '1:1', 'A sunlit bakery counter with fresh sourdough loaves, warm morning light'),
|
|
66
|
+
raster('natural', 'photo', 'Natural photo', 'Candid and true to life, in soft available light.', '4:3', 'A florist arranging tulips in a small shop'),
|
|
67
|
+
raster('product', 'photo', 'Studio product shot', 'The product alone on a clean backdrop, in soft studio light.', '1:1', 'A matte ceramic coffee mug in sage green'),
|
|
68
|
+
raster('lifestyle', 'photo', 'Lifestyle with people', 'People using or enjoying the subject in a real setting. The people are made up, not anyone real.', '4:3', 'Friends sharing pastries at an outdoor café table'),
|
|
69
|
+
raster('architecture', 'photo', 'Architecture & interiors', 'Buildings and rooms with straight lines and natural daylight.', '4:3', 'A bright Scandinavian living room with oak floors'),
|
|
70
|
+
raster('food', 'photo', 'Food', 'Styled dishes in soft window light.', '3:4', 'A bowl of ramen with a soft-boiled egg and scallions'),
|
|
71
|
+
raster('aerial', 'photo', 'Aerial & landscape', 'A sweeping view from above or across the land.', '16:9', 'A winding coastal road at sunset'),
|
|
72
|
+
raster('render-3d', 'art', '3D render', 'Smooth, stylized 3D shapes in soft studio light.', '1:1', 'A stack of colorful gift boxes'),
|
|
73
|
+
raster('watercolor', 'art', 'Watercolor', 'Soft washes of color on textured paper.', '4:3', 'A harbor town with fishing boats'),
|
|
74
|
+
raster('oil-painting', 'art', 'Oil painting', 'Rich color and visible brushstrokes on canvas.', '4:3', 'A vase of sunflowers on a wooden table'),
|
|
75
|
+
raster('flat-art', 'art', 'Flat vector art', 'Clean flat shapes and solid colors, made as an image rather than an SVG.', '4:3', 'A team working together around a laptop'),
|
|
76
|
+
raster('line-drawing', 'art', 'Line drawing', 'Clean ink lines on a plain background.', '1:1', 'A bicycle leaning against a lamppost'),
|
|
77
|
+
raster('cartoon', 'art', 'Cartoon or anime', 'Bold outlines and bright colors, with original characters only.', '1:1', 'A cheerful robot watering plants'),
|
|
78
|
+
raster('background', 'design', 'Background or texture', 'Low contrast with no focal point, so text can sit on top.', '16:9', 'Soft gradient waves in calm blues'),
|
|
79
|
+
raster('banner', 'design', 'Banner or hero image', 'Wide, with calm space on one side for a headline. No lettering.', '16:9', 'A yoga mat and plants by a sunny window'),
|
|
80
|
+
raster('social', 'design', 'Social post graphic', 'A bold, centered picture for a social post.', '1:1', 'A summer sale feel with citrus fruit and bright colors'),
|
|
81
|
+
raster('mockup', 'design', 'Mockup', 'Your design shown on a device screen or on packaging.', '4:3', 'A phone on a desk showing a bakery’s online menu')
|
|
82
|
+
];
|
|
83
|
+
/** A kind by id; `undefined` for an id no kind has. */ export function aiMediaKind(id) {
|
|
84
|
+
return AI_MEDIA_KINDS.find((kind)=>kind.id === id);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The kinds this deployment offers: every kind where the image provider is
|
|
88
|
+
* on, and only the SVG kinds where it is not, so nothing the door would
|
|
89
|
+
* refuse is ever listed.
|
|
90
|
+
*/ export function aiMediaKindsOffered(photos) {
|
|
91
|
+
return photos ? AI_MEDIA_KINDS : AI_MEDIA_KINDS.filter((kind)=>kind.mode === 'illustration');
|
|
92
|
+
}
|
|
93
|
+
/** Every SVG and raster style has exactly one kind; the spec holds the list to it. */ export const AI_MEDIA_KIND_IDS = [
|
|
94
|
+
...AI_SVG_STYLES,
|
|
95
|
+
...AI_RASTER_STYLES
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
//# sourceMappingURL=ai-media-kinds.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-media-kinds.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AI_RASTER_STYLES,\n AI_SVG_STYLES,\n type AiImageAspectRatio,\n type AiImageMode,\n type AiRasterStyle,\n type AiSvgStyle,\n} from '../providers/image-contract'\n\n/**\n * The kinds of picture Media's Create with AI offers (AGL-3602), as the Kind\n * menu lists them: four drawn as SVG by the text provider, and seventeen made\n * by the image provider — a photo whose description decides its style, six\n * photographic genres, six art styles and four design assets.\n *\n * Browser-safe: the dialog reads this to draw the menu, pick a shape and\n * decide which controls to show, with no request until Create. The style\n * wording a raster kind adds to the description is the server's\n * (`server/ai-media-raster-prompt.ts`), so the browser bundle does not carry\n * it.\n */\n\n/** The Kind menu's sections, in order. */\nexport const AI_MEDIA_KIND_GROUPS = ['vector', 'photo', 'art', 'design'] as const\nexport type AiMediaKindGroup = (typeof AI_MEDIA_KIND_GROUPS)[number]\n\n/** Each section's heading in the Kind menu. */\nexport const AI_MEDIA_KIND_GROUP_LABELS: Readonly<Record<AiMediaKindGroup, string>> = {\n vector: 'Vector',\n photo: 'Photo',\n art: 'Art',\n design: 'Design',\n}\n\n/** A kind's id: an SVG style, or an image-provider style. */\nexport type AiMediaKindId = AiSvgStyle | AiRasterStyle\n\n/**\n * Which colors a kind takes. `palette` offers the site theme's colors or the\n * person's own, sent with the request; `describe` sends none and suggests\n * naming colors in the description; `none` is a photograph, whose colors\n * are the scene's.\n */\nexport type AiMediaKindColors = 'palette' | 'describe' | 'none'\n\nexport interface AiMediaKind {\n id: AiMediaKindId\n group: AiMediaKindGroup\n /** The provider mode the door runs it in. */\n mode: AiImageMode\n label: string\n /** The line under the Kind menu once it is chosen. */\n hint: string\n /** The shape the dialog switches to when the kind is chosen. */\n aspectRatio: AiImageAspectRatio\n colors: AiMediaKindColors\n /** The description field's example. */\n placeholder: string\n}\n\nconst vector = (\n id: AiSvgStyle,\n label: string,\n hint: string,\n placeholder: string,\n): AiMediaKind => ({\n id,\n group: 'vector',\n mode: 'illustration',\n label,\n hint,\n aspectRatio: '1:1',\n colors: 'palette',\n placeholder,\n})\n\nconst raster = (\n id: AiRasterStyle,\n group: Exclude<AiMediaKindGroup, 'vector'>,\n label: string,\n hint: string,\n aspectRatio: AiImageAspectRatio,\n placeholder: string,\n): AiMediaKind => ({\n id,\n group,\n mode: 'photo',\n label,\n hint,\n aspectRatio,\n colors: group === 'photo' ? 'none' : 'describe',\n placeholder,\n})\n\nexport const AI_MEDIA_KINDS: readonly AiMediaKind[] = [\n vector(\n 'illustration',\n 'Illustration',\n 'A spot illustration or small scene, drawn as an SVG and sharp at any size.',\n 'A friendly delivery van with a parcel, simple and flat',\n ),\n vector(\n 'icon',\n 'Icon',\n 'A single icon in one color, or two tones, readable at small sizes.',\n 'A shopping bag with a heart',\n ),\n vector(\n 'pattern',\n 'Pattern',\n 'A seamless background that tiles.',\n 'Scattered coffee beans and leaves',\n ),\n vector(\n 'logo',\n 'Logo mark',\n 'A simple symbol, without lettering. Not a real brand’s logo.',\n 'A mountain peak inside a circle',\n ),\n raster(\n 'photo',\n 'photo',\n 'Photo',\n 'A realistic photo; your description decides the style.',\n '1:1',\n 'A sunlit bakery counter with fresh sourdough loaves, warm morning light',\n ),\n raster(\n 'natural',\n 'photo',\n 'Natural photo',\n 'Candid and true to life, in soft available light.',\n '4:3',\n 'A florist arranging tulips in a small shop',\n ),\n raster(\n 'product',\n 'photo',\n 'Studio product shot',\n 'The product alone on a clean backdrop, in soft studio light.',\n '1:1',\n 'A matte ceramic coffee mug in sage green',\n ),\n raster(\n 'lifestyle',\n 'photo',\n 'Lifestyle with people',\n 'People using or enjoying the subject in a real setting. The people are made up, not anyone real.',\n '4:3',\n 'Friends sharing pastries at an outdoor café table',\n ),\n raster(\n 'architecture',\n 'photo',\n 'Architecture & interiors',\n 'Buildings and rooms with straight lines and natural daylight.',\n '4:3',\n 'A bright Scandinavian living room with oak floors',\n ),\n raster(\n 'food',\n 'photo',\n 'Food',\n 'Styled dishes in soft window light.',\n '3:4',\n 'A bowl of ramen with a soft-boiled egg and scallions',\n ),\n raster(\n 'aerial',\n 'photo',\n 'Aerial & landscape',\n 'A sweeping view from above or across the land.',\n '16:9',\n 'A winding coastal road at sunset',\n ),\n raster(\n 'render-3d',\n 'art',\n '3D render',\n 'Smooth, stylized 3D shapes in soft studio light.',\n '1:1',\n 'A stack of colorful gift boxes',\n ),\n raster(\n 'watercolor',\n 'art',\n 'Watercolor',\n 'Soft washes of color on textured paper.',\n '4:3',\n 'A harbor town with fishing boats',\n ),\n raster(\n 'oil-painting',\n 'art',\n 'Oil painting',\n 'Rich color and visible brushstrokes on canvas.',\n '4:3',\n 'A vase of sunflowers on a wooden table',\n ),\n raster(\n 'flat-art',\n 'art',\n 'Flat vector art',\n 'Clean flat shapes and solid colors, made as an image rather than an SVG.',\n '4:3',\n 'A team working together around a laptop',\n ),\n raster(\n 'line-drawing',\n 'art',\n 'Line drawing',\n 'Clean ink lines on a plain background.',\n '1:1',\n 'A bicycle leaning against a lamppost',\n ),\n raster(\n 'cartoon',\n 'art',\n 'Cartoon or anime',\n 'Bold outlines and bright colors, with original characters only.',\n '1:1',\n 'A cheerful robot watering plants',\n ),\n raster(\n 'background',\n 'design',\n 'Background or texture',\n 'Low contrast with no focal point, so text can sit on top.',\n '16:9',\n 'Soft gradient waves in calm blues',\n ),\n raster(\n 'banner',\n 'design',\n 'Banner or hero image',\n 'Wide, with calm space on one side for a headline. No lettering.',\n '16:9',\n 'A yoga mat and plants by a sunny window',\n ),\n raster(\n 'social',\n 'design',\n 'Social post graphic',\n 'A bold, centered picture for a social post.',\n '1:1',\n 'A summer sale feel with citrus fruit and bright colors',\n ),\n raster(\n 'mockup',\n 'design',\n 'Mockup',\n 'Your design shown on a device screen or on packaging.',\n '4:3',\n 'A phone on a desk showing a bakery’s online menu',\n ),\n]\n\n/** A kind by id; `undefined` for an id no kind has. */\nexport function aiMediaKind(id: string): AiMediaKind | undefined {\n return AI_MEDIA_KINDS.find((kind) => kind.id === id)\n}\n\n/**\n * The kinds this deployment offers: every kind where the image provider is\n * on, and only the SVG kinds where it is not, so nothing the door would\n * refuse is ever listed.\n */\nexport function aiMediaKindsOffered(photos: boolean): readonly AiMediaKind[] {\n return photos ? AI_MEDIA_KINDS : AI_MEDIA_KINDS.filter((kind) => kind.mode === 'illustration')\n}\n\n/** Every SVG and raster style has exactly one kind; the spec holds the list to it. */\nexport const AI_MEDIA_KIND_IDS: readonly AiMediaKindId[] = [...AI_SVG_STYLES, ...AI_RASTER_STYLES]\n"],"names":["AI_RASTER_STYLES","AI_SVG_STYLES","AI_MEDIA_KIND_GROUPS","AI_MEDIA_KIND_GROUP_LABELS","vector","photo","art","design","id","label","hint","placeholder","group","mode","aspectRatio","colors","raster","AI_MEDIA_KINDS","aiMediaKind","find","kind","aiMediaKindsOffered","photos","filter","AI_MEDIA_KIND_IDS"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,gBAAgB,EAChBC,aAAa,QAKR,iCAA6B;AAEpC;;;;;;;;;;;CAWC,GAED,wCAAwC,GACxC,OAAO,MAAMC,uBAAuB;IAAC;IAAU;IAAS;IAAO;CAAS,CAAS;AAGjF,6CAA6C,GAC7C,OAAO,MAAMC,6BAAyE;IACpFC,QAAQ;IACRC,OAAO;IACPC,KAAK;IACLC,QAAQ;AACV,EAAC;AA4BD,MAAMH,SAAS,CACbI,IACAC,OACAC,MACAC,cACiB,CAAA;QACjBH;QACAI,OAAO;QACPC,MAAM;QACNJ;QACAC;QACAI,aAAa;QACbC,QAAQ;QACRJ;IACF,CAAA;AAEA,MAAMK,SAAS,CACbR,IACAI,OACAH,OACAC,MACAI,aACAH,cACiB,CAAA;QACjBH;QACAI;QACAC,MAAM;QACNJ;QACAC;QACAI;QACAC,QAAQH,UAAU,UAAU,SAAS;QACrCD;IACF,CAAA;AAEA,OAAO,MAAMM,iBAAyC;IACpDb,OACE,gBACA,gBACA,8EACA;IAEFA,OACE,QACA,QACA,sEACA;IAEFA,OACE,WACA,WACA,qCACA;IAEFA,OACE,QACA,aACA,gEACA;IAEFY,OACE,SACA,SACA,SACA,0DACA,OACA;IAEFA,OACE,WACA,SACA,iBACA,qDACA,OACA;IAEFA,OACE,WACA,SACA,uBACA,gEACA,OACA;IAEFA,OACE,aACA,SACA,yBACA,oGACA,OACA;IAEFA,OACE,gBACA,SACA,4BACA,iEACA,OACA;IAEFA,OACE,QACA,SACA,QACA,uCACA,OACA;IAEFA,OACE,UACA,SACA,sBACA,kDACA,QACA;IAEFA,OACE,aACA,OACA,aACA,oDACA,OACA;IAEFA,OACE,cACA,OACA,cACA,2CACA,OACA;IAEFA,OACE,gBACA,OACA,gBACA,kDACA,OACA;IAEFA,OACE,YACA,OACA,mBACA,4EACA,OACA;IAEFA,OACE,gBACA,OACA,gBACA,0CACA,OACA;IAEFA,OACE,WACA,OACA,oBACA,mEACA,OACA;IAEFA,OACE,cACA,UACA,yBACA,6DACA,QACA;IAEFA,OACE,UACA,UACA,wBACA,mEACA,QACA;IAEFA,OACE,UACA,UACA,uBACA,+CACA,OACA;IAEFA,OACE,UACA,UACA,UACA,yDACA,OACA;CAEH,CAAA;AAED,qDAAqD,GACrD,OAAO,SAASE,YAAYV,EAAU;IACpC,OAAOS,eAAeE,IAAI,CAAC,CAACC,OAASA,KAAKZ,EAAE,KAAKA;AACnD;AAEA;;;;CAIC,GACD,OAAO,SAASa,oBAAoBC,MAAe;IACjD,OAAOA,SAASL,iBAAiBA,eAAeM,MAAM,CAAC,CAACH,OAASA,KAAKP,IAAI,KAAK;AACjF;AAEA,oFAAoF,GACpF,OAAO,MAAMW,oBAA8C;OAAIvB;OAAkBD;CAAiB,CAAA"}
|
|
@@ -85,6 +85,16 @@ export interface AiPlanCapabilities {
|
|
|
85
85
|
* SITE plan's worst case leaves room for. Absent for every other job.
|
|
86
86
|
*/
|
|
87
87
|
freeSitePages?: number;
|
|
88
|
+
/**
|
|
89
|
+
* Whether this job's pages are written in the compact layout language
|
|
90
|
+
* (AGL-3660), whose compiler draws a section's repeated items itself: as
|
|
91
|
+
* instances of a component the plan places there, or, with none, in their
|
|
92
|
+
* compact form, a title over its text, which is smaller than a block rule 1
|
|
93
|
+
* calls a repeat. A plan is then not asked for a component for a section's
|
|
94
|
+
* repeated items (AGL-3616); a section two pages share still is one. The
|
|
95
|
+
* plan step sets it for a `build` job. Absent is `false`.
|
|
96
|
+
*/
|
|
97
|
+
repeatsCompiled?: boolean;
|
|
88
98
|
}
|
|
89
99
|
/** What a job of one kind builds from its own plan, where it builds only some creations. */
|
|
90
100
|
export interface AiPlanJobScope {
|
|
@@ -115,6 +125,12 @@ export declare function aiCreationNoun(kind: AiBuildPlanCreateKind): string;
|
|
|
115
125
|
* planned as four sections of one item.
|
|
116
126
|
*/
|
|
117
127
|
export declare const AI_PLAN_INLINE_SENTENCE = "This workspace keeps no reusable components: draw a list's repeated items in one section.";
|
|
128
|
+
/**
|
|
129
|
+
* The sentence a build's request states (AGL-3616): its pages are written in
|
|
130
|
+
* the layout language, which draws a section's repeated items itself, so the
|
|
131
|
+
* plan counts them in the section's `items` and needs no component for them.
|
|
132
|
+
*/
|
|
133
|
+
export declare const AI_PLAN_COMPILED_REPEATS_SENTENCE = "A section's repeated items need no component here: count them in its items and the page draws them. A section two pages share is still one component.";
|
|
118
134
|
/**
|
|
119
135
|
* The sentence a request states when the site has no saved form and this job
|
|
120
136
|
* may create none (AGL-3596): the plan places no form, rather than one drawn
|
|
@@ -63,6 +63,11 @@ const ALLOWED = {
|
|
|
63
63
|
* each item, which is how a live About page's four practice areas came to be
|
|
64
64
|
* planned as four sections of one item.
|
|
65
65
|
*/ export const AI_PLAN_INLINE_SENTENCE = "This workspace keeps no reusable components: draw a list's repeated items in one section.";
|
|
66
|
+
/**
|
|
67
|
+
* The sentence a build's request states (AGL-3616): its pages are written in
|
|
68
|
+
* the layout language, which draws a section's repeated items itself, so the
|
|
69
|
+
* plan counts them in the section's `items` and needs no component for them.
|
|
70
|
+
*/ export const AI_PLAN_COMPILED_REPEATS_SENTENCE = "A section's repeated items need no component here: count them in its items and the page draws them. A section two pages share is still one component.";
|
|
66
71
|
/**
|
|
67
72
|
* The sentence a request states when the site has no saved form and this job
|
|
68
73
|
* may create none (AGL-3596): the plan places no form, rather than one drawn
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-plan-capabilities.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AI_BUILD_PLAN_CREATE_KINDS,\n AI_BUILD_PLAN_CREATION_NOUNS,\n type AiBuildPlan,\n type AiBuildPlanCreateKind,\n} from './ai-build-plan'\n\n/**\n * What a plan may create on one site (AGL-3030): the workspace's plan and the\n * site's counts against its limits, narrowed to what the job itself builds.\n *\n * A plan that names a creation nothing can make is a dead end the member pays\n * for twice: once for the plan, and again for the plan they describe after\n * being told to make the creation by hand — which, on a workspace whose plan\n * does not include it, they cannot. So the plan step is TOLD what may be\n * created before it answers, and the plan rules refuse a creation outside it\n * with the one re-ask every other rule gets.\n *\n * ── Where the doctrine builds inline ─────────────────────────────────────\n *\n * The building doctrine makes a repeat a reusable component and a form a\n * saved form placed by id. A workspace whose plan keeps no reusable\n * components draws a list's repeated items in one section instead\n * (AGL-3071); `reusableComponents` is the one flag every validator reads for\n * that.\n *\n * A form is never drawn inline (AGL-3596): it is a saved form, counted\n * against the site's form allowance like one a member makes. Where the site\n * has none to place and may create none, the page places no form at all.\n *\n * ── Kept out of the cached prefix ────────────────────────────────────────\n *\n * The capabilities are per workspace and per site, so they ride the plan's\n * USER turn, never a system block: the doctrine's cached prefix stays one\n * entry for the platform. The doctrine states the rule once — create only\n * what the request says may be created, and build inline where it says the\n * workspace keeps no reusable components — and the request states the facts.\n *\n * This module imports nothing at runtime beyond the plan's own model, so the\n * validators, the plan step, the doors and their specs read one vocabulary.\n * What a workspace's plan and counts come to is read by\n * `readAiPlanCapabilities` in `jobs/ai-job-drafts.ts`, beside the band\n * arithmetic the create route and the draft writer share.\n */\n\n/** One creation kind, as a job on this site may make it or not. */\nexport interface AiPlanCreation {\n allowed: boolean\n /** How many more the site may hold; `null` when the plan counts none. */\n left: number | null\n /**\n * Why not, as a clause a sentence completes: \"this workspace's plan does\n * not include reusable components\". `null` when allowed.\n */\n reason: string | null\n}\n\nexport interface AiPlanCapabilities {\n /**\n * Whether the workspace keeps reusable components. Where it does not, a\n * list's repeated items are drawn in one section. Forms are not read off\n * it: whether one may be created is `create.form` (AGL-3596).\n */\n reusableComponents: boolean\n /** Every creation kind a plan may name, and whether this job may make one here. */\n create: Readonly<Record<AiBuildPlanCreateKind, AiPlanCreation>>\n /**\n * Whether the workspace spends the Free taste, whose monthly credits are a\n * wall (AGL-2925): a plan is then held to the sections the wall's worst\n * case pays for (AGL-3070). Absent is `false`.\n */\n freeTaste?: boolean\n /**\n * A Free workspace's site scaffold (AGL-3594): the most pages its plan may\n * build, which the plan step sets for a `site` job on the Free taste. The\n * Free wall then holds the plan to that many pages and to the sections a\n * SITE plan's worst case leaves room for. Absent for every other job.\n */\n freeSitePages?: number\n}\n\n/** What a job of one kind builds from its own plan, where it builds only some creations. */\nexport interface AiPlanJobScope {\n /** The job as a refusal names it: \"a page job\". */\n noun: string\n /** The creation kinds the job builds itself; any other is refused before the plan is kept. */\n creates: readonly AiBuildPlanCreateKind[]\n}\n\nconst ALLOWED: AiPlanCreation = { allowed: true, left: null, reason: null }\n\n/**\n * Capabilities that restrict nothing: what a plan is held to when no\n * workspace was read — an org-level job, an eval, a spec about something\n * else. The doctrine applies whole.\n */\nexport function aiUnrestrictedPlanCapabilities(): AiPlanCapabilities {\n return {\n reusableComponents: true,\n create: Object.fromEntries(\n AI_BUILD_PLAN_CREATE_KINDS.map((kind) => [kind, ALLOWED]),\n ) as Record<AiBuildPlanCreateKind, AiPlanCreation>,\n }\n}\n\n/**\n * The workspace's capabilities narrowed to what a job of one kind builds.\n * A creation the workspace itself cannot make keeps the workspace's reason,\n * which is the one a member can act on; one the job does not build says so.\n */\nexport function aiPlanCapabilitiesForJob(\n capabilities: AiPlanCapabilities,\n scope: AiPlanJobScope | null | undefined,\n): AiPlanCapabilities {\n if (!scope) return capabilities\n const create = { ...capabilities.create }\n for (const kind of AI_BUILD_PLAN_CREATE_KINDS) {\n if (!create[kind].allowed || scope.creates.includes(kind)) continue\n create[kind] = { allowed: false, left: null, reason: `${scope.noun} does not build one` }\n }\n return { ...capabilities, create }\n}\n\n/** \"a layout\", \"an email design\": a creation kind with its article. */\nexport function aiCreationNoun(kind: AiBuildPlanCreateKind): string {\n const noun = AI_BUILD_PLAN_CREATION_NOUNS[kind].noun\n return `${/^[aeiou]/.test(noun) ? 'an' : 'a'} ${noun}`\n}\n\n/**\n * The sentence a request states when the workspace keeps no reusable\n * components. It says a list's repeated items go in one section (AGL-3071):\n * \"a repeated item in its own section\" reads just as well as a section for\n * each item, which is how a live About page's four practice areas came to be\n * planned as four sections of one item.\n */\nexport const AI_PLAN_INLINE_SENTENCE =\n \"This workspace keeps no reusable components: draw a list's repeated items in one section.\"\n\n/**\n * The sentence a request states when the site has no saved form and this job\n * may create none (AGL-3596): the plan places no form, rather than one drawn\n * on the page with no saved form behind it.\n */\nexport const AI_PLAN_NO_FORM_SENTENCE =\n 'This site has no saved form and may create none: plan no form, and no section that collects answers.'\n\n/**\n * What the plan step's user turn says the job may create here: one line a\n * kind, and the inline sentence where the workspace keeps no reusable\n * components. Per workspace and per site, so it is never a system block.\n */\nexport function aiPlanCapabilityLines(capabilities: AiPlanCapabilities): string[] {\n const lines = ['What this job may create on this site:']\n for (const kind of AI_BUILD_PLAN_CREATE_KINDS) {\n const entry = capabilities.create[kind]\n const noun = AI_BUILD_PLAN_CREATION_NOUNS[kind].noun\n if (entry.allowed) {\n lines.push(`- ${noun}: yes${entry.left === null ? '' : `, ${entry.left} more`}`)\n } else {\n lines.push(`- ${noun}: no, because ${entry.reason ?? 'it cannot be made here'}`)\n }\n }\n if (!capabilities.reusableComponents) lines.push(AI_PLAN_INLINE_SENTENCE)\n return lines\n}\n\n/**\n * Whether a plan on this site can place a saved form at all (AGL-3596): the\n * site has one, or the job may create one. Absent capabilities restrict\n * nothing, as everywhere else.\n */\nexport function aiPlanCanPlaceForm(\n inventory: { forms: readonly unknown[] } | null,\n capabilities: Pick<AiPlanCapabilities, 'create'> | null,\n): boolean {\n if (inventory?.forms.length) return true\n return capabilities?.create.form.allowed !== false\n}\n\n/** What to do instead of a creation the job may not make, for the re-ask and the review. */\nfunction insteadOf(kind: AiBuildPlanCreateKind, capabilities: AiPlanCapabilities): string {\n switch (kind) {\n case 'component':\n return capabilities.reusableComponents\n ? 'Place a component the site already has.'\n : \"Draw a list's repeated items in one section instead.\"\n case 'form':\n return 'Place a form the site already has, or leave the form off the page.'\n case 'layout':\n return 'Put the page in a layout the site already has.'\n case 'template':\n return 'Build the page without a template.'\n case 'theme-change':\n return \"Use the colors the theme already has.\"\n case 'dataset':\n return 'Bind a dataset or collection the site already has, or keep the list short enough to write out.'\n case 'email':\n return 'Leave the email out of this job.'\n }\n}\n\n/** One creation a plan names that this job may not make here. */\nexport interface AiPlanUncreatable {\n kind: AiBuildPlanCreateKind\n name: string\n /** The plan path of the creation: `create[2]`. */\n path: string\n /** Why not, as a clause: \"this site has room for 1 more\". */\n reason: string\n /** One customer-safe sentence: what was refused, why, and what to do instead. */\n message: string\n}\n\n/**\n * Why the nth creation of a kind may not be made here, as a clause; `null`\n * when it may. A kind the site may make only so many more of refuses the ones\n * past that count, not the first of them.\n */\nfunction refusedReason(capability: AiPlanCreation, nth: number): string | null {\n const overCount = capability.allowed && capability.left !== null && nth > capability.left\n if (capability.allowed && !overCount) return null\n return overCount\n ? `this site has room for ${capability.left} more`\n : (capability.reason ?? 'it cannot be made here')\n}\n\n/** The creations a plan names that the capabilities refuse, in plan order. */\nexport function aiPlanUncreatable(\n plan: Pick<AiBuildPlan, 'create'>,\n capabilities: AiPlanCapabilities,\n): AiPlanUncreatable[] {\n const refused: AiPlanUncreatable[] = []\n const counted = new Map<AiBuildPlanCreateKind, number>()\n plan.create.forEach((entry, index) => {\n const nth = (counted.get(entry.kind) ?? 0) + 1\n counted.set(entry.kind, nth)\n const reason = refusedReason(capabilities.create[entry.kind], nth)\n if (reason === null) return\n refused.push({\n kind: entry.kind,\n name: entry.name,\n path: `create[${index}]`,\n reason,\n message: `The plan creates ${aiCreationNoun(entry.kind)} named \"${entry.name}\", and ${reason}. ${insteadOf(entry.kind, capabilities)}`,\n })\n })\n return refused\n}\n\n/**\n * Why the plan could not add one more creation of a kind here, and what to\n * do instead; `null` when it could (AGL-3040). Counted past the creations of\n * that kind the plan already names, as `aiPlanUncreatable` counts them, so a\n * plan told to declare a creation is never told to declare one the next\n * answer is refused for.\n */\nexport function aiPlanUncreatableKind(\n plan: Pick<AiBuildPlan, 'create'>,\n kind: AiBuildPlanCreateKind,\n capabilities: AiPlanCapabilities,\n): { reason: string; instead: string } | null {\n const planned = plan.create.filter((entry) => entry.kind === kind).length\n const reason = refusedReason(capabilities.create[kind], planned + 1)\n return reason === null ? null : { reason, instead: insteadOf(kind, capabilities) }\n}\n"],"names":["AI_BUILD_PLAN_CREATE_KINDS","AI_BUILD_PLAN_CREATION_NOUNS","ALLOWED","allowed","left","reason","aiUnrestrictedPlanCapabilities","reusableComponents","create","Object","fromEntries","map","kind","aiPlanCapabilitiesForJob","capabilities","scope","creates","includes","noun","aiCreationNoun","test","AI_PLAN_INLINE_SENTENCE","AI_PLAN_NO_FORM_SENTENCE","aiPlanCapabilityLines","lines","entry","push","aiPlanCanPlaceForm","inventory","forms","length","form","insteadOf","refusedReason","capability","nth","overCount","aiPlanUncreatable","plan","refused","counted","Map","forEach","index","get","set","name","path","message","aiPlanUncreatableKind","planned","filter","instead"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-plan-capabilities.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AI_BUILD_PLAN_CREATE_KINDS,\n AI_BUILD_PLAN_CREATION_NOUNS,\n type AiBuildPlan,\n type AiBuildPlanCreateKind,\n} from './ai-build-plan'\n\n/**\n * What a plan may create on one site (AGL-3030): the workspace's plan and the\n * site's counts against its limits, narrowed to what the job itself builds.\n *\n * A plan that names a creation nothing can make is a dead end the member pays\n * for twice: once for the plan, and again for the plan they describe after\n * being told to make the creation by hand — which, on a workspace whose plan\n * does not include it, they cannot. So the plan step is TOLD what may be\n * created before it answers, and the plan rules refuse a creation outside it\n * with the one re-ask every other rule gets.\n *\n * ── Where the doctrine builds inline ─────────────────────────────────────\n *\n * The building doctrine makes a repeat a reusable component and a form a\n * saved form placed by id. A workspace whose plan keeps no reusable\n * components draws a list's repeated items in one section instead\n * (AGL-3071); `reusableComponents` is the one flag every validator reads for\n * that.\n *\n * A form is never drawn inline (AGL-3596): it is a saved form, counted\n * against the site's form allowance like one a member makes. Where the site\n * has none to place and may create none, the page places no form at all.\n *\n * ── Kept out of the cached prefix ────────────────────────────────────────\n *\n * The capabilities are per workspace and per site, so they ride the plan's\n * USER turn, never a system block: the doctrine's cached prefix stays one\n * entry for the platform. The doctrine states the rule once — create only\n * what the request says may be created, and build inline where it says the\n * workspace keeps no reusable components — and the request states the facts.\n *\n * This module imports nothing at runtime beyond the plan's own model, so the\n * validators, the plan step, the doors and their specs read one vocabulary.\n * What a workspace's plan and counts come to is read by\n * `readAiPlanCapabilities` in `jobs/ai-job-drafts.ts`, beside the band\n * arithmetic the create route and the draft writer share.\n */\n\n/** One creation kind, as a job on this site may make it or not. */\nexport interface AiPlanCreation {\n allowed: boolean\n /** How many more the site may hold; `null` when the plan counts none. */\n left: number | null\n /**\n * Why not, as a clause a sentence completes: \"this workspace's plan does\n * not include reusable components\". `null` when allowed.\n */\n reason: string | null\n}\n\nexport interface AiPlanCapabilities {\n /**\n * Whether the workspace keeps reusable components. Where it does not, a\n * list's repeated items are drawn in one section. Forms are not read off\n * it: whether one may be created is `create.form` (AGL-3596).\n */\n reusableComponents: boolean\n /** Every creation kind a plan may name, and whether this job may make one here. */\n create: Readonly<Record<AiBuildPlanCreateKind, AiPlanCreation>>\n /**\n * Whether the workspace spends the Free taste, whose monthly credits are a\n * wall (AGL-2925): a plan is then held to the sections the wall's worst\n * case pays for (AGL-3070). Absent is `false`.\n */\n freeTaste?: boolean\n /**\n * A Free workspace's site scaffold (AGL-3594): the most pages its plan may\n * build, which the plan step sets for a `site` job on the Free taste. The\n * Free wall then holds the plan to that many pages and to the sections a\n * SITE plan's worst case leaves room for. Absent for every other job.\n */\n freeSitePages?: number\n /**\n * Whether this job's pages are written in the compact layout language\n * (AGL-3660), whose compiler draws a section's repeated items itself: as\n * instances of a component the plan places there, or, with none, in their\n * compact form, a title over its text, which is smaller than a block rule 1\n * calls a repeat. A plan is then not asked for a component for a section's\n * repeated items (AGL-3616); a section two pages share still is one. The\n * plan step sets it for a `build` job. Absent is `false`.\n */\n repeatsCompiled?: boolean\n}\n\n/** What a job of one kind builds from its own plan, where it builds only some creations. */\nexport interface AiPlanJobScope {\n /** The job as a refusal names it: \"a page job\". */\n noun: string\n /** The creation kinds the job builds itself; any other is refused before the plan is kept. */\n creates: readonly AiBuildPlanCreateKind[]\n}\n\nconst ALLOWED: AiPlanCreation = { allowed: true, left: null, reason: null }\n\n/**\n * Capabilities that restrict nothing: what a plan is held to when no\n * workspace was read — an org-level job, an eval, a spec about something\n * else. The doctrine applies whole.\n */\nexport function aiUnrestrictedPlanCapabilities(): AiPlanCapabilities {\n return {\n reusableComponents: true,\n create: Object.fromEntries(\n AI_BUILD_PLAN_CREATE_KINDS.map((kind) => [kind, ALLOWED]),\n ) as Record<AiBuildPlanCreateKind, AiPlanCreation>,\n }\n}\n\n/**\n * The workspace's capabilities narrowed to what a job of one kind builds.\n * A creation the workspace itself cannot make keeps the workspace's reason,\n * which is the one a member can act on; one the job does not build says so.\n */\nexport function aiPlanCapabilitiesForJob(\n capabilities: AiPlanCapabilities,\n scope: AiPlanJobScope | null | undefined,\n): AiPlanCapabilities {\n if (!scope) return capabilities\n const create = { ...capabilities.create }\n for (const kind of AI_BUILD_PLAN_CREATE_KINDS) {\n if (!create[kind].allowed || scope.creates.includes(kind)) continue\n create[kind] = { allowed: false, left: null, reason: `${scope.noun} does not build one` }\n }\n return { ...capabilities, create }\n}\n\n/** \"a layout\", \"an email design\": a creation kind with its article. */\nexport function aiCreationNoun(kind: AiBuildPlanCreateKind): string {\n const noun = AI_BUILD_PLAN_CREATION_NOUNS[kind].noun\n return `${/^[aeiou]/.test(noun) ? 'an' : 'a'} ${noun}`\n}\n\n/**\n * The sentence a request states when the workspace keeps no reusable\n * components. It says a list's repeated items go in one section (AGL-3071):\n * \"a repeated item in its own section\" reads just as well as a section for\n * each item, which is how a live About page's four practice areas came to be\n * planned as four sections of one item.\n */\nexport const AI_PLAN_INLINE_SENTENCE =\n \"This workspace keeps no reusable components: draw a list's repeated items in one section.\"\n\n/**\n * The sentence a build's request states (AGL-3616): its pages are written in\n * the layout language, which draws a section's repeated items itself, so the\n * plan counts them in the section's `items` and needs no component for them.\n */\nexport const AI_PLAN_COMPILED_REPEATS_SENTENCE =\n \"A section's repeated items need no component here: count them in its items and the page draws them. A section two pages share is still one component.\"\n\n/**\n * The sentence a request states when the site has no saved form and this job\n * may create none (AGL-3596): the plan places no form, rather than one drawn\n * on the page with no saved form behind it.\n */\nexport const AI_PLAN_NO_FORM_SENTENCE =\n 'This site has no saved form and may create none: plan no form, and no section that collects answers.'\n\n/**\n * What the plan step's user turn says the job may create here: one line a\n * kind, and the inline sentence where the workspace keeps no reusable\n * components. Per workspace and per site, so it is never a system block.\n */\nexport function aiPlanCapabilityLines(capabilities: AiPlanCapabilities): string[] {\n const lines = ['What this job may create on this site:']\n for (const kind of AI_BUILD_PLAN_CREATE_KINDS) {\n const entry = capabilities.create[kind]\n const noun = AI_BUILD_PLAN_CREATION_NOUNS[kind].noun\n if (entry.allowed) {\n lines.push(`- ${noun}: yes${entry.left === null ? '' : `, ${entry.left} more`}`)\n } else {\n lines.push(`- ${noun}: no, because ${entry.reason ?? 'it cannot be made here'}`)\n }\n }\n if (!capabilities.reusableComponents) lines.push(AI_PLAN_INLINE_SENTENCE)\n return lines\n}\n\n/**\n * Whether a plan on this site can place a saved form at all (AGL-3596): the\n * site has one, or the job may create one. Absent capabilities restrict\n * nothing, as everywhere else.\n */\nexport function aiPlanCanPlaceForm(\n inventory: { forms: readonly unknown[] } | null,\n capabilities: Pick<AiPlanCapabilities, 'create'> | null,\n): boolean {\n if (inventory?.forms.length) return true\n return capabilities?.create.form.allowed !== false\n}\n\n/** What to do instead of a creation the job may not make, for the re-ask and the review. */\nfunction insteadOf(kind: AiBuildPlanCreateKind, capabilities: AiPlanCapabilities): string {\n switch (kind) {\n case 'component':\n return capabilities.reusableComponents\n ? 'Place a component the site already has.'\n : \"Draw a list's repeated items in one section instead.\"\n case 'form':\n return 'Place a form the site already has, or leave the form off the page.'\n case 'layout':\n return 'Put the page in a layout the site already has.'\n case 'template':\n return 'Build the page without a template.'\n case 'theme-change':\n return \"Use the colors the theme already has.\"\n case 'dataset':\n return 'Bind a dataset or collection the site already has, or keep the list short enough to write out.'\n case 'email':\n return 'Leave the email out of this job.'\n }\n}\n\n/** One creation a plan names that this job may not make here. */\nexport interface AiPlanUncreatable {\n kind: AiBuildPlanCreateKind\n name: string\n /** The plan path of the creation: `create[2]`. */\n path: string\n /** Why not, as a clause: \"this site has room for 1 more\". */\n reason: string\n /** One customer-safe sentence: what was refused, why, and what to do instead. */\n message: string\n}\n\n/**\n * Why the nth creation of a kind may not be made here, as a clause; `null`\n * when it may. A kind the site may make only so many more of refuses the ones\n * past that count, not the first of them.\n */\nfunction refusedReason(capability: AiPlanCreation, nth: number): string | null {\n const overCount = capability.allowed && capability.left !== null && nth > capability.left\n if (capability.allowed && !overCount) return null\n return overCount\n ? `this site has room for ${capability.left} more`\n : (capability.reason ?? 'it cannot be made here')\n}\n\n/** The creations a plan names that the capabilities refuse, in plan order. */\nexport function aiPlanUncreatable(\n plan: Pick<AiBuildPlan, 'create'>,\n capabilities: AiPlanCapabilities,\n): AiPlanUncreatable[] {\n const refused: AiPlanUncreatable[] = []\n const counted = new Map<AiBuildPlanCreateKind, number>()\n plan.create.forEach((entry, index) => {\n const nth = (counted.get(entry.kind) ?? 0) + 1\n counted.set(entry.kind, nth)\n const reason = refusedReason(capabilities.create[entry.kind], nth)\n if (reason === null) return\n refused.push({\n kind: entry.kind,\n name: entry.name,\n path: `create[${index}]`,\n reason,\n message: `The plan creates ${aiCreationNoun(entry.kind)} named \"${entry.name}\", and ${reason}. ${insteadOf(entry.kind, capabilities)}`,\n })\n })\n return refused\n}\n\n/**\n * Why the plan could not add one more creation of a kind here, and what to\n * do instead; `null` when it could (AGL-3040). Counted past the creations of\n * that kind the plan already names, as `aiPlanUncreatable` counts them, so a\n * plan told to declare a creation is never told to declare one the next\n * answer is refused for.\n */\nexport function aiPlanUncreatableKind(\n plan: Pick<AiBuildPlan, 'create'>,\n kind: AiBuildPlanCreateKind,\n capabilities: AiPlanCapabilities,\n): { reason: string; instead: string } | null {\n const planned = plan.create.filter((entry) => entry.kind === kind).length\n const reason = refusedReason(capabilities.create[kind], planned + 1)\n return reason === null ? null : { reason, instead: insteadOf(kind, capabilities) }\n}\n"],"names":["AI_BUILD_PLAN_CREATE_KINDS","AI_BUILD_PLAN_CREATION_NOUNS","ALLOWED","allowed","left","reason","aiUnrestrictedPlanCapabilities","reusableComponents","create","Object","fromEntries","map","kind","aiPlanCapabilitiesForJob","capabilities","scope","creates","includes","noun","aiCreationNoun","test","AI_PLAN_INLINE_SENTENCE","AI_PLAN_COMPILED_REPEATS_SENTENCE","AI_PLAN_NO_FORM_SENTENCE","aiPlanCapabilityLines","lines","entry","push","aiPlanCanPlaceForm","inventory","forms","length","form","insteadOf","refusedReason","capability","nth","overCount","aiPlanUncreatable","plan","refused","counted","Map","forEach","index","get","set","name","path","message","aiPlanUncreatableKind","planned","filter","instead"],"mappings":"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"}
|
|
@@ -55,6 +55,10 @@ export declare function aiSiteBuildPhase(job: Pick<AiJobSummary, 'status'> & Par
|
|
|
55
55
|
* the first page's.
|
|
56
56
|
*/
|
|
57
57
|
export declare function aiSiteBuildRows(job: Pick<AiJobSummary, 'status' | 'steps' | 'plan' | 'outputs' | 'review'> & Partial<Pick<AiJobSummary, 'items' | 'kind' | 'siteInputs' | 'sitePublish'>>): AiSiteBuildRow[];
|
|
58
|
+
/** The row the site's look is designed on (AGL-3660). */
|
|
59
|
+
export declare const AI_SITE_LOOK_ROW_LABEL = "Designing your look";
|
|
60
|
+
/** What the look's row says while it runs. */
|
|
61
|
+
export declare const AI_SITE_LOOK_HINT = "Choosing a base theme, colors, fonts and the style of your buttons, cards and forms for your kind of site. This takes a few seconds.";
|
|
58
62
|
/** What the planning row says while it runs. */
|
|
59
63
|
export declare const AI_SITE_PLAN_HINT = "Reading your answers and choosing your pages, what each one says, and the forms and layout they need. This usually takes under a minute.";
|
|
60
64
|
/** What the page being written says while it is the active row (AGL-3596). */
|