@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
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { AiTool } from '../providers/contract';
|
|
18
|
+
import type { AiGenerationCheckResult } from '../runtime/ai-doctrine';
|
|
19
|
+
/**
|
|
20
|
+
* The strict tool a site's first posts are written through (AGL-3676), and
|
|
21
|
+
* the check that holds each post to what the site's collection publishes.
|
|
22
|
+
*
|
|
23
|
+
* A post is published with the site, as its own words, so nothing in it may
|
|
24
|
+
* mark a gap for later: no square brackets, no link, no markup the entry
|
|
25
|
+
* renderer does not read. Its body is the markdown-lite the entry editor
|
|
26
|
+
* writes — `##` subheadings, `-` lists, `**bold**`, `*italic*` — and its
|
|
27
|
+
* title, excerpt and search description are plain text. The storefront copy
|
|
28
|
+
* rules hold every part (no health, financial or legal promise; no
|
|
29
|
+
* certification, award or endorsement the brief does not state; no price),
|
|
30
|
+
* and so does a rule of its own: no star rating, review score or testimonial
|
|
31
|
+
* nobody gave.
|
|
32
|
+
*/
|
|
33
|
+
export declare const AI_BLOG_POST_TOOL_NAME = "write_blog_post";
|
|
34
|
+
/** A post's bounds, as the tool states them and its check holds them. */
|
|
35
|
+
export declare const AI_BLOG_POST_LIMITS: {
|
|
36
|
+
readonly titleMax: 80;
|
|
37
|
+
readonly excerptMax: 200;
|
|
38
|
+
readonly bodyMin: 600;
|
|
39
|
+
readonly bodyMax: 4000;
|
|
40
|
+
readonly seoDescriptionMax: number;
|
|
41
|
+
};
|
|
42
|
+
/** A post as its check reads it. */
|
|
43
|
+
export interface AiBlogPost {
|
|
44
|
+
title: string;
|
|
45
|
+
excerpt: string;
|
|
46
|
+
/** Markdown-lite. */
|
|
47
|
+
body: string;
|
|
48
|
+
seoDescription: string;
|
|
49
|
+
}
|
|
50
|
+
export declare const AI_BLOG_POST_TOOL: AiTool;
|
|
51
|
+
/**
|
|
52
|
+
* A post's answer held to the collection it is published in and the copy
|
|
53
|
+
* rules. `merchantWords` are the brief and the site's own answers, the only
|
|
54
|
+
* source a stated claim or price may come from; `earlierTitles` are the posts
|
|
55
|
+
* already written, which this one must not repeat.
|
|
56
|
+
*/
|
|
57
|
+
export declare function checkAiBlogPost(answer: Record<string, unknown>, context: {
|
|
58
|
+
merchantWords: string;
|
|
59
|
+
earlierTitles?: readonly string[];
|
|
60
|
+
}): AiGenerationCheckResult<AiBlogPost>;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { SEO_LISTING_FIELDS } from "@aglyn/aglyn/app-utils/seo-listing-fields";
|
|
17
|
+
import { findInventedPrices, findStorefrontClaims, storefrontClaimViolations } from "../model/ai-storefront-claims.js";
|
|
18
|
+
import { detectOffVoiceCopy } from "../runtime/ai-doctrine-validators.js";
|
|
19
|
+
/**
|
|
20
|
+
* The strict tool a site's first posts are written through (AGL-3676), and
|
|
21
|
+
* the check that holds each post to what the site's collection publishes.
|
|
22
|
+
*
|
|
23
|
+
* A post is published with the site, as its own words, so nothing in it may
|
|
24
|
+
* mark a gap for later: no square brackets, no link, no markup the entry
|
|
25
|
+
* renderer does not read. Its body is the markdown-lite the entry editor
|
|
26
|
+
* writes — `##` subheadings, `-` lists, `**bold**`, `*italic*` — and its
|
|
27
|
+
* title, excerpt and search description are plain text. The storefront copy
|
|
28
|
+
* rules hold every part (no health, financial or legal promise; no
|
|
29
|
+
* certification, award or endorsement the brief does not state; no price),
|
|
30
|
+
* and so does a rule of its own: no star rating, review score or testimonial
|
|
31
|
+
* nobody gave.
|
|
32
|
+
*/ export const AI_BLOG_POST_TOOL_NAME = 'write_blog_post';
|
|
33
|
+
/** A post's bounds, as the tool states them and its check holds them. */ export const AI_BLOG_POST_LIMITS = {
|
|
34
|
+
titleMax: 80,
|
|
35
|
+
excerptMax: 200,
|
|
36
|
+
bodyMin: 600,
|
|
37
|
+
bodyMax: 4000,
|
|
38
|
+
seoDescriptionMax: SEO_LISTING_FIELDS.description.maxLength
|
|
39
|
+
};
|
|
40
|
+
const string = (description)=>({
|
|
41
|
+
type: 'string',
|
|
42
|
+
description
|
|
43
|
+
});
|
|
44
|
+
export const AI_BLOG_POST_TOOL = {
|
|
45
|
+
name: AI_BLOG_POST_TOOL_NAME,
|
|
46
|
+
description: 'Write one post of a new blog. Every field is required.',
|
|
47
|
+
strict: true,
|
|
48
|
+
inputSchema: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
additionalProperties: false,
|
|
51
|
+
required: [
|
|
52
|
+
'title',
|
|
53
|
+
'excerpt',
|
|
54
|
+
'body',
|
|
55
|
+
'seoDescription'
|
|
56
|
+
],
|
|
57
|
+
properties: {
|
|
58
|
+
title: string(`Plain text, at most ${AI_BLOG_POST_LIMITS.titleMax} characters.`),
|
|
59
|
+
excerpt: string(`One or two plain sentences, at most ${AI_BLOG_POST_LIMITS.excerptMax} characters.`),
|
|
60
|
+
body: string(`Markdown-lite, ${AI_BLOG_POST_LIMITS.bodyMin} to ${AI_BLOG_POST_LIMITS.bodyMax} characters: short paragraphs, ## subheadings, - lists, **bold**. No title line.`),
|
|
61
|
+
seoDescription: string(`One plain sentence, at most ${AI_BLOG_POST_LIMITS.seoDescriptionMax} characters.`)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
function fail(findings, at, code, message, value) {
|
|
66
|
+
findings.violations.push({
|
|
67
|
+
rule: null,
|
|
68
|
+
code,
|
|
69
|
+
message,
|
|
70
|
+
paths: [
|
|
71
|
+
at
|
|
72
|
+
]
|
|
73
|
+
});
|
|
74
|
+
if (!(at in findings.offending)) findings.offending[at] = value;
|
|
75
|
+
}
|
|
76
|
+
const line = (value)=>typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '';
|
|
77
|
+
/** Paragraphs: spaces folded within a line, at most one blank line between paragraphs. */ const paragraphs = (value)=>typeof value === 'string' ? value.replace(/\r\n?/g, '\n').split('\n').map((part)=>part.replace(/[ \t\f\v]+/g, ' ').trimEnd()).join('\n').replace(/\n{3,}/g, '\n\n').trim() : '';
|
|
78
|
+
const HTML = /<\/?[a-z][^>]*>/i;
|
|
79
|
+
const PLAIN_MARKS = /(^|\n)\s*#{1,6}\s|\*\*[^*\n]+\*\*|__[^_\n]+__|`/;
|
|
80
|
+
const EMOJI = RegExp("[\\u00A9\\u00AE\\u203C\\u2049\\u2122\\u2139\\u2194-\\u2199\\u21A9-\\u21AA\\u231A-\\u231B\\u2328\\u23CF\\u23E9-\\u23F3\\u23F8-\\u23FA\\u24C2\\u25AA-\\u25AB\\u25B6\\u25C0\\u25FB-\\u25FE\\u2600-\\u2604\\u260E\\u2611\\u2614-\\u2615\\u2618\\u261D\\u2620\\u2622-\\u2623\\u2626\\u262A\\u262E-\\u262F\\u2638-\\u263A\\u2640\\u2642\\u2648-\\u2653\\u265F-\\u2660\\u2663\\u2665-\\u2666\\u2668\\u267B\\u267E-\\u267F\\u2692-\\u2697\\u2699\\u269B-\\u269C\\u26A0-\\u26A1\\u26A7\\u26AA-\\u26AB\\u26B0-\\u26B1\\u26BD-\\u26BE\\u26C4-\\u26C5\\u26C8\\u26CE-\\u26CF\\u26D1\\u26D3-\\u26D4\\u26E9-\\u26EA\\u26F0-\\u26F5\\u26F7-\\u26FA\\u26FD\\u2702\\u2705\\u2708-\\u270D\\u270F\\u2712\\u2714\\u2716\\u271D\\u2721\\u2728\\u2733-\\u2734\\u2744\\u2747\\u274C\\u274E\\u2753-\\u2755\\u2757\\u2763-\\u2764\\u2795-\\u2797\\u27A1\\u27B0\\u27BF\\u2934-\\u2935\\u2B05-\\u2B07\\u2B1B-\\u2B1C\\u2B50\\u2B55\\u3030\\u303D\\u3297\\u3299\\u{1F004}\\u{1F02C}-\\u{1F02F}\\u{1F094}-\\u{1F09F}\\u{1F0AF}-\\u{1F0B0}\\u{1F0C0}\\u{1F0CF}-\\u{1F0D0}\\u{1F0F6}-\\u{1F0FF}\\u{1F170}-\\u{1F171}\\u{1F17E}-\\u{1F17F}\\u{1F18E}\\u{1F191}-\\u{1F19A}\\u{1F1AE}-\\u{1F1E5}\\u{1F201}-\\u{1F20F}\\u{1F21A}\\u{1F22F}\\u{1F232}-\\u{1F23A}\\u{1F23C}-\\u{1F23F}\\u{1F249}-\\u{1F25F}\\u{1F266}-\\u{1F321}\\u{1F324}-\\u{1F393}\\u{1F396}-\\u{1F397}\\u{1F399}-\\u{1F39B}\\u{1F39E}-\\u{1F3F0}\\u{1F3F3}-\\u{1F3F5}\\u{1F3F7}-\\u{1F3FA}\\u{1F400}-\\u{1F4FD}\\u{1F4FF}-\\u{1F53D}\\u{1F549}-\\u{1F54E}\\u{1F550}-\\u{1F567}\\u{1F56F}-\\u{1F570}\\u{1F573}-\\u{1F57A}\\u{1F587}\\u{1F58A}-\\u{1F58D}\\u{1F590}\\u{1F595}-\\u{1F596}\\u{1F5A4}-\\u{1F5A5}\\u{1F5A8}\\u{1F5B1}-\\u{1F5B2}\\u{1F5BC}\\u{1F5C2}-\\u{1F5C4}\\u{1F5D1}-\\u{1F5D3}\\u{1F5DC}-\\u{1F5DE}\\u{1F5E1}\\u{1F5E3}\\u{1F5E8}\\u{1F5EF}\\u{1F5F3}\\u{1F5FA}-\\u{1F64F}\\u{1F680}-\\u{1F6C5}\\u{1F6CB}-\\u{1F6D2}\\u{1F6D5}-\\u{1F6E5}\\u{1F6E9}\\u{1F6EB}-\\u{1F6F0}\\u{1F6F3}-\\u{1F6FF}\\u{1F7DA}-\\u{1F7FF}\\u{1F80C}-\\u{1F80F}\\u{1F848}-\\u{1F84F}\\u{1F85A}-\\u{1F85F}\\u{1F888}-\\u{1F88F}\\u{1F8AE}-\\u{1F8AF}\\u{1F8BC}-\\u{1F8BF}\\u{1F8C2}-\\u{1F8CF}\\u{1F8D9}-\\u{1F8FF}\\u{1F90C}-\\u{1F93A}\\u{1F93C}-\\u{1F945}\\u{1F947}-\\u{1F9FF}\\u{1FA58}-\\u{1FA5F}\\u{1FA6E}-\\u{1FAFF}\\u{1FC00}-\\u{1FFFD}]", "u");
|
|
81
|
+
const BRACKET = /[[\]]/;
|
|
82
|
+
const URL = /\bhttps?:\/\/|\bwww\./i;
|
|
83
|
+
/** A heading deeper than the entry renderer's subheadings, or a title line. */ const TITLE_HEADING = /(^|\n)\s*#\s/;
|
|
84
|
+
/** A rating or review score nobody gave. */ const RATING = /\b\d(?:\.\d)?\s*(?:\/\s*(?:5|10)\b|out of (?:5|five|10|ten)\b|stars?\b)|\b(?:five|5|four|4)[- ]star\b|\brated\b|\breviews? (?:say|rave|agree)\b/i;
|
|
85
|
+
/** One plain-text field: there, within its length, unmarked. */ function checkPlain(findings, at, raw, label, max) {
|
|
86
|
+
if (typeof raw !== 'string') {
|
|
87
|
+
fail(findings, at, 'type', `${label} must be text.`, raw);
|
|
88
|
+
return '';
|
|
89
|
+
}
|
|
90
|
+
const value = line(raw);
|
|
91
|
+
if (!value) {
|
|
92
|
+
fail(findings, at, 'missing', `${label} is required.`, raw);
|
|
93
|
+
return '';
|
|
94
|
+
}
|
|
95
|
+
if (value.length > max) fail(findings, at, 'too-long', `${label} is ${value.length} characters; it may have ${max}.`, raw);
|
|
96
|
+
if (HTML.test(value) || PLAIN_MARKS.test(value)) fail(findings, at, 'markup', `${label} is plain text, with no formatting marks.`, raw);
|
|
97
|
+
if (EMOJI.test(value)) fail(findings, at, 'emoji', `${label} holds no emoji.`, raw);
|
|
98
|
+
if (URL.test(value)) fail(findings, at, 'link', `${label} holds no link.`, raw);
|
|
99
|
+
if (BRACKET.test(value)) {
|
|
100
|
+
fail(findings, at, 'bracket', `${label} is published as written, so it marks no missing fact in brackets.`, raw);
|
|
101
|
+
}
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* A post's answer held to the collection it is published in and the copy
|
|
106
|
+
* rules. `merchantWords` are the brief and the site's own answers, the only
|
|
107
|
+
* source a stated claim or price may come from; `earlierTitles` are the posts
|
|
108
|
+
* already written, which this one must not repeat.
|
|
109
|
+
*/ export function checkAiBlogPost(answer, context) {
|
|
110
|
+
var _context_earlierTitles;
|
|
111
|
+
const findings = {
|
|
112
|
+
violations: [],
|
|
113
|
+
offending: {}
|
|
114
|
+
};
|
|
115
|
+
const limits = AI_BLOG_POST_LIMITS;
|
|
116
|
+
const title = checkPlain(findings, 'title', answer['title'], 'The title', limits.titleMax);
|
|
117
|
+
if (title && ((_context_earlierTitles = context.earlierTitles) != null ? _context_earlierTitles : []).some((earlier)=>earlier.trim().toLowerCase() === title.toLowerCase())) {
|
|
118
|
+
fail(findings, 'title', 'title-duplicate', 'Another post already has this title; write about something else.', answer['title']);
|
|
119
|
+
}
|
|
120
|
+
const excerpt = checkPlain(findings, 'excerpt', answer['excerpt'], 'The excerpt', limits.excerptMax);
|
|
121
|
+
const seoDescription = checkPlain(findings, 'seoDescription', answer['seoDescription'], 'The search description', limits.seoDescriptionMax);
|
|
122
|
+
const raw = answer['body'];
|
|
123
|
+
const body = paragraphs(raw);
|
|
124
|
+
if (typeof raw !== 'string' || !body) {
|
|
125
|
+
fail(findings, 'body', 'missing', 'The body is required.', raw);
|
|
126
|
+
} else {
|
|
127
|
+
if (body.length < limits.bodyMin || body.length > limits.bodyMax) {
|
|
128
|
+
fail(findings, 'body', 'length', `The body is ${body.length} characters; write ${limits.bodyMin} to ${limits.bodyMax}.`, raw);
|
|
129
|
+
}
|
|
130
|
+
if (HTML.test(body)) fail(findings, 'body', 'markup', 'The body is markdown-lite, with no HTML.', raw);
|
|
131
|
+
if (TITLE_HEADING.test(body)) fail(findings, 'body', 'title-line', 'The title renders on its own; the body uses ## subheadings only.', raw);
|
|
132
|
+
if (EMOJI.test(body)) fail(findings, 'body', 'emoji', 'The body holds no emoji.', raw);
|
|
133
|
+
if (URL.test(body)) fail(findings, 'body', 'link', 'The body holds no link.', raw);
|
|
134
|
+
if (BRACKET.test(body)) {
|
|
135
|
+
fail(findings, 'body', 'bracket', 'The body is published as written, so it holds no square brackets: no links and no missing fact marked.', raw);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const samples = [
|
|
139
|
+
{
|
|
140
|
+
at: 'title',
|
|
141
|
+
text: title
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
at: 'excerpt',
|
|
145
|
+
text: excerpt
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
at: 'body',
|
|
149
|
+
text: body
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
at: 'seoDescription',
|
|
153
|
+
text: seoDescription
|
|
154
|
+
}
|
|
155
|
+
];
|
|
156
|
+
for (const sample of samples){
|
|
157
|
+
const rating = RATING.exec(sample.text);
|
|
158
|
+
if (rating && !context.merchantWords.toLowerCase().includes(rating[0].toLowerCase())) {
|
|
159
|
+
fail(findings, sample.at, 'rating-invented', `The post gives a rating or review nobody gave. Remove: "${rating[0]}".`, sample.text);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
findings.violations.push(...storefrontClaimViolations(findStorefrontClaims(samples, context.merchantWords), findInventedPrices(samples, context.merchantWords)), ...detectOffVoiceCopy(samples, null));
|
|
163
|
+
return {
|
|
164
|
+
value: findings.violations.length ? null : {
|
|
165
|
+
title,
|
|
166
|
+
excerpt,
|
|
167
|
+
body,
|
|
168
|
+
seoDescription
|
|
169
|
+
},
|
|
170
|
+
violations: findings.violations,
|
|
171
|
+
offending: findings.offending
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
//# sourceMappingURL=ai-blog-post-tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/tools/ai-blog-post-tool.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 { SEO_LISTING_FIELDS } from '@aglyn/aglyn/app-utils/seo-listing-fields'\nimport {\n findInventedPrices,\n findStorefrontClaims,\n storefrontClaimViolations,\n type AiStorefrontCopySample,\n} from '../model/ai-storefront-claims'\nimport type { AiTool } from '../providers/contract'\nimport type { AiGenerationCheckResult } from '../runtime/ai-doctrine'\nimport { detectOffVoiceCopy, type AiDoctrineViolation } from '../runtime/ai-doctrine-validators'\n\n/**\n * The strict tool a site's first posts are written through (AGL-3676), and\n * the check that holds each post to what the site's collection publishes.\n *\n * A post is published with the site, as its own words, so nothing in it may\n * mark a gap for later: no square brackets, no link, no markup the entry\n * renderer does not read. Its body is the markdown-lite the entry editor\n * writes — `##` subheadings, `-` lists, `**bold**`, `*italic*` — and its\n * title, excerpt and search description are plain text. The storefront copy\n * rules hold every part (no health, financial or legal promise; no\n * certification, award or endorsement the brief does not state; no price),\n * and so does a rule of its own: no star rating, review score or testimonial\n * nobody gave.\n */\n\nexport const AI_BLOG_POST_TOOL_NAME = 'write_blog_post'\n\n/** A post's bounds, as the tool states them and its check holds them. */\nexport const AI_BLOG_POST_LIMITS = {\n titleMax: 80,\n excerptMax: 200,\n bodyMin: 600,\n bodyMax: 4_000,\n seoDescriptionMax: SEO_LISTING_FIELDS.description.maxLength,\n} as const\n\n/** A post as its check reads it. */\nexport interface AiBlogPost {\n title: string\n excerpt: string\n /** Markdown-lite. */\n body: string\n seoDescription: string\n}\n\nconst string = (description: string) => ({ type: 'string', description })\n\nexport const AI_BLOG_POST_TOOL: AiTool = {\n name: AI_BLOG_POST_TOOL_NAME,\n description: 'Write one post of a new blog. Every field is required.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['title', 'excerpt', 'body', 'seoDescription'],\n properties: {\n title: string(`Plain text, at most ${AI_BLOG_POST_LIMITS.titleMax} characters.`),\n excerpt: string(`One or two plain sentences, at most ${AI_BLOG_POST_LIMITS.excerptMax} characters.`),\n body: string(\n `Markdown-lite, ${AI_BLOG_POST_LIMITS.bodyMin} to ${AI_BLOG_POST_LIMITS.bodyMax} characters: short paragraphs, ## subheadings, - lists, **bold**. No title line.`,\n ),\n seoDescription: string(`One plain sentence, at most ${AI_BLOG_POST_LIMITS.seoDescriptionMax} characters.`),\n },\n },\n}\n\ninterface Findings {\n violations: AiDoctrineViolation[]\n offending: Record<string, unknown>\n}\n\nfunction fail(findings: Findings, at: string, code: string, message: string, value: unknown): void {\n findings.violations.push({ rule: null, code, message, paths: [at] })\n if (!(at in findings.offending)) findings.offending[at] = value\n}\n\nconst line = (value: unknown): string => (typeof value === 'string' ? value.replace(/\\s+/g, ' ').trim() : '')\n\n/** Paragraphs: spaces folded within a line, at most one blank line between paragraphs. */\nconst paragraphs = (value: unknown): string =>\n typeof value === 'string'\n ? value\n .replace(/\\r\\n?/g, '\\n')\n .split('\\n')\n .map((part) => part.replace(/[ \\t\\f\\v]+/g, ' ').trimEnd())\n .join('\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n : ''\n\nconst HTML = /<\\/?[a-z][^>]*>/i\nconst PLAIN_MARKS = /(^|\\n)\\s*#{1,6}\\s|\\*\\*[^*\\n]+\\*\\*|__[^_\\n]+__|`/\nconst EMOJI = /\\p{Extended_Pictographic}/u\nconst BRACKET = /[[\\]]/\nconst URL = /\\bhttps?:\\/\\/|\\bwww\\./i\n/** A heading deeper than the entry renderer's subheadings, or a title line. */\nconst TITLE_HEADING = /(^|\\n)\\s*#\\s/\n/** A rating or review score nobody gave. */\nconst RATING = /\\b\\d(?:\\.\\d)?\\s*(?:\\/\\s*(?:5|10)\\b|out of (?:5|five|10|ten)\\b|stars?\\b)|\\b(?:five|5|four|4)[- ]star\\b|\\brated\\b|\\breviews? (?:say|rave|agree)\\b/i\n\n/** One plain-text field: there, within its length, unmarked. */\nfunction checkPlain(findings: Findings, at: string, raw: unknown, label: string, max: number): string {\n if (typeof raw !== 'string') {\n fail(findings, at, 'type', `${label} must be text.`, raw)\n return ''\n }\n const value = line(raw)\n if (!value) {\n fail(findings, at, 'missing', `${label} is required.`, raw)\n return ''\n }\n if (value.length > max) fail(findings, at, 'too-long', `${label} is ${value.length} characters; it may have ${max}.`, raw)\n if (HTML.test(value) || PLAIN_MARKS.test(value)) fail(findings, at, 'markup', `${label} is plain text, with no formatting marks.`, raw)\n if (EMOJI.test(value)) fail(findings, at, 'emoji', `${label} holds no emoji.`, raw)\n if (URL.test(value)) fail(findings, at, 'link', `${label} holds no link.`, raw)\n if (BRACKET.test(value)) {\n fail(findings, at, 'bracket', `${label} is published as written, so it marks no missing fact in brackets.`, raw)\n }\n return value\n}\n\n/**\n * A post's answer held to the collection it is published in and the copy\n * rules. `merchantWords` are the brief and the site's own answers, the only\n * source a stated claim or price may come from; `earlierTitles` are the posts\n * already written, which this one must not repeat.\n */\nexport function checkAiBlogPost(\n answer: Record<string, unknown>,\n context: { merchantWords: string; earlierTitles?: readonly string[] },\n): AiGenerationCheckResult<AiBlogPost> {\n const findings: Findings = { violations: [], offending: {} }\n const limits = AI_BLOG_POST_LIMITS\n const title = checkPlain(findings, 'title', answer['title'], 'The title', limits.titleMax)\n if (title && (context.earlierTitles ?? []).some((earlier) => earlier.trim().toLowerCase() === title.toLowerCase())) {\n fail(findings, 'title', 'title-duplicate', 'Another post already has this title; write about something else.', answer['title'])\n }\n const excerpt = checkPlain(findings, 'excerpt', answer['excerpt'], 'The excerpt', limits.excerptMax)\n const seoDescription = checkPlain(\n findings,\n 'seoDescription',\n answer['seoDescription'],\n 'The search description',\n limits.seoDescriptionMax,\n )\n const raw = answer['body']\n const body = paragraphs(raw)\n if (typeof raw !== 'string' || !body) {\n fail(findings, 'body', 'missing', 'The body is required.', raw)\n } else {\n if (body.length < limits.bodyMin || body.length > limits.bodyMax) {\n fail(findings, 'body', 'length', `The body is ${body.length} characters; write ${limits.bodyMin} to ${limits.bodyMax}.`, raw)\n }\n if (HTML.test(body)) fail(findings, 'body', 'markup', 'The body is markdown-lite, with no HTML.', raw)\n if (TITLE_HEADING.test(body)) fail(findings, 'body', 'title-line', 'The title renders on its own; the body uses ## subheadings only.', raw)\n if (EMOJI.test(body)) fail(findings, 'body', 'emoji', 'The body holds no emoji.', raw)\n if (URL.test(body)) fail(findings, 'body', 'link', 'The body holds no link.', raw)\n if (BRACKET.test(body)) {\n fail(findings, 'body', 'bracket', 'The body is published as written, so it holds no square brackets: no links and no missing fact marked.', raw)\n }\n }\n const samples: AiStorefrontCopySample[] = [\n { at: 'title', text: title },\n { at: 'excerpt', text: excerpt },\n { at: 'body', text: body },\n { at: 'seoDescription', text: seoDescription },\n ]\n for (const sample of samples) {\n const rating = RATING.exec(sample.text)\n if (rating && !context.merchantWords.toLowerCase().includes(rating[0].toLowerCase())) {\n fail(findings, sample.at, 'rating-invented', `The post gives a rating or review nobody gave. Remove: \"${rating[0]}\".`, sample.text)\n }\n }\n findings.violations.push(\n ...storefrontClaimViolations(\n findStorefrontClaims(samples, context.merchantWords),\n findInventedPrices(samples, context.merchantWords),\n ),\n ...detectOffVoiceCopy(samples, null),\n )\n return {\n value: findings.violations.length ? null : { title, excerpt, body, seoDescription },\n violations: findings.violations,\n offending: findings.offending,\n }\n}\n"],"names":["SEO_LISTING_FIELDS","findInventedPrices","findStorefrontClaims","storefrontClaimViolations","detectOffVoiceCopy","AI_BLOG_POST_TOOL_NAME","AI_BLOG_POST_LIMITS","titleMax","excerptMax","bodyMin","bodyMax","seoDescriptionMax","description","maxLength","string","type","AI_BLOG_POST_TOOL","name","strict","inputSchema","additionalProperties","required","properties","title","excerpt","body","seoDescription","fail","findings","at","code","message","value","violations","push","rule","paths","offending","line","replace","trim","paragraphs","split","map","part","trimEnd","join","HTML","PLAIN_MARKS","EMOJI","BRACKET","URL","TITLE_HEADING","RATING","checkPlain","raw","label","max","length","test","checkAiBlogPost","answer","context","limits","earlierTitles","some","earlier","toLowerCase","samples","text","sample","rating","exec","merchantWords","includes"],"mappings":"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"}
|
|
@@ -52,7 +52,11 @@ import type { AiGenerationCheckResult } from '../runtime/ai-doctrine';
|
|
|
52
52
|
* is compiled once for the whole union instead of once per variant;
|
|
53
53
|
* - a variant serves every step type that carries the same fields, named for
|
|
54
54
|
* what they hold: the six steps that name a record share `reference`, and
|
|
55
|
-
* the three that carry one line share `text
|
|
55
|
+
* the three that carry one line share `text`. A step whose fields are a
|
|
56
|
+
* subset of another's rides that variant with the rest empty: `exitFlow`
|
|
57
|
+
* with an empty `reference`, `wait` with `waitForEvent`'s empty `event`.
|
|
58
|
+
* Each of those saved a whole object once the order, return and funnel
|
|
59
|
+
* triggers (AGL-3611, AGL-3605) grew the events' enum past the proof;
|
|
56
60
|
* - every field stays required and every vocabulary but one stays an enum,
|
|
57
61
|
* so the model is still decoded into the Actions editor's words. The one
|
|
58
62
|
* is the event a waitForEvent step waits for: the trigger's `event` already
|
|
@@ -63,7 +67,7 @@ import type { AiGenerationCheckResult } from '../runtime/ai-doctrine';
|
|
|
63
67
|
* - a description is free: a compiler drops it (`aiToolSchemaCompiledBytes`),
|
|
64
68
|
* so the words a field needs go there and never into the grammar.
|
|
65
69
|
*
|
|
66
|
-
*
|
|
70
|
+
* Eight variants carry all seventeen step types, and the reader below maps the
|
|
67
71
|
* shared fields back onto the step's own key.
|
|
68
72
|
*/
|
|
69
73
|
export declare const AI_AUTOMATION_TOOL_NAME = "submit_automation";
|
|
@@ -52,7 +52,11 @@ import { HOSTILE_TEXT } from "../runtime/ai-node-tree.js";
|
|
|
52
52
|
* is compiled once for the whole union instead of once per variant;
|
|
53
53
|
* - a variant serves every step type that carries the same fields, named for
|
|
54
54
|
* what they hold: the six steps that name a record share `reference`, and
|
|
55
|
-
* the three that carry one line share `text
|
|
55
|
+
* the three that carry one line share `text`. A step whose fields are a
|
|
56
|
+
* subset of another's rides that variant with the rest empty: `exitFlow`
|
|
57
|
+
* with an empty `reference`, `wait` with `waitForEvent`'s empty `event`.
|
|
58
|
+
* Each of those saved a whole object once the order, return and funnel
|
|
59
|
+
* triggers (AGL-3611, AGL-3605) grew the events' enum past the proof;
|
|
56
60
|
* - every field stays required and every vocabulary but one stays an enum,
|
|
57
61
|
* so the model is still decoded into the Actions editor's words. The one
|
|
58
62
|
* is the event a waitForEvent step waits for: the trigger's `event` already
|
|
@@ -63,7 +67,7 @@ import { HOSTILE_TEXT } from "../runtime/ai-node-tree.js";
|
|
|
63
67
|
* - a description is free: a compiler drops it (`aiToolSchemaCompiledBytes`),
|
|
64
68
|
* so the words a field needs go there and never into the grammar.
|
|
65
69
|
*
|
|
66
|
-
*
|
|
70
|
+
* Eight variants carry all seventeen step types, and the reader below maps the
|
|
67
71
|
* shared fields back onto the step's own key.
|
|
68
72
|
*/ export const AI_AUTOMATION_TOOL_NAME = 'submit_automation';
|
|
69
73
|
export const AI_WORKFLOW_EXPLANATION_TOOL_NAME = 'submit_explanation';
|
|
@@ -211,12 +215,15 @@ const minutes = (what)=>({
|
|
|
211
215
|
};
|
|
212
216
|
}
|
|
213
217
|
/** Every step type an automation may carry, grouped by the fields it needs. */ export const AI_AUTOMATION_STEP_VARIANTS = [
|
|
218
|
+
// `exitFlow` rides here with an empty reference: a variant of its own
|
|
219
|
+
// costs the grammar a whole object for a step that carries nothing.
|
|
214
220
|
stepVariant([
|
|
215
|
-
...REFERENCE_STEPS
|
|
221
|
+
...REFERENCE_STEPS,
|
|
222
|
+
'exitFlow'
|
|
216
223
|
], {
|
|
217
224
|
reference: {
|
|
218
225
|
type: 'string',
|
|
219
|
-
description: 'The record the step acts on — the email list, campaign, workflow, dataset or outbound webhook — in the words of the description, such as "newsletter". Never an id.'
|
|
226
|
+
description: 'The record the step acts on — the email list, campaign, workflow, dataset or outbound webhook — in the words of the description, such as "newsletter". Never an id. Empty for exitFlow.'
|
|
220
227
|
}
|
|
221
228
|
}),
|
|
222
229
|
stepVariant([
|
|
@@ -257,24 +264,19 @@ const minutes = (what)=>({
|
|
|
257
264
|
]
|
|
258
265
|
}
|
|
259
266
|
}),
|
|
267
|
+
// `wait` shares `waitForEvent`'s variant, its event empty: one object in
|
|
268
|
+
// the grammar instead of two.
|
|
260
269
|
stepVariant([
|
|
261
|
-
'wait'
|
|
262
|
-
], {
|
|
263
|
-
minutes: minutes('How long to wait')
|
|
264
|
-
}),
|
|
265
|
-
stepVariant([
|
|
270
|
+
'wait',
|
|
266
271
|
'waitForEvent'
|
|
267
272
|
], {
|
|
268
273
|
// A string, not the events' enum again — see the header.
|
|
269
274
|
event: {
|
|
270
275
|
type: 'string',
|
|
271
|
-
description: '
|
|
276
|
+
description: 'waitForEvent: one of the trigger events. Empty for wait.'
|
|
272
277
|
},
|
|
273
|
-
minutes: minutes('
|
|
278
|
+
minutes: minutes('wait: how long to wait. waitForEvent: when to give up')
|
|
274
279
|
}),
|
|
275
|
-
stepVariant([
|
|
276
|
-
'exitFlow'
|
|
277
|
-
], {}),
|
|
278
280
|
stepVariant([
|
|
279
281
|
'setContactStage'
|
|
280
282
|
], {
|