@aglyn/aglyn 1.0.0-beta.229 → 1.0.0-beta.231
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/analytics-events.d.ts +18 -0
- package/src/lib/app-utils/analytics-events.js +2 -0
- package/src/lib/app-utils/analytics-events.js.map +1 -1
- package/src/lib/app-utils/crm.d.ts +14 -1
- package/src/lib/app-utils/crm.js +19 -2
- package/src/lib/app-utils/crm.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +111 -9
- package/src/lib/app-utils/docs-help.generated.js +272 -3
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +810 -61
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/host-status.d.ts +85 -0
- package/src/lib/app-utils/host-status.js +115 -0
- package/src/lib/app-utils/host-status.js.map +1 -0
- package/src/lib/app-utils/lockdown.js +1 -1
- package/src/lib/app-utils/lockdown.js.map +1 -1
- package/src/lib/app-utils/media-filter.d.ts +136 -0
- package/src/lib/app-utils/media-filter.js +400 -0
- package/src/lib/app-utils/media-filter.js.map +1 -0
- package/src/lib/app-utils/mobile-push.d.ts +98 -0
- package/src/lib/app-utils/mobile-push.js +97 -0
- package/src/lib/app-utils/mobile-push.js.map +1 -0
- package/src/lib/app-utils/notification-push.d.ts +38 -0
- package/src/lib/app-utils/notification-push.js +54 -0
- package/src/lib/app-utils/notification-push.js.map +1 -0
- package/src/lib/app-utils/notifications.d.ts +7 -0
- package/src/lib/app-utils/notifications.js.map +1 -1
- package/src/lib/app-utils/organizations.js +5 -2
- package/src/lib/app-utils/organizations.js.map +1 -1
- package/src/lib/app-utils/plan-entitlements.js +20 -0
- package/src/lib/app-utils/plan-entitlements.js.map +1 -1
- package/src/lib/app-utils/plugin-host-events.generated.d.ts +1 -1
- package/src/lib/app-utils/plugin-host-events.generated.js +164 -0
- package/src/lib/app-utils/plugin-host-events.generated.js.map +1 -1
- package/src/lib/app-utils/plugin-release-flags.generated.d.ts +1 -1
- package/src/lib/app-utils/plugin-release-flags.generated.js +7 -0
- package/src/lib/app-utils/plugin-release-flags.generated.js.map +1 -1
- package/src/lib/app-utils/realm-host-surface.generated.js +3 -0
- package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
- package/src/lib/app-utils/release-flags.js +6 -2
- package/src/lib/app-utils/release-flags.js.map +1 -1
- package/src/lib/app-utils/scope-tokens.d.ts +16 -1
- package/src/lib/app-utils/scope-tokens.js +15 -1
- package/src/lib/app-utils/scope-tokens.js.map +1 -1
- package/src/lib/app-utils/site-journey.d.ts +143 -0
- package/src/lib/app-utils/site-journey.js +282 -0
- package/src/lib/app-utils/site-journey.js.map +1 -0
- package/src/lib/app-utils/site-list-query.d.ts +47 -0
- package/src/lib/app-utils/site-list-query.js +142 -0
- package/src/lib/app-utils/site-list-query.js.map +1 -0
- package/src/lib/app-utils/site-wide-outbox.d.ts +95 -0
- package/src/lib/app-utils/site-wide-outbox.js +117 -0
- package/src/lib/app-utils/site-wide-outbox.js.map +1 -0
- package/src/lib/app-utils/transfer-launcher-context.d.ts +6 -0
- package/src/lib/app-utils/transfer-launcher-context.js.map +1 -1
- package/src/lib/app-utils/upload-inspection.js +7 -0
- package/src/lib/app-utils/upload-inspection.js.map +1 -1
- package/src/lib/app-utils/webhook-delivery.js +4 -1
- package/src/lib/app-utils/webhook-delivery.js.map +1 -1
- package/src/lib/foundation/definitions/org-billing.types.d.ts +24 -0
- package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
- package/src/lib/foundation/definitions/organization.types.d.ts +18 -7
- package/src/lib/foundation/definitions/organization.types.js.map +1 -1
- package/src/lib/foundation/definitions/write-deny-coverage.util.d.ts +4 -1
- package/src/lib/foundation/definitions/write-deny-coverage.util.js +12 -2
- package/src/lib/foundation/definitions/write-deny-coverage.util.js.map +1 -1
- package/src/lib/plugin-manager/enabled-plugins.js +4 -2
- package/src/lib/plugin-manager/enabled-plugins.js.map +1 -1
- package/src/lib/plugin-manager/feature-plugins.d.ts +173 -0
- package/src/lib/plugin-manager/feature-plugins.js +62 -1
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.js +251 -2
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
- package/src/lib/plugin-manager/plugin-ai-capabilities.d.ts +192 -0
- package/src/lib/plugin-manager/plugin-ai-capabilities.js +157 -0
- package/src/lib/plugin-manager/plugin-ai-capabilities.js.map +1 -0
- package/src/lib/plugin-manager/plugin-checkout-extras.d.ts +168 -0
- package/src/lib/plugin-manager/plugin-checkout-extras.js +172 -0
- package/src/lib/plugin-manager/plugin-checkout-extras.js.map +1 -0
- package/src/lib/plugin-manager/plugin-contributions.d.ts +7 -0
- package/src/lib/plugin-manager/plugin-contributions.js +1 -1
- package/src/lib/plugin-manager/plugin-contributions.js.map +1 -1
- package/src/lib/plugin-manager/plugin-domain-events.d.ts +138 -0
- package/src/lib/plugin-manager/plugin-domain-events.js +148 -0
- package/src/lib/plugin-manager/plugin-domain-events.js.map +1 -0
- package/src/lib/plugin-manager/plugin-events.d.ts +51 -0
- package/src/lib/plugin-manager/plugin-events.js +4 -0
- package/src/lib/plugin-manager/plugin-events.js.map +1 -1
- package/src/lib/plugin-manager/plugin-fulfillment-providers.d.ts +101 -0
- package/src/lib/plugin-manager/plugin-fulfillment-providers.js +83 -0
- package/src/lib/plugin-manager/plugin-fulfillment-providers.js.map +1 -0
- package/src/lib/plugin-manager/plugin-permissions.js +21 -5
- package/src/lib/plugin-manager/plugin-permissions.js.map +1 -1
- package/src/lib/plugin-manager/plugin-person-records.d.ts +90 -0
- package/src/lib/plugin-manager/plugin-person-records.js +26 -0
- package/src/lib/plugin-manager/plugin-person-records.js.map +1 -1
- package/src/lib/plugin-manager/plugin-product-catalog.d.ts +204 -0
- package/src/lib/plugin-manager/plugin-product-catalog.js +43 -0
- package/src/lib/plugin-manager/plugin-product-catalog.js.map +1 -0
- package/src/lib/plugin-manager/plugin-shipment-records.d.ts +210 -0
- package/src/lib/plugin-manager/plugin-shipment-records.js +63 -0
- package/src/lib/plugin-manager/plugin-shipment-records.js.map +1 -0
- package/src/lib/plugin-manager/plugin-shipping-rates.d.ts +151 -0
- package/src/lib/plugin-manager/plugin-shipping-rates.js +62 -0
- package/src/lib/plugin-manager/plugin-shipping-rates.js.map +1 -0
- package/src/lib/plugin-manager/plugin-sms-messaging.d.ts +103 -0
- package/src/lib/plugin-manager/plugin-sms-messaging.js +39 -0
- package/src/lib/plugin-manager/plugin-sms-messaging.js.map +1 -0
- package/src/lib/plugin-manager/plugin-stock-levels.d.ts +81 -0
- package/src/lib/plugin-manager/plugin-stock-levels.js +32 -0
- package/src/lib/plugin-manager/plugin-stock-levels.js.map +1 -0
- package/src/lib/plugin-manager/plugin-tax-profile.d.ts +154 -0
- package/src/lib/plugin-manager/plugin-tax-profile.js +56 -0
- package/src/lib/plugin-manager/plugin-tax-profile.js.map +1 -1
- package/src/lib/plugin-manager/plugin-theme-font-catalog.d.ts +59 -0
- package/src/lib/plugin-manager/plugin-theme-font-catalog.js +40 -0
- package/src/lib/plugin-manager/plugin-theme-font-catalog.js.map +1 -0
- package/src/lib/plugin-manager/plugin-tracking-pages.d.ts +55 -0
- package/src/lib/plugin-manager/plugin-tracking-pages.js +76 -0
- package/src/lib/plugin-manager/plugin-tracking-pages.js.map +1 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js +3 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* What an AI build can make, contributed by the plugin that owns each thing
|
|
19
|
+
* (AGL-3616).
|
|
20
|
+
*
|
|
21
|
+
* A build turns one request — "a few pages, a contact form and a way to book
|
|
22
|
+
* me" — into a plan of ITEMS, each an operation (`op`) with arguments, and
|
|
23
|
+
* runs them in dependency order. The AI plugin plans and meters the build;
|
|
24
|
+
* it does not know what a plugin's resource is, and the package map forbids
|
|
25
|
+
* it importing that plugin. So every operation is a CAPABILITY the owner
|
|
26
|
+
* registers from its server entry, and the build asks for one by `op`:
|
|
27
|
+
*
|
|
28
|
+
* - the planner offers the model only the operations registered here, in
|
|
29
|
+
* the owner's own words (`noun`, `intents`, `argsSchema`);
|
|
30
|
+
* - admission removes an item whose capability this site, plan or member
|
|
31
|
+
* cannot use (`feature`, `permission`, `quota`, `freeAllowed`), with a
|
|
32
|
+
* sentence, so the rest of the plan still runs;
|
|
33
|
+
* - the item is executed one of two ways: `runnerKind` hands it to an
|
|
34
|
+
* existing AI job runner (the AI plugin's own operations), and
|
|
35
|
+
* `draftResource` hands its content to the owner's writer on
|
|
36
|
+
* `plugin-resource-drafts`, which checks it, meets the allowance and
|
|
37
|
+
* writes a draft that nothing publishes.
|
|
38
|
+
*
|
|
39
|
+
* An operation lights up the moment its owner registers it; nothing in the
|
|
40
|
+
* AI plugin names it. Register by CALLING `registerPluginAiCapability` from a
|
|
41
|
+
* function the server entry runs — a top-level side effect is dropped by a
|
|
42
|
+
* bundler that sees no import of its result.
|
|
43
|
+
*
|
|
44
|
+
* Server-only: import this file by its own path, never from the barrel that
|
|
45
|
+
* every published page loads.
|
|
46
|
+
*
|
|
47
|
+
* ## One capability per operation
|
|
48
|
+
*
|
|
49
|
+
* An operation has one owner. A second plugin registering an `op` another
|
|
50
|
+
* plugin already owns is refused, naming both, and the incumbent keeps
|
|
51
|
+
* serving; the same plugin registering again replaces its own.
|
|
52
|
+
*/
|
|
53
|
+
/** A value an item's arguments may hold: scalars and lists of strings only. */
|
|
54
|
+
export type PluginAiCapabilityArgValue = string | number | boolean | readonly string[];
|
|
55
|
+
/** An item's arguments, in the shape its capability's `argsSchema` declares. */
|
|
56
|
+
export type PluginAiCapabilityArgs = Readonly<Record<string, PluginAiCapabilityArgValue>>;
|
|
57
|
+
/**
|
|
58
|
+
* One argument, as a JSON Schema property the model fills in directly. The
|
|
59
|
+
* subset is deliberately small: a flat object of scalars and string lists is
|
|
60
|
+
* what a planner can be held to, and what `pluginAiCapabilityArgsProblems`
|
|
61
|
+
* checks without a schema library.
|
|
62
|
+
*/
|
|
63
|
+
export interface PluginAiCapabilityArgProperty {
|
|
64
|
+
type: 'string' | 'integer' | 'number' | 'boolean' | 'array';
|
|
65
|
+
/** Read by the model: what the value means and how to choose it. */
|
|
66
|
+
description: string;
|
|
67
|
+
/** For `string`: the allowed values. */
|
|
68
|
+
enum?: readonly string[];
|
|
69
|
+
/** For `integer`/`number`. */
|
|
70
|
+
minimum?: number;
|
|
71
|
+
maximum?: number;
|
|
72
|
+
/** For `string`, and each item of an `array`. */
|
|
73
|
+
maxLength?: number;
|
|
74
|
+
/** For `array`: always a list of strings. */
|
|
75
|
+
items?: {
|
|
76
|
+
type: 'string';
|
|
77
|
+
enum?: readonly string[];
|
|
78
|
+
maxLength?: number;
|
|
79
|
+
};
|
|
80
|
+
maxItems?: number;
|
|
81
|
+
}
|
|
82
|
+
/** A flat JSON Schema object: no nesting, no extra keys. */
|
|
83
|
+
export interface PluginAiCapabilityArgsSchema {
|
|
84
|
+
type: 'object';
|
|
85
|
+
properties: Readonly<Record<string, PluginAiCapabilityArgProperty>>;
|
|
86
|
+
required?: readonly string[];
|
|
87
|
+
additionalProperties: false;
|
|
88
|
+
}
|
|
89
|
+
/** How the items that depend on a failed item of this capability behave. */
|
|
90
|
+
export type PluginAiCapabilityDegrade =
|
|
91
|
+
/** Dependents are built without it (a page loses the block that used it). */
|
|
92
|
+
'omit'
|
|
93
|
+
/** Dependents use something that already exists in its place (a site's own layout). */
|
|
94
|
+
| 'fallback';
|
|
95
|
+
/** One planned item, as a capability sees it. */
|
|
96
|
+
export interface PluginAiCapabilityItem {
|
|
97
|
+
/** The item's name in the plan, unique within it; other items cite it as `new:<name>`. */
|
|
98
|
+
name: string;
|
|
99
|
+
args: PluginAiCapabilityArgs;
|
|
100
|
+
}
|
|
101
|
+
/** What a capability's `draftContent` is told beside the item. */
|
|
102
|
+
export interface PluginAiCapabilityDraftContext {
|
|
103
|
+
hostId: string;
|
|
104
|
+
/**
|
|
105
|
+
* The drafts written for the items this one depends on, keyed by the
|
|
106
|
+
* reference the plan used (`new:<name>`), each with its operation and the
|
|
107
|
+
* id its own writer or runner gave it. A dependency that did not succeed is
|
|
108
|
+
* absent.
|
|
109
|
+
*/
|
|
110
|
+
dependencies: Readonly<Record<string, {
|
|
111
|
+
op: string;
|
|
112
|
+
id: string;
|
|
113
|
+
}>>;
|
|
114
|
+
}
|
|
115
|
+
export interface PluginAiCapability {
|
|
116
|
+
/** The operation's name in a plan, stable across releases: `page`, `note`. */
|
|
117
|
+
op: string;
|
|
118
|
+
/** What one is called in a sentence a person reads: "contact form". */
|
|
119
|
+
noun: string;
|
|
120
|
+
/** Where a person finds the draft afterwards: "Notes → Drafts". */
|
|
121
|
+
where: string;
|
|
122
|
+
/**
|
|
123
|
+
* What a person might ask for that this makes, as short phrases the chat's
|
|
124
|
+
* instructions list. True of the shipped product: never a promise the
|
|
125
|
+
* writer cannot keep.
|
|
126
|
+
*/
|
|
127
|
+
intents: readonly string[];
|
|
128
|
+
/** The item's arguments, which the planner fills in. */
|
|
129
|
+
argsSchema: PluginAiCapabilityArgsSchema;
|
|
130
|
+
/** How many items of this operation one plan may hold. */
|
|
131
|
+
maxPerPlan: number;
|
|
132
|
+
/** Whether a workspace on the free plan may have one made. */
|
|
133
|
+
freeAllowed: boolean;
|
|
134
|
+
/** The entitlement a workspace needs, checked with the platform's entitlement read. */
|
|
135
|
+
feature?: string;
|
|
136
|
+
/** The site permission the member needs. */
|
|
137
|
+
permission?: string;
|
|
138
|
+
/** The allowance one counts against, which the owner's writer meets. */
|
|
139
|
+
quota?: string;
|
|
140
|
+
/**
|
|
141
|
+
* The resource whose writer on `plugin-resource-drafts` makes the draft.
|
|
142
|
+
* Exactly one of this and `runnerKind` is set.
|
|
143
|
+
*/
|
|
144
|
+
draftResource?: string;
|
|
145
|
+
/**
|
|
146
|
+
* The AI job kind whose runner makes it — the AI plugin's own operations.
|
|
147
|
+
* Exactly one of this and `draftResource` is set.
|
|
148
|
+
*/
|
|
149
|
+
runnerKind?: string;
|
|
150
|
+
/** The most this item can cost, in AI credits; a draft written without a model costs 0. */
|
|
151
|
+
estimateCredits(args: PluginAiCapabilityArgs): number;
|
|
152
|
+
/** Operations an item of this one may depend on. */
|
|
153
|
+
dependsOnOps?: readonly string[];
|
|
154
|
+
degrade: PluginAiCapabilityDegrade;
|
|
155
|
+
/**
|
|
156
|
+
* Blocks a page places when it uses an item of this operation (the block
|
|
157
|
+
* names the page palette knows). When the item fails, a dependent page is
|
|
158
|
+
* built without them, and says so.
|
|
159
|
+
*/
|
|
160
|
+
pageBlocks?: readonly string[];
|
|
161
|
+
/**
|
|
162
|
+
* The writer's content for an item, from its arguments and its built
|
|
163
|
+
* dependencies. Pure. Absent means the arguments ARE the content.
|
|
164
|
+
*/
|
|
165
|
+
draftContent?(item: PluginAiCapabilityItem, context: PluginAiCapabilityDraftContext): Readonly<Record<string, unknown>>;
|
|
166
|
+
}
|
|
167
|
+
export declare const PLUGIN_AI_CAPABILITIES: import("./plugin-services").PluginServiceContract<PluginAiCapability>;
|
|
168
|
+
/** Why a capability cannot be registered, or `null`. */
|
|
169
|
+
export declare function pluginAiCapabilityProblem(capability: PluginAiCapability): string | null;
|
|
170
|
+
/**
|
|
171
|
+
* Registers what a plugin's AI builds can make. The owner is the loader's
|
|
172
|
+
* marker when a register fn is running, else `options.pluginId`. An `op`
|
|
173
|
+
* another plugin owns throws naming both; a malformed capability throws.
|
|
174
|
+
*/
|
|
175
|
+
export declare function registerPluginAiCapability(capability: PluginAiCapability, options?: {
|
|
176
|
+
pluginId?: string;
|
|
177
|
+
}): void;
|
|
178
|
+
export interface ResolvedPluginAiCapability {
|
|
179
|
+
/** The plugin that owns the operation. */
|
|
180
|
+
pluginId: string;
|
|
181
|
+
capability: PluginAiCapability;
|
|
182
|
+
}
|
|
183
|
+
/** Every registered capability with its owner, in registration order. */
|
|
184
|
+
export declare function pluginAiCapabilities(): ResolvedPluginAiCapability[];
|
|
185
|
+
/** The capability for an operation, with its owner, or `null` when nothing registered it. */
|
|
186
|
+
export declare function pluginAiCapability(op: string): ResolvedPluginAiCapability | null;
|
|
187
|
+
/**
|
|
188
|
+
* What is wrong with an item's arguments against its schema; empty when
|
|
189
|
+
* nothing is. Pure, and the same answer on both sides of the seam: the
|
|
190
|
+
* planner holds the model to it, and an owner may hold its content to it.
|
|
191
|
+
*/
|
|
192
|
+
export declare function pluginAiCapabilityArgsProblems(schema: PluginAiCapabilityArgsSchema, args: Readonly<Record<string, unknown>>): string[];
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { getRegisteringPluginId } from "../app-utils/registering-plugin.js";
|
|
18
|
+
import { definePluginServiceContract, registerPluginService, resolvePluginServices } from "./plugin-services.js";
|
|
19
|
+
export const PLUGIN_AI_CAPABILITIES = definePluginServiceContract('core.ai-capabilities', {
|
|
20
|
+
multiple: true
|
|
21
|
+
});
|
|
22
|
+
const OP_PATTERN = /^[a-z][a-z0-9-]{0,39}$/;
|
|
23
|
+
/** Why a capability cannot be registered, or `null`. */ export function pluginAiCapabilityProblem(capability) {
|
|
24
|
+
var _capability_argsSchema_required;
|
|
25
|
+
var _capability_argsSchema;
|
|
26
|
+
if (!OP_PATTERN.test(capability.op)) {
|
|
27
|
+
return `ai capability op "${capability.op}" must be lowercase letters, digits and dashes`;
|
|
28
|
+
}
|
|
29
|
+
const executors = [
|
|
30
|
+
capability.draftResource,
|
|
31
|
+
capability.runnerKind
|
|
32
|
+
].filter((one)=>typeof one === 'string' && one.trim());
|
|
33
|
+
if (executors.length !== 1) {
|
|
34
|
+
return `ai capability "${capability.op}" needs exactly one of draftResource and runnerKind`;
|
|
35
|
+
}
|
|
36
|
+
if (!Number.isInteger(capability.maxPerPlan) || capability.maxPerPlan < 1) {
|
|
37
|
+
return `ai capability "${capability.op}" needs a maxPerPlan of at least 1`;
|
|
38
|
+
}
|
|
39
|
+
if (((_capability_argsSchema = capability.argsSchema) == null ? void 0 : _capability_argsSchema.type) !== 'object' || capability.argsSchema.additionalProperties !== false) {
|
|
40
|
+
return `ai capability "${capability.op}" needs a flat object argsSchema with additionalProperties: false`;
|
|
41
|
+
}
|
|
42
|
+
for (const required of (_capability_argsSchema_required = capability.argsSchema.required) != null ? _capability_argsSchema_required : []){
|
|
43
|
+
if (!(required in capability.argsSchema.properties)) {
|
|
44
|
+
return `ai capability "${capability.op}" requires "${required}", which its schema does not declare`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (!capability.noun.trim() || !capability.where.trim()) {
|
|
48
|
+
return `ai capability "${capability.op}" needs a noun and a where`;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Registers what a plugin's AI builds can make. The owner is the loader's
|
|
54
|
+
* marker when a register fn is running, else `options.pluginId`. An `op`
|
|
55
|
+
* another plugin owns throws naming both; a malformed capability throws.
|
|
56
|
+
*/ export function registerPluginAiCapability(capability, options) {
|
|
57
|
+
var _ref, _getRegisteringPluginId;
|
|
58
|
+
const problem = pluginAiCapabilityProblem(capability);
|
|
59
|
+
if (problem) throw new Error(problem);
|
|
60
|
+
const key = capability.op;
|
|
61
|
+
const pluginId = ((_ref = (_getRegisteringPluginId = getRegisteringPluginId()) != null ? _getRegisteringPluginId : options == null ? void 0 : options.pluginId) != null ? _ref : '').trim();
|
|
62
|
+
const incumbent = resolvePluginServices(PLUGIN_AI_CAPABILITIES).find((entry)=>entry.key === key);
|
|
63
|
+
if (incumbent && pluginId && incumbent.pluginId !== pluginId) {
|
|
64
|
+
throw new Error(`ai capability "${key}" is already registered by "${incumbent.pluginId}"; ` + `refused "${pluginId}"`);
|
|
65
|
+
}
|
|
66
|
+
registerPluginService(PLUGIN_AI_CAPABILITIES, capability, _extends({}, (options == null ? void 0 : options.pluginId) ? {
|
|
67
|
+
pluginId: options.pluginId
|
|
68
|
+
} : {}, {
|
|
69
|
+
key
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
/** Every registered capability with its owner, in registration order. */ export function pluginAiCapabilities() {
|
|
73
|
+
return resolvePluginServices(PLUGIN_AI_CAPABILITIES).map((entry)=>({
|
|
74
|
+
pluginId: entry.pluginId,
|
|
75
|
+
capability: entry.impl
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
/** The capability for an operation, with its owner, or `null` when nothing registered it. */ export function pluginAiCapability(op) {
|
|
79
|
+
const key = op.trim();
|
|
80
|
+
const entry = resolvePluginServices(PLUGIN_AI_CAPABILITIES).find((one)=>one.key === key);
|
|
81
|
+
return entry ? {
|
|
82
|
+
pluginId: entry.pluginId,
|
|
83
|
+
capability: entry.impl
|
|
84
|
+
} : null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* What is wrong with an item's arguments against its schema; empty when
|
|
88
|
+
* nothing is. Pure, and the same answer on both sides of the seam: the
|
|
89
|
+
* planner holds the model to it, and an owner may hold its content to it.
|
|
90
|
+
*/ export function pluginAiCapabilityArgsProblems(schema, args) {
|
|
91
|
+
var _schema_required;
|
|
92
|
+
const problems = [];
|
|
93
|
+
for (const name of (_schema_required = schema.required) != null ? _schema_required : []){
|
|
94
|
+
const value = args[name];
|
|
95
|
+
if (value === undefined || value === null || value === '') problems.push(`"${name}" is required`);
|
|
96
|
+
}
|
|
97
|
+
for (const [name, value] of Object.entries(args)){
|
|
98
|
+
const property = schema.properties[name];
|
|
99
|
+
if (!property) {
|
|
100
|
+
problems.push(`"${name}" is not an argument`);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (value === undefined || value === null) continue;
|
|
104
|
+
switch(property.type){
|
|
105
|
+
case 'string':
|
|
106
|
+
if (typeof value !== 'string') problems.push(`"${name}" must be text`);
|
|
107
|
+
else {
|
|
108
|
+
if (property.enum && !property.enum.includes(value)) {
|
|
109
|
+
problems.push(`"${name}" must be one of ${property.enum.join(', ')}`);
|
|
110
|
+
}
|
|
111
|
+
if (property.maxLength !== undefined && value.length > property.maxLength) {
|
|
112
|
+
problems.push(`"${name}" must be at most ${property.maxLength} characters`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
break;
|
|
116
|
+
case 'integer':
|
|
117
|
+
case 'number':
|
|
118
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || property.type === 'integer' && !Number.isInteger(value)) {
|
|
119
|
+
problems.push(`"${name}" must be ${property.type === 'integer' ? 'a whole number' : 'a number'}`);
|
|
120
|
+
} else {
|
|
121
|
+
if (property.minimum !== undefined && value < property.minimum) {
|
|
122
|
+
problems.push(`"${name}" must be at least ${property.minimum}`);
|
|
123
|
+
}
|
|
124
|
+
if (property.maximum !== undefined && value > property.maximum) {
|
|
125
|
+
problems.push(`"${name}" must be at most ${property.maximum}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
break;
|
|
129
|
+
case 'boolean':
|
|
130
|
+
if (typeof value !== 'boolean') problems.push(`"${name}" must be true or false`);
|
|
131
|
+
break;
|
|
132
|
+
case 'array':
|
|
133
|
+
if (!Array.isArray(value) || value.some((one)=>typeof one !== 'string')) {
|
|
134
|
+
problems.push(`"${name}" must be a list of text`);
|
|
135
|
+
} else {
|
|
136
|
+
if (property.maxItems !== undefined && value.length > property.maxItems) {
|
|
137
|
+
problems.push(`"${name}" must hold at most ${property.maxItems}`);
|
|
138
|
+
}
|
|
139
|
+
const items = property.items;
|
|
140
|
+
for (const one of value){
|
|
141
|
+
if ((items == null ? void 0 : items.enum) && !items.enum.includes(one)) {
|
|
142
|
+
problems.push(`"${name}" holds "${one}", which is not one of ${items.enum.join(', ')}`);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
if ((items == null ? void 0 : items.maxLength) !== undefined && one.length > items.maxLength) {
|
|
146
|
+
problems.push(`"${name}" holds an entry longer than ${items.maxLength} characters`);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return problems;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
//# sourceMappingURL=plugin-ai-capabilities.js.map
|
|
@@ -0,0 +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 /** 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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"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Optional lines a buyer may add at checkout, offered by another plugin
|
|
19
|
+
* (AGL-3635).
|
|
20
|
+
*
|
|
21
|
+
* Package protection is the first: a plugin that insures parcels quotes a
|
|
22
|
+
* premium for the basket, the seller shows it as a box the buyer ticks, and
|
|
23
|
+
* the seller charges it as one more line. The seller never learns which
|
|
24
|
+
* insurer answered and the insurer never reads the seller's documents: the
|
|
25
|
+
* offer crosses here, and what was bought is recorded on the sale, which the
|
|
26
|
+
* seller's own events then carry.
|
|
27
|
+
*
|
|
28
|
+
* ## Money
|
|
29
|
+
*
|
|
30
|
+
* Every amount is integer cents in the request's currency. An offer is
|
|
31
|
+
* advisory until the sale: the seller asks again when the buyer pays and
|
|
32
|
+
* charges THAT answer, so a stale price in a drawer is never what is
|
|
33
|
+
* charged. An offer whose amount is not a positive integer, or whose
|
|
34
|
+
* currency differs from the request's, is dropped rather than rounded.
|
|
35
|
+
*
|
|
36
|
+
* ## Nobody home is an empty list
|
|
37
|
+
*
|
|
38
|
+
* A provider that is not configured, not switched on for the site, slow or
|
|
39
|
+
* failing answers nothing, and the seller sells exactly as it did before.
|
|
40
|
+
* {@link quotePluginCheckoutExtras} never throws.
|
|
41
|
+
*
|
|
42
|
+
* Import this module by its own subpath
|
|
43
|
+
* (`@aglyn/aglyn/plugin-manager/plugin-checkout-extras`); it is not in the
|
|
44
|
+
* barrel.
|
|
45
|
+
*/
|
|
46
|
+
/** One line of the basket, as a provider prices it. */
|
|
47
|
+
export interface PluginCheckoutExtraLine {
|
|
48
|
+
/** The seller's id for the item, when it has one. */
|
|
49
|
+
itemId?: string;
|
|
50
|
+
name: string;
|
|
51
|
+
sku?: string;
|
|
52
|
+
quantity: number;
|
|
53
|
+
/** Per unit, integer cents, before discounts. */
|
|
54
|
+
unitCents: number;
|
|
55
|
+
/** Whether the line travels in a parcel. */
|
|
56
|
+
ships: boolean;
|
|
57
|
+
}
|
|
58
|
+
/** What a seller asks: the basket a buyer is about to pay for. */
|
|
59
|
+
export interface PluginCheckoutExtraRequest {
|
|
60
|
+
hostId: string;
|
|
61
|
+
/** ISO-4217, lower case. */
|
|
62
|
+
currency: string;
|
|
63
|
+
/** The goods' value, integer cents, before discounts and shipping. */
|
|
64
|
+
itemsCents: number;
|
|
65
|
+
lines: PluginCheckoutExtraLine[];
|
|
66
|
+
/** Where the buyer said it goes, when they said. */
|
|
67
|
+
destination?: {
|
|
68
|
+
country?: string;
|
|
69
|
+
postalCode?: string;
|
|
70
|
+
};
|
|
71
|
+
/** Aborted when the seller stops waiting; a provider passes it to its fetches. */
|
|
72
|
+
signal?: AbortSignal;
|
|
73
|
+
}
|
|
74
|
+
/** What a provider offers for that basket. */
|
|
75
|
+
export interface PluginCheckoutExtraOffer {
|
|
76
|
+
/** Stable within the provider: `package-protection`. Lower-case words and dashes. */
|
|
77
|
+
key: string;
|
|
78
|
+
/** What the buyer reads beside the box: `Package protection`. */
|
|
79
|
+
label: string;
|
|
80
|
+
/** One sentence under it. */
|
|
81
|
+
description?: string;
|
|
82
|
+
/** Integer cents, above zero. */
|
|
83
|
+
amountCents: number;
|
|
84
|
+
/** ISO-4217, lower case; must be the request's. */
|
|
85
|
+
currency: string;
|
|
86
|
+
/** Whether the box starts ticked. The merchant decides; default unticked. */
|
|
87
|
+
defaultSelected?: boolean;
|
|
88
|
+
/** The provider's id for this quote, carried onto the sale (64 characters at most). */
|
|
89
|
+
quoteRef?: string;
|
|
90
|
+
/** A page the buyer can read about it, `https:` only. */
|
|
91
|
+
termsUrl?: string;
|
|
92
|
+
}
|
|
93
|
+
export interface PluginCheckoutExtraProvider {
|
|
94
|
+
/** The offer for this basket, or `null`. Throwing reads as `null`. */
|
|
95
|
+
offer(request: PluginCheckoutExtraRequest): Promise<PluginCheckoutExtraOffer | null>;
|
|
96
|
+
}
|
|
97
|
+
/** An offer as the seller sees it: whose it is, and the id it is chosen by. */
|
|
98
|
+
export interface QuotedPluginCheckoutExtra extends Required<Pick<PluginCheckoutExtraOffer, 'defaultSelected'>> {
|
|
99
|
+
/** `{pluginId}.{key}`: what a buyer's choice names. */
|
|
100
|
+
id: string;
|
|
101
|
+
pluginId: string;
|
|
102
|
+
key: string;
|
|
103
|
+
label: string;
|
|
104
|
+
description?: string;
|
|
105
|
+
amountCents: number;
|
|
106
|
+
currency: string;
|
|
107
|
+
quoteRef?: string;
|
|
108
|
+
termsUrl?: string;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* What a sale recorded of one extra the buyer took. The seller stores this
|
|
112
|
+
* on the sale and puts it in the events it raises about the sale, so the
|
|
113
|
+
* provider learns what was bought from the seller's own facts.
|
|
114
|
+
*/
|
|
115
|
+
export interface PluginCheckoutExtraSold {
|
|
116
|
+
id: string;
|
|
117
|
+
pluginId: string;
|
|
118
|
+
key: string;
|
|
119
|
+
label: string;
|
|
120
|
+
amountCents: number;
|
|
121
|
+
quoteRef?: string;
|
|
122
|
+
}
|
|
123
|
+
/** The most extras one checkout carries. */
|
|
124
|
+
export declare const MAX_CHECKOUT_EXTRAS = 3;
|
|
125
|
+
/** Joins the providers. Re-registering under the same plugin replaces its own. */
|
|
126
|
+
export declare function registerPluginCheckoutExtra(provider: PluginCheckoutExtraProvider, options?: {
|
|
127
|
+
pluginId?: string;
|
|
128
|
+
}): void;
|
|
129
|
+
/** Whether any plugin offers extras: a seller with none skips the question. */
|
|
130
|
+
export declare function hasPluginCheckoutExtras(): boolean;
|
|
131
|
+
/**
|
|
132
|
+
* An offer held to the contract, or `null`: a positive integer amount in the
|
|
133
|
+
* request's currency, a key and a label, and nothing else a page would render
|
|
134
|
+
* that it should not.
|
|
135
|
+
*/
|
|
136
|
+
export declare function normalizePluginCheckoutExtra(pluginId: string, offer: PluginCheckoutExtraOffer | null | undefined, currency: string): QuotedPluginCheckoutExtra | null;
|
|
137
|
+
/**
|
|
138
|
+
* Asks every provider at once and keeps what answered within `timeoutMs`,
|
|
139
|
+
* each one held to the contract. Never throws; a provider that throws, is
|
|
140
|
+
* late or answers nonsense is simply absent. At most
|
|
141
|
+
* {@link MAX_CHECKOUT_EXTRAS}, in the providers' resolve order.
|
|
142
|
+
*/
|
|
143
|
+
export declare function quotePluginCheckoutExtras(request: Omit<PluginCheckoutExtraRequest, 'signal'>, options: {
|
|
144
|
+
timeoutMs: number;
|
|
145
|
+
}): Promise<QuotedPluginCheckoutExtra[]>;
|
|
146
|
+
/**
|
|
147
|
+
* The buyer's choice, read from a request body: the ids of offers they took,
|
|
148
|
+
* de-duplicated and bounded. Ids only; the amounts are always asked again.
|
|
149
|
+
*/
|
|
150
|
+
export declare function readChosenCheckoutExtras(value: unknown): string[];
|
|
151
|
+
/** The metadata keys the extras ride under: `extra0`, `extra1`, `extra2`. */
|
|
152
|
+
export declare const CHECKOUT_EXTRA_METADATA_PREFIX = "extra";
|
|
153
|
+
/**
|
|
154
|
+
* The extras a sale carries, packed for a payment processor's metadata: one
|
|
155
|
+
* key per extra (`extra0` …), each `[id, cents, label, quoteRef?]` as JSON,
|
|
156
|
+
* well inside a 500-character value. One key each, so a long quote id can
|
|
157
|
+
* never truncate another extra out of the record.
|
|
158
|
+
*/
|
|
159
|
+
export declare function encodeCheckoutExtrasMetadata(extras: readonly QuotedPluginCheckoutExtra[]): Record<string, string>;
|
|
160
|
+
/**
|
|
161
|
+
* Reads {@link encodeCheckoutExtrasMetadata} back off the metadata object.
|
|
162
|
+
* Anything unreadable is dropped, never guessed.
|
|
163
|
+
*/
|
|
164
|
+
export declare function decodeCheckoutExtrasMetadata(metadata: Record<string, unknown> | null | undefined): PluginCheckoutExtraSold[];
|
|
165
|
+
/** The cents a sale's extras add up to. */
|
|
166
|
+
export declare function checkoutExtrasCents(extras: ReadonlyArray<{
|
|
167
|
+
amountCents: number;
|
|
168
|
+
}> | null | undefined): number;
|