@aglyn/plugins-ai 1.0.0-beta.235 → 1.0.0-beta.237
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-credits-prompt.component.d.ts +37 -0
- package/src/lib/components/ai-credits-prompt.component.js +67 -0
- package/src/lib/components/ai-credits-prompt.component.js.map +1 -0
- package/src/lib/components/ai-job-plan.component.d.ts +5 -4
- package/src/lib/components/ai-job-plan.component.js +37 -12
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-job-requests.d.ts +10 -0
- package/src/lib/components/ai-job-requests.js +12 -4
- package/src/lib/components/ai-job-requests.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +26 -7
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.js +68 -22
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/assist-build-card.component.d.ts +3 -1
- package/src/lib/components/assist-build-card.component.js +58 -7
- package/src/lib/components/assist-build-card.component.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +54 -6
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.js +2 -1
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/jobs/ai-job-admission.d.ts +22 -2
- package/src/lib/jobs/ai-job-admission.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.d.ts +24 -11
- package/src/lib/jobs/ai-job-build-step.js +50 -34
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.js +1 -1
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.js +4 -0
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.d.ts +13 -0
- package/src/lib/jobs/ai-job-page-step.js +34 -3
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +23 -0
- package/src/lib/jobs/ai-job-plan-step.js +75 -15
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +34 -3
- package/src/lib/jobs/ai-job-site-content.js +132 -32
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.js +45 -15
- 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 +18 -1
- package/src/lib/jobs/ai-jobs.js +29 -13
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +105 -18
- package/src/lib/jobs/ai-layout-stock-photos.js +356 -114
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/layout-language/ai-layout-compiler.d.ts +27 -0
- package/src/lib/layout-language/ai-layout-compiler.js +78 -11
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-gaps.js +3 -1
- package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
- package/src/lib/layout-language/ai-layout-language.js +1 -1
- package/src/lib/layout-language/ai-layout-language.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +37 -1
- package/src/lib/layout-language/ai-layout-listings.js +66 -11
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +2 -1
- package/src/lib/layout-language/ai-layout-pictures.js +5 -2
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/model/ai-build-job.d.ts +31 -0
- package/src/lib/model/ai-build-job.js +86 -1
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-progress.d.ts +8 -0
- package/src/lib/model/ai-build-progress.js +33 -0
- package/src/lib/model/ai-build-progress.js.map +1 -1
- package/src/lib/model/ai-credit-estimate.d.ts +182 -0
- package/src/lib/model/ai-credit-estimate.js +190 -0
- package/src/lib/model/ai-credit-estimate.js.map +1 -0
- package/src/lib/model/ai-jobs.types.d.ts +15 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +59 -22
- package/src/lib/model/ai-site-job.js +104 -23
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.js +3 -3
- package/src/lib/model/ai-site-kinds.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.js +10 -1
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-node-tree.d.ts +11 -0
- package/src/lib/runtime/ai-node-tree.js +26 -0
- package/src/lib/runtime/ai-node-tree.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +539 -7
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/server/ai-jobs-resume.js +93 -35
- package/src/lib/server/ai-jobs-resume.js.map +1 -1
- package/src/lib/server/ai-jobs-route.js +45 -4
- package/src/lib/server/ai-jobs-route.js.map +1 -1
- package/src/lib/server/assist-chat.js +4 -1
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-edit.js +3 -1
- package/src/lib/server/assist-edit.js.map +1 -1
- package/src/lib/server/assist-music.d.ts +36 -0
- package/src/lib/server/assist-music.js +36 -0
- package/src/lib/server/assist-music.js.map +1 -0
- package/src/lib/server.js +6 -0
- package/src/lib/server.js.map +1 -1
|
@@ -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 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 { runAiRequest, type AiEffort, type AiSystemBlock } from '../runtime/ai-runtime'\nimport type { AssistTokenUsage } from '../usage/assist-usage'\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 * 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 * 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\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 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 ...(AI_ROUTING_TABLE['job.text'].effort ? { effort: AI_ROUTING_TABLE['job.text'].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","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","result","system","messages","role","content","thinking","effort","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 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 { runAiRequest, type AiEffort, type AiSystemBlock } from '../runtime/ai-runtime'\nimport type { AssistTokenUsage } from '../usage/assist-usage'\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 * 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\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 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 ...(AI_ROUTING_TABLE['job.text'].effort ? { effort: AI_ROUTING_TABLE['job.text'].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","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","result","system","messages","role","content","thinking","effort","stream","kind","outputs","usage","estCostUsd","stopReason","refused","trim","resource","id","hostId","label"],"mappings":"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"}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
17
|
import { AI_JOB_PLAN_STEP, type AiJob, type AiJobApplied, type AiJobItemLedger, type AiJobKind, type AiJobOutput, type AiJobPlan, type AiJobReview, type AiJobSitePublish, type AiJobStatus, type AiJobStepTokens, type AiJobSummary } from '../model/ai-jobs.types';
|
|
18
|
+
import type { AiJobCreditsConfirmation } from '../model/ai-credit-estimate';
|
|
18
19
|
import type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types';
|
|
19
20
|
import { type AiActivityActor } from '../activity/ai-activity';
|
|
20
21
|
import { type AiEffort, type AiUsage } from '../runtime/ai-runtime';
|
|
@@ -256,6 +257,8 @@ export interface CreateAiJobInput {
|
|
|
256
257
|
createdBy: string;
|
|
257
258
|
/** The creator's address, for the activity row; the document stores the uid only. */
|
|
258
259
|
createdByEmail?: string | null;
|
|
260
|
+
/** A Free start the creator chose to build past what was left (AGL-3722). */
|
|
261
|
+
creditsConfirmed?: AiJobCreditsConfirmation | null;
|
|
259
262
|
}
|
|
260
263
|
/**
|
|
261
264
|
* A new job, `queued`, with its step plan laid out and nothing run.
|
|
@@ -497,7 +500,10 @@ export declare function settleAiBuildJob(firestore: Firestore, orgId: string, jo
|
|
|
497
500
|
* `changed: false` for a job that is not a finished build, or has nothing to
|
|
498
501
|
* try again.
|
|
499
502
|
*/
|
|
500
|
-
export declare function retryAiBuildJob(firestore: Firestore, orgId: string, jobId: string, now?: Date
|
|
503
|
+
export declare function retryAiBuildJob(firestore: Firestore, orgId: string, jobId: string, now?: Date, options?: {
|
|
504
|
+
/** A Free Try again the person chose to run past what was left (AGL-3722). */
|
|
505
|
+
creditsConfirmed?: AiJobCreditsConfirmation | null;
|
|
506
|
+
}): Promise<{
|
|
501
507
|
job: AiJob;
|
|
502
508
|
changed: boolean;
|
|
503
509
|
retried: string[];
|
|
@@ -523,6 +529,17 @@ export declare function resumeAiJob(firestore: Firestore, orgId: string, jobId:
|
|
|
523
529
|
* pass put its pages live.
|
|
524
530
|
*/
|
|
525
531
|
publishConfirmed?: boolean;
|
|
532
|
+
/**
|
|
533
|
+
* A Free build or start the person chose to build past what was left
|
|
534
|
+
* (AGL-3722), kept on the job: who, when, what was left.
|
|
535
|
+
*/
|
|
536
|
+
creditsConfirmed?: AiJobCreditsConfirmation | null;
|
|
537
|
+
/**
|
|
538
|
+
* The smaller first build the person chose instead (AGL-3722): its plan
|
|
539
|
+
* — the home page and what it needs — is the one confirmed, in place of
|
|
540
|
+
* the plan the job proposed. Only while that plan waits for confirmation.
|
|
541
|
+
*/
|
|
542
|
+
reducePlan?: (plan: AiJobPlan) => AiJobPlan | null;
|
|
526
543
|
}): Promise<{
|
|
527
544
|
job: AiJob;
|
|
528
545
|
changed: boolean;
|
package/src/lib/jobs/ai-jobs.js
CHANGED
|
@@ -383,7 +383,7 @@ function jobFrom(snapshot) {
|
|
|
383
383
|
} : {}));
|
|
384
384
|
// A job is a resource the console lists and routes to, named as one.
|
|
385
385
|
const ref = jobsCollection(firestore, input.orgId).doc(createResourceUid());
|
|
386
|
-
const data = {
|
|
386
|
+
const data = _extends({
|
|
387
387
|
orgId: input.orgId,
|
|
388
388
|
hostId: (_input_hostId = input.hostId) != null ? _input_hostId : null,
|
|
389
389
|
kind: input.kind,
|
|
@@ -403,7 +403,9 @@ function jobFrom(snapshot) {
|
|
|
403
403
|
expiresAt: assistExchangeExpiry(now),
|
|
404
404
|
error: null,
|
|
405
405
|
lease: null
|
|
406
|
-
}
|
|
406
|
+
}, input.creditsConfirmed ? {
|
|
407
|
+
creditsConfirmed: input.creditsConfirmed
|
|
408
|
+
} : {});
|
|
407
409
|
await ref.set(data);
|
|
408
410
|
await logAiJobCreated(input.orgId, {
|
|
409
411
|
uid: input.createdBy,
|
|
@@ -970,6 +972,7 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
970
972
|
* ended meanwhile — a person's cancel, which pays for what ran — is left as it
|
|
971
973
|
* ended and given nothing back.
|
|
972
974
|
*/ async function failOurFailure(firestore, orgId, jobId, message, detail, refund, now) {
|
|
975
|
+
var _detail_ours;
|
|
973
976
|
var _current_items;
|
|
974
977
|
const current = await getAiJob(firestore, orgId, jobId);
|
|
975
978
|
// A build that got as far as its items settles item by item (AGL-3616):
|
|
@@ -996,8 +999,10 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
996
999
|
now
|
|
997
1000
|
}));
|
|
998
1001
|
}
|
|
1002
|
+
// Given back as ours either way; ANNOUNCED as ours unless the step said the
|
|
1003
|
+
// failure is the customer's own limit (`customerLimit`), which pages no one.
|
|
999
1004
|
return failAiJob(firestore, orgId, jobId, message, _extends({}, detail, {
|
|
1000
|
-
ours: true
|
|
1005
|
+
ours: (_detail_ours = detail.ours) != null ? _detail_ours : true
|
|
1001
1006
|
}), now);
|
|
1002
1007
|
}
|
|
1003
1008
|
// ── A build's settlement (AGL-3616) ──────────────────────────────────────
|
|
@@ -1222,7 +1227,7 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1222
1227
|
* re-asks the plan. Held at what the retried items are estimated to cost.
|
|
1223
1228
|
* `changed: false` for a job that is not a finished build, or has nothing to
|
|
1224
1229
|
* try again.
|
|
1225
|
-
*/ export async function retryAiBuildJob(firestore, orgId, jobId, now = new Date()) {
|
|
1230
|
+
*/ export async function retryAiBuildJob(firestore, orgId, jobId, now = new Date(), options = {}) {
|
|
1226
1231
|
let retried = [];
|
|
1227
1232
|
const result = await transition(firestore, orgId, jobId, (current)=>{
|
|
1228
1233
|
var _current_plan, _current_items;
|
|
@@ -1232,7 +1237,7 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1232
1237
|
const next = aiBuildRetryLedger(current.items, current.kind === 'site' ? aiLedgerUnits(current.items) : aiBuildUnits(plan));
|
|
1233
1238
|
if (!next.ledger.some((row)=>row.status === 'pending')) return null;
|
|
1234
1239
|
retried = next.retried;
|
|
1235
|
-
return {
|
|
1240
|
+
return _extends({
|
|
1236
1241
|
status: 'queued',
|
|
1237
1242
|
items: next.ledger,
|
|
1238
1243
|
steps: current.steps.map((step)=>step.name === AI_JOB_PLAN_STEP ? step : _extends({}, step, {
|
|
@@ -1247,9 +1252,12 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1247
1252
|
lease: null,
|
|
1248
1253
|
creditsReserved: Math.max(AI_JOB_STEP_RESERVE_CREDITS, current.kind === 'site' ? next.retried.length * AI_JOB_STEP_RESERVE_CREDITS : aiBuildCreditEstimate(plan, {
|
|
1249
1254
|
slots: new Set(next.retried)
|
|
1250
|
-
}))
|
|
1255
|
+
}))
|
|
1256
|
+
}, options.creditsConfirmed ? {
|
|
1257
|
+
creditsConfirmed: options.creditsConfirmed
|
|
1258
|
+
} : {}, {
|
|
1251
1259
|
updatedAt: now
|
|
1252
|
-
};
|
|
1260
|
+
});
|
|
1253
1261
|
});
|
|
1254
1262
|
return _extends({}, result, {
|
|
1255
1263
|
retried: result.changed ? retried : []
|
|
@@ -1268,6 +1276,7 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1268
1276
|
* (AGL-3031), creations included, where that is more.
|
|
1269
1277
|
*/ export async function resumeAiJob(firestore, orgId, jobId, actor, now = new Date(), options = {}) {
|
|
1270
1278
|
return transition(firestore, orgId, jobId, (current)=>{
|
|
1279
|
+
var _options_reducePlan;
|
|
1271
1280
|
var _current_review, _current_inputs;
|
|
1272
1281
|
// A job the meter paused (AGL-3660) carries on from the step it paused
|
|
1273
1282
|
// on: that step is already pending, so it is queued again with nothing
|
|
@@ -1287,7 +1296,8 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1287
1296
|
}) : step);
|
|
1288
1297
|
const outstanding = steps.filter((step)=>step.status === 'pending').length;
|
|
1289
1298
|
const pending = outstanding > 0;
|
|
1290
|
-
const
|
|
1299
|
+
const proposed = ((_current_review = current.review) == null ? void 0 : _current_review.reason) === 'plan' && current.plan ? current.plan : null;
|
|
1300
|
+
const confirming = proposed && options.reducePlan ? (_options_reducePlan = options.reducePlan(proposed)) != null ? _options_reducePlan : proposed : proposed;
|
|
1291
1301
|
const confirmed = confirming ? {
|
|
1292
1302
|
plan: _extends({}, confirming, {
|
|
1293
1303
|
status: 'confirmed',
|
|
@@ -1305,7 +1315,9 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1305
1315
|
steps,
|
|
1306
1316
|
review: null,
|
|
1307
1317
|
error: null
|
|
1308
|
-
}, confirmed, publish, {
|
|
1318
|
+
}, confirmed, publish, options.creditsConfirmed ? {
|
|
1319
|
+
creditsConfirmed: options.creditsConfirmed
|
|
1320
|
+
} : {}, {
|
|
1309
1321
|
creditsReserved: pending ? Math.max(outstanding * AI_JOB_STEP_RESERVE_CREDITS, confirming ? aiJobCreditEstimate(current.kind, confirming) : 0) : 0,
|
|
1310
1322
|
updatedAt: now
|
|
1311
1323
|
});
|
|
@@ -1632,10 +1644,12 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1632
1644
|
}, now);
|
|
1633
1645
|
return {
|
|
1634
1646
|
outcome: 'failed',
|
|
1635
|
-
job: await failOurFailure(firestore, orgId, jobId, outcome.failure, {
|
|
1647
|
+
job: await failOurFailure(firestore, orgId, jobId, outcome.failure, _extends({
|
|
1636
1648
|
stepIndex,
|
|
1637
1649
|
error: `step failure before the provider: ${outcome.failure}`
|
|
1638
|
-
}, {
|
|
1650
|
+
}, outcome.customerLimit ? {
|
|
1651
|
+
ours: false
|
|
1652
|
+
} : {}), {
|
|
1639
1653
|
reason: 'step-failure',
|
|
1640
1654
|
stepCredits: 0,
|
|
1641
1655
|
free: (_reservation_free1 = reservation.free) != null ? _reservation_free1 : null
|
|
@@ -1703,10 +1717,12 @@ async function transition(firestore, orgId, jobId, apply) {
|
|
|
1703
1717
|
error: message,
|
|
1704
1718
|
tokens
|
|
1705
1719
|
}, now);
|
|
1706
|
-
const detail = {
|
|
1720
|
+
const detail = _extends({
|
|
1707
1721
|
stepIndex,
|
|
1708
1722
|
error: outcome.refused ? 'stop_reason refusal' : `step failure: ${message}`
|
|
1709
|
-
}
|
|
1723
|
+
}, outcome.customerLimit ? {
|
|
1724
|
+
ours: false
|
|
1725
|
+
} : {});
|
|
1710
1726
|
// A model declining the brief is not our failure; a step's own is (AGL-3594).
|
|
1711
1727
|
return {
|
|
1712
1728
|
outcome: 'failed',
|