@aglyn/plugins-ai 1.0.0-beta.272 → 1.0.0-beta.274
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/components/ai-site-start-card.component.js +237 -234
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/jobs/ai-empty-site-sweep.d.ts +12 -1
- package/src/lib/jobs/ai-empty-site-sweep.js +15 -3
- package/src/lib/jobs/ai-empty-site-sweep.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +21 -2
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-seo-step.js +8 -4
- package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +39 -1
- package/src/lib/jobs/ai-job-site-datasets.js +111 -15
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +12 -3
- package/src/lib/jobs/ai-job-site-step.js +85 -11
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.d.ts +8 -0
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +3 -0
- package/src/lib/jobs/ai-jobs.js +4 -0
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-owner-photos.js +4 -4
- package/src/lib/jobs/ai-layout-owner-photos.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +35 -0
- package/src/lib/jobs/ai-layout-stock-photos.js +114 -43
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/ai-site-late-photos.d.ts +143 -0
- package/src/lib/jobs/ai-site-late-photos.js +486 -0
- package/src/lib/jobs/ai-site-late-photos.js.map +1 -0
- package/src/lib/jobs/ai-site-publish.d.ts +18 -0
- package/src/lib/jobs/ai-site-publish.js +123 -5
- package/src/lib/jobs/ai-site-publish.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-site-late-photos-laurel-fork.d.ts +83 -0
- package/src/lib/jobs/fixtures/ai-site-late-photos-laurel-fork.js +1553 -0
- package/src/lib/jobs/fixtures/ai-site-late-photos-laurel-fork.js.map +1 -0
- package/src/lib/layout-language/ai-layout-pictures.d.ts +50 -13
- package/src/lib/layout-language/ai-layout-pictures.js +81 -19
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +10 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-site-photos.d.ts +119 -0
- package/src/lib/model/ai-site-photos.js +128 -0
- package/src/lib/model/ai-site-photos.js.map +1 -0
- package/src/lib/runtime/ai-doctrine-validators.d.ts +3 -1
- package/src/lib/runtime/ai-doctrine-validators.js +3 -1
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/seo-fields.d.ts +29 -4
- package/src/lib/runtime/seo-fields.js +58 -11
- package/src/lib/runtime/seo-fields.js.map +1 -1
- package/src/lib/tools/ai-seo-tool.d.ts +25 -1
- package/src/lib/tools/ai-seo-tool.js +61 -0
- package/src/lib/tools/ai-seo-tool.js.map +1 -1
|
@@ -20,6 +20,7 @@ import { type AiStepKind } from '../providers/catalog';
|
|
|
20
20
|
import { type AiEffort, type AiSystemBlock } from '../runtime/ai-runtime';
|
|
21
21
|
import type { AssistTokenUsage } from '../usage/assist-usage';
|
|
22
22
|
import type { AiSiteImportState } from '../model/ai-site-import';
|
|
23
|
+
import type { AiSitePhotos } from '../model/ai-site-photos';
|
|
23
24
|
/**
|
|
24
25
|
* The `text` step (AGL-2904): a brief in, a short piece of copy out.
|
|
25
26
|
*
|
|
@@ -136,6 +137,13 @@ export interface AiJobStepOutcome {
|
|
|
136
137
|
* job document itself.
|
|
137
138
|
*/
|
|
138
139
|
siteImport?: AiSiteImportState;
|
|
140
|
+
/**
|
|
141
|
+
* A `site` job's pictures left on a starter photo after this pass
|
|
142
|
+
* (AGL-3988), whole: what its late photo pass fills before the site is
|
|
143
|
+
* published. A page's or a dataset's pass reports its own; the site step
|
|
144
|
+
* merges them and the machine keeps the result on the job.
|
|
145
|
+
*/
|
|
146
|
+
sitePhotos?: AiSitePhotos;
|
|
139
147
|
}
|
|
140
148
|
/** What one `build` pass came to for the unit it ran (AGL-3616). */
|
|
141
149
|
export interface AiJobItemOutcome {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-text-step.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 { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport type {\n AiJob,\n AiJobOutput,\n AiJobPlan,\n AiJobReview,\n AiJobInputRequest,\n AiJobItemFailure,\n AiJobItemLedger,\n AiJobItemStatus,\n AiJobSitePublish,\n} from '../model/ai-jobs.types'\nimport { AI_STEP_TIERS, type AiStepKind } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport { aiDevEffortFor } from '../runtime/ai-dev-env'\nimport { runAiRequest, type AiEffort, type AiSystemBlock } from '../runtime/ai-runtime'\nimport type { AssistTokenUsage } from '../usage/assist-usage'\nimport type { AiSiteImportState } from '../model/ai-site-import'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiOverlayCopyRequested } from '../model/ai-overlay-copy'\nimport { runAiOverlayCopy } from './ai-job-overlay-copy'\n\n/**\n * The `text` step (AGL-2904): a brief in, a short piece of copy out.\n *\n * The one step kind this issue ships for real, so the state machine — the\n * lease, the reservation, the record, the beat — is exercised end to end by\n * something that spends tokens, rather than by a stub that could pass with\n * the meter unplugged. Every other kind refuses until its own issue lands.\n *\n * The balanced tier with adaptive thinking: a brief is open-ended in a way the copy\n * assistant's element mode is not, and the cost is one call per job. The\n * static system block is cached; the brief rides in the user turn, so no\n * per-org byte sits inside the cached prefix (AGL-2352, enforced by the\n * runtime before the request leaves).\n */\n\n/** The model the text step runs on: the routing table's answer for `job.text`. */\nexport function aiJobTextModel(): string {\n return aiModelForStep('job.text')\n}\n\n/** The text step's answer ceiling, from the routing table. */\nexport const AI_JOB_TEXT_MAX_TOKENS = AI_ROUTING_TABLE['job.text'].maxTokens\n\n/**\n * The text step's time (AGL-3035): ONE request — no re-ask, and no site\n * inventory to look anything up in — at the routing table's ceiling on the\n * tier `job.text` is served from, with the step's reads and writes. That fits\n * an inline door's budget, so a door runs a text job where it is asked for,\n * and the least time is the helper's worst case rather than a figure anyone\n * chose; a slower tier asks less so its worst case still fits.\n */\nexport const AI_JOB_TEXT_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.text'],\n maxTokens: AI_JOB_TEXT_MAX_TOKENS,\n attempts: 1,\n lookups: 0,\n})\n\n/** The least time one text step needs before it starts. */\nexport const AI_JOB_TEXT_STEP_MINIMUM_MS = AI_JOB_TEXT_STEP_BUDGET.minimumMs\n\n/** How much of a brief is sent. Past this a brief is a document, not a brief. */\nexport const AI_JOB_BRIEF_MAX_CHARS = 4_000\n\n/**\n * What a step hands back to the machine. The machine meters `usage` at\n * `model`'s rates, records the step and appends the outputs; a step never\n * touches the job document or the meter itself.\n */\nexport interface AiJobStepOutcome {\n outputs: AiJobOutput[]\n /**\n * The step made progress and has more of the same work to do (AGL-2910):\n * a site audit works through its pages a batch at a time. The machine\n * records this pass — its spend and its outputs — and hands the SAME step\n * back to run again, so every pass is one reservation and one provider\n * exchange, and the job document says how far it got.\n */\n continue?: boolean\n usage: AssistTokenUsage\n estCostUsd: number\n model: string\n stopReason: string | null\n /**\n * The thinking effort the request asked for, recorded on the step\n * (AGL-2937); absent or `null` when the request named none.\n */\n effort?: AiEffort | null\n /** The model declined the brief (`stop_reason: 'refusal'`). Tokens were spent. */\n refused?: boolean\n /**\n * The model answered and nothing usable came of it — no structured answer\n * even after the step's own re-ask, say (AGL-2938). Tokens were spent, so\n * the machine meters the step before it fails the job with this sentence,\n * which is customer-safe and the only thing a customer reads.\n */\n failure?: string\n /** The plan the plan step proposed, which the machine keeps on the job (AGL-2935). */\n plan?: AiJobPlan\n /**\n * The step stopped for a person (AGL-2935). The machine records what it\n * spent and parks the job `needs_review`: a `plan` review completes the\n * step, so confirming runs the next one; a `doctrine` review hands the\n * step back, so trying again runs the same one.\n */\n review?: AiJobReview\n /**\n * With a `plan` review: the plan needs an answer only the person can give\n * first (AGL-3978) — a tracking change with no pixel ID. The machine keeps\n * the plan and its review and parks the job `needs_input` with these\n * fields instead of `needs_review`; the answer returns it to the review.\n */\n input?: AiJobInputRequest\n /**\n * The answer was refused by the platform's own checks, not the model's\n * (AGL-3594): the machine meters the step as any other and then gives the\n * job's credits back to the meters it was charged to (`refundJobCredits`),\n * a bounded number of times a day.\n */\n uncredited?: boolean\n /**\n * The `failure` is the workspace's own limit, not a fault of ours: the\n * month's Free AI credits are spent (a 429 from the kind's admission, asked\n * when the plan is kept). The machine fails the job and gives the step's\n * credits back exactly as for our failure, but announces it as NOT ours, so\n * no staff alert is raised for a customer reaching their allowance.\n */\n customerLimit?: boolean\n /**\n * What a guided site start put live on its last pass (AGL-3596), kept on\n * the job by the machine in the write that records the pass: a step never\n * writes the job document itself.\n */\n sitePublish?: AiJobSitePublish\n /**\n * A `build` pass's unit and what it came to (AGL-3616), which the machine\n * writes to the job's item ledger in the transaction that records the\n * pass's spend. An item's failure is never the step's: it is a row, and\n * the build goes on.\n */\n item?: AiJobItemOutcome\n /** A `build`'s ledger to start from, written by the machine in the same transaction (AGL-3616). */\n items?: AiJobItemLedger[]\n /**\n * An `import` job's waves after this pass (AGL-3976), kept on the job by\n * the machine in the write that records the pass: a step never writes the\n * job document itself.\n */\n siteImport?: AiSiteImportState\n}\n\n/** What one `build` pass came to for the unit it ran (AGL-3616). */\nexport interface AiJobItemOutcome {\n slot: string\n /** `running` while the unit asks for another pass of its own. */\n status: AiJobItemStatus\n failure?: AiJobItemFailure | null\n /** Customer-safe: what the person should know about the item. */\n note?: string | null\n degradedBy?: string[]\n /** The ids of the outputs this pass produced for the item. */\n outputs?: string[]\n}\n\nexport interface AiJobStepContext {\n job: AiJob\n stepIndex: number\n now: Date\n /** Aborts the provider call when the runner's budget ends. */\n signal?: AbortSignal\n /**\n * The Admin SDK handle the machine runs on, for a step that reads what it\n * builds from — a site's theme, say — or writes a draft. A step never\n * writes the job document, the meter or the lease through it.\n */\n firestore: FirebaseFirestore.Firestore\n /** The org document the machine read for the step's reservation. */\n org?: Partial<AglynOrgBilling> | null\n /**\n * The model a step of this kind runs on for THIS job (AGL-2942): the\n * creator's pick where the plan and the allotment allowlists allow it,\n * the routing table otherwise. Absent, a runner asks the table itself.\n */\n modelFor?: (kind: AiStepKind) => string | undefined\n}\n\nexport type AiJobStepRunner = (\n context: AiJobStepContext,\n) => Promise<AiJobStepOutcome>\n\n/**\n * The static block, byte-identical on every request so it caches. The\n * customer's brief and the org's inputs never enter it.\n */\nexport const AI_JOB_TEXT_SYSTEM: AiSystemBlock[] = [\n {\n text:\n 'You write short, publishable copy for a website builder. You are ' +\n 'given a brief from the person who owns the site. Answer with the ' +\n 'copy only: no preamble, no headings unless the brief asks for them, ' +\n 'no closing remarks, no markdown fences. Keep to the length the brief ' +\n 'implies; when it implies none, write under 150 words. Write in the ' +\n 'language of the brief. Never invent facts, prices, names or claims ' +\n 'the brief did not give you; where a detail is missing, leave a ' +\n 'clearly marked placeholder in square brackets.',\n cacheBreakpoint: true,\n },\n]\n\n/** The brief as the user turn, with the kind-specific inputs stated plainly. */\nexport function aiJobTextPrompt(job: Pick<AiJob, 'brief' | 'inputs'>): string {\n const brief = job.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)\n const tone = typeof job.inputs?.['tone'] === 'string' ? job.inputs['tone'] : ''\n const audience =\n typeof job.inputs?.['audience'] === 'string' ? job.inputs['audience'] : ''\n const lines = [`Brief: ${brief}`]\n if (tone) lines.push(`Tone: ${tone}`)\n if (audience) lines.push(`Audience: ${audience}`)\n return lines.join('\\n')\n}\n\nexport const runAiJobTextStep: AiJobStepRunner = async ({ job, signal, modelFor }) => {\n const model = modelFor?.('job.text') ?? aiJobTextModel()\n // Overlay copy (AGL-3603): the same one request, answered as fields.\n if (aiOverlayCopyRequested(job.inputs)) {\n return runAiOverlayCopy({\n job,\n model,\n maxTokens: AI_JOB_TEXT_STEP_BUDGET.maxTokens(model),\n ...(signal ? { signal } : {}),\n })\n }\n // A development run's effort A/B arm (AGL-3781), else the row's.\n const effort = aiDevEffortFor('job.text') ?? AI_ROUTING_TABLE['job.text'].effort\n const result = await runAiRequest({\n model,\n system: AI_JOB_TEXT_SYSTEM,\n messages: [{ role: 'user', content: aiJobTextPrompt(job) }],\n // The routing ceiling, lowered on a tier too slow to answer it inside the\n // least time the step registered.\n maxTokens: AI_JOB_TEXT_STEP_BUDGET.maxTokens(model),\n ...(AI_ROUTING_TABLE['job.text'].thinking ? { thinking: AI_ROUTING_TABLE['job.text'].thinking } : {}),\n ...(effort ? { effort } : {}),\n stream: false,\n ...(signal ? { signal } : {}),\n })\n if (result.kind === 'refusal') {\n return {\n outputs: [],\n usage: result.usage,\n estCostUsd: result.estCostUsd,\n model,\n stopReason: result.stopReason,\n refused: true,\n }\n }\n const text = result.text.trim()\n return {\n outputs: text\n ? [\n {\n resource: 'text',\n // A text output has no document of its own; the id names the\n // step within the job so a later step can address it.\n id: 'draft',\n hostId: job.hostId ?? null,\n label: 'Draft copy',\n text,\n },\n ]\n : [],\n usage: result.usage,\n estCostUsd: result.estCostUsd,\n model,\n stopReason: result.stopReason,\n }\n}\n"],"names":["AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","aiDevEffortFor","runAiRequest","aiJobStepBudget","aiOverlayCopyRequested","runAiOverlayCopy","aiJobTextModel","AI_JOB_TEXT_MAX_TOKENS","maxTokens","AI_JOB_TEXT_STEP_BUDGET","tier","attempts","lookups","AI_JOB_TEXT_STEP_MINIMUM_MS","minimumMs","AI_JOB_BRIEF_MAX_CHARS","AI_JOB_TEXT_SYSTEM","text","cacheBreakpoint","aiJobTextPrompt","job","brief","slice","tone","inputs","audience","lines","push","join","runAiJobTextStep","signal","modelFor","model","effort","result","system","messages","role","content","thinking","stream","kind","outputs","usage","estCostUsd","stopReason","refused","trim","resource","id","hostId","label"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-text-step.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 { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport type {\n AiJob,\n AiJobOutput,\n AiJobPlan,\n AiJobReview,\n AiJobInputRequest,\n AiJobItemFailure,\n AiJobItemLedger,\n AiJobItemStatus,\n AiJobSitePublish,\n} from '../model/ai-jobs.types'\nimport { AI_STEP_TIERS, type AiStepKind } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport { aiDevEffortFor } from '../runtime/ai-dev-env'\nimport { runAiRequest, type AiEffort, type AiSystemBlock } from '../runtime/ai-runtime'\nimport type { AssistTokenUsage } from '../usage/assist-usage'\nimport type { AiSiteImportState } from '../model/ai-site-import'\nimport type { AiSitePhotos } from '../model/ai-site-photos'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiOverlayCopyRequested } from '../model/ai-overlay-copy'\nimport { runAiOverlayCopy } from './ai-job-overlay-copy'\n\n/**\n * The `text` step (AGL-2904): a brief in, a short piece of copy out.\n *\n * The one step kind this issue ships for real, so the state machine — the\n * lease, the reservation, the record, the beat — is exercised end to end by\n * something that spends tokens, rather than by a stub that could pass with\n * the meter unplugged. Every other kind refuses until its own issue lands.\n *\n * The balanced tier with adaptive thinking: a brief is open-ended in a way the copy\n * assistant's element mode is not, and the cost is one call per job. The\n * static system block is cached; the brief rides in the user turn, so no\n * per-org byte sits inside the cached prefix (AGL-2352, enforced by the\n * runtime before the request leaves).\n */\n\n/** The model the text step runs on: the routing table's answer for `job.text`. */\nexport function aiJobTextModel(): string {\n return aiModelForStep('job.text')\n}\n\n/** The text step's answer ceiling, from the routing table. */\nexport const AI_JOB_TEXT_MAX_TOKENS = AI_ROUTING_TABLE['job.text'].maxTokens\n\n/**\n * The text step's time (AGL-3035): ONE request — no re-ask, and no site\n * inventory to look anything up in — at the routing table's ceiling on the\n * tier `job.text` is served from, with the step's reads and writes. That fits\n * an inline door's budget, so a door runs a text job where it is asked for,\n * and the least time is the helper's worst case rather than a figure anyone\n * chose; a slower tier asks less so its worst case still fits.\n */\nexport const AI_JOB_TEXT_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.text'],\n maxTokens: AI_JOB_TEXT_MAX_TOKENS,\n attempts: 1,\n lookups: 0,\n})\n\n/** The least time one text step needs before it starts. */\nexport const AI_JOB_TEXT_STEP_MINIMUM_MS = AI_JOB_TEXT_STEP_BUDGET.minimumMs\n\n/** How much of a brief is sent. Past this a brief is a document, not a brief. */\nexport const AI_JOB_BRIEF_MAX_CHARS = 4_000\n\n/**\n * What a step hands back to the machine. The machine meters `usage` at\n * `model`'s rates, records the step and appends the outputs; a step never\n * touches the job document or the meter itself.\n */\nexport interface AiJobStepOutcome {\n outputs: AiJobOutput[]\n /**\n * The step made progress and has more of the same work to do (AGL-2910):\n * a site audit works through its pages a batch at a time. The machine\n * records this pass — its spend and its outputs — and hands the SAME step\n * back to run again, so every pass is one reservation and one provider\n * exchange, and the job document says how far it got.\n */\n continue?: boolean\n usage: AssistTokenUsage\n estCostUsd: number\n model: string\n stopReason: string | null\n /**\n * The thinking effort the request asked for, recorded on the step\n * (AGL-2937); absent or `null` when the request named none.\n */\n effort?: AiEffort | null\n /** The model declined the brief (`stop_reason: 'refusal'`). Tokens were spent. */\n refused?: boolean\n /**\n * The model answered and nothing usable came of it — no structured answer\n * even after the step's own re-ask, say (AGL-2938). Tokens were spent, so\n * the machine meters the step before it fails the job with this sentence,\n * which is customer-safe and the only thing a customer reads.\n */\n failure?: string\n /** The plan the plan step proposed, which the machine keeps on the job (AGL-2935). */\n plan?: AiJobPlan\n /**\n * The step stopped for a person (AGL-2935). The machine records what it\n * spent and parks the job `needs_review`: a `plan` review completes the\n * step, so confirming runs the next one; a `doctrine` review hands the\n * step back, so trying again runs the same one.\n */\n review?: AiJobReview\n /**\n * With a `plan` review: the plan needs an answer only the person can give\n * first (AGL-3978) — a tracking change with no pixel ID. The machine keeps\n * the plan and its review and parks the job `needs_input` with these\n * fields instead of `needs_review`; the answer returns it to the review.\n */\n input?: AiJobInputRequest\n /**\n * The answer was refused by the platform's own checks, not the model's\n * (AGL-3594): the machine meters the step as any other and then gives the\n * job's credits back to the meters it was charged to (`refundJobCredits`),\n * a bounded number of times a day.\n */\n uncredited?: boolean\n /**\n * The `failure` is the workspace's own limit, not a fault of ours: the\n * month's Free AI credits are spent (a 429 from the kind's admission, asked\n * when the plan is kept). The machine fails the job and gives the step's\n * credits back exactly as for our failure, but announces it as NOT ours, so\n * no staff alert is raised for a customer reaching their allowance.\n */\n customerLimit?: boolean\n /**\n * What a guided site start put live on its last pass (AGL-3596), kept on\n * the job by the machine in the write that records the pass: a step never\n * writes the job document itself.\n */\n sitePublish?: AiJobSitePublish\n /**\n * A `build` pass's unit and what it came to (AGL-3616), which the machine\n * writes to the job's item ledger in the transaction that records the\n * pass's spend. An item's failure is never the step's: it is a row, and\n * the build goes on.\n */\n item?: AiJobItemOutcome\n /** A `build`'s ledger to start from, written by the machine in the same transaction (AGL-3616). */\n items?: AiJobItemLedger[]\n /**\n * An `import` job's waves after this pass (AGL-3976), kept on the job by\n * the machine in the write that records the pass: a step never writes the\n * job document itself.\n */\n siteImport?: AiSiteImportState\n /**\n * A `site` job's pictures left on a starter photo after this pass\n * (AGL-3988), whole: what its late photo pass fills before the site is\n * published. A page's or a dataset's pass reports its own; the site step\n * merges them and the machine keeps the result on the job.\n */\n sitePhotos?: AiSitePhotos\n}\n\n/** What one `build` pass came to for the unit it ran (AGL-3616). */\nexport interface AiJobItemOutcome {\n slot: string\n /** `running` while the unit asks for another pass of its own. */\n status: AiJobItemStatus\n failure?: AiJobItemFailure | null\n /** Customer-safe: what the person should know about the item. */\n note?: string | null\n degradedBy?: string[]\n /** The ids of the outputs this pass produced for the item. */\n outputs?: string[]\n}\n\nexport interface AiJobStepContext {\n job: AiJob\n stepIndex: number\n now: Date\n /** Aborts the provider call when the runner's budget ends. */\n signal?: AbortSignal\n /**\n * The Admin SDK handle the machine runs on, for a step that reads what it\n * builds from — a site's theme, say — or writes a draft. A step never\n * writes the job document, the meter or the lease through it.\n */\n firestore: FirebaseFirestore.Firestore\n /** The org document the machine read for the step's reservation. */\n org?: Partial<AglynOrgBilling> | null\n /**\n * The model a step of this kind runs on for THIS job (AGL-2942): the\n * creator's pick where the plan and the allotment allowlists allow it,\n * the routing table otherwise. Absent, a runner asks the table itself.\n */\n modelFor?: (kind: AiStepKind) => string | undefined\n}\n\nexport type AiJobStepRunner = (\n context: AiJobStepContext,\n) => Promise<AiJobStepOutcome>\n\n/**\n * The static block, byte-identical on every request so it caches. The\n * customer's brief and the org's inputs never enter it.\n */\nexport const AI_JOB_TEXT_SYSTEM: AiSystemBlock[] = [\n {\n text:\n 'You write short, publishable copy for a website builder. You are ' +\n 'given a brief from the person who owns the site. Answer with the ' +\n 'copy only: no preamble, no headings unless the brief asks for them, ' +\n 'no closing remarks, no markdown fences. Keep to the length the brief ' +\n 'implies; when it implies none, write under 150 words. Write in the ' +\n 'language of the brief. Never invent facts, prices, names or claims ' +\n 'the brief did not give you; where a detail is missing, leave a ' +\n 'clearly marked placeholder in square brackets.',\n cacheBreakpoint: true,\n },\n]\n\n/** The brief as the user turn, with the kind-specific inputs stated plainly. */\nexport function aiJobTextPrompt(job: Pick<AiJob, 'brief' | 'inputs'>): string {\n const brief = job.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)\n const tone = typeof job.inputs?.['tone'] === 'string' ? job.inputs['tone'] : ''\n const audience =\n typeof job.inputs?.['audience'] === 'string' ? job.inputs['audience'] : ''\n const lines = [`Brief: ${brief}`]\n if (tone) lines.push(`Tone: ${tone}`)\n if (audience) lines.push(`Audience: ${audience}`)\n return lines.join('\\n')\n}\n\nexport const runAiJobTextStep: AiJobStepRunner = async ({ job, signal, modelFor }) => {\n const model = modelFor?.('job.text') ?? aiJobTextModel()\n // Overlay copy (AGL-3603): the same one request, answered as fields.\n if (aiOverlayCopyRequested(job.inputs)) {\n return runAiOverlayCopy({\n job,\n model,\n maxTokens: AI_JOB_TEXT_STEP_BUDGET.maxTokens(model),\n ...(signal ? { signal } : {}),\n })\n }\n // A development run's effort A/B arm (AGL-3781), else the row's.\n const effort = aiDevEffortFor('job.text') ?? AI_ROUTING_TABLE['job.text'].effort\n const result = await runAiRequest({\n model,\n system: AI_JOB_TEXT_SYSTEM,\n messages: [{ role: 'user', content: aiJobTextPrompt(job) }],\n // The routing ceiling, lowered on a tier too slow to answer it inside the\n // least time the step registered.\n maxTokens: AI_JOB_TEXT_STEP_BUDGET.maxTokens(model),\n ...(AI_ROUTING_TABLE['job.text'].thinking ? { thinking: AI_ROUTING_TABLE['job.text'].thinking } : {}),\n ...(effort ? { effort } : {}),\n stream: false,\n ...(signal ? { signal } : {}),\n })\n if (result.kind === 'refusal') {\n return {\n outputs: [],\n usage: result.usage,\n estCostUsd: result.estCostUsd,\n model,\n stopReason: result.stopReason,\n refused: true,\n }\n }\n const text = result.text.trim()\n return {\n outputs: text\n ? [\n {\n resource: 'text',\n // A text output has no document of its own; the id names the\n // step within the job so a later step can address it.\n id: 'draft',\n hostId: job.hostId ?? null,\n label: 'Draft copy',\n text,\n },\n ]\n : [],\n usage: result.usage,\n estCostUsd: result.estCostUsd,\n model,\n stopReason: result.stopReason,\n }\n}\n"],"names":["AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","aiDevEffortFor","runAiRequest","aiJobStepBudget","aiOverlayCopyRequested","runAiOverlayCopy","aiJobTextModel","AI_JOB_TEXT_MAX_TOKENS","maxTokens","AI_JOB_TEXT_STEP_BUDGET","tier","attempts","lookups","AI_JOB_TEXT_STEP_MINIMUM_MS","minimumMs","AI_JOB_BRIEF_MAX_CHARS","AI_JOB_TEXT_SYSTEM","text","cacheBreakpoint","aiJobTextPrompt","job","brief","slice","tone","inputs","audience","lines","push","join","runAiJobTextStep","signal","modelFor","model","effort","result","system","messages","role","content","thinking","stream","kind","outputs","usage","estCostUsd","stopReason","refused","trim","resource","id","hostId","label"],"mappings":"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"}
|
|
@@ -26,6 +26,7 @@ import { type AiJobRefundReason } from '../usage/assist-job-refund';
|
|
|
26
26
|
import type { AiJobItemOutcome } from './ai-job-text-step';
|
|
27
27
|
import { type FreeAssistAccount } from '../usage/assist-free-taste';
|
|
28
28
|
import { type AiSiteImportState } from '../model/ai-site-import';
|
|
29
|
+
import type { AiSitePhotos } from '../model/ai-site-photos';
|
|
29
30
|
/**
|
|
30
31
|
* AI generation jobs — the Firestore state machine (AGL-2904).
|
|
31
32
|
*
|
|
@@ -372,6 +373,8 @@ export interface RecordStepInput {
|
|
|
372
373
|
item?: AiJobItemRecord;
|
|
373
374
|
/** An `import` job's waves after this pass (AGL-3976), kept in this write. */
|
|
374
375
|
siteImport?: AiSiteImportState;
|
|
376
|
+
/** A `site` job's pictures left on a starter after this pass (AGL-3988), kept in this write. */
|
|
377
|
+
sitePhotos?: AiSitePhotos;
|
|
375
378
|
}
|
|
376
379
|
/** One `build` pass as its item's row records it (AGL-3616). */
|
|
377
380
|
export interface AiJobItemRecord extends AiJobItemOutcome {
|
package/src/lib/jobs/ai-jobs.js
CHANGED
|
@@ -888,6 +888,8 @@ function nextStepIndex(job) {
|
|
|
888
888
|
sitePublish: input.sitePublish
|
|
889
889
|
} : {}, input.siteImport ? {
|
|
890
890
|
siteImport: input.siteImport
|
|
891
|
+
} : {}, input.sitePhotos ? {
|
|
892
|
+
sitePhotos: input.sitePhotos
|
|
891
893
|
} : {}, items ? {
|
|
892
894
|
items
|
|
893
895
|
} : {}, ((_input_item = input.item) == null ? void 0 : _input_item.creditsRefunded) ? {
|
|
@@ -2043,6 +2045,8 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
2043
2045
|
sitePublish: outcome.sitePublish
|
|
2044
2046
|
} : {}, job.kind === 'import' && outcome.siteImport ? {
|
|
2045
2047
|
siteImport: outcome.siteImport
|
|
2048
|
+
} : {}, job.kind === 'site' && outcome.sitePhotos ? {
|
|
2049
|
+
sitePhotos: outcome.sitePhotos
|
|
2046
2050
|
} : {}, aiJobSettlesByItem(job.kind) && outcome.items ? {
|
|
2047
2051
|
items: outcome.items
|
|
2048
2052
|
} : {}, itemRecord ? {
|