@aglyn/plugins-ai 1.0.0-beta.230 → 1.0.0-beta.232
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-assist-provider-on-host.component.d.ts +66 -0
- package/src/lib/components/ai-assist-provider-on-host.component.js +78 -0
- package/src/lib/components/ai-assist-provider-on-host.component.js.map +1 -0
- package/src/lib/components/ai-assist-provider.component.js +9 -10
- package/src/lib/components/ai-assist-provider.component.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-job-plan.component.js +3 -1
- package/src/lib/components/ai-job-plan.component.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-permissions-on-host.component.d.ts +3 -46
- package/src/lib/components/ai-permissions-on-host.component.js +5 -53
- package/src/lib/components/ai-permissions-on-host.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-build-page.component.js +20 -8
- package/src/lib/components/ai-site-build-page.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/jobs/ai-build-capabilities.d.ts +13 -1
- package/src/lib/jobs/ai-build-capabilities.js +101 -7
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
- package/src/lib/jobs/ai-build-unit-outcome.js +4 -2
- package/src/lib/jobs/ai-build-unit-outcome.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-auto-confirm.d.ts +8 -0
- package/src/lib/jobs/ai-job-auto-confirm.js +30 -6
- package/src/lib/jobs/ai-job-auto-confirm.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.js +14 -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-generation.d.ts +8 -0
- package/src/lib/jobs/ai-job-generation.js +13 -2
- package/src/lib/jobs/ai-job-generation.js.map +1 -1
- package/src/lib/jobs/ai-job-insight-step.js +29 -5
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +78 -0
- package/src/lib/jobs/ai-job-layout-language.js +200 -0
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -0
- package/src/lib/jobs/ai-job-layout-site-pages.d.ts +13 -2
- package/src/lib/jobs/ai-job-layout-site-pages.js +18 -2
- package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.d.ts +5 -0
- package/src/lib/jobs/ai-job-layout-step.js +107 -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 +194 -0
- package/src/lib/jobs/ai-job-page-language.js +418 -0
- package/src/lib/jobs/ai-job-page-language.js.map +1 -0
- package/src/lib/jobs/ai-job-page-sections.d.ts +37 -1
- package/src/lib/jobs/ai-job-page-sections.js +93 -11
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.d.ts +4 -4
- package/src/lib/jobs/ai-job-page-step.js +115 -14
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +20 -6
- package/src/lib/jobs/ai-job-plan-step.js +62 -22
- 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 +30 -5
- package/src/lib/jobs/ai-job-site-step.js +183 -27
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-workflow-step.js +2 -2
- package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs-notify.d.ts +6 -1
- package/src/lib/jobs/ai-jobs-notify.js +15 -3
- package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +1 -1
- package/src/lib/jobs/ai-jobs.js +17 -8
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-full-height.d.ts +40 -0
- package/src/lib/jobs/ai-layout-full-height.js +82 -0
- package/src/lib/jobs/ai-layout-full-height.js.map +1 -0
- package/src/lib/jobs/ai-layout-site-facts.d.ts +54 -0
- package/src/lib/jobs/ai-layout-site-facts.js +153 -0
- package/src/lib/jobs/ai-layout-site-facts.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.d.ts +154 -0
- package/src/lib/layout-language/ai-layout-compiler.js +1235 -0
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -0
- package/src/lib/layout-language/ai-layout-copy.d.ts +73 -0
- package/src/lib/layout-language/ai-layout-copy.js +137 -0
- package/src/lib/layout-language/ai-layout-copy.js.map +1 -0
- package/src/lib/layout-language/ai-layout-frame.d.ts +52 -0
- package/src/lib/layout-language/ai-layout-frame.js +458 -0
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -0
- package/src/lib/layout-language/ai-layout-gaps.d.ts +63 -0
- package/src/lib/layout-language/ai-layout-gaps.js +345 -0
- package/src/lib/layout-language/ai-layout-gaps.js.map +1 -0
- package/src/lib/layout-language/ai-layout-language.d.ts +168 -0
- package/src/lib/layout-language/ai-layout-language.js +529 -0
- package/src/lib/layout-language/ai-layout-language.js.map +1 -0
- package/src/lib/layout-language/ai-layout-links.d.ts +80 -0
- package/src/lib/layout-language/ai-layout-links.js +109 -0
- package/src/lib/layout-language/ai-layout-links.js.map +1 -0
- 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 +41 -0
- package/src/lib/layout-language/ai-layout-store.js +75 -0
- package/src/lib/layout-language/ai-layout-store.js.map +1 -0
- package/src/lib/layout-language/ai-layout-tree.d.ts +59 -0
- package/src/lib/layout-language/ai-layout-tree.js +84 -0
- package/src/lib/layout-language/ai-layout-tree.js.map +1 -0
- package/src/lib/lazy-widget.d.ts +45 -0
- package/src/lib/lazy-widget.js +53 -0
- package/src/lib/lazy-widget.js.map +1 -0
- package/src/lib/model/ai-build-job.js +2 -1
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-progress.d.ts +20 -3
- package/src/lib/model/ai-build-progress.js +52 -7
- 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-host-publish-context.d.ts +51 -0
- package/src/lib/model/ai-host-publish-context.js +104 -0
- package/src/lib/model/ai-host-publish-context.js.map +1 -0
- package/src/lib/model/ai-job-activity.d.ts +22 -0
- package/src/lib/model/ai-job-activity.js +23 -1
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-job-failure-copy.d.ts +38 -1
- package/src/lib/model/ai-job-failure-copy.js +56 -1
- package/src/lib/model/ai-job-failure-copy.js.map +1 -1
- package/src/lib/model/ai-job-notice.d.ts +3 -1
- package/src/lib/model/ai-job-notice.js +31 -9
- package/src/lib/model/ai-job-notice.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +22 -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-site-build-progress.d.ts +9 -1
- package/src/lib/model/ai-site-build-progress.js +54 -14
- 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 +18 -2
- package/src/lib/model/ai-site-job.js +21 -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-seo.d.ts +1 -1
- package/src/lib/model/ai-site-start.d.ts +15 -3
- package/src/lib/model/ai-site-start.js +21 -4
- 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/plugin-config.js +2 -0
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +99 -48
- 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 +38 -8
- package/src/lib/providers/catalog.js +106 -14
- 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 +82 -0
- package/src/lib/runtime/ai-doctrine-validators.js +436 -4
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +16 -3
- 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-icon-library.d.ts +257 -0
- package/src/lib/runtime/ai-icon-library.js +254 -0
- package/src/lib/runtime/ai-icon-library.js.map +1 -0
- 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-node-tree.d.ts +9 -0
- package/src/lib/runtime/ai-node-tree.js +30 -4
- package/src/lib/runtime/ai-node-tree.js.map +1 -1
- package/src/lib/runtime/ai-palette.d.ts +6 -0
- package/src/lib/runtime/ai-palette.generated.js +70 -0
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/ai-palette.js.map +1 -1
- 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/tools/ai-workflow-tool.d.ts +6 -2
- package/src/lib/tools/ai-workflow-tool.js +16 -14
- package/src/lib/tools/ai-workflow-tool.js.map +1 -1
- 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-job-notice.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 { AglynNotification } from '@aglyn/aglyn/app-utils/notifications'\nimport { aiJobKindNoun } from './ai-job-activity'\nimport { aiJobRefundCopy } from './ai-job-failure-copy'\nimport type { AiJobKind, AiJobOutput, AiJobReviewReason, AiJobSitePublish } from './ai-jobs.types'\n\n/** The besigner segment each versioned resource lives under. */\nexport const AI_JOB_BESIGNER_SEGMENT: Partial<Record<AiJobOutput['resource'], string>> = {\n screen: 'screens',\n reusableComponent: 'components',\n layout: 'layouts',\n template: 'templates',\n // An email design is a screen: it opens in the screen besigner, as the\n // Emails page's own Edit design does.\n emailScreen: 'screens',\n}\n\n/**\n * The query parameter a console address carries to open AI jobs on one job\n * (AGL-3593): the Assist panel reads it on arrival.\n */\nexport const AI_JOB_LINK_PARAM = 'aiJob'\n\n/** Where a site's \"Building your site\" page lives, under the site (AGL-3594). */\nexport const AI_SITE_BUILD_HREF = '/ai-jobs'\n\n/** A change the person who started a job is told about. */\nexport type AiJobNoticeTransition = 'needs-review' | 'done' | 'failed'\n\n/** The fields of a stored job a notice is written from. */\nexport interface AiJobNoticeSource {\n $id: string\n orgId: string\n hostId?: string | null\n kind: AiJobKind\n review?: { reason: AiJobReviewReason; message?: string | null } | null\n outputs?: readonly AiJobOutput[] | null\n error?: string | null\n /** What a guided site start put live (AGL-3596); absent on every other job. */\n sitePublish?: Pick<AiJobSitePublish, 'published' | 'drafts'> | null\n /** What the job spent, and gave back for a failure on our side (AGL-3596). */\n creditsSpent?: number\n refundedCredits?: number\n}\n\n/** A stopped job's sentence with what became of its credits, from the job's recorded give-back (AGL-3596). */\nfunction withRefund(job: AiJobNoticeSource, text: string, status: 'failed' | 'needs_review'): string {\n const refund = aiJobRefundCopy({ status, refundedCredits: job.refundedCredits, creditsSpent: job.creditsSpent })\n return refund ? `${text} ${refund}` : text\n}\n\nexport type AiJobNotice = Pick<\n AglynNotification,\n 'type' | 'title' | 'body' | 'link' | 'orgId' | 'hostId' | 'level'\n>\n\n/**\n * Where a notice's link goes, in the stored form the console rewrites when it\n * is followed (`normalizeNotificationLink`): `/{hostId}/…` for the job's site,\n * `/org…` for a job of the workspace's.\n *\n * A job that is done leads to what it built (AGL-3593): several pages of the\n * job's site open that site's Pages list, one versioned output opens in the\n * editor on the version the job wrote. Everything else — a plan to confirm, a\n * job that stopped, a done job with nothing of its own to open — opens AI\n * jobs on the job.\n */\nexport function aiJobNoticeLink(job: AiJobNoticeSource, to: AiJobNoticeTransition): string {\n const base = job.hostId ? `/${job.hostId}` : '/org'\n // A site job's every notice opens its \"Building your site\" page (AGL-3594),\n // which leads with the next step whatever the job's state.\n if (job.kind === 'site' && job.hostId) return `${base}${AI_SITE_BUILD_HREF}/${encodeURIComponent(job.$id)}`\n if (to === 'done' && job.hostId) {\n const own = (job.outputs ?? []).filter((output) => output.hostId === job.hostId)\n const screens = own.filter((output) => output.resource === 'screen')\n if (screens.length > 1) return `${base}/screens`\n const versioned = own.find(\n (output) => AI_JOB_BESIGNER_SEGMENT[output.resource] && output.versionId,\n )\n if (own.length === 1 && versioned) {\n const segment = AI_JOB_BESIGNER_SEGMENT[versioned.resource]\n return `${base}/${segment}/${versioned.id}/versions/${versioned.versionId}/besigner`\n }\n }\n return `${base}?${AI_JOB_LINK_PARAM}=${encodeURIComponent(job.$id)}`\n}\n\n/**\n * The in-app notification for one change of one job (AGL-3593), for the\n * person who created it. Customer-safe throughout: a stopped job's sentence is\n * the job's own `error`, which the machine writes as a fixed sentence.\n */\nexport function aiJobNotice(job: AiJobNoticeSource, to: AiJobNoticeTransition): AiJobNotice {\n const noun = aiJobKindNoun(job.kind)\n const scope = { orgId: job.orgId, ...(job.hostId ? { hostId: job.hostId } : {}) }\n const link = aiJobNoticeLink(job, to)\n if (to === 'needs-review') {\n if (job.review?.reason === 'plan') {\n return {\n type: 'content.aiJobNeedsYou',\n level: 'warning',\n title:\n noun === 'AI job'\n ? 'Your plan is ready: confirm it to build'\n : `Your ${noun} plan is ready: confirm it to build`,\n body: 'Nothing is built until you confirm the plan. It waits in AI jobs.',\n link,\n ...scope,\n }\n }\n return {\n type: 'content.aiJobNeedsYou',\n level: 'warning',\n title: `Your ${noun === 'AI job' ? 'AI job' : `${noun} job`} needs you`,\n body: withRefund(job, job.review?.message || job.error || 'It stopped for your decision. It waits in AI jobs.', 'needs_review'),\n link,\n ...scope,\n }\n }\n if (to === 'done') {\n // A guided site start publishes its pages when it finishes (AGL-3596):\n // with one live, the site is, and the build page lists any that stayed\n // drafts with the reason for each.\n const live = job.kind === 'site' ? (job.sitePublish?.published.length ?? 0) : 0\n if (live > 0) {\n const drafts = job.sitePublish?.drafts.length ?? 0\n return {\n type: 'content.aiJobDone',\n level: 'success',\n title: 'Your site is live',\n body:\n drafts > 0\n ? `Your pages are published, except ${drafts === 1 ? 'one that stayed a draft' : `${drafts} that stayed drafts`}. Open it to see why.`\n : 'Your pages are published. Open it to view your site or edit your pages.',\n link,\n ...scope,\n }\n }\n const pages = (job.outputs ?? []).filter((output) => output.resource === 'screen').length\n const title =\n job.kind === 'site' || pages > 1\n ? 'Your site’s draft pages are ready'\n : job.kind === 'page'\n ? 'Your draft page is ready'\n : noun === 'AI job'\n ? 'Your AI job finished'\n : `Your ${noun} is ready`\n return {\n type: 'content.aiJobDone',\n level: 'success',\n title,\n body:\n job.kind === 'insight' || job.kind === 'text'\n ? 'Open AI jobs to read it.'\n : 'Everything it built is an unpublished draft until you publish it.',\n link,\n ...scope,\n }\n }\n return {\n type: 'content.aiJobFailed',\n level: 'warning',\n title: `Your ${noun === 'AI job' ? 'AI job' : `${noun} job`} stopped`,\n body: withRefund(job, job.error || 'It stopped before it finished.', 'failed'),\n link,\n ...scope,\n }\n}\n"],"names":["aiJobKindNoun","aiJobRefundCopy","AI_JOB_BESIGNER_SEGMENT","screen","reusableComponent","layout","template","emailScreen","AI_JOB_LINK_PARAM","AI_SITE_BUILD_HREF","withRefund","job","text","status","refund","refundedCredits","creditsSpent","aiJobNoticeLink","to","base","hostId","kind","encodeURIComponent","$id","own","outputs","filter","output","screens","resource","length","versioned","find","versionId","segment","id","aiJobNotice","noun","scope","orgId","link","review","reason","type","level","title","body","message","error","live","sitePublish","published","drafts","pages"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-job-notice.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 { AglynNotification } from '@aglyn/aglyn/app-utils/notifications'\nimport { aiBuildOutcomeLine, aiSitePartialCopy } from './ai-build-progress'\nimport { aiJobKindNoun } from './ai-job-activity'\nimport { aiCustomerSafeCopy, aiJobRefundCopy } from './ai-job-failure-copy'\nimport type { AiJobItemLedger, AiJobKind, AiJobOutput, AiJobReviewReason, AiJobSitePublish } from './ai-jobs.types'\n\n/** The besigner segment each versioned resource lives under. */\nexport const AI_JOB_BESIGNER_SEGMENT: Partial<Record<AiJobOutput['resource'], string>> = {\n screen: 'screens',\n reusableComponent: 'components',\n layout: 'layouts',\n template: 'templates',\n // An email design is a screen: it opens in the screen besigner, as the\n // Emails page's own Edit design does.\n emailScreen: 'screens',\n}\n\n/**\n * The query parameter a console address carries to open AI jobs on one job\n * (AGL-3593): the Assist panel reads it on arrival.\n */\nexport const AI_JOB_LINK_PARAM = 'aiJob'\n\n/** Where a site's \"Building your site\" page lives, under the site (AGL-3594). */\nexport const AI_SITE_BUILD_HREF = '/ai-jobs'\n\n/** A change the person who started a job is told about. */\nexport type AiJobNoticeTransition = 'needs-review' | 'done' | 'failed'\n\n/** The fields of a stored job a notice is written from. */\nexport interface AiJobNoticeSource {\n $id: string\n orgId: string\n hostId?: string | null\n kind: AiJobKind\n review?: { reason: AiJobReviewReason; message?: string | null } | null\n outputs?: readonly AiJobOutput[] | null\n error?: string | null\n /** What a guided site start put live (AGL-3596); absent on every other job. */\n sitePublish?: Pick<AiJobSitePublish, 'published' | 'drafts'> | null\n /** What the job spent, and gave back for a failure on our side (AGL-3596). */\n creditsSpent?: number\n refundedCredits?: number\n /** A build's or a guided start's items (AGL-3616), so a finished one says what was not built. */\n items?: AiJobItemLedger[] | null\n}\n\n/** A stopped job's sentence with what became of its credits, from the job's recorded give-back (AGL-3596). */\nfunction withRefund(job: AiJobNoticeSource, text: string, status: 'failed' | 'needs_review'): string {\n const refund = aiJobRefundCopy({ status, refundedCredits: job.refundedCredits, creditsSpent: job.creditsSpent })\n return refund ? `${text} ${refund}` : text\n}\n\nexport type AiJobNotice = Pick<\n AglynNotification,\n 'type' | 'title' | 'body' | 'link' | 'orgId' | 'hostId' | 'level'\n>\n\n/**\n * Where a notice's link goes, in the stored form the console rewrites when it\n * is followed (`normalizeNotificationLink`): `/{hostId}/…` for the job's site,\n * `/org…` for a job of the workspace's.\n *\n * A job that is done leads to what it built (AGL-3593): several pages of the\n * job's site open that site's Pages list, one versioned output opens in the\n * editor on the version the job wrote. Everything else — a plan to confirm, a\n * job that stopped, a done job with nothing of its own to open — opens AI\n * jobs on the job.\n */\nexport function aiJobNoticeLink(job: AiJobNoticeSource, to: AiJobNoticeTransition): string {\n const base = job.hostId ? `/${job.hostId}` : '/org'\n // A site job's every notice opens its \"Building your site\" page (AGL-3594),\n // which leads with the next step whatever the job's state.\n if (job.kind === 'site' && job.hostId) return `${base}${AI_SITE_BUILD_HREF}/${encodeURIComponent(job.$id)}`\n if (to === 'done' && job.hostId) {\n const own = (job.outputs ?? []).filter((output) => output.hostId === job.hostId)\n const screens = own.filter((output) => output.resource === 'screen')\n if (screens.length > 1) return `${base}/screens`\n const versioned = own.find(\n (output) => AI_JOB_BESIGNER_SEGMENT[output.resource] && output.versionId,\n )\n if (own.length === 1 && versioned) {\n const segment = AI_JOB_BESIGNER_SEGMENT[versioned.resource]\n return `${base}/${segment}/${versioned.id}/versions/${versioned.versionId}/besigner`\n }\n }\n return `${base}?${AI_JOB_LINK_PARAM}=${encodeURIComponent(job.$id)}`\n}\n\n/**\n * The in-app notification for one change of one job (AGL-3593), for the\n * person who created it. Customer-safe throughout: a stopped job's sentence is\n * the job's own `error`, which the machine writes as a fixed sentence.\n */\nexport function aiJobNotice(job: AiJobNoticeSource, to: AiJobNoticeTransition): AiJobNotice {\n const noun = aiJobKindNoun(job.kind)\n const scope = { orgId: job.orgId, ...(job.hostId ? { hostId: job.hostId } : {}) }\n const link = aiJobNoticeLink(job, to)\n if (to === 'needs-review') {\n if (job.review?.reason === 'plan') {\n return {\n type: 'content.aiJobNeedsYou',\n level: 'warning',\n title:\n noun === 'AI job'\n ? 'Your plan is ready: confirm it to build'\n : `Your ${noun} plan is ready: confirm it to build`,\n body: 'Nothing is built until you confirm the plan. It waits in AI jobs.',\n link,\n ...scope,\n }\n }\n return {\n type: 'content.aiJobNeedsYou',\n level: 'warning',\n title: `Your ${noun === 'AI job' ? 'AI job' : `${noun} job`} needs you`,\n // The doctrine's own words never reach a customer (AGL-3596).\n body: withRefund(\n job,\n aiCustomerSafeCopy(job.review?.message || job.error || 'It stopped for your decision. It waits in AI jobs.', { page: job.kind === 'page' }),\n 'needs_review',\n ),\n link,\n ...scope,\n }\n }\n if (to === 'done') {\n // A job that finished with part of it unbuilt (AGL-3596) says so in the\n // words its page uses, as a warning: \"done\" alone hid a page that failed.\n const items = job.items ?? []\n const unbuilt = items.some((row) => row.status === 'failed' || row.status === 'skipped')\n const level = unbuilt ? 'warning' : 'success'\n const partial =\n job.kind === 'site'\n ? aiSitePartialCopy({ items, status: 'done' })\n : unbuilt\n ? aiBuildOutcomeLine({ items, status: 'done' })\n : null\n // A guided site start publishes its pages when it finishes (AGL-3596):\n // with one live, the site is, and the build page lists any that stayed\n // drafts with the reason for each.\n const live = job.kind === 'site' ? (job.sitePublish?.published.length ?? 0) : 0\n if (live > 0) {\n const drafts = job.sitePublish?.drafts.length ?? 0\n const stayed = drafts === 1 ? 'one that stayed a draft' : `${drafts} that stayed drafts`\n return {\n type: 'content.aiJobDone',\n level,\n title: partial ? 'Your site is live, but part of it wasn’t built' : 'Your site is live',\n body: partial\n ? `${partial} ${drafts > 0 ? `What was built is published, except ${stayed}.` : 'What was built is published.'}`\n : drafts > 0\n ? `Your pages are published, except ${stayed}. Open it to see why.`\n : 'Your pages are published. Open it to view your site or edit your pages.',\n link,\n ...scope,\n }\n }\n const pages = (job.outputs ?? []).filter((output) => output.resource === 'screen').length\n const title =\n job.kind === 'site' || pages > 1\n ? 'Your site’s draft pages are ready'\n : job.kind === 'page'\n ? 'Your draft page is ready'\n : noun === 'AI job'\n ? 'Your AI job finished'\n : `Your ${noun} is ready`\n return {\n type: 'content.aiJobDone',\n level,\n title,\n body:\n job.kind === 'insight' || job.kind === 'text'\n ? 'Open AI jobs to read it.'\n : [partial, 'Everything it built is an unpublished draft until you publish it.'].filter(Boolean).join(' '),\n link,\n ...scope,\n }\n }\n return {\n type: 'content.aiJobFailed',\n level: 'warning',\n title: `Your ${noun === 'AI job' ? 'AI job' : `${noun} job`} stopped`,\n body: withRefund(job, aiCustomerSafeCopy(job.error || 'It stopped before it finished.', { page: job.kind === 'page' }), 'failed'),\n link,\n ...scope,\n }\n}\n"],"names":["aiBuildOutcomeLine","aiSitePartialCopy","aiJobKindNoun","aiCustomerSafeCopy","aiJobRefundCopy","AI_JOB_BESIGNER_SEGMENT","screen","reusableComponent","layout","template","emailScreen","AI_JOB_LINK_PARAM","AI_SITE_BUILD_HREF","withRefund","job","text","status","refund","refundedCredits","creditsSpent","aiJobNoticeLink","to","base","hostId","kind","encodeURIComponent","$id","own","outputs","filter","output","screens","resource","length","versioned","find","versionId","segment","id","aiJobNotice","noun","scope","orgId","link","review","reason","type","level","title","body","message","error","page","items","unbuilt","some","row","partial","live","sitePublish","published","drafts","stayed","pages","Boolean","join"],"mappings":"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"}
|
|
@@ -154,7 +154,9 @@ export interface AiJobStepRunStop {
|
|
|
154
154
|
}
|
|
155
155
|
export type AiJobOutputResource = 'screen' | 'reusableComponent' | 'layout' | 'template' | 'form' | 'emailScreen' | 'campaign' | 'product' | 'experiment' | 'workflow' | 'text' | 'theme' | 'seo' | 'insight' | 'crm'
|
|
156
156
|
/** A draft another plugin wrote for a build (AGL-3616), named by `draftResource`. */
|
|
157
|
-
| 'draft' | 'logic' | 'orgAutomation'
|
|
157
|
+
| 'draft' | 'logic' | 'orgAutomation'
|
|
158
|
+
/** A post a guided start wrote into the site's blog (AGL-3676): a content collection entry. */
|
|
159
|
+
| 'entry';
|
|
158
160
|
/**
|
|
159
161
|
* One thing a job wrote. Addressed by resource and id so the console can
|
|
160
162
|
* build the "open draft" link without knowing what the runner did; `text`
|
|
@@ -342,6 +344,12 @@ export interface AiJobItemFailure {
|
|
|
342
344
|
reason: AiJobItemFailureReason;
|
|
343
345
|
/** Customer-safe. */
|
|
344
346
|
message: string;
|
|
347
|
+
/**
|
|
348
|
+
* The checks' own sentence behind a `doctrine-refused` failure (AGL-3596),
|
|
349
|
+
* rule numbers and all: kept on the job for staff and debugging, never on
|
|
350
|
+
* the wire a customer reads.
|
|
351
|
+
*/
|
|
352
|
+
detail?: string;
|
|
345
353
|
}
|
|
346
354
|
export interface AiJobItemLedger {
|
|
347
355
|
/** The unit's slot within the plan: `c0` a creation, `p0` a page, `i0` an item. */
|
|
@@ -368,6 +376,12 @@ export interface AiJobItemLedger {
|
|
|
368
376
|
degradedBy?: string[];
|
|
369
377
|
/** Customer-safe: what the person should know about this item. */
|
|
370
378
|
note?: string | null;
|
|
379
|
+
/**
|
|
380
|
+
* When this attempt settled, as an ISO string (AGL-3596): the moment the
|
|
381
|
+
* next item started, which is what its progress row counts from. Absent
|
|
382
|
+
* while the item is open, and on a ledger written before it was kept.
|
|
383
|
+
*/
|
|
384
|
+
settledAt?: string | null;
|
|
371
385
|
}
|
|
372
386
|
/** Whether a build's own planning was charged or given back (AGL-3616). */
|
|
373
387
|
export interface AiJobOrchestration {
|
|
@@ -538,6 +552,13 @@ export interface AiJobSummary {
|
|
|
538
552
|
* on every other kind.
|
|
539
553
|
*/
|
|
540
554
|
siteInputs?: Record<string, unknown> | null;
|
|
555
|
+
/**
|
|
556
|
+
* Whether the machine confirms this job's plan itself (AGL-3594): a guided
|
|
557
|
+
* site start. Its plan parks for the instant the confirmation takes, and a
|
|
558
|
+
* surface reads that park as the build going on (AGL-3596). Absent on every
|
|
559
|
+
* other job.
|
|
560
|
+
*/
|
|
561
|
+
autoConfirm?: boolean;
|
|
541
562
|
/** A `build` job's items (AGL-3616); absent on every other job. */
|
|
542
563
|
items?: AiJobItemLedger[];
|
|
543
564
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-jobs.types.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 { ITimestamp } from '@aglyn/shared-util-timestamp'\nimport type { AiEffort } from '../providers/contract'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport type { AiBuildPlan } from './ai-build-plan'\n\n/**\n * AI generation jobs (AGL-2904).\n *\n * A job is one customer brief carried through one or more model steps by a\n * Firestore state machine, so that work longer than a request can survive\n * the request: the console route runs what it can inline, and the platform\n * job beat resumes whatever is left. The document is the whole state — no\n * process holds anything the next beat cannot read back.\n *\n * ── Outputs are drafts, never a publish ──────────────────────────────────\n *\n * Every output a job produces is a NEW draft or a new unpublished version of\n * an existing resource. A job never flips a version pointer, never registers\n * a route and never sends anything; a person opens the draft the job names\n * and publishes it through the door that already exists for that resource.\n * That is what lets a job run with no visitor, no session and no confirm\n * gate — the thing it writes cannot be seen by anyone but the workspace\n * until a member chooses otherwise.\n *\n * ── The lease ────────────────────────────────────────────────────────────\n *\n * A step runs under a lease: an owner id and an `until` instant. Two beats\n * fire a minute apart against a 120 s function timeout, and a console route\n * may still be running the first step when the first beat looks, so the\n * lease is what stops the same step being run — and metered — twice. A\n * claim is a transaction that refuses while another owner's lease is live;\n * an expired lease is simply claimable, which is also how a step abandoned\n * by a frozen process is recovered.\n */\n\n/**\n * What a job produces. `text` and `theme` have runners; every other kind is\n * registered and refuses with \"not available yet\" until its own issue lands,\n * so a kind's presence in the union says the job model accepts it, not that\n * a runner exists.\n */\nexport type AiJobKind =\n | 'page'\n | 'component'\n | 'layout'\n | 'template'\n | 'edit'\n | 'seo'\n | 'site'\n | 'email'\n | 'campaign'\n | 'form'\n | 'experiment'\n | 'insight'\n | 'products'\n | 'crm'\n | 'onboarding'\n | 'workflow'\n | 'logic'\n | 'text'\n | 'theme'\n | 'build'\n\n/** The union as a value, so a route can validate a body against it. */\nexport const AI_JOB_KINDS: readonly AiJobKind[] = [\n 'page',\n 'component',\n 'layout',\n 'template',\n 'edit',\n 'seo',\n 'site',\n 'email',\n 'campaign',\n 'form',\n 'experiment',\n 'insight',\n 'products',\n 'crm',\n 'onboarding',\n 'workflow',\n 'logic',\n 'text',\n 'theme',\n 'build',\n]\n\n/**\n * `queued` — waiting for a runner; `running` — a step holds the lease;\n * `needs_input` — the job stopped for something only the workspace can\n * change (credits, a cap, a switch), and the beat tries it again once that\n * may have changed; `needs_review` — the job stopped for a person's decision\n * (AGL-2935): its plan is ready to confirm, or an answer broke a building\n * rule on its re-ask. No timer can make that decision, so neither the beat\n * nor anything but that person resumes it. The last three are terminal.\n */\nexport type AiJobStatus =\n | 'queued'\n | 'running'\n | 'needs_input'\n | 'needs_review'\n | 'done'\n | 'failed'\n | 'canceled'\n\nexport const AI_JOB_TERMINAL_STATUSES: readonly AiJobStatus[] = [\n 'done',\n 'failed',\n 'canceled',\n]\n\nexport type AiJobStepStatus = 'pending' | 'running' | 'done' | 'failed'\n\n/** The step a planned kind runs first (AGL-2935): its plan, before anything is built. */\nexport const AI_JOB_PLAN_STEP = 'plan'\n\nexport interface AiJobStep {\n /** Stable within the job (`plan`, `draft`, `generate`), unique per job. */\n name: string\n status: AiJobStepStatus\n startedAt?: ITimestamp | null\n endedAt?: ITimestamp | null\n /** Credits this step has actually cost, at the plan's credit rate. */\n creditsSpent: number\n /** Customer-safe only; provider detail goes to the server log. */\n error?: string | null\n /**\n * How many times a runner has been handed this step. A retryable provider\n * failure hands the step back as `pending`; the count is what stops a\n * provider outage re-running a step forever.\n */\n attempts?: number\n /**\n * Passes of a step that works through a list (AGL-2910): each one a\n * recorded, metered exchange that asked to continue. Attempts count again\n * from zero after a pass; this count is what bounds the step.\n */\n passes?: number\n /**\n * What the step's model calls cost in tokens and time (AGL-2937), summed\n * over every run that reached the provider. Absent on a step none has.\n */\n tokens?: AiJobStepTokens\n /**\n * The ids the step writes its drafts under, by draft (AGL-3079): minted\n * when the job is created, so every run of the step names the same drafts.\n * Absent on a step that writes none; a step that records none names its\n * drafts by the job's own id.\n */\n draftIds?: Partial<Record<AiJobDraftSlot, string>>\n}\n\n/**\n * A draft a job's kind always writes (AGL-3079): a screen, layout, template,\n * form or component; an email design; a campaign; an automation.\n */\nexport type AiJobDraftSlot =\n | 'screen'\n | 'layout'\n | 'template'\n | 'form'\n | 'component'\n | 'email'\n | 'campaign'\n | 'workflow'\n\n/**\n * A step's measure (AGL-2937): the four token counts the meter prices, the\n * time spent in the runner, and the model and thinking effort of the last\n * run. The machine writes it beside `creditsSpent`, so what a kind of step\n * costs in tokens is read off the job rather than inferred from the month.\n */\nexport interface AiJobStepTokens {\n /** Prompt tokens sent uncached. */\n input: number\n /** Prompt tokens read from the prompt cache. */\n cachedRead: number\n /** Prompt tokens written to the prompt cache. */\n cacheWrite: number\n /** Tokens generated, thinking included. */\n output: number\n /** The model the last run was served by. */\n model: string | null\n /** The thinking effort the last run asked for; `null` when it named none. */\n effort: AiEffort | null\n /** Milliseconds spent in the runner, summed over runs. */\n latencyMs: number\n /** Runs that reached the provider. */\n runs: number\n /**\n * Why the step's latest runs stopped (AGL-3042), oldest first and the last\n * run last: each run's stop reason and the tokens it generated, so a pass\n * cut off at its ceiling reads off the job rather than out of the sums. At\n * most `AI_JOB_STEP_LAST_RUNS` are kept; absent on a step measured before.\n */\n lastRuns?: AiJobStepRunStop[]\n}\n\n/** One run of a step, by why it stopped (AGL-3042). */\nexport interface AiJobStepRunStop {\n /**\n * The stop reason of the run's last model call — `tool_use` or `end_turn`\n * for an answer, `max_tokens` for one cut off at its ceiling, `refusal` —\n * and `null` when the provider named none.\n */\n stopReason: string | null\n /** Tokens the run generated, over every model call it made. */\n output: number\n}\n\nexport type AiJobOutputResource =\n | 'screen'\n | 'reusableComponent'\n | 'layout'\n | 'template'\n | 'form'\n | 'emailScreen'\n | 'campaign'\n | 'product'\n | 'experiment'\n | 'workflow'\n | 'text'\n | 'theme'\n | 'seo'\n | 'insight'\n | 'crm'\n /** A draft another plugin wrote for a build (AGL-3616), named by `draftResource`. */\n | 'draft'\n | 'logic'\n | 'orgAutomation'\n\n/**\n * One thing a job wrote. Addressed by resource and id so the console can\n * build the \"open draft\" link without knowing what the runner did; `text`\n * has no document of its own, so its body rides on the output itself.\n *\n * A `theme` output is a proposal rather than a document (AGL-2938). A site's\n * theme is fields on its host document, and writing those fields is the save\n * the Theme section's editor performs, which a job never does. So the change\n * set rides on the output as `proposal`, and a person puts it in the editor\n * and saves it there, or does not.\n *\n * An `seo` output is a proposal rather than a document (AGL-2910): search\n * listing values for a page or a product, or a site audit's findings and\n * fixes. A screen's listing is served from the screen document itself, so a\n * job never writes it; the values ride on the output as `proposal`, a person\n * puts them in the editor and saves them there, and an audit's content fixes\n * become new versions only when a person applies them.\n *\n * A `crm` output is a proposal too (AGL-2917): a record's summary and next\n * step, an email draft, or an import's column matches. The CRM's own task\n * form, stage route, composer and import drawer are the writes, and a person\n * makes each of them, or does not.\n *\n * A `logic` output is a proposal too (AGL-3603): a site function or variable\n * the Functions & Variables editor opens unsaved. Saving it is the editor's\n * write, which a person makes, or does not.\n *\n * An `orgAutomation` output is a proposal too (AGL-3603): one of the\n * workspace's automations, which the Org automations editor opens unsaved and\n * switched off. `hostId` is `null`: it belongs to no one site.\n */\nexport interface AiJobOutput {\n resource: AiJobOutputResource\n id: string\n /** The unpublished version the job created, where the resource has versions. */\n versionId?: string | null\n /** `null` for an output that belongs to the org rather than one site. */\n hostId: string | null\n /**\n * The site's subdomain, which is what a console URL names a site by: the\n * `[host]` segment resolves by subdomain, so a link built from `hostId`\n * names no site the console can open.\n */\n hostSubdomain?: string | null\n label: string\n /** The body of a `text` output. Absent on every other resource. */\n text?: string\n /**\n * The change set of an output a person applies rather than opens — a\n * `theme` or `seo` proposal — in the shape its job kind's runner defines.\n */\n proposal?: Record<string, unknown>\n /**\n * What the person decides next about this output, in customer-safe words:\n * a routing the draft could not store, or part of the brief it could not\n * build. Absent when there is nothing to decide.\n */\n note?: string | null\n /**\n * What a first visit is estimated to transfer, as the doctrine measured\n * the generated document (AGL-2935) — the weight the proposal shows before\n * anything is applied. Absent where the output is not a page's document.\n */\n load?: AiLoadEstimate | null\n /**\n * For a `draft` output (AGL-3616): the resource whose owner wrote it, as\n * its writer is registered on the resource-draft seam.\n */\n draftResource?: string\n}\n\nexport interface AiJobLease {\n /** The process that holds the step: a route request id or a beat id. */\n owner: string\n until: ITimestamp\n}\n\n/** Why a job stopped for a person (AGL-2935). */\nexport type AiJobReviewReason =\n /** The plan step proposed a plan; generation waits for it to be confirmed. */\n | 'plan'\n /** An answer broke a building rule, and so did the answer to the re-ask. */\n | 'doctrine'\n /**\n * The site is at an allowance its plan sets for what the step writes, such\n * as its shared layouts or its templates (AGL-2909). Freeing one, or a plan\n * that holds more, is the member's decision; trying again runs the step\n * again.\n */\n | 'limit'\n\n/**\n * A building rule an answer broke, as the job keeps it: the number, the code,\n * the sentence, and where in the answer it was broken (AGL-3078).\n */\nexport interface AiJobRuleFinding {\n rule: number | null\n code: string\n /** Customer-safe. */\n message: string\n /** The nodes the finding names, by the ids the refused answer gave them. Absent when it names none. */\n nodeIds?: string[]\n /** The plan entries or answer fields it names, as `screens[0].sections[2]`. Absent when it names none. */\n paths?: string[]\n}\n\n/**\n * One node of the parts of a refused answer its findings name (AGL-3078), as\n * the review keeps it: the node's place, its element and the names of what it\n * sets, and never its copy. A Grid keeps the values of its layout props as\n * they were written, so a review can tell a shape the rules refuse from one\n * the model was asked to mend and did not.\n */\nexport interface AiJobReviewOutlineNode {\n /** The node's id, as the refused answer wrote it. */\n id: string\n /** How far below a node a finding names it sits: 0 for that node. */\n depth: number\n componentId: string\n /** The props it sets, by name only. */\n props: string[]\n /** The `sx` keys it sets, by name only; absent when it sets none. */\n sx?: string[]\n /** A Grid's layout props, `container`, `size`, `direction`, `spacing` and the rest, as written. */\n grid?: Record<string, string | number | boolean | null>\n /** Its children's element ids, in order. */\n children: string[]\n}\n\nexport interface AiJobReview {\n reason: AiJobReviewReason\n /** Customer-safe: what the person is asked to decide. */\n message: string\n /** The rules the last answer broke; empty for a plan. */\n findings: AiJobRuleFinding[]\n /**\n * The parts of the refused answer its findings name, each node with the\n * nodes below it (AGL-3078); absent when they name none. Kept with the job\n * for diagnosis, and shown to staff.\n */\n outline?: AiJobReviewOutlineNode[]\n /**\n * The checks' own sentence, where `message` was written for the member\n * instead (AGL-3594): rule numbers and all, for staff. Its presence is what\n * tells the drawer the findings are staff reading too.\n */\n detail?: string\n /**\n * Why trying again cannot work yet (AGL-3594) — the Free allowance left is\n * less than the step can cost — or absent when it can. The button is drawn\n * disabled with this beside it.\n */\n retryRefusal?: string\n}\n\nexport type AiJobPlanStatus = 'proposed' | 'confirmed'\n\n/** The plan a job builds from, as the job keeps it once its plan step proposed it. */\nexport interface AiJobPlan extends AiBuildPlan {\n status: AiJobPlanStatus\n /** Names for the inventory ids the plan references, read when it was made. */\n labels: Record<string, string>\n proposedAt: ITimestamp\n confirmedAt: ITimestamp | null\n /** The member who confirmed it. */\n confirmedBy: string | null\n /**\n * A digest of the whole request that produced this plan (AGL-2937): the\n * job's kind, site, brief and scalar inputs, the model that answered, and\n * the prompt as it was rendered — the site inventory included. A later job\n * whose request hashes the same reuses this plan rather than paying for the\n * same answer again. A hash of the request, never of the answer, and no\n * part of the brief is recoverable from it.\n */\n key?: string\n /** The job this plan was reused from, when it was not asked for again. */\n reusedFrom?: string\n}\n\n/**\n * Where one item of a `build` job stands (AGL-3616). A build settles item by\n * item: a failed item gives back its own spend and the rest of the plan still\n * runs, so the job keeps one row per item, written only by the machine's\n * `recordStep` — in the same transaction as the spend it accounts for — and\n * by the resume door's retry.\n */\nexport type AiJobItemStatus = 'pending' | 'running' | 'succeeded' | 'degraded' | 'failed' | 'skipped'\n\n/** Why an item failed: a give-back reason when it was ours, else the model's refusal or a review. */\nexport type AiJobItemFailureReason =\n | 'plan-refused'\n | 'doctrine-refused'\n | 'provider'\n | 'timeout'\n | 'step-failure'\n | 'unavailable'\n | 'refused'\n | 'review'\n\nexport interface AiJobItemFailure {\n /** Whether the failure was on our side: only then is the item's spend given back. */\n ours: boolean\n reason: AiJobItemFailureReason\n /** Customer-safe. */\n message: string\n}\n\nexport interface AiJobItemLedger {\n /** The unit's slot within the plan: `c0` a creation, `p0` a page, `i0` an item. */\n slot: string\n /** The capability operation that builds it: `page`, `form`, or an owner's op. */\n op: string\n /** What a person reads: the page's title, the creation's or item's name. */\n label: string\n status: AiJobItemStatus\n /** Bumped by Try again; the refund key names it, so each attempt gives back once. */\n attempt: number\n /** What this item's passes cost, every attempt. */\n creditsSpent: number\n /** What this attempt's passes cost so far: what a failure of this attempt gives back. */\n attemptCredits?: number\n /** What was given back for this item's failures. */\n creditsRefunded: number\n /** The key the latest give-back for this item was made under. */\n refundKey?: string\n failure?: AiJobItemFailure | null\n /** The ids of the outputs this item produced. */\n outputs: string[]\n /** The slots whose failure changed how this item was built. */\n degradedBy?: string[]\n /** Customer-safe: what the person should know about this item. */\n note?: string | null\n}\n\n/** Whether a build's own planning was charged or given back (AGL-3616). */\nexport interface AiJobOrchestration {\n /** What the plan step spent. */\n creditsSpent: number\n /** `charged` once anything was delivered; `refunded` when nothing was. */\n settled: 'pending' | 'charged' | 'refunded'\n}\n\nexport interface AiJob {\n $id: string\n orgId: string\n hostId?: string | null\n kind: AiJobKind\n status: AiJobStatus\n /** The customer's brief, verbatim. Expires with the document. */\n brief: string\n /** Kind-specific inputs the runner reads (a screen id to edit, a tone). */\n inputs: Record<string, unknown>\n /**\n * The model the creator picked for the job's steps (AGL-2942), or `null`\n * for Auto. Each step honors it only where the plan and the allotment\n * allowlists allow it, and runs on the routing table otherwise.\n */\n model?: string | null\n steps: AiJobStep[]\n outputs: AiJobOutput[]\n /**\n * The nominal credit figure held against the job while steps are\n * outstanding. The meter's real bound is the per-step message reservation\n * and the org's monthly ceiling; this is the number the console shows so\n * a running job is not read as free.\n */\n creditsReserved: number\n creditsSpent: number\n /**\n * Credits the job gave back because it failed on our side (AGL-3594),\n * summed over its give-backs; absent when it gave none. `creditsSpent`\n * stays what ran, so what the job cost is the one less the other.\n */\n refundedCredits?: number\n /** Why the latest give-back was made (`AiJobRefundReason`). */\n refundReason?: string\n /** How many give-backs the job has had: the next one's key. */\n refunds?: number\n createdBy: string\n createdAt: ITimestamp\n updatedAt: ITimestamp\n /** TTL — the brief is verbatim customer text and expires like an exchange. */\n expiresAt: ITimestamp\n /** Customer-safe only. */\n error?: string | null\n lease?: AiJobLease | null\n /** The plan the job builds from, once its plan step proposed one. */\n plan?: AiJobPlan | null\n /** What the person is asked to decide while the job is `needs_review`. */\n review?: AiJobReview | null\n /**\n * What a person applied from the job's outputs (AGL-2910). Written by the\n * door that applied them, never by a step runner, and absent until then.\n */\n applied?: AiJobApplied | null\n /**\n * What a guided site start put live when it finished (AGL-3596): written\n * once by the site step's last pass, never by a person. Absent on every\n * other job, which leaves its pages as drafts.\n */\n sitePublish?: AiJobSitePublish | null\n /** A `build` job's items, one row each (AGL-3616); absent on every other kind. */\n items?: AiJobItemLedger[] | null\n /** A `build` job's planning spend and how it settled (AGL-3616). */\n orchestration?: AiJobOrchestration | null\n}\n\n/**\n * A guided site start's publish (AGL-3596): the pages that went live, with\n * the address each answers, and the pages that stayed drafts, each with the\n * plain sentence for why. A page that stayed a draft never fails the job.\n */\nexport interface AiJobSitePublish {\n /** The site's live address, `https://<subdomain>.aglyn.app/`; `null` when the site has no subdomain. */\n liveUrl: string | null\n published: Array<{ id: string; label: string; path: string }>\n drafts: Array<{ id: string; label: string; reason: string }>\n}\n\n/**\n * The record of an apply (AGL-2910): what became a new unpublished version,\n * and what waits in its editor for a person to save.\n */\nexport interface AiJobApplied {\n at: ITimestamp\n /** The member who applied. */\n by: string\n /** The new version opened for each resource, by resource id. */\n versions: Record<string, string>\n /** Resource ids whose proposed values wait in their editor, unsaved. */\n staged: string[]\n}\n\n/** The plan on the wire: every instant an ISO string. */\nexport interface AiJobPlanSummary extends AiBuildPlan {\n status: AiJobPlanStatus\n labels: Record<string, string>\n proposedAt: string | null\n confirmedAt: string | null\n confirmedBy: string | null\n key?: string\n reusedFrom?: string\n}\n\n/**\n * The wire form the console reads: every instant an ISO string, the lease\n * reduced to whether a runner holds the job. The brief and the outputs\n * travel as they are — the reader is a member of the org that wrote them.\n */\nexport interface AiJobSummary {\n id: string\n orgId: string\n hostId: string | null\n kind: AiJobKind\n status: AiJobStatus\n brief: string\n /**\n * The batch a door created this job as one of (AGL-2911), read off the\n * job's inputs; `null` for a job created on its own. The console groups a\n * run of jobs by it, which is the one thing it cannot do from a list whose\n * rows carry no inputs — and the only input the wire form carries, because\n * a batch id names nothing the member wrote.\n */\n batch: string | null\n steps: Array<{\n name: string\n status: AiJobStepStatus\n startedAt: string | null\n endedAt: string | null\n creditsSpent: number\n error: string | null\n }>\n outputs: AiJobOutput[]\n creditsReserved: number\n creditsSpent: number\n /** Credits given back for a failure on our side (AGL-3594); 0 when none. */\n refundedCredits?: number\n createdBy: string\n createdAt: string\n updatedAt: string\n error: string | null\n running: boolean\n plan: AiJobPlanSummary | null\n review: AiJobReview | null\n /** What a person applied from the outputs (AGL-2910); instants as ISO strings. */\n applied?: {\n at: string | null\n by: string\n versions: Record<string, string>\n staged: string[]\n } | null\n /** What a guided site start put live (AGL-3596); absent on every other job. */\n sitePublish?: AiJobSitePublish | null\n /**\n * A site job's answers (AGL-3596): the guided start's questions as the person\n * answered them, so a failed start's Try again reopens them filled in. The\n * scalar site inputs only, never anything else a job's inputs carry; absent\n * on every other kind.\n */\n siteInputs?: Record<string, unknown> | null\n /** A `build` job's items (AGL-3616); absent on every other job. */\n items?: AiJobItemLedger[]\n /**\n * Whether a build's request asked to publish (AGL-3616), so its plan card\n * offers the box that confirms it. Absent on every other job.\n */\n publishAsked?: boolean\n /** A `build` job's planning spend and how it settled (AGL-3616). */\n orchestration?: AiJobOrchestration\n}\n"],"names":["AI_JOB_KINDS","AI_JOB_TERMINAL_STATUSES","AI_JOB_PLAN_STEP"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAiED,qEAAqE,GACrE,OAAO,MAAMA,eAAqC;IAChD;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAA;AAoBD,OAAO,MAAMC,2BAAmD;IAC9D;IACA;IACA;CACD,CAAA;AAID,uFAAuF,GACvF,OAAO,MAAMC,mBAAmB,OAAM"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-jobs.types.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 { ITimestamp } from '@aglyn/shared-util-timestamp'\nimport type { AiEffort } from '../providers/contract'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport type { AiBuildPlan } from './ai-build-plan'\n\n/**\n * AI generation jobs (AGL-2904).\n *\n * A job is one customer brief carried through one or more model steps by a\n * Firestore state machine, so that work longer than a request can survive\n * the request: the console route runs what it can inline, and the platform\n * job beat resumes whatever is left. The document is the whole state — no\n * process holds anything the next beat cannot read back.\n *\n * ── Outputs are drafts, never a publish ──────────────────────────────────\n *\n * Every output a job produces is a NEW draft or a new unpublished version of\n * an existing resource. A job never flips a version pointer, never registers\n * a route and never sends anything; a person opens the draft the job names\n * and publishes it through the door that already exists for that resource.\n * That is what lets a job run with no visitor, no session and no confirm\n * gate — the thing it writes cannot be seen by anyone but the workspace\n * until a member chooses otherwise.\n *\n * ── The lease ────────────────────────────────────────────────────────────\n *\n * A step runs under a lease: an owner id and an `until` instant. Two beats\n * fire a minute apart against a 120 s function timeout, and a console route\n * may still be running the first step when the first beat looks, so the\n * lease is what stops the same step being run — and metered — twice. A\n * claim is a transaction that refuses while another owner's lease is live;\n * an expired lease is simply claimable, which is also how a step abandoned\n * by a frozen process is recovered.\n */\n\n/**\n * What a job produces. `text` and `theme` have runners; every other kind is\n * registered and refuses with \"not available yet\" until its own issue lands,\n * so a kind's presence in the union says the job model accepts it, not that\n * a runner exists.\n */\nexport type AiJobKind =\n | 'page'\n | 'component'\n | 'layout'\n | 'template'\n | 'edit'\n | 'seo'\n | 'site'\n | 'email'\n | 'campaign'\n | 'form'\n | 'experiment'\n | 'insight'\n | 'products'\n | 'crm'\n | 'onboarding'\n | 'workflow'\n | 'logic'\n | 'text'\n | 'theme'\n | 'build'\n\n/** The union as a value, so a route can validate a body against it. */\nexport const AI_JOB_KINDS: readonly AiJobKind[] = [\n 'page',\n 'component',\n 'layout',\n 'template',\n 'edit',\n 'seo',\n 'site',\n 'email',\n 'campaign',\n 'form',\n 'experiment',\n 'insight',\n 'products',\n 'crm',\n 'onboarding',\n 'workflow',\n 'logic',\n 'text',\n 'theme',\n 'build',\n]\n\n/**\n * `queued` — waiting for a runner; `running` — a step holds the lease;\n * `needs_input` — the job stopped for something only the workspace can\n * change (credits, a cap, a switch), and the beat tries it again once that\n * may have changed; `needs_review` — the job stopped for a person's decision\n * (AGL-2935): its plan is ready to confirm, or an answer broke a building\n * rule on its re-ask. No timer can make that decision, so neither the beat\n * nor anything but that person resumes it. The last three are terminal.\n */\nexport type AiJobStatus =\n | 'queued'\n | 'running'\n | 'needs_input'\n | 'needs_review'\n | 'done'\n | 'failed'\n | 'canceled'\n\nexport const AI_JOB_TERMINAL_STATUSES: readonly AiJobStatus[] = [\n 'done',\n 'failed',\n 'canceled',\n]\n\nexport type AiJobStepStatus = 'pending' | 'running' | 'done' | 'failed'\n\n/** The step a planned kind runs first (AGL-2935): its plan, before anything is built. */\nexport const AI_JOB_PLAN_STEP = 'plan'\n\nexport interface AiJobStep {\n /** Stable within the job (`plan`, `draft`, `generate`), unique per job. */\n name: string\n status: AiJobStepStatus\n startedAt?: ITimestamp | null\n endedAt?: ITimestamp | null\n /** Credits this step has actually cost, at the plan's credit rate. */\n creditsSpent: number\n /** Customer-safe only; provider detail goes to the server log. */\n error?: string | null\n /**\n * How many times a runner has been handed this step. A retryable provider\n * failure hands the step back as `pending`; the count is what stops a\n * provider outage re-running a step forever.\n */\n attempts?: number\n /**\n * Passes of a step that works through a list (AGL-2910): each one a\n * recorded, metered exchange that asked to continue. Attempts count again\n * from zero after a pass; this count is what bounds the step.\n */\n passes?: number\n /**\n * What the step's model calls cost in tokens and time (AGL-2937), summed\n * over every run that reached the provider. Absent on a step none has.\n */\n tokens?: AiJobStepTokens\n /**\n * The ids the step writes its drafts under, by draft (AGL-3079): minted\n * when the job is created, so every run of the step names the same drafts.\n * Absent on a step that writes none; a step that records none names its\n * drafts by the job's own id.\n */\n draftIds?: Partial<Record<AiJobDraftSlot, string>>\n}\n\n/**\n * A draft a job's kind always writes (AGL-3079): a screen, layout, template,\n * form or component; an email design; a campaign; an automation.\n */\nexport type AiJobDraftSlot =\n | 'screen'\n | 'layout'\n | 'template'\n | 'form'\n | 'component'\n | 'email'\n | 'campaign'\n | 'workflow'\n\n/**\n * A step's measure (AGL-2937): the four token counts the meter prices, the\n * time spent in the runner, and the model and thinking effort of the last\n * run. The machine writes it beside `creditsSpent`, so what a kind of step\n * costs in tokens is read off the job rather than inferred from the month.\n */\nexport interface AiJobStepTokens {\n /** Prompt tokens sent uncached. */\n input: number\n /** Prompt tokens read from the prompt cache. */\n cachedRead: number\n /** Prompt tokens written to the prompt cache. */\n cacheWrite: number\n /** Tokens generated, thinking included. */\n output: number\n /** The model the last run was served by. */\n model: string | null\n /** The thinking effort the last run asked for; `null` when it named none. */\n effort: AiEffort | null\n /** Milliseconds spent in the runner, summed over runs. */\n latencyMs: number\n /** Runs that reached the provider. */\n runs: number\n /**\n * Why the step's latest runs stopped (AGL-3042), oldest first and the last\n * run last: each run's stop reason and the tokens it generated, so a pass\n * cut off at its ceiling reads off the job rather than out of the sums. At\n * most `AI_JOB_STEP_LAST_RUNS` are kept; absent on a step measured before.\n */\n lastRuns?: AiJobStepRunStop[]\n}\n\n/** One run of a step, by why it stopped (AGL-3042). */\nexport interface AiJobStepRunStop {\n /**\n * The stop reason of the run's last model call — `tool_use` or `end_turn`\n * for an answer, `max_tokens` for one cut off at its ceiling, `refusal` —\n * and `null` when the provider named none.\n */\n stopReason: string | null\n /** Tokens the run generated, over every model call it made. */\n output: number\n}\n\nexport type AiJobOutputResource =\n | 'screen'\n | 'reusableComponent'\n | 'layout'\n | 'template'\n | 'form'\n | 'emailScreen'\n | 'campaign'\n | 'product'\n | 'experiment'\n | 'workflow'\n | 'text'\n | 'theme'\n | 'seo'\n | 'insight'\n | 'crm'\n /** A draft another plugin wrote for a build (AGL-3616), named by `draftResource`. */\n | 'draft'\n | 'logic'\n | 'orgAutomation'\n /** A post a guided start wrote into the site's blog (AGL-3676): a content collection entry. */\n | 'entry'\n\n/**\n * One thing a job wrote. Addressed by resource and id so the console can\n * build the \"open draft\" link without knowing what the runner did; `text`\n * has no document of its own, so its body rides on the output itself.\n *\n * A `theme` output is a proposal rather than a document (AGL-2938). A site's\n * theme is fields on its host document, and writing those fields is the save\n * the Theme section's editor performs, which a job never does. So the change\n * set rides on the output as `proposal`, and a person puts it in the editor\n * and saves it there, or does not.\n *\n * An `seo` output is a proposal rather than a document (AGL-2910): search\n * listing values for a page or a product, or a site audit's findings and\n * fixes. A screen's listing is served from the screen document itself, so a\n * job never writes it; the values ride on the output as `proposal`, a person\n * puts them in the editor and saves them there, and an audit's content fixes\n * become new versions only when a person applies them.\n *\n * A `crm` output is a proposal too (AGL-2917): a record's summary and next\n * step, an email draft, or an import's column matches. The CRM's own task\n * form, stage route, composer and import drawer are the writes, and a person\n * makes each of them, or does not.\n *\n * A `logic` output is a proposal too (AGL-3603): a site function or variable\n * the Functions & Variables editor opens unsaved. Saving it is the editor's\n * write, which a person makes, or does not.\n *\n * An `orgAutomation` output is a proposal too (AGL-3603): one of the\n * workspace's automations, which the Org automations editor opens unsaved and\n * switched off. `hostId` is `null`: it belongs to no one site.\n */\nexport interface AiJobOutput {\n resource: AiJobOutputResource\n id: string\n /** The unpublished version the job created, where the resource has versions. */\n versionId?: string | null\n /** `null` for an output that belongs to the org rather than one site. */\n hostId: string | null\n /**\n * The site's subdomain, which is what a console URL names a site by: the\n * `[host]` segment resolves by subdomain, so a link built from `hostId`\n * names no site the console can open.\n */\n hostSubdomain?: string | null\n label: string\n /** The body of a `text` output. Absent on every other resource. */\n text?: string\n /**\n * The change set of an output a person applies rather than opens — a\n * `theme` or `seo` proposal — in the shape its job kind's runner defines.\n */\n proposal?: Record<string, unknown>\n /**\n * What the person decides next about this output, in customer-safe words:\n * a routing the draft could not store, or part of the brief it could not\n * build. Absent when there is nothing to decide.\n */\n note?: string | null\n /**\n * What a first visit is estimated to transfer, as the doctrine measured\n * the generated document (AGL-2935) — the weight the proposal shows before\n * anything is applied. Absent where the output is not a page's document.\n */\n load?: AiLoadEstimate | null\n /**\n * For a `draft` output (AGL-3616): the resource whose owner wrote it, as\n * its writer is registered on the resource-draft seam.\n */\n draftResource?: string\n}\n\nexport interface AiJobLease {\n /** The process that holds the step: a route request id or a beat id. */\n owner: string\n until: ITimestamp\n}\n\n/** Why a job stopped for a person (AGL-2935). */\nexport type AiJobReviewReason =\n /** The plan step proposed a plan; generation waits for it to be confirmed. */\n | 'plan'\n /** An answer broke a building rule, and so did the answer to the re-ask. */\n | 'doctrine'\n /**\n * The site is at an allowance its plan sets for what the step writes, such\n * as its shared layouts or its templates (AGL-2909). Freeing one, or a plan\n * that holds more, is the member's decision; trying again runs the step\n * again.\n */\n | 'limit'\n\n/**\n * A building rule an answer broke, as the job keeps it: the number, the code,\n * the sentence, and where in the answer it was broken (AGL-3078).\n */\nexport interface AiJobRuleFinding {\n rule: number | null\n code: string\n /** Customer-safe. */\n message: string\n /** The nodes the finding names, by the ids the refused answer gave them. Absent when it names none. */\n nodeIds?: string[]\n /** The plan entries or answer fields it names, as `screens[0].sections[2]`. Absent when it names none. */\n paths?: string[]\n}\n\n/**\n * One node of the parts of a refused answer its findings name (AGL-3078), as\n * the review keeps it: the node's place, its element and the names of what it\n * sets, and never its copy. A Grid keeps the values of its layout props as\n * they were written, so a review can tell a shape the rules refuse from one\n * the model was asked to mend and did not.\n */\nexport interface AiJobReviewOutlineNode {\n /** The node's id, as the refused answer wrote it. */\n id: string\n /** How far below a node a finding names it sits: 0 for that node. */\n depth: number\n componentId: string\n /** The props it sets, by name only. */\n props: string[]\n /** The `sx` keys it sets, by name only; absent when it sets none. */\n sx?: string[]\n /** A Grid's layout props, `container`, `size`, `direction`, `spacing` and the rest, as written. */\n grid?: Record<string, string | number | boolean | null>\n /** Its children's element ids, in order. */\n children: string[]\n}\n\nexport interface AiJobReview {\n reason: AiJobReviewReason\n /** Customer-safe: what the person is asked to decide. */\n message: string\n /** The rules the last answer broke; empty for a plan. */\n findings: AiJobRuleFinding[]\n /**\n * The parts of the refused answer its findings name, each node with the\n * nodes below it (AGL-3078); absent when they name none. Kept with the job\n * for diagnosis, and shown to staff.\n */\n outline?: AiJobReviewOutlineNode[]\n /**\n * The checks' own sentence, where `message` was written for the member\n * instead (AGL-3594): rule numbers and all, for staff. Its presence is what\n * tells the drawer the findings are staff reading too.\n */\n detail?: string\n /**\n * Why trying again cannot work yet (AGL-3594) — the Free allowance left is\n * less than the step can cost — or absent when it can. The button is drawn\n * disabled with this beside it.\n */\n retryRefusal?: string\n}\n\nexport type AiJobPlanStatus = 'proposed' | 'confirmed'\n\n/** The plan a job builds from, as the job keeps it once its plan step proposed it. */\nexport interface AiJobPlan extends AiBuildPlan {\n status: AiJobPlanStatus\n /** Names for the inventory ids the plan references, read when it was made. */\n labels: Record<string, string>\n proposedAt: ITimestamp\n confirmedAt: ITimestamp | null\n /** The member who confirmed it. */\n confirmedBy: string | null\n /**\n * A digest of the whole request that produced this plan (AGL-2937): the\n * job's kind, site, brief and scalar inputs, the model that answered, and\n * the prompt as it was rendered — the site inventory included. A later job\n * whose request hashes the same reuses this plan rather than paying for the\n * same answer again. A hash of the request, never of the answer, and no\n * part of the brief is recoverable from it.\n */\n key?: string\n /** The job this plan was reused from, when it was not asked for again. */\n reusedFrom?: string\n}\n\n/**\n * Where one item of a `build` job stands (AGL-3616). A build settles item by\n * item: a failed item gives back its own spend and the rest of the plan still\n * runs, so the job keeps one row per item, written only by the machine's\n * `recordStep` — in the same transaction as the spend it accounts for — and\n * by the resume door's retry.\n */\nexport type AiJobItemStatus = 'pending' | 'running' | 'succeeded' | 'degraded' | 'failed' | 'skipped'\n\n/** Why an item failed: a give-back reason when it was ours, else the model's refusal or a review. */\nexport type AiJobItemFailureReason =\n | 'plan-refused'\n | 'doctrine-refused'\n | 'provider'\n | 'timeout'\n | 'step-failure'\n | 'unavailable'\n | 'refused'\n | 'review'\n\nexport interface AiJobItemFailure {\n /** Whether the failure was on our side: only then is the item's spend given back. */\n ours: boolean\n reason: AiJobItemFailureReason\n /** Customer-safe. */\n message: string\n /**\n * The checks' own sentence behind a `doctrine-refused` failure (AGL-3596),\n * rule numbers and all: kept on the job for staff and debugging, never on\n * the wire a customer reads.\n */\n detail?: string\n}\n\nexport interface AiJobItemLedger {\n /** The unit's slot within the plan: `c0` a creation, `p0` a page, `i0` an item. */\n slot: string\n /** The capability operation that builds it: `page`, `form`, or an owner's op. */\n op: string\n /** What a person reads: the page's title, the creation's or item's name. */\n label: string\n status: AiJobItemStatus\n /** Bumped by Try again; the refund key names it, so each attempt gives back once. */\n attempt: number\n /** What this item's passes cost, every attempt. */\n creditsSpent: number\n /** What this attempt's passes cost so far: what a failure of this attempt gives back. */\n attemptCredits?: number\n /** What was given back for this item's failures. */\n creditsRefunded: number\n /** The key the latest give-back for this item was made under. */\n refundKey?: string\n failure?: AiJobItemFailure | null\n /** The ids of the outputs this item produced. */\n outputs: string[]\n /** The slots whose failure changed how this item was built. */\n degradedBy?: string[]\n /** Customer-safe: what the person should know about this item. */\n note?: string | null\n /**\n * When this attempt settled, as an ISO string (AGL-3596): the moment the\n * next item started, which is what its progress row counts from. Absent\n * while the item is open, and on a ledger written before it was kept.\n */\n settledAt?: string | null\n}\n\n/** Whether a build's own planning was charged or given back (AGL-3616). */\nexport interface AiJobOrchestration {\n /** What the plan step spent. */\n creditsSpent: number\n /** `charged` once anything was delivered; `refunded` when nothing was. */\n settled: 'pending' | 'charged' | 'refunded'\n}\n\nexport interface AiJob {\n $id: string\n orgId: string\n hostId?: string | null\n kind: AiJobKind\n status: AiJobStatus\n /** The customer's brief, verbatim. Expires with the document. */\n brief: string\n /** Kind-specific inputs the runner reads (a screen id to edit, a tone). */\n inputs: Record<string, unknown>\n /**\n * The model the creator picked for the job's steps (AGL-2942), or `null`\n * for Auto. Each step honors it only where the plan and the allotment\n * allowlists allow it, and runs on the routing table otherwise.\n */\n model?: string | null\n steps: AiJobStep[]\n outputs: AiJobOutput[]\n /**\n * The nominal credit figure held against the job while steps are\n * outstanding. The meter's real bound is the per-step message reservation\n * and the org's monthly ceiling; this is the number the console shows so\n * a running job is not read as free.\n */\n creditsReserved: number\n creditsSpent: number\n /**\n * Credits the job gave back because it failed on our side (AGL-3594),\n * summed over its give-backs; absent when it gave none. `creditsSpent`\n * stays what ran, so what the job cost is the one less the other.\n */\n refundedCredits?: number\n /** Why the latest give-back was made (`AiJobRefundReason`). */\n refundReason?: string\n /** How many give-backs the job has had: the next one's key. */\n refunds?: number\n createdBy: string\n createdAt: ITimestamp\n updatedAt: ITimestamp\n /** TTL — the brief is verbatim customer text and expires like an exchange. */\n expiresAt: ITimestamp\n /** Customer-safe only. */\n error?: string | null\n lease?: AiJobLease | null\n /** The plan the job builds from, once its plan step proposed one. */\n plan?: AiJobPlan | null\n /** What the person is asked to decide while the job is `needs_review`. */\n review?: AiJobReview | null\n /**\n * What a person applied from the job's outputs (AGL-2910). Written by the\n * door that applied them, never by a step runner, and absent until then.\n */\n applied?: AiJobApplied | null\n /**\n * What a guided site start put live when it finished (AGL-3596): written\n * once by the site step's last pass, never by a person. Absent on every\n * other job, which leaves its pages as drafts.\n */\n sitePublish?: AiJobSitePublish | null\n /** A `build` job's items, one row each (AGL-3616); absent on every other kind. */\n items?: AiJobItemLedger[] | null\n /** A `build` job's planning spend and how it settled (AGL-3616). */\n orchestration?: AiJobOrchestration | null\n}\n\n/**\n * A guided site start's publish (AGL-3596): the pages that went live, with\n * the address each answers, and the pages that stayed drafts, each with the\n * plain sentence for why. A page that stayed a draft never fails the job.\n */\nexport interface AiJobSitePublish {\n /** The site's live address, `https://<subdomain>.aglyn.app/`; `null` when the site has no subdomain. */\n liveUrl: string | null\n published: Array<{ id: string; label: string; path: string }>\n drafts: Array<{ id: string; label: string; reason: string }>\n}\n\n/**\n * The record of an apply (AGL-2910): what became a new unpublished version,\n * and what waits in its editor for a person to save.\n */\nexport interface AiJobApplied {\n at: ITimestamp\n /** The member who applied. */\n by: string\n /** The new version opened for each resource, by resource id. */\n versions: Record<string, string>\n /** Resource ids whose proposed values wait in their editor, unsaved. */\n staged: string[]\n}\n\n/** The plan on the wire: every instant an ISO string. */\nexport interface AiJobPlanSummary extends AiBuildPlan {\n status: AiJobPlanStatus\n labels: Record<string, string>\n proposedAt: string | null\n confirmedAt: string | null\n confirmedBy: string | null\n key?: string\n reusedFrom?: string\n}\n\n/**\n * The wire form the console reads: every instant an ISO string, the lease\n * reduced to whether a runner holds the job. The brief and the outputs\n * travel as they are — the reader is a member of the org that wrote them.\n */\nexport interface AiJobSummary {\n id: string\n orgId: string\n hostId: string | null\n kind: AiJobKind\n status: AiJobStatus\n brief: string\n /**\n * The batch a door created this job as one of (AGL-2911), read off the\n * job's inputs; `null` for a job created on its own. The console groups a\n * run of jobs by it, which is the one thing it cannot do from a list whose\n * rows carry no inputs — and the only input the wire form carries, because\n * a batch id names nothing the member wrote.\n */\n batch: string | null\n steps: Array<{\n name: string\n status: AiJobStepStatus\n startedAt: string | null\n endedAt: string | null\n creditsSpent: number\n error: string | null\n }>\n outputs: AiJobOutput[]\n creditsReserved: number\n creditsSpent: number\n /** Credits given back for a failure on our side (AGL-3594); 0 when none. */\n refundedCredits?: number\n createdBy: string\n createdAt: string\n updatedAt: string\n error: string | null\n running: boolean\n plan: AiJobPlanSummary | null\n review: AiJobReview | null\n /** What a person applied from the outputs (AGL-2910); instants as ISO strings. */\n applied?: {\n at: string | null\n by: string\n versions: Record<string, string>\n staged: string[]\n } | null\n /** What a guided site start put live (AGL-3596); absent on every other job. */\n sitePublish?: AiJobSitePublish | null\n /**\n * A site job's answers (AGL-3596): the guided start's questions as the person\n * answered them, so a failed start's Try again reopens them filled in. The\n * scalar site inputs only, never anything else a job's inputs carry; absent\n * on every other kind.\n */\n siteInputs?: Record<string, unknown> | null\n /**\n * Whether the machine confirms this job's plan itself (AGL-3594): a guided\n * site start. Its plan parks for the instant the confirmation takes, and a\n * surface reads that park as the build going on (AGL-3596). Absent on every\n * other job.\n */\n autoConfirm?: boolean\n /** A `build` job's items (AGL-3616); absent on every other job. */\n items?: AiJobItemLedger[]\n /**\n * Whether a build's request asked to publish (AGL-3616), so its plan card\n * offers the box that confirms it. Absent on every other job.\n */\n publishAsked?: boolean\n /** A `build` job's planning spend and how it settled (AGL-3616). */\n orchestration?: AiJobOrchestration\n}\n"],"names":["AI_JOB_KINDS","AI_JOB_TERMINAL_STATUSES","AI_JOB_PLAN_STEP"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAiED,qEAAqE,GACrE,OAAO,MAAMA,eAAqC;IAChD;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAA;AAoBD,OAAO,MAAMC,2BAAmD;IAC9D;IACA;IACA;CACD,CAAA;AAID,uFAAuF,GACvF,OAAO,MAAMC,mBAAmB,OAAM"}
|
|
@@ -33,7 +33,11 @@ import type { AiDoctrineViolation } from '../runtime/ai-doctrine-validators';
|
|
|
33
33
|
* computes, and anything in it worth checking.
|
|
34
34
|
*
|
|
35
35
|
* Nothing is written: a proposal rides on the job's output, and the logic
|
|
36
|
-
* editor is where a person saves it, or does not.
|
|
36
|
+
* editor is where a person saves it, or does not. The one exception is a
|
|
37
|
+
* NEW function asked for by a build (AGL-3616), which the person confirmed
|
|
38
|
+
* as a plan: that unit's job writes the checked function through the logic
|
|
39
|
+
* plugin's `function` draft writer, a record nothing on the site uses until
|
|
40
|
+
* a page binds it.
|
|
37
41
|
*/
|
|
38
42
|
export declare const AI_LOGIC_JOB_MODES: readonly ["function", "variable", "explain"];
|
|
39
43
|
export type AiLogicJobMode = (typeof AI_LOGIC_JOB_MODES)[number];
|
|
@@ -106,3 +110,16 @@ export declare const AI_LOGIC_GONE_COPY = "That function no longer exists.";
|
|
|
106
110
|
export declare const AI_LOGIC_NO_FUNCTION_COPY = "The AI could not write a function the site can run from this description. Try describing what goes in, and what should come out.";
|
|
107
111
|
export declare const AI_LOGIC_NO_VARIABLE_COPY = "The AI could not write a variable from this description. Try naming what it holds, and its value.";
|
|
108
112
|
export declare const AI_LOGIC_NO_EXPLANATION_COPY = "The AI could not explain this function. Try again.";
|
|
113
|
+
/**
|
|
114
|
+
* The resource the logic plugin's `function` draft writer is registered
|
|
115
|
+
* under. A `logic` job run as a unit of a build writes its function through
|
|
116
|
+
* it; one a person started proposes it and writes nothing.
|
|
117
|
+
*/
|
|
118
|
+
export declare const AI_LOGIC_FUNCTION_RESOURCE = "function";
|
|
119
|
+
/** Whether a job is a build's unit rather than one a person started: a derived job carries its origin. */
|
|
120
|
+
export declare function aiLogicJobIsBuildUnit(job: {
|
|
121
|
+
inputs?: Readonly<Record<string, unknown>> | null;
|
|
122
|
+
}): boolean;
|
|
123
|
+
export declare const AI_LOGIC_FUNCTION_UNAVAILABLE_COPY = "Functions are not available on this site.";
|
|
124
|
+
export declare const AI_LOGIC_FUNCTION_SAVE_FAILURE_COPY = "The function was written but could not be saved. Try the job again.";
|
|
125
|
+
export declare const AI_LOGIC_FUNCTION_WRITTEN_NOTE = "A new site function, checked against the expression grammar and run once. Nothing on the site uses it until a page binds it.";
|
|
@@ -31,7 +31,11 @@ import { functionGlobals, VARIABLE_NAME_PATTERN } from "@aglyn/aglyn/app-utils/v
|
|
|
31
31
|
* computes, and anything in it worth checking.
|
|
32
32
|
*
|
|
33
33
|
* Nothing is written: a proposal rides on the job's output, and the logic
|
|
34
|
-
* editor is where a person saves it, or does not.
|
|
34
|
+
* editor is where a person saves it, or does not. The one exception is a
|
|
35
|
+
* NEW function asked for by a build (AGL-3616), which the person confirmed
|
|
36
|
+
* as a plan: that unit's job writes the checked function through the logic
|
|
37
|
+
* plugin's `function` draft writer, a record nothing on the site uses until
|
|
38
|
+
* a page binds it.
|
|
35
39
|
*/ export const AI_LOGIC_JOB_MODES = [
|
|
36
40
|
'function',
|
|
37
41
|
'variable',
|
|
@@ -108,11 +112,13 @@ const MARKUP = /<\s*\/?\s*(script|iframe|style|object|embed)\b|javascript:/i;
|
|
|
108
112
|
* is kept: an expression it cannot parse, a name it cannot find, a SET on a
|
|
109
113
|
* site variable, or a run that fails on starting values is refused.
|
|
110
114
|
*/ export function checkAiLogicFunction(answer, context) {
|
|
115
|
+
var _context_editing;
|
|
111
116
|
const violations = [];
|
|
112
117
|
const name = str(answer['name']);
|
|
113
118
|
if (!VARIABLE_NAME_PATTERN.test(name)) {
|
|
114
119
|
violations.push(violation('logic-name', 'The function name must start with a letter or _, then letters, digits or _; at most 40 characters.'));
|
|
115
|
-
} else if (name !== context.editing && context.functions.
|
|
120
|
+
} else if (name.toLowerCase() !== ((_context_editing = context.editing) == null ? void 0 : _context_editing.toLowerCase()) && context.functions.some((taken)=>taken.toLowerCase() === name.toLowerCase())) {
|
|
121
|
+
// Case-insensitive, as the Functions card and the logic plugin's writer compare names.
|
|
116
122
|
violations.push(violation('logic-name-taken', `The site already has a function named ${name}. Choose another name.`));
|
|
117
123
|
}
|
|
118
124
|
const own = new Set();
|
|
@@ -248,7 +254,8 @@ const MARKUP = /<\s*\/?\s*(script|iframe|style|object|embed)\b|javascript:/i;
|
|
|
248
254
|
const name = str(answer['name']);
|
|
249
255
|
if (!VARIABLE_NAME_PATTERN.test(name)) {
|
|
250
256
|
violations.push(violation('logic-name', 'The variable name must start with a letter or _, then letters, digits or _; at most 40 characters.'));
|
|
251
|
-
} else if (context.variables.some((variable)=>variable.name === name)) {
|
|
257
|
+
} else if (context.variables.some((variable)=>variable.name.toLowerCase() === name.toLowerCase())) {
|
|
258
|
+
// Case-insensitive, as the Variables card and the logic plugin's writer compare names.
|
|
252
259
|
violations.push(violation('logic-name-taken', `The site already has a variable named ${name}. Choose another name.`));
|
|
253
260
|
}
|
|
254
261
|
const type = answer['type'];
|
|
@@ -323,5 +330,19 @@ export const AI_LOGIC_GONE_COPY = 'That function no longer exists.';
|
|
|
323
330
|
export const AI_LOGIC_NO_FUNCTION_COPY = 'The AI could not write a function the site can run from this description. Try describing what goes in, and what should come out.';
|
|
324
331
|
export const AI_LOGIC_NO_VARIABLE_COPY = 'The AI could not write a variable from this description. Try naming what it holds, and its value.';
|
|
325
332
|
export const AI_LOGIC_NO_EXPLANATION_COPY = 'The AI could not explain this function. Try again.';
|
|
333
|
+
// ── A function a build writes (AGL-3616) ──────────────────────────────────
|
|
334
|
+
/**
|
|
335
|
+
* The resource the logic plugin's `function` draft writer is registered
|
|
336
|
+
* under. A `logic` job run as a unit of a build writes its function through
|
|
337
|
+
* it; one a person started proposes it and writes nothing.
|
|
338
|
+
*/ export const AI_LOGIC_FUNCTION_RESOURCE = 'function';
|
|
339
|
+
/** Whether a job is a build's unit rather than one a person started: a derived job carries its origin. */ export function aiLogicJobIsBuildUnit(job) {
|
|
340
|
+
var _job_inputs;
|
|
341
|
+
const origin = (_job_inputs = job.inputs) == null ? void 0 : _job_inputs['originJobId'];
|
|
342
|
+
return typeof origin === 'string' && origin.length > 0;
|
|
343
|
+
}
|
|
344
|
+
export const AI_LOGIC_FUNCTION_UNAVAILABLE_COPY = 'Functions are not available on this site.';
|
|
345
|
+
export const AI_LOGIC_FUNCTION_SAVE_FAILURE_COPY = 'The function was written but could not be saved. Try the job again.';
|
|
346
|
+
export const AI_LOGIC_FUNCTION_WRITTEN_NOTE = 'A new site function, checked against the expression grammar and run once. Nothing on the site uses it until a page binds it.';
|
|
326
347
|
|
|
327
348
|
//# sourceMappingURL=ai-logic-job.js.map
|
|
@@ -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":"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"}
|
|
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"}
|