@aglyn/aglyn 1.0.0-beta.246 → 1.0.0-beta.249
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 +11 -11
- package/src/lib/app-utils/contact-custom-fields.d.ts +23 -1
- package/src/lib/app-utils/contact-custom-fields.js +84 -2
- package/src/lib/app-utils/contact-custom-fields.js.map +1 -1
- package/src/lib/app-utils/crm.d.ts +7 -1
- package/src/lib/app-utils/crm.js.map +1 -1
- package/src/lib/app-utils/default-site.js +21 -33
- package/src/lib/app-utils/default-site.js.map +1 -1
- package/src/lib/app-utils/docs-help-sections.generated.js +8 -8
- package/src/lib/app-utils/docs-help-sections.generated.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +8 -8
- package/src/lib/app-utils/docs-help.generated.js +9 -5
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +129 -59
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/element-pick-protocol.d.ts +54 -0
- package/src/lib/app-utils/element-pick-protocol.js +41 -0
- package/src/lib/app-utils/element-pick-protocol.js.map +1 -0
- package/src/lib/app-utils/element-pick-target.d.ts +32 -0
- package/src/lib/app-utils/element-pick-target.js +47 -0
- package/src/lib/app-utils/element-pick-target.js.map +1 -0
- package/src/lib/app-utils/error-beacon.d.ts +19 -0
- package/src/lib/app-utils/error-beacon.js +31 -5
- package/src/lib/app-utils/error-beacon.js.map +1 -1
- package/src/lib/app-utils/form-contract.js +13 -6
- package/src/lib/app-utils/form-contract.js.map +1 -1
- package/src/lib/app-utils/forms.d.ts +31 -0
- package/src/lib/app-utils/forms.js +213 -16
- package/src/lib/app-utils/forms.js.map +1 -1
- package/src/lib/app-utils/node-definition-sanitizer.js +12 -0
- package/src/lib/app-utils/node-definition-sanitizer.js.map +1 -1
- package/src/lib/app-utils/seo-locale.d.ts +14 -0
- package/src/lib/app-utils/seo-locale.js +15 -0
- package/src/lib/app-utils/seo-locale.js.map +1 -1
- package/src/lib/app-utils/seo-site-scan.js +18 -3
- package/src/lib/app-utils/seo-site-scan.js.map +1 -1
- package/src/lib/app-utils/site-live-url.d.ts +24 -0
- package/src/lib/app-utils/site-live-url.js +60 -0
- package/src/lib/app-utils/site-live-url.js.map +1 -0
- package/src/lib/app-utils/starter-template-nodes.d.ts +13 -0
- package/src/lib/app-utils/starter-template-nodes.js +40 -0
- package/src/lib/app-utils/starter-template-nodes.js.map +1 -1
- package/src/lib/app-utils/starter-templates.d.ts +1 -1
- package/src/lib/app-utils/starter-templates.js +19 -31
- package/src/lib/app-utils/starter-templates.js.map +1 -1
- package/src/lib/plugin-manager/feature-plugins.d.ts +74 -3
- package/src/lib/plugin-manager/feature-plugins.js +9 -0
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
- package/src/lib/plugin-manager/plugin-ai-capabilities.d.ts +11 -0
- package/src/lib/plugin-manager/plugin-ai-capabilities.js.map +1 -1
- package/src/lib/plugin-manager/plugin-resource-drafts.d.ts +23 -0
- package/src/lib/plugin-manager/plugin-resource-drafts.js.map +1 -1
- package/src/lib/plugin-manager/stock-photo-provider.d.ts +67 -0
- package/src/lib/plugin-manager/stock-photo-provider.js +14 -1
- package/src/lib/plugin-manager/stock-photo-provider.js.map +1 -1
|
@@ -97,6 +97,8 @@ export interface PluginAiCapabilityItem {
|
|
|
97
97
|
/** The item's name in the plan, unique within it; other items cite it as `new:<name>`. */
|
|
98
98
|
name: string;
|
|
99
99
|
args: PluginAiCapabilityArgs;
|
|
100
|
+
/** Why the planner says the request needs it, where it said. */
|
|
101
|
+
why?: string;
|
|
100
102
|
}
|
|
101
103
|
/** What a capability's `draftContent` is told beside the item. */
|
|
102
104
|
export interface PluginAiCapabilityDraftContext {
|
|
@@ -156,6 +158,15 @@ export interface PluginAiCapability {
|
|
|
156
158
|
live?: boolean;
|
|
157
159
|
/** The most this item can cost, in AI credits; a draft written without a model costs 0. */
|
|
158
160
|
estimateCredits(args: PluginAiCapabilityArgs): number;
|
|
161
|
+
/**
|
|
162
|
+
* The question to ask the person when an item leaves out something only
|
|
163
|
+
* they can give — an account id the planner must never make up — or
|
|
164
|
+
* `null` when the item can be built. Pure. A plan holding such an item is
|
|
165
|
+
* not kept: the build asks this instead of building nothing, or building
|
|
166
|
+
* it from a made-up value. Absent: every item that passes its schema can
|
|
167
|
+
* be built.
|
|
168
|
+
*/
|
|
169
|
+
missingInput?(item: PluginAiCapabilityItem): string | null;
|
|
159
170
|
/** Operations an item of this one may depend on. */
|
|
160
171
|
dependsOnOps?: readonly string[];
|
|
161
172
|
degrade: PluginAiCapabilityDegrade;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-ai-capabilities.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginServices,\n} from './plugin-services'\n\n/**\n * What an AI build can make, contributed by the plugin that owns each thing\n * (AGL-3616).\n *\n * A build turns one request — \"a few pages, a contact form and a way to book\n * me\" — into a plan of ITEMS, each an operation (`op`) with arguments, and\n * runs them in dependency order. The AI plugin plans and meters the build;\n * it does not know what a plugin's resource is, and the package map forbids\n * it importing that plugin. So every operation is a CAPABILITY the owner\n * registers from its server entry, and the build asks for one by `op`:\n *\n * - the planner offers the model only the operations registered here, in\n * the owner's own words (`noun`, `intents`, `argsSchema`);\n * - admission removes an item whose capability this site, plan or member\n * cannot use (`feature`, `permission`, `quota`, `freeAllowed`), with a\n * sentence, so the rest of the plan still runs;\n * - the item is executed one of two ways: `runnerKind` hands it to an\n * existing AI job runner (the AI plugin's own operations), and\n * `draftResource` hands its content to the owner's writer on\n * `plugin-resource-drafts`, which checks it, meets the allowance and\n * writes a draft that nothing publishes.\n *\n * An operation lights up the moment its owner registers it; nothing in the\n * AI plugin names it. Register by CALLING `registerPluginAiCapability` from a\n * function the server entry runs — a top-level side effect is dropped by a\n * bundler that sees no import of its result.\n *\n * Server-only: import this file by its own path, never from the barrel that\n * every published page loads.\n *\n * ## One capability per operation\n *\n * An operation has one owner. A second plugin registering an `op` another\n * plugin already owns is refused, naming both, and the incumbent keeps\n * serving; the same plugin registering again replaces its own.\n */\n\n/** A value an item's arguments may hold: scalars and lists of strings only. */\nexport type PluginAiCapabilityArgValue = string | number | boolean | readonly string[]\n\n/** An item's arguments, in the shape its capability's `argsSchema` declares. */\nexport type PluginAiCapabilityArgs = Readonly<Record<string, PluginAiCapabilityArgValue>>\n\n/**\n * One argument, as a JSON Schema property the model fills in directly. The\n * subset is deliberately small: a flat object of scalars and string lists is\n * what a planner can be held to, and what `pluginAiCapabilityArgsProblems`\n * checks without a schema library.\n */\nexport interface PluginAiCapabilityArgProperty {\n type: 'string' | 'integer' | 'number' | 'boolean' | 'array'\n /** Read by the model: what the value means and how to choose it. */\n description: string\n /** For `string`: the allowed values. */\n enum?: readonly string[]\n /** For `integer`/`number`. */\n minimum?: number\n maximum?: number\n /** For `string`, and each item of an `array`. */\n maxLength?: number\n /** For `array`: always a list of strings. */\n items?: { type: 'string'; enum?: readonly string[]; maxLength?: number }\n maxItems?: number\n}\n\n/** A flat JSON Schema object: no nesting, no extra keys. */\nexport interface PluginAiCapabilityArgsSchema {\n type: 'object'\n properties: Readonly<Record<string, PluginAiCapabilityArgProperty>>\n required?: readonly string[]\n additionalProperties: false\n}\n\n/** How the items that depend on a failed item of this capability behave. */\nexport type PluginAiCapabilityDegrade =\n /** Dependents are built without it (a page loses the block that used it). */\n | 'omit'\n /** Dependents use something that already exists in its place (a site's own layout). */\n | 'fallback'\n\n/** One planned item, as a capability sees it. */\nexport interface PluginAiCapabilityItem {\n /** The item's name in the plan, unique within it; other items cite it as `new:<name>`. */\n name: string\n args: PluginAiCapabilityArgs\n}\n\n/** What a capability's `draftContent` is told beside the item. */\nexport interface PluginAiCapabilityDraftContext {\n hostId: string\n /**\n * The drafts written for the items this one depends on, keyed by the\n * reference the plan used (`new:<name>`), each with its operation and the\n * id its own writer or runner gave it. A dependency that did not succeed is\n * absent.\n */\n dependencies: Readonly<Record<string, { op: string; id: string }>>\n}\n\nexport interface PluginAiCapability {\n /** The operation's name in a plan, stable across releases: `page`, `note`. */\n op: string\n /** What one is called in a sentence a person reads: \"contact form\". */\n noun: string\n /** Where a person finds the draft afterwards: \"Notes → Drafts\". */\n where: string\n /**\n * What a person might ask for that this makes, as short phrases the chat's\n * instructions list. True of the shipped product: never a promise the\n * writer cannot keep.\n */\n intents: readonly string[]\n /** The item's arguments, which the planner fills in. */\n argsSchema: PluginAiCapabilityArgsSchema\n /** How many items of this operation one plan may hold. */\n maxPerPlan: number\n /** Whether a workspace on the free plan may have one made. */\n freeAllowed: boolean\n /** The entitlement a workspace needs, checked with the platform's entitlement read. */\n feature?: string\n /** The site permission the member needs. */\n permission?: string\n /** The allowance one counts against, which the owner's writer meets. */\n quota?: string\n /**\n * The resource whose writer on `plugin-resource-drafts` makes the draft.\n * Exactly one of this and `runnerKind` is set.\n */\n draftResource?: string\n /**\n * The AI job kind whose runner makes it — the AI plugin's own operations.\n * Exactly one of this and `draftResource` is set.\n */\n runnerKind?: string\n /**\n * The writer's write takes effect at once, because the resource has no\n * draft state (a redirect rule, AGL-3745; a CRM pipeline or custom field,\n * AGL-3746), so the job reports the item as live in `where` rather than as\n * a draft. Absent: the writer writes a draft.\n */\n live?: boolean\n /** The most this item can cost, in AI credits; a draft written without a model costs 0. */\n estimateCredits(args: PluginAiCapabilityArgs): number\n /** Operations an item of this one may depend on. */\n dependsOnOps?: readonly string[]\n degrade: PluginAiCapabilityDegrade\n /**\n * Blocks a page places when it uses an item of this operation (the block\n * names the page palette knows). When the item fails, a dependent page is\n * built without them, and says so.\n */\n pageBlocks?: readonly string[]\n /**\n * The writer's content for an item, from its arguments and its built\n * dependencies. Pure. Absent means the arguments ARE the content.\n */\n draftContent?(\n item: PluginAiCapabilityItem,\n context: PluginAiCapabilityDraftContext,\n ): Readonly<Record<string, unknown>>\n}\n\nexport const PLUGIN_AI_CAPABILITIES = definePluginServiceContract<PluginAiCapability>(\n 'core.ai-capabilities',\n { multiple: true },\n)\n\nconst OP_PATTERN = /^[a-z][a-z0-9-]{0,39}$/\n\n/** Why a capability cannot be registered, or `null`. */\nexport function pluginAiCapabilityProblem(capability: PluginAiCapability): string | null {\n if (!OP_PATTERN.test(capability.op)) {\n return `ai capability op \"${capability.op}\" must be lowercase letters, digits and dashes`\n }\n const executors = [capability.draftResource, capability.runnerKind].filter(\n (one) => typeof one === 'string' && one.trim(),\n )\n if (executors.length !== 1) {\n return `ai capability \"${capability.op}\" needs exactly one of draftResource and runnerKind`\n }\n if (!Number.isInteger(capability.maxPerPlan) || capability.maxPerPlan < 1) {\n return `ai capability \"${capability.op}\" needs a maxPerPlan of at least 1`\n }\n if (capability.argsSchema?.type !== 'object' || capability.argsSchema.additionalProperties !== false) {\n return `ai capability \"${capability.op}\" needs a flat object argsSchema with additionalProperties: false`\n }\n for (const required of capability.argsSchema.required ?? []) {\n if (!(required in capability.argsSchema.properties)) {\n return `ai capability \"${capability.op}\" requires \"${required}\", which its schema does not declare`\n }\n }\n if (!capability.noun.trim() || !capability.where.trim()) {\n return `ai capability \"${capability.op}\" needs a noun and a where`\n }\n return null\n}\n\n/**\n * Registers what a plugin's AI builds can make. The owner is the loader's\n * marker when a register fn is running, else `options.pluginId`. An `op`\n * another plugin owns throws naming both; a malformed capability throws.\n */\nexport function registerPluginAiCapability(\n capability: PluginAiCapability,\n options?: { pluginId?: string },\n): void {\n const problem = pluginAiCapabilityProblem(capability)\n if (problem) throw new Error(problem)\n const key = capability.op\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n const incumbent = resolvePluginServices(PLUGIN_AI_CAPABILITIES).find(\n (entry) => entry.key === key,\n )\n if (incumbent && pluginId && incumbent.pluginId !== pluginId) {\n throw new Error(\n `ai capability \"${key}\" is already registered by \"${incumbent.pluginId}\"; ` +\n `refused \"${pluginId}\"`,\n )\n }\n registerPluginService(PLUGIN_AI_CAPABILITIES, capability, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n key,\n })\n}\n\nexport interface ResolvedPluginAiCapability {\n /** The plugin that owns the operation. */\n pluginId: string\n capability: PluginAiCapability\n}\n\n/** Every registered capability with its owner, in registration order. */\nexport function pluginAiCapabilities(): ResolvedPluginAiCapability[] {\n return resolvePluginServices(PLUGIN_AI_CAPABILITIES).map((entry) => ({\n pluginId: entry.pluginId,\n capability: entry.impl,\n }))\n}\n\n/** The capability for an operation, with its owner, or `null` when nothing registered it. */\nexport function pluginAiCapability(op: string): ResolvedPluginAiCapability | null {\n const key = op.trim()\n const entry = resolvePluginServices(PLUGIN_AI_CAPABILITIES).find((one) => one.key === key)\n return entry ? { pluginId: entry.pluginId, capability: entry.impl } : null\n}\n\n/**\n * What is wrong with an item's arguments against its schema; empty when\n * nothing is. Pure, and the same answer on both sides of the seam: the\n * planner holds the model to it, and an owner may hold its content to it.\n */\nexport function pluginAiCapabilityArgsProblems(\n schema: PluginAiCapabilityArgsSchema,\n args: Readonly<Record<string, unknown>>,\n): string[] {\n const problems: string[] = []\n for (const name of schema.required ?? []) {\n const value = args[name]\n if (value === undefined || value === null || value === '') problems.push(`\"${name}\" is required`)\n }\n for (const [name, value] of Object.entries(args)) {\n const property = schema.properties[name]\n if (!property) {\n problems.push(`\"${name}\" is not an argument`)\n continue\n }\n if (value === undefined || value === null) continue\n switch (property.type) {\n case 'string':\n if (typeof value !== 'string') problems.push(`\"${name}\" must be text`)\n else {\n if (property.enum && !property.enum.includes(value)) {\n problems.push(`\"${name}\" must be one of ${property.enum.join(', ')}`)\n }\n if (property.maxLength !== undefined && value.length > property.maxLength) {\n problems.push(`\"${name}\" must be at most ${property.maxLength} characters`)\n }\n }\n break\n case 'integer':\n case 'number':\n if (\n typeof value !== 'number' ||\n !Number.isFinite(value) ||\n (property.type === 'integer' && !Number.isInteger(value))\n ) {\n problems.push(`\"${name}\" must be ${property.type === 'integer' ? 'a whole number' : 'a number'}`)\n } else {\n if (property.minimum !== undefined && value < property.minimum) {\n problems.push(`\"${name}\" must be at least ${property.minimum}`)\n }\n if (property.maximum !== undefined && value > property.maximum) {\n problems.push(`\"${name}\" must be at most ${property.maximum}`)\n }\n }\n break\n case 'boolean':\n if (typeof value !== 'boolean') problems.push(`\"${name}\" must be true or false`)\n break\n case 'array':\n if (!Array.isArray(value) || value.some((one) => typeof one !== 'string')) {\n problems.push(`\"${name}\" must be a list of text`)\n } else {\n if (property.maxItems !== undefined && value.length > property.maxItems) {\n problems.push(`\"${name}\" must hold at most ${property.maxItems}`)\n }\n const items = property.items\n for (const one of value as string[]) {\n if (items?.enum && !items.enum.includes(one)) {\n problems.push(`\"${name}\" holds \"${one}\", which is not one of ${items.enum.join(', ')}`)\n break\n }\n if (items?.maxLength !== undefined && one.length > items.maxLength) {\n problems.push(`\"${name}\" holds an entry longer than ${items.maxLength} characters`)\n break\n }\n }\n }\n break\n }\n }\n return problems\n}\n"],"names":["getRegisteringPluginId","definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_AI_CAPABILITIES","multiple","OP_PATTERN","pluginAiCapabilityProblem","capability","test","op","executors","draftResource","runnerKind","filter","one","trim","length","Number","isInteger","maxPerPlan","argsSchema","type","additionalProperties","required","properties","noun","where","registerPluginAiCapability","options","problem","Error","key","pluginId","incumbent","find","entry","pluginAiCapabilities","map","impl","pluginAiCapability","pluginAiCapabilityArgsProblems","schema","args","problems","name","value","undefined","push","Object","entries","property","enum","includes","join","maxLength","isFinite","minimum","maximum","Array","isArray","some","maxItems","items"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-ai-capabilities.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginServices,\n} from './plugin-services'\n\n/**\n * What an AI build can make, contributed by the plugin that owns each thing\n * (AGL-3616).\n *\n * A build turns one request — \"a few pages, a contact form and a way to book\n * me\" — into a plan of ITEMS, each an operation (`op`) with arguments, and\n * runs them in dependency order. The AI plugin plans and meters the build;\n * it does not know what a plugin's resource is, and the package map forbids\n * it importing that plugin. So every operation is a CAPABILITY the owner\n * registers from its server entry, and the build asks for one by `op`:\n *\n * - the planner offers the model only the operations registered here, in\n * the owner's own words (`noun`, `intents`, `argsSchema`);\n * - admission removes an item whose capability this site, plan or member\n * cannot use (`feature`, `permission`, `quota`, `freeAllowed`), with a\n * sentence, so the rest of the plan still runs;\n * - the item is executed one of two ways: `runnerKind` hands it to an\n * existing AI job runner (the AI plugin's own operations), and\n * `draftResource` hands its content to the owner's writer on\n * `plugin-resource-drafts`, which checks it, meets the allowance and\n * writes a draft that nothing publishes.\n *\n * An operation lights up the moment its owner registers it; nothing in the\n * AI plugin names it. Register by CALLING `registerPluginAiCapability` from a\n * function the server entry runs — a top-level side effect is dropped by a\n * bundler that sees no import of its result.\n *\n * Server-only: import this file by its own path, never from the barrel that\n * every published page loads.\n *\n * ## One capability per operation\n *\n * An operation has one owner. A second plugin registering an `op` another\n * plugin already owns is refused, naming both, and the incumbent keeps\n * serving; the same plugin registering again replaces its own.\n */\n\n/** A value an item's arguments may hold: scalars and lists of strings only. */\nexport type PluginAiCapabilityArgValue = string | number | boolean | readonly string[]\n\n/** An item's arguments, in the shape its capability's `argsSchema` declares. */\nexport type PluginAiCapabilityArgs = Readonly<Record<string, PluginAiCapabilityArgValue>>\n\n/**\n * One argument, as a JSON Schema property the model fills in directly. The\n * subset is deliberately small: a flat object of scalars and string lists is\n * what a planner can be held to, and what `pluginAiCapabilityArgsProblems`\n * checks without a schema library.\n */\nexport interface PluginAiCapabilityArgProperty {\n type: 'string' | 'integer' | 'number' | 'boolean' | 'array'\n /** Read by the model: what the value means and how to choose it. */\n description: string\n /** For `string`: the allowed values. */\n enum?: readonly string[]\n /** For `integer`/`number`. */\n minimum?: number\n maximum?: number\n /** For `string`, and each item of an `array`. */\n maxLength?: number\n /** For `array`: always a list of strings. */\n items?: { type: 'string'; enum?: readonly string[]; maxLength?: number }\n maxItems?: number\n}\n\n/** A flat JSON Schema object: no nesting, no extra keys. */\nexport interface PluginAiCapabilityArgsSchema {\n type: 'object'\n properties: Readonly<Record<string, PluginAiCapabilityArgProperty>>\n required?: readonly string[]\n additionalProperties: false\n}\n\n/** How the items that depend on a failed item of this capability behave. */\nexport type PluginAiCapabilityDegrade =\n /** Dependents are built without it (a page loses the block that used it). */\n | 'omit'\n /** Dependents use something that already exists in its place (a site's own layout). */\n | 'fallback'\n\n/** One planned item, as a capability sees it. */\nexport interface PluginAiCapabilityItem {\n /** The item's name in the plan, unique within it; other items cite it as `new:<name>`. */\n name: string\n args: PluginAiCapabilityArgs\n /** Why the planner says the request needs it, where it said. */\n why?: string\n}\n\n/** What a capability's `draftContent` is told beside the item. */\nexport interface PluginAiCapabilityDraftContext {\n hostId: string\n /**\n * The drafts written for the items this one depends on, keyed by the\n * reference the plan used (`new:<name>`), each with its operation and the\n * id its own writer or runner gave it. A dependency that did not succeed is\n * absent.\n */\n dependencies: Readonly<Record<string, { op: string; id: string }>>\n}\n\nexport interface PluginAiCapability {\n /** The operation's name in a plan, stable across releases: `page`, `note`. */\n op: string\n /** What one is called in a sentence a person reads: \"contact form\". */\n noun: string\n /** Where a person finds the draft afterwards: \"Notes → Drafts\". */\n where: string\n /**\n * What a person might ask for that this makes, as short phrases the chat's\n * instructions list. True of the shipped product: never a promise the\n * writer cannot keep.\n */\n intents: readonly string[]\n /** The item's arguments, which the planner fills in. */\n argsSchema: PluginAiCapabilityArgsSchema\n /** How many items of this operation one plan may hold. */\n maxPerPlan: number\n /** Whether a workspace on the free plan may have one made. */\n freeAllowed: boolean\n /** The entitlement a workspace needs, checked with the platform's entitlement read. */\n feature?: string\n /** The site permission the member needs. */\n permission?: string\n /** The allowance one counts against, which the owner's writer meets. */\n quota?: string\n /**\n * The resource whose writer on `plugin-resource-drafts` makes the draft.\n * Exactly one of this and `runnerKind` is set.\n */\n draftResource?: string\n /**\n * The AI job kind whose runner makes it — the AI plugin's own operations.\n * Exactly one of this and `draftResource` is set.\n */\n runnerKind?: string\n /**\n * The writer's write takes effect at once, because the resource has no\n * draft state (a redirect rule, AGL-3745; a CRM pipeline or custom field,\n * AGL-3746), so the job reports the item as live in `where` rather than as\n * a draft. Absent: the writer writes a draft.\n */\n live?: boolean\n /** The most this item can cost, in AI credits; a draft written without a model costs 0. */\n estimateCredits(args: PluginAiCapabilityArgs): number\n /**\n * The question to ask the person when an item leaves out something only\n * they can give — an account id the planner must never make up — or\n * `null` when the item can be built. Pure. A plan holding such an item is\n * not kept: the build asks this instead of building nothing, or building\n * it from a made-up value. Absent: every item that passes its schema can\n * be built.\n */\n missingInput?(item: PluginAiCapabilityItem): string | null\n /** Operations an item of this one may depend on. */\n dependsOnOps?: readonly string[]\n degrade: PluginAiCapabilityDegrade\n /**\n * Blocks a page places when it uses an item of this operation (the block\n * names the page palette knows). When the item fails, a dependent page is\n * built without them, and says so.\n */\n pageBlocks?: readonly string[]\n /**\n * The writer's content for an item, from its arguments and its built\n * dependencies. Pure. Absent means the arguments ARE the content.\n */\n draftContent?(\n item: PluginAiCapabilityItem,\n context: PluginAiCapabilityDraftContext,\n ): Readonly<Record<string, unknown>>\n}\n\nexport const PLUGIN_AI_CAPABILITIES = definePluginServiceContract<PluginAiCapability>(\n 'core.ai-capabilities',\n { multiple: true },\n)\n\nconst OP_PATTERN = /^[a-z][a-z0-9-]{0,39}$/\n\n/** Why a capability cannot be registered, or `null`. */\nexport function pluginAiCapabilityProblem(capability: PluginAiCapability): string | null {\n if (!OP_PATTERN.test(capability.op)) {\n return `ai capability op \"${capability.op}\" must be lowercase letters, digits and dashes`\n }\n const executors = [capability.draftResource, capability.runnerKind].filter(\n (one) => typeof one === 'string' && one.trim(),\n )\n if (executors.length !== 1) {\n return `ai capability \"${capability.op}\" needs exactly one of draftResource and runnerKind`\n }\n if (!Number.isInteger(capability.maxPerPlan) || capability.maxPerPlan < 1) {\n return `ai capability \"${capability.op}\" needs a maxPerPlan of at least 1`\n }\n if (capability.argsSchema?.type !== 'object' || capability.argsSchema.additionalProperties !== false) {\n return `ai capability \"${capability.op}\" needs a flat object argsSchema with additionalProperties: false`\n }\n for (const required of capability.argsSchema.required ?? []) {\n if (!(required in capability.argsSchema.properties)) {\n return `ai capability \"${capability.op}\" requires \"${required}\", which its schema does not declare`\n }\n }\n if (!capability.noun.trim() || !capability.where.trim()) {\n return `ai capability \"${capability.op}\" needs a noun and a where`\n }\n return null\n}\n\n/**\n * Registers what a plugin's AI builds can make. The owner is the loader's\n * marker when a register fn is running, else `options.pluginId`. An `op`\n * another plugin owns throws naming both; a malformed capability throws.\n */\nexport function registerPluginAiCapability(\n capability: PluginAiCapability,\n options?: { pluginId?: string },\n): void {\n const problem = pluginAiCapabilityProblem(capability)\n if (problem) throw new Error(problem)\n const key = capability.op\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n const incumbent = resolvePluginServices(PLUGIN_AI_CAPABILITIES).find(\n (entry) => entry.key === key,\n )\n if (incumbent && pluginId && incumbent.pluginId !== pluginId) {\n throw new Error(\n `ai capability \"${key}\" is already registered by \"${incumbent.pluginId}\"; ` +\n `refused \"${pluginId}\"`,\n )\n }\n registerPluginService(PLUGIN_AI_CAPABILITIES, capability, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n key,\n })\n}\n\nexport interface ResolvedPluginAiCapability {\n /** The plugin that owns the operation. */\n pluginId: string\n capability: PluginAiCapability\n}\n\n/** Every registered capability with its owner, in registration order. */\nexport function pluginAiCapabilities(): ResolvedPluginAiCapability[] {\n return resolvePluginServices(PLUGIN_AI_CAPABILITIES).map((entry) => ({\n pluginId: entry.pluginId,\n capability: entry.impl,\n }))\n}\n\n/** The capability for an operation, with its owner, or `null` when nothing registered it. */\nexport function pluginAiCapability(op: string): ResolvedPluginAiCapability | null {\n const key = op.trim()\n const entry = resolvePluginServices(PLUGIN_AI_CAPABILITIES).find((one) => one.key === key)\n return entry ? { pluginId: entry.pluginId, capability: entry.impl } : null\n}\n\n/**\n * What is wrong with an item's arguments against its schema; empty when\n * nothing is. Pure, and the same answer on both sides of the seam: the\n * planner holds the model to it, and an owner may hold its content to it.\n */\nexport function pluginAiCapabilityArgsProblems(\n schema: PluginAiCapabilityArgsSchema,\n args: Readonly<Record<string, unknown>>,\n): string[] {\n const problems: string[] = []\n for (const name of schema.required ?? []) {\n const value = args[name]\n if (value === undefined || value === null || value === '') problems.push(`\"${name}\" is required`)\n }\n for (const [name, value] of Object.entries(args)) {\n const property = schema.properties[name]\n if (!property) {\n problems.push(`\"${name}\" is not an argument`)\n continue\n }\n if (value === undefined || value === null) continue\n switch (property.type) {\n case 'string':\n if (typeof value !== 'string') problems.push(`\"${name}\" must be text`)\n else {\n if (property.enum && !property.enum.includes(value)) {\n problems.push(`\"${name}\" must be one of ${property.enum.join(', ')}`)\n }\n if (property.maxLength !== undefined && value.length > property.maxLength) {\n problems.push(`\"${name}\" must be at most ${property.maxLength} characters`)\n }\n }\n break\n case 'integer':\n case 'number':\n if (\n typeof value !== 'number' ||\n !Number.isFinite(value) ||\n (property.type === 'integer' && !Number.isInteger(value))\n ) {\n problems.push(`\"${name}\" must be ${property.type === 'integer' ? 'a whole number' : 'a number'}`)\n } else {\n if (property.minimum !== undefined && value < property.minimum) {\n problems.push(`\"${name}\" must be at least ${property.minimum}`)\n }\n if (property.maximum !== undefined && value > property.maximum) {\n problems.push(`\"${name}\" must be at most ${property.maximum}`)\n }\n }\n break\n case 'boolean':\n if (typeof value !== 'boolean') problems.push(`\"${name}\" must be true or false`)\n break\n case 'array':\n if (!Array.isArray(value) || value.some((one) => typeof one !== 'string')) {\n problems.push(`\"${name}\" must be a list of text`)\n } else {\n if (property.maxItems !== undefined && value.length > property.maxItems) {\n problems.push(`\"${name}\" must hold at most ${property.maxItems}`)\n }\n const items = property.items\n for (const one of value as string[]) {\n if (items?.enum && !items.enum.includes(one)) {\n problems.push(`\"${name}\" holds \"${one}\", which is not one of ${items.enum.join(', ')}`)\n break\n }\n if (items?.maxLength !== undefined && one.length > items.maxLength) {\n problems.push(`\"${name}\" holds an entry longer than ${items.maxLength} characters`)\n break\n }\n }\n }\n break\n }\n }\n return problems\n}\n"],"names":["getRegisteringPluginId","definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_AI_CAPABILITIES","multiple","OP_PATTERN","pluginAiCapabilityProblem","capability","test","op","executors","draftResource","runnerKind","filter","one","trim","length","Number","isInteger","maxPerPlan","argsSchema","type","additionalProperties","required","properties","noun","where","registerPluginAiCapability","options","problem","Error","key","pluginId","incumbent","find","entry","pluginAiCapabilities","map","impl","pluginAiCapability","pluginAiCapabilityArgsProblems","schema","args","problems","name","value","undefined","push","Object","entries","property","enum","includes","join","maxLength","isFinite","minimum","maximum","Array","isArray","some","maxItems","items"],"mappings":"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"}
|
|
@@ -67,6 +67,20 @@ export interface PluginDraftContext {
|
|
|
67
67
|
/** The org document the caller already read, for the owner's entitlement and allowance reads. */
|
|
68
68
|
org: Readonly<Record<string, unknown>> | null;
|
|
69
69
|
now: Date;
|
|
70
|
+
/**
|
|
71
|
+
* The act the draft is one part of, where there is one: an AI build's job
|
|
72
|
+
* (AGL-3755). Drafts of one act share what must not repeat across them (a
|
|
73
|
+
* cover photo), and a writer that fetches something for the draft reads
|
|
74
|
+
* what the act says the business is.
|
|
75
|
+
*/
|
|
76
|
+
act?: PluginDraftAct;
|
|
77
|
+
}
|
|
78
|
+
/** The act a draft is one part of ({@link PluginDraftContext.act}). */
|
|
79
|
+
export interface PluginDraftAct {
|
|
80
|
+
/** Stable across the act's retries. */
|
|
81
|
+
id: string;
|
|
82
|
+
/** What the act says the business is, in its own words: "a sourdough bakery in Austin". */
|
|
83
|
+
business?: string;
|
|
70
84
|
}
|
|
71
85
|
/** Why a draft cannot be made. */
|
|
72
86
|
export interface PluginDraftRefusal {
|
|
@@ -82,6 +96,15 @@ export interface PluginDraftRecord {
|
|
|
82
96
|
versionId: string | null;
|
|
83
97
|
/** What the owner reports about the draft beyond its identity, in the shape its writer documents. */
|
|
84
98
|
facts: Readonly<Record<string, unknown>>;
|
|
99
|
+
/**
|
|
100
|
+
* For a resource its writer makes live when the member may (an event the
|
|
101
|
+
* publishing role publishes, AGL-3755): whether this one went live. `false`
|
|
102
|
+
* means it was kept as a draft, and `notice` says why. Absent: the
|
|
103
|
+
* capability's own `live` stands.
|
|
104
|
+
*/
|
|
105
|
+
live?: boolean;
|
|
106
|
+
/** Customer-safe: a sentence a caller shows beside the record, such as why it stayed a draft. */
|
|
107
|
+
notice?: string;
|
|
85
108
|
}
|
|
86
109
|
export interface PluginDraftRequest extends PluginDraftContext {
|
|
87
110
|
/** The draft's id, chosen by the caller: one per act, so a write asked again finds its draft. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-resource-drafts.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 { ReusableComponentTree } from '../app-utils/compose-reusable-components'\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginServices,\n} from './plugin-services'\n\n/**\n * Drafts one plugin writes for another (AGL-2912).\n *\n * A resource belongs to the plugin that models it: the document's shape, the\n * allowance a create counts against, who may create one, and what makes one\n * well-formed. A different plugin that produces such a resource — an importer\n * turning a file into pages, a generator turning a brief into an email, a\n * migration bringing campaigns over from another tool — must not write the\n * document itself, because it would restate the owner's rules and drift from\n * them the day the owner changes one. Nor may it import the owner, which the\n * package map forbids.\n *\n * So the owner registers a WRITER for the resource from its server surfaces,\n * and a caller asks for one by resource name. Every rule stays the owner's:\n *\n * - `refusal` answers, before the caller spends anything, whether this\n * member may create one on this site now: their role, the site's room;\n * - `check` answers, synchronously and without I/O, whether content is\n * well-formed, so a caller can hold what it produces to the owner's rules\n * before it keeps it;\n * - `read` reports the draft already written under an id, so a caller that\n * runs again finds its draft rather than making a second;\n * - `write` makes the draft, meets the allowance inside its own write, and\n * never publishes, schedules or sends anything.\n *\n * Built on the typed service registry (`plugin-services`): one contract, every\n * writer registered under its resource name as the service's key.\n *\n * ## The registry carries no authorization of the CALLER\n *\n * A writer is a pre-authorized operation. It applies the owner's rules to the\n * member the draft is for, and knows nothing about whether the caller may act\n * for that member. A caller establishes, in its own terms and before it asks:\n * who is asking, that the org owns the site, and that the owning plugin is\n * switched on for that site.\n *\n * ## One writer per resource\n *\n * A resource has one owner. A second plugin registering a writer for a\n * resource another plugin already writes is refused, naming both, and the\n * incumbent keeps serving; the same plugin registering again — from its second\n * server surface, or on a hot reload — replaces its own.\n */\n\n/** Who a draft is for, on which site, and when. */\nexport interface PluginDraftContext {\n orgId: string\n hostId: string\n /** The member the draft is made for: the owner applies its rules to them and records them as the author. */\n uid: string\n /** The org document the caller already read, for the owner's entitlement and allowance reads. */\n org: Readonly<Record<string, unknown>> | null\n now: Date\n}\n\n/** Why a draft cannot be made. */\nexport interface PluginDraftRefusal {\n status: 400 | 403 | 404 | 409\n /** Customer-safe: a caller shows it as it stands. */\n error: string\n}\n\n/** A draft that exists. */\nexport interface PluginDraftRecord {\n id: string\n name: string\n /** The version the draft opens on, where the resource has versions. */\n versionId: string | null\n /** What the owner reports about the draft beyond its identity, in the shape its writer documents. */\n facts: Readonly<Record<string, unknown>>\n}\n\nexport interface PluginDraftRequest extends PluginDraftContext {\n /** The draft's id, chosen by the caller: one per act, so a write asked again finds its draft. */\n id: string\n /** The name asked for; the owner may number it to keep it unique. */\n name: string\n /** The resource's content, in the shape the owner's writer documents and validates. */\n content: Readonly<Record<string, unknown>>\n}\n\nexport type PluginDraftCheck =\n | { ok: true; facts: Readonly<Record<string, unknown>> }\n | { ok: false; problems: string[] }\n\n/** What a check is told beside the content. */\nexport interface PluginDraftCheckContext {\n hostId: string\n /**\n * The site's published reusable components the content places, keyed by id\n * — what `loadReferencedComponents` reads (AGL-3287).\n *\n * `check` does no I/O, so a caller whose content may place a component loads\n * the definitions first and hands them in; a writer whose resource renders\n * placed components composes them before judging, so what passes is what\n * would render. Absent means none were loaded, and the content is judged as\n * written.\n */\n components?: Readonly<Record<string, ReusableComponentTree | undefined>>\n}\n\nexport type PluginDraftWrite =\n | (PluginDraftRecord & { ok: true; replayed: boolean })\n | (PluginDraftRefusal & { ok: false })\n\nexport interface PluginResourceDraftWriter {\n /** Whether this member may create one on this site now; `null` admits. */\n refusal(context: PluginDraftContext): Promise<PluginDraftRefusal | null>\n /** Whether content is well-formed, with what the owner derived from it. Pure. */\n check(\n content: Readonly<Record<string, unknown>>,\n context: PluginDraftCheckContext,\n ): PluginDraftCheck\n /** The draft written under `id`, or `null`. */\n read(context: { hostId: string; id: string }): Promise<PluginDraftRecord | null>\n /** Makes the draft; a refusal is returned, never thrown. */\n write(request: PluginDraftRequest): Promise<PluginDraftWrite>\n}\n\nexport const PLUGIN_RESOURCE_DRAFTS =\n definePluginServiceContract<PluginResourceDraftWriter>('core.resource-drafts', {\n multiple: true,\n })\n\n/**\n * Registers the writer for a resource. The owner is the loader's marker when\n * a register fn is running, else `options.pluginId`; with neither the\n * registration throws. A writer for a resource another plugin writes throws\n * naming both.\n */\nexport function registerPluginResourceDraftWriter(\n resource: string,\n writer: PluginResourceDraftWriter,\n options?: { pluginId?: string },\n): void {\n const key = resource.trim()\n if (!key) throw new Error('a resource draft writer needs a resource name')\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n const incumbent = resolvePluginServices(PLUGIN_RESOURCE_DRAFTS).find(\n (entry) => entry.key === key,\n )\n if (incumbent && pluginId && incumbent.pluginId !== pluginId) {\n throw new Error(\n `resource \"${key}\" already has a draft writer from \"${incumbent.pluginId}\"; ` +\n `refused \"${pluginId}\"`,\n )\n }\n registerPluginService(PLUGIN_RESOURCE_DRAFTS, writer, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n key,\n })\n}\n\nexport interface ResolvedPluginDraftWriter {\n /** The plugin that owns the resource. */\n pluginId: string\n writer: PluginResourceDraftWriter\n}\n\n/** The writer for a resource, with its owner, or `null` when no plugin writes it. */\nexport function pluginResourceDraftWriter(resource: string): ResolvedPluginDraftWriter | null {\n const key = resource.trim()\n const entry = resolvePluginServices(PLUGIN_RESOURCE_DRAFTS).find((one) => one.key === key)\n return entry ? { pluginId: entry.pluginId, writer: entry.impl } : null\n}\n"],"names":["getRegisteringPluginId","definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_RESOURCE_DRAFTS","multiple","registerPluginResourceDraftWriter","resource","writer","options","key","trim","Error","pluginId","incumbent","find","entry","pluginResourceDraftWriter","one","impl"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAGD,SAASA,sBAAsB,QAAQ,qCAAiC;AACxE,SACEC,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AAyH1B,OAAO,MAAMC,yBACXH,4BAAuD,wBAAwB;IAC7EI,UAAU;AACZ,GAAE;AAEJ;;;;;CAKC,GACD,OAAO,SAASC,kCACdC,QAAgB,EAChBC,MAAiC,EACjCC,OAA+B;QAIbT,MAAAA;IAFlB,MAAMU,MAAMH,SAASI,IAAI;IACzB,IAAI,CAACD,KAAK,MAAM,IAAIE,MAAM;IAC1B,MAAMC,WAAW,EAACb,QAAAA,0BAAAA,oCAAAA,0BAA4BS,2BAAAA,QAASI,QAAQ,YAA7Cb,OAAiD,IAAIW,IAAI;IAC3E,MAAMG,YAAYX,sBAAsBC,wBAAwBW,IAAI,CAClE,CAACC,QAAUA,MAAMN,GAAG,KAAKA;IAE3B,IAAII,aAAaD,YAAYC,UAAUD,QAAQ,KAAKA,UAAU;QAC5D,MAAM,IAAID,MACR,CAAC,UAAU,EAAEF,IAAI,mCAAmC,EAAEI,UAAUD,QAAQ,CAAC,GAAG,CAAC,GAC3E,CAAC,SAAS,EAAEA,SAAS,CAAC,CAAC;IAE7B;IACAX,sBAAsBE,wBAAwBI,QAAQ,aAChDC,CAAAA,2BAAAA,QAASI,QAAQ,IAAG;QAAEA,UAAUJ,QAAQI,QAAQ;IAAC,IAAI,CAAC;QAC1DH;;AAEJ;AAQA,mFAAmF,GACnF,OAAO,SAASO,0BAA0BV,QAAgB;IACxD,MAAMG,MAAMH,SAASI,IAAI;IACzB,MAAMK,QAAQb,sBAAsBC,wBAAwBW,IAAI,CAAC,CAACG,MAAQA,IAAIR,GAAG,KAAKA;IACtF,OAAOM,QAAQ;QAAEH,UAAUG,MAAMH,QAAQ;QAAEL,QAAQQ,MAAMG,IAAI;IAAC,IAAI;AACpE"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-resource-drafts.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 { ReusableComponentTree } from '../app-utils/compose-reusable-components'\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginServices,\n} from './plugin-services'\n\n/**\n * Drafts one plugin writes for another (AGL-2912).\n *\n * A resource belongs to the plugin that models it: the document's shape, the\n * allowance a create counts against, who may create one, and what makes one\n * well-formed. A different plugin that produces such a resource — an importer\n * turning a file into pages, a generator turning a brief into an email, a\n * migration bringing campaigns over from another tool — must not write the\n * document itself, because it would restate the owner's rules and drift from\n * them the day the owner changes one. Nor may it import the owner, which the\n * package map forbids.\n *\n * So the owner registers a WRITER for the resource from its server surfaces,\n * and a caller asks for one by resource name. Every rule stays the owner's:\n *\n * - `refusal` answers, before the caller spends anything, whether this\n * member may create one on this site now: their role, the site's room;\n * - `check` answers, synchronously and without I/O, whether content is\n * well-formed, so a caller can hold what it produces to the owner's rules\n * before it keeps it;\n * - `read` reports the draft already written under an id, so a caller that\n * runs again finds its draft rather than making a second;\n * - `write` makes the draft, meets the allowance inside its own write, and\n * never publishes, schedules or sends anything.\n *\n * Built on the typed service registry (`plugin-services`): one contract, every\n * writer registered under its resource name as the service's key.\n *\n * ## The registry carries no authorization of the CALLER\n *\n * A writer is a pre-authorized operation. It applies the owner's rules to the\n * member the draft is for, and knows nothing about whether the caller may act\n * for that member. A caller establishes, in its own terms and before it asks:\n * who is asking, that the org owns the site, and that the owning plugin is\n * switched on for that site.\n *\n * ## One writer per resource\n *\n * A resource has one owner. A second plugin registering a writer for a\n * resource another plugin already writes is refused, naming both, and the\n * incumbent keeps serving; the same plugin registering again — from its second\n * server surface, or on a hot reload — replaces its own.\n */\n\n/** Who a draft is for, on which site, and when. */\nexport interface PluginDraftContext {\n orgId: string\n hostId: string\n /** The member the draft is made for: the owner applies its rules to them and records them as the author. */\n uid: string\n /** The org document the caller already read, for the owner's entitlement and allowance reads. */\n org: Readonly<Record<string, unknown>> | null\n now: Date\n /**\n * The act the draft is one part of, where there is one: an AI build's job\n * (AGL-3755). Drafts of one act share what must not repeat across them (a\n * cover photo), and a writer that fetches something for the draft reads\n * what the act says the business is.\n */\n act?: PluginDraftAct\n}\n\n/** The act a draft is one part of ({@link PluginDraftContext.act}). */\nexport interface PluginDraftAct {\n /** Stable across the act's retries. */\n id: string\n /** What the act says the business is, in its own words: \"a sourdough bakery in Austin\". */\n business?: string\n}\n\n/** Why a draft cannot be made. */\nexport interface PluginDraftRefusal {\n status: 400 | 403 | 404 | 409\n /** Customer-safe: a caller shows it as it stands. */\n error: string\n}\n\n/** A draft that exists. */\nexport interface PluginDraftRecord {\n id: string\n name: string\n /** The version the draft opens on, where the resource has versions. */\n versionId: string | null\n /** What the owner reports about the draft beyond its identity, in the shape its writer documents. */\n facts: Readonly<Record<string, unknown>>\n /**\n * For a resource its writer makes live when the member may (an event the\n * publishing role publishes, AGL-3755): whether this one went live. `false`\n * means it was kept as a draft, and `notice` says why. Absent: the\n * capability's own `live` stands.\n */\n live?: boolean\n /** Customer-safe: a sentence a caller shows beside the record, such as why it stayed a draft. */\n notice?: string\n}\n\nexport interface PluginDraftRequest extends PluginDraftContext {\n /** The draft's id, chosen by the caller: one per act, so a write asked again finds its draft. */\n id: string\n /** The name asked for; the owner may number it to keep it unique. */\n name: string\n /** The resource's content, in the shape the owner's writer documents and validates. */\n content: Readonly<Record<string, unknown>>\n}\n\nexport type PluginDraftCheck =\n | { ok: true; facts: Readonly<Record<string, unknown>> }\n | { ok: false; problems: string[] }\n\n/** What a check is told beside the content. */\nexport interface PluginDraftCheckContext {\n hostId: string\n /**\n * The site's published reusable components the content places, keyed by id\n * — what `loadReferencedComponents` reads (AGL-3287).\n *\n * `check` does no I/O, so a caller whose content may place a component loads\n * the definitions first and hands them in; a writer whose resource renders\n * placed components composes them before judging, so what passes is what\n * would render. Absent means none were loaded, and the content is judged as\n * written.\n */\n components?: Readonly<Record<string, ReusableComponentTree | undefined>>\n}\n\nexport type PluginDraftWrite =\n | (PluginDraftRecord & { ok: true; replayed: boolean })\n | (PluginDraftRefusal & { ok: false })\n\nexport interface PluginResourceDraftWriter {\n /** Whether this member may create one on this site now; `null` admits. */\n refusal(context: PluginDraftContext): Promise<PluginDraftRefusal | null>\n /** Whether content is well-formed, with what the owner derived from it. Pure. */\n check(\n content: Readonly<Record<string, unknown>>,\n context: PluginDraftCheckContext,\n ): PluginDraftCheck\n /** The draft written under `id`, or `null`. */\n read(context: { hostId: string; id: string }): Promise<PluginDraftRecord | null>\n /** Makes the draft; a refusal is returned, never thrown. */\n write(request: PluginDraftRequest): Promise<PluginDraftWrite>\n}\n\nexport const PLUGIN_RESOURCE_DRAFTS =\n definePluginServiceContract<PluginResourceDraftWriter>('core.resource-drafts', {\n multiple: true,\n })\n\n/**\n * Registers the writer for a resource. The owner is the loader's marker when\n * a register fn is running, else `options.pluginId`; with neither the\n * registration throws. A writer for a resource another plugin writes throws\n * naming both.\n */\nexport function registerPluginResourceDraftWriter(\n resource: string,\n writer: PluginResourceDraftWriter,\n options?: { pluginId?: string },\n): void {\n const key = resource.trim()\n if (!key) throw new Error('a resource draft writer needs a resource name')\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n const incumbent = resolvePluginServices(PLUGIN_RESOURCE_DRAFTS).find(\n (entry) => entry.key === key,\n )\n if (incumbent && pluginId && incumbent.pluginId !== pluginId) {\n throw new Error(\n `resource \"${key}\" already has a draft writer from \"${incumbent.pluginId}\"; ` +\n `refused \"${pluginId}\"`,\n )\n }\n registerPluginService(PLUGIN_RESOURCE_DRAFTS, writer, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n key,\n })\n}\n\nexport interface ResolvedPluginDraftWriter {\n /** The plugin that owns the resource. */\n pluginId: string\n writer: PluginResourceDraftWriter\n}\n\n/** The writer for a resource, with its owner, or `null` when no plugin writes it. */\nexport function pluginResourceDraftWriter(resource: string): ResolvedPluginDraftWriter | null {\n const key = resource.trim()\n const entry = resolvePluginServices(PLUGIN_RESOURCE_DRAFTS).find((one) => one.key === key)\n return entry ? { pluginId: entry.pluginId, writer: entry.impl } : null\n}\n"],"names":["getRegisteringPluginId","definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_RESOURCE_DRAFTS","multiple","registerPluginResourceDraftWriter","resource","writer","options","key","trim","Error","pluginId","incumbent","find","entry","pluginResourceDraftWriter","one","impl"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAGD,SAASA,sBAAsB,QAAQ,qCAAiC;AACxE,SACEC,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AAiJ1B,OAAO,MAAMC,yBACXH,4BAAuD,wBAAwB;IAC7EI,UAAU;AACZ,GAAE;AAEJ;;;;;CAKC,GACD,OAAO,SAASC,kCACdC,QAAgB,EAChBC,MAAiC,EACjCC,OAA+B;QAIbT,MAAAA;IAFlB,MAAMU,MAAMH,SAASI,IAAI;IACzB,IAAI,CAACD,KAAK,MAAM,IAAIE,MAAM;IAC1B,MAAMC,WAAW,EAACb,QAAAA,0BAAAA,oCAAAA,0BAA4BS,2BAAAA,QAASI,QAAQ,YAA7Cb,OAAiD,IAAIW,IAAI;IAC3E,MAAMG,YAAYX,sBAAsBC,wBAAwBW,IAAI,CAClE,CAACC,QAAUA,MAAMN,GAAG,KAAKA;IAE3B,IAAII,aAAaD,YAAYC,UAAUD,QAAQ,KAAKA,UAAU;QAC5D,MAAM,IAAID,MACR,CAAC,UAAU,EAAEF,IAAI,mCAAmC,EAAEI,UAAUD,QAAQ,CAAC,GAAG,CAAC,GAC3E,CAAC,SAAS,EAAEA,SAAS,CAAC,CAAC;IAE7B;IACAX,sBAAsBE,wBAAwBI,QAAQ,aAChDC,CAAAA,2BAAAA,QAASI,QAAQ,IAAG;QAAEA,UAAUJ,QAAQI,QAAQ;IAAC,IAAI,CAAC;QAC1DH;;AAEJ;AAQA,mFAAmF,GACnF,OAAO,SAASO,0BAA0BV,QAAgB;IACxD,MAAMG,MAAMH,SAASI,IAAI;IACzB,MAAMK,QAAQb,sBAAsBC,wBAAwBW,IAAI,CAAC,CAACG,MAAQA,IAAIR,GAAG,KAAKA;IACtF,OAAOM,QAAQ;QAAEH,UAAUG,MAAMH,QAAQ;QAAEL,QAAQQ,MAAMG,IAAI;IAAC,IAAI;AACpE"}
|
|
@@ -144,3 +144,70 @@ export declare function registerStockPhotoProvider(provider: StockPhotoProvider,
|
|
|
144
144
|
* leaves the caller's own fallback standing.
|
|
145
145
|
*/
|
|
146
146
|
export declare function stockPhotoProvider(): StockPhotoProvider | null;
|
|
147
|
+
/**
|
|
148
|
+
* A PHOTO FOR A SUBJECT, KEPT IN THE SITE'S LIBRARY (AGL-3755).
|
|
149
|
+
*
|
|
150
|
+
* The providers above answer searches; choosing well among the hits is
|
|
151
|
+
* work of its own — subject nouns rather than whole sentences, hits scored
|
|
152
|
+
* by how their tags meet the subject and the business's world, nothing
|
|
153
|
+
* placed twice in one act, the business's own world when the subject finds
|
|
154
|
+
* nothing, and a starter photo when the library is down — and it lives in
|
|
155
|
+
* the plugin that learned it on live site starts (the AI plugin). A plugin's
|
|
156
|
+
* draft writer that wants a picture (an event's cover) asks
|
|
157
|
+
* {@link stockPhotoPicker} for the deployment's one picker and gets a photo
|
|
158
|
+
* the site can serve, or `null` where no picker is registered. It never
|
|
159
|
+
* imports the plugin that picks.
|
|
160
|
+
*
|
|
161
|
+
* The picker copies a stock photo into the site's own library as the member
|
|
162
|
+
* named, through `core.media-ingest`, and only once: a photo the library
|
|
163
|
+
* already holds (by its source key) is reused, never stored a second time.
|
|
164
|
+
*/
|
|
165
|
+
export interface StockPhotoPickRequest {
|
|
166
|
+
hostId: string;
|
|
167
|
+
/** The member the photo is stored as; their access, the site's lockdown and the plan's storage are checked as for an upload of theirs. */
|
|
168
|
+
uid: string;
|
|
169
|
+
/** What the photo shows, in plain words: an event's title, a product's name. The picker reads the subject's nouns out of it. */
|
|
170
|
+
subject: string;
|
|
171
|
+
/** The business in its own words ("a sourdough bakery"), which holds the photo to its world and is searched when the subject finds nothing. */
|
|
172
|
+
business?: string;
|
|
173
|
+
/** The asset's description in the library; the subject where absent. */
|
|
174
|
+
alt?: string;
|
|
175
|
+
/** The frame the photo fills; `horizontal` where absent. */
|
|
176
|
+
orientation?: StockPhotoOrientation;
|
|
177
|
+
/**
|
|
178
|
+
* The act the photo is one of (an AI job's id): no photo is placed twice
|
|
179
|
+
* within it. Absent, only the site library's own reuse applies.
|
|
180
|
+
*/
|
|
181
|
+
scope?: string;
|
|
182
|
+
/** Turns the choice among equally good photos; the same seed picks the same photo again. */
|
|
183
|
+
seed?: string;
|
|
184
|
+
/** Source keys or asset srcs the act already shows. */
|
|
185
|
+
avoid?: readonly string[];
|
|
186
|
+
signal?: AbortSignal;
|
|
187
|
+
}
|
|
188
|
+
export interface StockPhotoPick {
|
|
189
|
+
/** What an `image` node's `src` holds: the asset's media reference, or a starter photo's path. */
|
|
190
|
+
src: string;
|
|
191
|
+
/** An address an `<img src>` or an image-address field takes as it stands: site-relative. */
|
|
192
|
+
url: string;
|
|
193
|
+
width: number;
|
|
194
|
+
height: number;
|
|
195
|
+
/**
|
|
196
|
+
* `library`: a stock photo in the site's own media library, copied there
|
|
197
|
+
* now or reused. `starter`: one of the platform's starter photos, the
|
|
198
|
+
* picture of last resort when the library is down or found nothing.
|
|
199
|
+
*/
|
|
200
|
+
origin: 'library' | 'starter';
|
|
201
|
+
}
|
|
202
|
+
export interface StockPhotoPicker {
|
|
203
|
+
/** A photo for the subject, or `null` when none can be had. Never throws. */
|
|
204
|
+
pick(request: StockPhotoPickRequest): Promise<StockPhotoPick | null>;
|
|
205
|
+
}
|
|
206
|
+
/** One picker per deployment: the plugin whose choosing it is registers it. */
|
|
207
|
+
export declare const STOCK_PHOTO_PICKER: import("./plugin-services").PluginServiceContract<StockPhotoPicker>;
|
|
208
|
+
/** Registers the deployment's picker; registering again replaces it. */
|
|
209
|
+
export declare function registerStockPhotoPicker(picker: StockPhotoPicker, options?: {
|
|
210
|
+
pluginId?: string;
|
|
211
|
+
}): void;
|
|
212
|
+
/** The registered picker, or `null` in a process that registered none. */
|
|
213
|
+
export declare function stockPhotoPicker(): StockPhotoPicker | null;
|
|
@@ -14,7 +14,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
14
14
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
|
-
*/ import { definePluginServiceContract, registerPluginService, resolvePluginServices } from "./plugin-services.js";
|
|
17
|
+
*/ import { definePluginServiceContract, registerPluginService, resolvePluginService, resolvePluginServices } from "./plugin-services.js";
|
|
18
18
|
/** The longest query a caller sends; libraries refuse or truncate longer ones. */ export const STOCK_PHOTO_QUERY_MAX_CHARS = 100;
|
|
19
19
|
/**
|
|
20
20
|
* Many plugins may each register a library; a caller is handed the first
|
|
@@ -55,5 +55,18 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
55
55
|
}
|
|
56
56
|
return null;
|
|
57
57
|
}
|
|
58
|
+
/** One picker per deployment: the plugin whose choosing it is registers it. */ export const STOCK_PHOTO_PICKER = definePluginServiceContract('core.stock-photo-picker', {
|
|
59
|
+
multiple: false
|
|
60
|
+
});
|
|
61
|
+
/** Registers the deployment's picker; registering again replaces it. */ export function registerStockPhotoPicker(picker, options) {
|
|
62
|
+
if (typeof (picker == null ? void 0 : picker.pick) !== 'function') throw new Error('a stock photo picker needs pick');
|
|
63
|
+
registerPluginService(STOCK_PHOTO_PICKER, picker, _extends({}, (options == null ? void 0 : options.pluginId) ? {
|
|
64
|
+
pluginId: options.pluginId
|
|
65
|
+
} : {}));
|
|
66
|
+
}
|
|
67
|
+
/** The registered picker, or `null` in a process that registered none. */ export function stockPhotoPicker() {
|
|
68
|
+
var _resolvePluginService;
|
|
69
|
+
return (_resolvePluginService = resolvePluginService(STOCK_PHOTO_PICKER)) != null ? _resolvePluginService : null;
|
|
70
|
+
}
|
|
58
71
|
|
|
59
72
|
//# sourceMappingURL=stock-photo-provider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/stock-photo-provider.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginServices,\n} from './plugin-services'\n\n/**\n * STOCK PHOTOS (AGL-3660): a library of licensed photos outside this\n * platform that a server process searches and copies from.\n *\n * Core defines the contract and never names a library. A plugin registers a\n * provider from its server declarations, and a caller — the AI site build is\n * the first — asks for {@link stockPhotoProvider} and gets the first one\n * this deployment configured, or `null`. Nothing here stores a photo: a\n * caller that keeps one copies its bytes into the site's own media library\n * (`plugin-media-ingest.ts`), because a page that names a library's own\n * address would hotlink it, and most libraries' terms refuse that.\n *\n * What a provider owes its library's terms — caching searches, its rate\n * limit, the hosts it may fetch from — it keeps inside itself, so a caller\n * cannot get them wrong. What it owes the caller is {@link\n * StockPhotoProvider.credit}: the words and links the asset records about\n * where it came from, and whether the library requires them shown.\n *\n * A provider that is registered and not configured (its key unset) answers\n * as absent, so a deployment without the key behaves as one without the\n * plugin.\n */\n\n/** The shape a caller wants; a provider maps it onto its own filter. */\nexport type StockPhotoOrientation = 'horizontal' | 'vertical' | 'any'\n\nexport interface StockPhotoSearchRequest {\n /** Plain search words, at most {@link STOCK_PHOTO_QUERY_MAX_CHARS}. */\n query: string\n orientation?: StockPhotoOrientation\n /** The smallest original a hit may have, in pixels. */\n minWidth?: number\n minHeight?: number\n /** A hint that the photo should show people, where the library can filter by it. */\n people?: boolean\n}\n\n/** The longest query a caller sends; libraries refuse or truncate longer ones. */\nexport const STOCK_PHOTO_QUERY_MAX_CHARS = 100\n\n/** One photo a search found. */\nexport interface StockPhoto {\n /** The registering provider's id. */\n provider: string\n /** The library's own id for the photo, stable across searches. */\n id: string\n /** The size of the copy {@link StockPhotoProvider.download} fetches. */\n width: number\n height: number\n /** The photo's page at the library, for the credit. */\n pageUrl: string\n /** The contributor's name as the library shows it. */\n photographer: string\n /** The contributor's page at the library, when it has one. */\n photographerUrl?: string\n /** The library's tags, lowercase. */\n tags: string[]\n /**\n * The library's own description of the photo, where it writes one (a\n * Pexels hit's `alt`). A caller ranks hits by it with the tags.\n */\n alt?: string\n}\n\n/** What an asset copied from a library records about where it came from. */\nexport interface StockPhotoCredit {\n /** The library's name, as its terms ask it written. */\n providerLabel: string\n /** The license the photo is used under. */\n license: string\n licenseUrl: string\n /** Whether the license requires the credit shown wherever the photo is. */\n attributionRequired: boolean\n /** A sentence crediting the photo, for the asset's description. */\n text: string\n}\n\nexport interface StockPhotoSearchResult {\n photos: StockPhoto[]\n /** Whether the answer came from the provider's own cache. */\n cached: boolean\n}\n\nexport interface StockPhotoDownload {\n bytes: Uint8Array\n /** `image/jpeg`, `image/png` or `image/webp`. */\n contentType: string\n}\n\nexport interface StockPhotoProvider {\n /** A stable id, the prefix of every source key: `pixabay`. */\n id: string\n /** The library's name, in customer copy. */\n label: string\n /** Whether this deployment holds what the provider needs to search. */\n isConfigured(): boolean\n /**\n * The photos matching `request`, best first. `null` when the provider\n * cannot ask right now — its rate limit is spent, or the library failed —\n * which a caller treats as \"no photo\", never as an error to retry.\n */\n search(\n request: StockPhotoSearchRequest,\n options?: { signal?: AbortSignal },\n ): Promise<StockPhotoSearchResult | null>\n /**\n * The photo's bytes, fetched only from the library's own image hosts, or\n * `null` when they cannot be had within `maxBytes` and the signal.\n */\n download(\n photo: StockPhoto,\n options: { maxBytes: number; signal?: AbortSignal },\n ): Promise<StockPhotoDownload | null>\n /** How an asset copied from `photo` credits it. */\n credit(photo: StockPhoto): StockPhotoCredit\n}\n\n/**\n * Many plugins may each register a library; a caller is handed the first\n * configured one, highest priority first.\n */\nexport const STOCK_PHOTO_PROVIDERS = definePluginServiceContract<StockPhotoProvider>(\n 'core.stock-photos',\n { multiple: true },\n)\n\n/** The key an asset copied from a library is found again by: `{provider}:{id}`. */\nexport function stockPhotoSourceKey(photo: Pick<StockPhoto, 'provider' | 'id'>): string {\n return `${photo.provider}:${photo.id}`\n}\n\n/**\n * Registers a library. The owner is the loader's marker when a register fn\n * is running, else `options.pluginId`; the provider's own id tells two\n * libraries of one plugin apart.\n */\nexport function registerStockPhotoProvider(\n provider: StockPhotoProvider,\n options?: { pluginId?: string; priority?: number },\n): void {\n if (!provider?.id?.trim()) throw new Error('a stock photo provider needs an id')\n registerPluginService(STOCK_PHOTO_PROVIDERS, provider, {\n key: provider.id,\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n ...(options?.priority !== undefined ? { priority: options.priority } : {}),\n })\n}\n\n/**\n * The first registered library this deployment configured, or `null`.\n * Synchronous and free of I/O. A provider whose configuration check throws\n * answers as unconfigured: a stock photo is decoration, and its failure\n * leaves the caller's own fallback standing.\n */\nexport function stockPhotoProvider(): StockPhotoProvider | null {\n for (const { impl } of resolvePluginServices(STOCK_PHOTO_PROVIDERS)) {\n try {\n if (impl.isConfigured()) return impl\n } catch {\n // Unconfigured, as the note says.\n }\n }\n return null\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginServices","STOCK_PHOTO_QUERY_MAX_CHARS","STOCK_PHOTO_PROVIDERS","multiple","stockPhotoSourceKey","photo","provider","id","registerStockPhotoProvider","options","trim","Error","key","pluginId","priority","undefined","stockPhotoProvider","impl","isConfigured"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AAuC1B,gFAAgF,GAChF,OAAO,MAAMC,8BAA8B,IAAG;AA+E9C;;;CAGC,GACD,OAAO,MAAMC,wBAAwBJ,4BACnC,qBACA;IAAEK,UAAU;AAAK,GAClB;AAED,iFAAiF,GACjF,OAAO,SAASC,oBAAoBC,KAA0C;IAC5E,OAAO,GAAGA,MAAMC,QAAQ,CAAC,CAAC,EAAED,MAAME,EAAE,EAAE;AACxC;AAEA;;;;CAIC,GACD,OAAO,SAASC,2BACdF,QAA4B,EAC5BG,OAAkD;QAE7CH;IAAL,IAAI,EAACA,6BAAAA,eAAAA,SAAUC,EAAE,qBAAZD,aAAcI,IAAI,KAAI,MAAM,IAAIC,MAAM;IAC3CZ,sBAAsBG,uBAAuBI,UAAU;QACrDM,KAAKN,SAASC,EAAE;OACZE,CAAAA,2BAAAA,QAASI,QAAQ,IAAG;QAAEA,UAAUJ,QAAQI,QAAQ;IAAC,IAAI,CAAC,GACtDJ,CAAAA,2BAAAA,QAASK,QAAQ,MAAKC,YAAY;QAAED,UAAUL,QAAQK,QAAQ;IAAC,IAAI,CAAC;AAE5E;AAEA;;;;;CAKC,GACD,OAAO,SAASE;IACd,KAAK,MAAM,EAAEC,IAAI,EAAE,IAAIjB,sBAAsBE,uBAAwB;QACnE,IAAI;YACF,IAAIe,KAAKC,YAAY,IAAI,OAAOD;QAClC,EAAE,eAAM;QACN,kCAAkC;QACpC;IACF;IACA,OAAO;AACT"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/stock-photo-provider.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginService,\n resolvePluginServices,\n} from './plugin-services'\n\n/**\n * STOCK PHOTOS (AGL-3660): a library of licensed photos outside this\n * platform that a server process searches and copies from.\n *\n * Core defines the contract and never names a library. A plugin registers a\n * provider from its server declarations, and a caller — the AI site build is\n * the first — asks for {@link stockPhotoProvider} and gets the first one\n * this deployment configured, or `null`. Nothing here stores a photo: a\n * caller that keeps one copies its bytes into the site's own media library\n * (`plugin-media-ingest.ts`), because a page that names a library's own\n * address would hotlink it, and most libraries' terms refuse that.\n *\n * What a provider owes its library's terms — caching searches, its rate\n * limit, the hosts it may fetch from — it keeps inside itself, so a caller\n * cannot get them wrong. What it owes the caller is {@link\n * StockPhotoProvider.credit}: the words and links the asset records about\n * where it came from, and whether the library requires them shown.\n *\n * A provider that is registered and not configured (its key unset) answers\n * as absent, so a deployment without the key behaves as one without the\n * plugin.\n */\n\n/** The shape a caller wants; a provider maps it onto its own filter. */\nexport type StockPhotoOrientation = 'horizontal' | 'vertical' | 'any'\n\nexport interface StockPhotoSearchRequest {\n /** Plain search words, at most {@link STOCK_PHOTO_QUERY_MAX_CHARS}. */\n query: string\n orientation?: StockPhotoOrientation\n /** The smallest original a hit may have, in pixels. */\n minWidth?: number\n minHeight?: number\n /** A hint that the photo should show people, where the library can filter by it. */\n people?: boolean\n}\n\n/** The longest query a caller sends; libraries refuse or truncate longer ones. */\nexport const STOCK_PHOTO_QUERY_MAX_CHARS = 100\n\n/** One photo a search found. */\nexport interface StockPhoto {\n /** The registering provider's id. */\n provider: string\n /** The library's own id for the photo, stable across searches. */\n id: string\n /** The size of the copy {@link StockPhotoProvider.download} fetches. */\n width: number\n height: number\n /** The photo's page at the library, for the credit. */\n pageUrl: string\n /** The contributor's name as the library shows it. */\n photographer: string\n /** The contributor's page at the library, when it has one. */\n photographerUrl?: string\n /** The library's tags, lowercase. */\n tags: string[]\n /**\n * The library's own description of the photo, where it writes one (a\n * Pexels hit's `alt`). A caller ranks hits by it with the tags.\n */\n alt?: string\n}\n\n/** What an asset copied from a library records about where it came from. */\nexport interface StockPhotoCredit {\n /** The library's name, as its terms ask it written. */\n providerLabel: string\n /** The license the photo is used under. */\n license: string\n licenseUrl: string\n /** Whether the license requires the credit shown wherever the photo is. */\n attributionRequired: boolean\n /** A sentence crediting the photo, for the asset's description. */\n text: string\n}\n\nexport interface StockPhotoSearchResult {\n photos: StockPhoto[]\n /** Whether the answer came from the provider's own cache. */\n cached: boolean\n}\n\nexport interface StockPhotoDownload {\n bytes: Uint8Array\n /** `image/jpeg`, `image/png` or `image/webp`. */\n contentType: string\n}\n\nexport interface StockPhotoProvider {\n /** A stable id, the prefix of every source key: `pixabay`. */\n id: string\n /** The library's name, in customer copy. */\n label: string\n /** Whether this deployment holds what the provider needs to search. */\n isConfigured(): boolean\n /**\n * The photos matching `request`, best first. `null` when the provider\n * cannot ask right now — its rate limit is spent, or the library failed —\n * which a caller treats as \"no photo\", never as an error to retry.\n */\n search(\n request: StockPhotoSearchRequest,\n options?: { signal?: AbortSignal },\n ): Promise<StockPhotoSearchResult | null>\n /**\n * The photo's bytes, fetched only from the library's own image hosts, or\n * `null` when they cannot be had within `maxBytes` and the signal.\n */\n download(\n photo: StockPhoto,\n options: { maxBytes: number; signal?: AbortSignal },\n ): Promise<StockPhotoDownload | null>\n /** How an asset copied from `photo` credits it. */\n credit(photo: StockPhoto): StockPhotoCredit\n}\n\n/**\n * Many plugins may each register a library; a caller is handed the first\n * configured one, highest priority first.\n */\nexport const STOCK_PHOTO_PROVIDERS = definePluginServiceContract<StockPhotoProvider>(\n 'core.stock-photos',\n { multiple: true },\n)\n\n/** The key an asset copied from a library is found again by: `{provider}:{id}`. */\nexport function stockPhotoSourceKey(photo: Pick<StockPhoto, 'provider' | 'id'>): string {\n return `${photo.provider}:${photo.id}`\n}\n\n/**\n * Registers a library. The owner is the loader's marker when a register fn\n * is running, else `options.pluginId`; the provider's own id tells two\n * libraries of one plugin apart.\n */\nexport function registerStockPhotoProvider(\n provider: StockPhotoProvider,\n options?: { pluginId?: string; priority?: number },\n): void {\n if (!provider?.id?.trim()) throw new Error('a stock photo provider needs an id')\n registerPluginService(STOCK_PHOTO_PROVIDERS, provider, {\n key: provider.id,\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n ...(options?.priority !== undefined ? { priority: options.priority } : {}),\n })\n}\n\n/**\n * The first registered library this deployment configured, or `null`.\n * Synchronous and free of I/O. A provider whose configuration check throws\n * answers as unconfigured: a stock photo is decoration, and its failure\n * leaves the caller's own fallback standing.\n */\nexport function stockPhotoProvider(): StockPhotoProvider | null {\n for (const { impl } of resolvePluginServices(STOCK_PHOTO_PROVIDERS)) {\n try {\n if (impl.isConfigured()) return impl\n } catch {\n // Unconfigured, as the note says.\n }\n }\n return null\n}\n\n/**\n * A PHOTO FOR A SUBJECT, KEPT IN THE SITE'S LIBRARY (AGL-3755).\n *\n * The providers above answer searches; choosing well among the hits is\n * work of its own — subject nouns rather than whole sentences, hits scored\n * by how their tags meet the subject and the business's world, nothing\n * placed twice in one act, the business's own world when the subject finds\n * nothing, and a starter photo when the library is down — and it lives in\n * the plugin that learned it on live site starts (the AI plugin). A plugin's\n * draft writer that wants a picture (an event's cover) asks\n * {@link stockPhotoPicker} for the deployment's one picker and gets a photo\n * the site can serve, or `null` where no picker is registered. It never\n * imports the plugin that picks.\n *\n * The picker copies a stock photo into the site's own library as the member\n * named, through `core.media-ingest`, and only once: a photo the library\n * already holds (by its source key) is reused, never stored a second time.\n */\nexport interface StockPhotoPickRequest {\n hostId: string\n /** The member the photo is stored as; their access, the site's lockdown and the plan's storage are checked as for an upload of theirs. */\n uid: string\n /** What the photo shows, in plain words: an event's title, a product's name. The picker reads the subject's nouns out of it. */\n subject: string\n /** The business in its own words (\"a sourdough bakery\"), which holds the photo to its world and is searched when the subject finds nothing. */\n business?: string\n /** The asset's description in the library; the subject where absent. */\n alt?: string\n /** The frame the photo fills; `horizontal` where absent. */\n orientation?: StockPhotoOrientation\n /**\n * The act the photo is one of (an AI job's id): no photo is placed twice\n * within it. Absent, only the site library's own reuse applies.\n */\n scope?: string\n /** Turns the choice among equally good photos; the same seed picks the same photo again. */\n seed?: string\n /** Source keys or asset srcs the act already shows. */\n avoid?: readonly string[]\n signal?: AbortSignal\n}\n\nexport interface StockPhotoPick {\n /** What an `image` node's `src` holds: the asset's media reference, or a starter photo's path. */\n src: string\n /** An address an `<img src>` or an image-address field takes as it stands: site-relative. */\n url: string\n width: number\n height: number\n /**\n * `library`: a stock photo in the site's own media library, copied there\n * now or reused. `starter`: one of the platform's starter photos, the\n * picture of last resort when the library is down or found nothing.\n */\n origin: 'library' | 'starter'\n}\n\nexport interface StockPhotoPicker {\n /** A photo for the subject, or `null` when none can be had. Never throws. */\n pick(request: StockPhotoPickRequest): Promise<StockPhotoPick | null>\n}\n\n/** One picker per deployment: the plugin whose choosing it is registers it. */\nexport const STOCK_PHOTO_PICKER = definePluginServiceContract<StockPhotoPicker>(\n 'core.stock-photo-picker',\n { multiple: false },\n)\n\n/** Registers the deployment's picker; registering again replaces it. */\nexport function registerStockPhotoPicker(\n picker: StockPhotoPicker,\n options?: { pluginId?: string },\n): void {\n if (typeof picker?.pick !== 'function') throw new Error('a stock photo picker needs pick')\n registerPluginService(STOCK_PHOTO_PICKER, picker, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** The registered picker, or `null` in a process that registered none. */\nexport function stockPhotoPicker(): StockPhotoPicker | null {\n return resolvePluginService(STOCK_PHOTO_PICKER) ?? null\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginService","resolvePluginServices","STOCK_PHOTO_QUERY_MAX_CHARS","STOCK_PHOTO_PROVIDERS","multiple","stockPhotoSourceKey","photo","provider","id","registerStockPhotoProvider","options","trim","Error","key","pluginId","priority","undefined","stockPhotoProvider","impl","isConfigured","STOCK_PHOTO_PICKER","registerStockPhotoPicker","picker","pick","stockPhotoPicker"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,oBAAoB,EACpBC,qBAAqB,QAChB,uBAAmB;AAuC1B,gFAAgF,GAChF,OAAO,MAAMC,8BAA8B,IAAG;AA+E9C;;;CAGC,GACD,OAAO,MAAMC,wBAAwBL,4BACnC,qBACA;IAAEM,UAAU;AAAK,GAClB;AAED,iFAAiF,GACjF,OAAO,SAASC,oBAAoBC,KAA0C;IAC5E,OAAO,GAAGA,MAAMC,QAAQ,CAAC,CAAC,EAAED,MAAME,EAAE,EAAE;AACxC;AAEA;;;;CAIC,GACD,OAAO,SAASC,2BACdF,QAA4B,EAC5BG,OAAkD;QAE7CH;IAAL,IAAI,EAACA,6BAAAA,eAAAA,SAAUC,EAAE,qBAAZD,aAAcI,IAAI,KAAI,MAAM,IAAIC,MAAM;IAC3Cb,sBAAsBI,uBAAuBI,UAAU;QACrDM,KAAKN,SAASC,EAAE;OACZE,CAAAA,2BAAAA,QAASI,QAAQ,IAAG;QAAEA,UAAUJ,QAAQI,QAAQ;IAAC,IAAI,CAAC,GACtDJ,CAAAA,2BAAAA,QAASK,QAAQ,MAAKC,YAAY;QAAED,UAAUL,QAAQK,QAAQ;IAAC,IAAI,CAAC;AAE5E;AAEA;;;;;CAKC,GACD,OAAO,SAASE;IACd,KAAK,MAAM,EAAEC,IAAI,EAAE,IAAIjB,sBAAsBE,uBAAwB;QACnE,IAAI;YACF,IAAIe,KAAKC,YAAY,IAAI,OAAOD;QAClC,EAAE,eAAM;QACN,kCAAkC;QACpC;IACF;IACA,OAAO;AACT;AAgEA,6EAA6E,GAC7E,OAAO,MAAME,qBAAqBtB,4BAChC,2BACA;IAAEM,UAAU;AAAM,GACnB;AAED,sEAAsE,GACtE,OAAO,SAASiB,yBACdC,MAAwB,EACxBZ,OAA+B;IAE/B,IAAI,QAAOY,0BAAAA,OAAQC,IAAI,MAAK,YAAY,MAAM,IAAIX,MAAM;IACxDb,sBAAsBqB,oBAAoBE,QAAQ,aAC5CZ,CAAAA,2BAAAA,QAASI,QAAQ,IAAG;QAAEA,UAAUJ,QAAQI,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA,wEAAwE,GACxE,OAAO,SAASU;QACPxB;IAAP,QAAOA,wBAAAA,qBAAqBoB,+BAArBpB,wBAA4C;AACrD"}
|