@datalayer/agent-runtimes 1.3.111 → 1.3.113
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/dist-embed/datalayer-app-main.js +4 -4
- package/lib/apps/apps/appspec.js +2 -0
- package/lib/apps/apps/checks.js +10 -6
- package/lib/examples/PapSignInDisplayExample.d.ts +4 -0
- package/lib/examples/PapSignInDisplayExample.js +23 -0
- package/lib/examples/example-selector.js +1 -0
- package/lib/examples/index.d.ts +1 -0
- package/lib/examples/index.js +1 -0
- package/lib/examples/main.js +2 -0
- package/lib/examples/pap/PapSignInDisplayView.d.ts +15 -0
- package/lib/examples/pap/PapSignInDisplayView.js +49 -0
- package/lib/examples/pap/PapTraceView.d.ts +2 -3
- package/lib/examples/pap/PapTraceView.js +3 -4
- package/lib/examples/pap/papSignInCases.d.ts +12 -0
- package/lib/examples/pap/papSignInCases.js +88 -0
- package/lib/protocols/a2a/index.d.ts +52 -0
- package/lib/protocols/a2a/index.js +164 -0
- package/lib/protocols/agentProtocols.d.ts +15 -0
- package/lib/protocols/agentProtocols.js +47 -0
- package/lib/protocols/agentic-commerce/index.d.ts +86 -0
- package/lib/protocols/agentic-commerce/index.js +267 -0
- package/lib/protocols/agentic-commerce/models.d.ts +3292 -0
- package/lib/protocols/agentic-commerce/models.js +10 -0
- package/lib/protocols/commerce.d.ts +42 -0
- package/lib/protocols/commerce.js +47 -0
- package/lib/protocols/core.d.ts +44 -0
- package/lib/protocols/core.js +62 -0
- package/lib/protocols/mcp/index.d.ts +4 -0
- package/lib/protocols/mcp/index.js +37 -0
- package/lib/protocols/pact/index.d.ts +141 -0
- package/lib/protocols/pact/index.js +363 -0
- package/lib/protocols/ucp/index.d.ts +79 -0
- package/lib/protocols/ucp/index.js +277 -0
- package/lib/protocols/web-bot-auth/index.d.ts +161 -0
- package/lib/protocols/web-bot-auth/index.js +427 -0
- package/lib/specs/actions.js +4 -0
- package/lib/specs/apps.d.ts +2 -0
- package/lib/specs/apps.js +481 -0
- package/lib/specs/appspecSchema.js +200 -2
- package/lib/types/agentspecs.d.ts +25 -0
- package/package.json +4 -1
- package/scripts/codegen/generate_agentic_commerce.sh +74 -0
- package/scripts/codegen/generate_apps.py +11 -0
- package/scripts/validate_pap_examples.py +27 -2
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* PACT 1.0, Personal Agent Consent & Trust, as a Reactor Plugin and a
|
|
7
|
+
* personal agent (AGENT_PROTOCOLS PR-10 to PR-13) — the twin of
|
|
8
|
+
* `agent_runtimes.protocols.pact`. Built against
|
|
9
|
+
* `openpactprotocol/openpactprotocol` `docs/spec.md` at 838c6bd
|
|
10
|
+
* (2026-10-06), reviewed 2026-10-12.
|
|
11
|
+
*
|
|
12
|
+
* The personal-agent JWT (§3.2) is signed by the host's key through
|
|
13
|
+
* `pact.signer` (ES256, the Web Bot Auth signer's contract); the delegation
|
|
14
|
+
* token (§5) is kept in `pact.delegationStore`, never handed to the agent;
|
|
15
|
+
* every receipt (§5.6) is verified with `jose` before it is kept in
|
|
16
|
+
* `pact.receiptStore`.
|
|
17
|
+
*
|
|
18
|
+
* @module protocols/pact
|
|
19
|
+
*/
|
|
20
|
+
import { contribution, defineContributionPoint, definePlugin, } from '@datalayer/reactor';
|
|
21
|
+
import { compactVerify, importJWK } from 'jose';
|
|
22
|
+
import { DEFAULT_ORDER, ProtocolDescriptions, firstContribution, } from '../core';
|
|
23
|
+
export const PACT_VERSION = '1.0';
|
|
24
|
+
export const PACT_PLUGIN = 'agent-runtimes.protocols.pact';
|
|
25
|
+
export const DELEGATION_HEADER = 'X-A2A-User-Delegation';
|
|
26
|
+
export const DEVICE_CODE_GRANT = 'urn:ietf:params:oauth:grant-type:device_code';
|
|
27
|
+
export class PactError extends Error {
|
|
28
|
+
}
|
|
29
|
+
export class InMemoryDelegationStore {
|
|
30
|
+
held = new Map();
|
|
31
|
+
async put(brand, user, token, scopes) {
|
|
32
|
+
this.held.set(`${brand} ${user}`, { token, scopes });
|
|
33
|
+
}
|
|
34
|
+
async get(brand, user) {
|
|
35
|
+
return this.held.get(`${brand} ${user}`);
|
|
36
|
+
}
|
|
37
|
+
async delete(brand, user) {
|
|
38
|
+
this.held.delete(`${brand} ${user}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export class InMemoryReceiptStore {
|
|
42
|
+
receipts = [];
|
|
43
|
+
async keep(brand, user, claims) {
|
|
44
|
+
this.receipts.push({ brand, user, claims });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export const PactSigners = defineContributionPoint('pact.signer');
|
|
48
|
+
export const PactDelegationStores = defineContributionPoint('pact.delegationStore');
|
|
49
|
+
export const PactReceiptStores = defineContributionPoint('pact.receiptStore');
|
|
50
|
+
export const PactDelegatedProfiles = defineContributionPoint('pact.extension.delegated');
|
|
51
|
+
const POINTS = [
|
|
52
|
+
PactSigners,
|
|
53
|
+
PactDelegationStores,
|
|
54
|
+
PactReceiptStores,
|
|
55
|
+
PactDelegatedProfiles,
|
|
56
|
+
];
|
|
57
|
+
export const PactPlugin = definePlugin({
|
|
58
|
+
name: PACT_PLUGIN,
|
|
59
|
+
version: '1.0.0',
|
|
60
|
+
description: `PACT ${PACT_VERSION}: a personal agent at a business, and what the person let it do.`,
|
|
61
|
+
contributionPoints: POINTS,
|
|
62
|
+
contributes: [
|
|
63
|
+
contribution(ProtocolDescriptions, {
|
|
64
|
+
id: 'pact',
|
|
65
|
+
name: 'Personal Agent Consent & Trust',
|
|
66
|
+
layer: 'consent',
|
|
67
|
+
version: PACT_VERSION,
|
|
68
|
+
roles: ['personal-agent', 'provider'],
|
|
69
|
+
status: 'partial',
|
|
70
|
+
specification: 'https://github.com/openpactprotocol/openpactprotocol/blob/main/docs/spec.md',
|
|
71
|
+
plugin: PACT_PLUGIN,
|
|
72
|
+
points: POINTS.map(point => point.id),
|
|
73
|
+
}, { id: 'pact', order: DEFAULT_ORDER }),
|
|
74
|
+
contribution(PactDelegatedProfiles, { grantType: DEVICE_CODE_GRANT }, { id: 'device-code', order: DEFAULT_ORDER }),
|
|
75
|
+
],
|
|
76
|
+
// Stores are state: one per reactor, not one per module.
|
|
77
|
+
register: ctx => {
|
|
78
|
+
ctx.contribute(PactDelegationStores, new InMemoryDelegationStore(), {
|
|
79
|
+
id: 'memory',
|
|
80
|
+
order: DEFAULT_ORDER,
|
|
81
|
+
});
|
|
82
|
+
ctx.contribute(PactReceiptStores, new InMemoryReceiptStore(), {
|
|
83
|
+
id: 'memory',
|
|
84
|
+
order: DEFAULT_ORDER,
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
const READ_VERBS = new Set(['read', 'view', 'list', 'status', 'lookup']);
|
|
89
|
+
function actionClassOf(id) {
|
|
90
|
+
const verb = id.split(':').pop()?.toLowerCase() ?? '';
|
|
91
|
+
return READ_VERBS.has(verb) ? 'read' : 'write';
|
|
92
|
+
}
|
|
93
|
+
function bearer(scheme) {
|
|
94
|
+
return (String(scheme?.httpAuthSecurityScheme?.scheme ?? '').toLowerCase() ===
|
|
95
|
+
'bearer');
|
|
96
|
+
}
|
|
97
|
+
/** A Brand's card as PACT 1.0 reads it (§2.1, §5.1); throws when it is not one. */
|
|
98
|
+
export function readCard(card) {
|
|
99
|
+
const iface = (card.supportedInterfaces ?? []).find((entry) => entry.protocolBinding === 'HTTP+JSON' && entry.protocolVersion === '1.0');
|
|
100
|
+
if (!iface) {
|
|
101
|
+
throw new PactError('the card offers no HTTP+JSON 1.0 interface');
|
|
102
|
+
}
|
|
103
|
+
const schemes = card.securitySchemes ?? {};
|
|
104
|
+
const requirements = (card.securityRequirements ?? []).map((entry) => Object.keys(entry.schemes ?? {}));
|
|
105
|
+
const alone = requirements.find(names => names.length === 1 && bearer(schemes[names[0]]));
|
|
106
|
+
if (!alone) {
|
|
107
|
+
throw new PactError('the card does not ask for the personal-agent JWT alone');
|
|
108
|
+
}
|
|
109
|
+
const identity = alone[0];
|
|
110
|
+
let delegation;
|
|
111
|
+
for (const names of requirements) {
|
|
112
|
+
if (names.length !== 2 || !names.includes(identity)) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const oauth = schemes[names.find(name => name !== identity)]?.oauth2SecurityScheme;
|
|
116
|
+
const device = oauth?.flows?.deviceCode;
|
|
117
|
+
if (!oauth || !device) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (!oauth.oauth2MetadataUrl) {
|
|
121
|
+
throw new PactError('the delegation names no RFC 8414 metadata');
|
|
122
|
+
}
|
|
123
|
+
delegation = {
|
|
124
|
+
deviceAuthorizationUrl: device.deviceAuthorizationUrl,
|
|
125
|
+
tokenUrl: device.tokenUrl,
|
|
126
|
+
metadataUrl: oauth.oauth2MetadataUrl,
|
|
127
|
+
scopes: Object.entries(device.scopes ?? {}).map(([id, description]) => ({
|
|
128
|
+
id,
|
|
129
|
+
description: String(description),
|
|
130
|
+
actionClass: actionClassOf(id),
|
|
131
|
+
})),
|
|
132
|
+
};
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
name: String(card.name ?? ''),
|
|
137
|
+
interfaceUrl: String(iface.url).replace(/\/$/, ''),
|
|
138
|
+
identityScheme: identity,
|
|
139
|
+
profile: delegation ? 'delegated' : 'identity',
|
|
140
|
+
delegation,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function b64url(bytes) {
|
|
144
|
+
let text = '';
|
|
145
|
+
for (const byte of bytes) {
|
|
146
|
+
text += String.fromCharCode(byte);
|
|
147
|
+
}
|
|
148
|
+
return btoa(text).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
149
|
+
}
|
|
150
|
+
/** One personal-agent JWT (§3.2): ES256 by the host's signer, `sub` opaque, ≤ 300 s. */
|
|
151
|
+
export async function signPersonalAgentJwt(signer, claims) {
|
|
152
|
+
if (signer.algorithm !== 'ecdsa-p256-sha256') {
|
|
153
|
+
throw new PactError('a personal-agent JWT is signed ES256 here (a P-256 key)');
|
|
154
|
+
}
|
|
155
|
+
if (!claims.subject || claims.subject.includes('@')) {
|
|
156
|
+
throw new PactError('`sub` is a stable, opaque id: never an address');
|
|
157
|
+
}
|
|
158
|
+
const lifetime = claims.lifetime ?? 120;
|
|
159
|
+
if (lifetime > 300) {
|
|
160
|
+
throw new PactError('a personal-agent JWT lives at most 300 seconds');
|
|
161
|
+
}
|
|
162
|
+
const now = Math.floor(Date.now() / 1000);
|
|
163
|
+
const encode = (value) => b64url(new TextEncoder().encode(JSON.stringify(value)));
|
|
164
|
+
const input = `${encode({ alg: 'ES256', kid: signer.keyid, typ: 'JWT' })}.${encode({
|
|
165
|
+
iss: claims.issuer,
|
|
166
|
+
sub: claims.subject,
|
|
167
|
+
aud: claims.audience,
|
|
168
|
+
iat: now,
|
|
169
|
+
exp: now + lifetime,
|
|
170
|
+
jti: crypto.randomUUID(),
|
|
171
|
+
})}`;
|
|
172
|
+
const signature = await signer.sign(new TextEncoder().encode(input));
|
|
173
|
+
return `${input}.${b64url(signature)}`;
|
|
174
|
+
}
|
|
175
|
+
/** A personal agent at one Brand. */
|
|
176
|
+
export class PactPersonalAgent {
|
|
177
|
+
options;
|
|
178
|
+
card;
|
|
179
|
+
pending = new Map();
|
|
180
|
+
jwks;
|
|
181
|
+
constructor(options) {
|
|
182
|
+
this.options = options;
|
|
183
|
+
}
|
|
184
|
+
get fetch() {
|
|
185
|
+
return this.options.fetchImpl ?? ((input, init) => fetch(input, init));
|
|
186
|
+
}
|
|
187
|
+
jwt(user) {
|
|
188
|
+
const signer = firstContribution(this.options.reactor, PactSigners, 'PACT signer');
|
|
189
|
+
return signPersonalAgentJwt(signer, {
|
|
190
|
+
issuer: this.options.issuer,
|
|
191
|
+
subject: user,
|
|
192
|
+
audience: this.options.audience,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
delegations() {
|
|
196
|
+
return firstContribution(this.options.reactor, PactDelegationStores, 'PACT delegation store');
|
|
197
|
+
}
|
|
198
|
+
async discover() {
|
|
199
|
+
const answer = await this.fetch(this.options.cardUrl);
|
|
200
|
+
if (answer.status !== 200) {
|
|
201
|
+
throw new PactError(`the card answered ${answer.status}`);
|
|
202
|
+
}
|
|
203
|
+
this.card = readCard(await answer.json());
|
|
204
|
+
return this.card;
|
|
205
|
+
}
|
|
206
|
+
async send(user, text, options = {}) {
|
|
207
|
+
const card = this.card ?? (await this.discover());
|
|
208
|
+
const headers = {
|
|
209
|
+
Authorization: `Bearer ${await this.jwt(user)}`,
|
|
210
|
+
'A2A-Version': '1.0',
|
|
211
|
+
'Content-Type': 'application/json',
|
|
212
|
+
};
|
|
213
|
+
const held = await this.delegations().get(card.interfaceUrl, user);
|
|
214
|
+
if (held) {
|
|
215
|
+
headers[DELEGATION_HEADER] = `Bearer ${held.token}`;
|
|
216
|
+
}
|
|
217
|
+
const answer = await this.fetch(`${card.interfaceUrl}/message:send`, {
|
|
218
|
+
method: 'POST',
|
|
219
|
+
headers,
|
|
220
|
+
body: JSON.stringify({
|
|
221
|
+
message: {
|
|
222
|
+
messageId: options.messageId ?? crypto.randomUUID(),
|
|
223
|
+
role: 'ROLE_USER',
|
|
224
|
+
parts: [{ text }],
|
|
225
|
+
...(options.contextId ? { contextId: options.contextId } : {}),
|
|
226
|
+
},
|
|
227
|
+
}),
|
|
228
|
+
});
|
|
229
|
+
if (answer.status === 401) {
|
|
230
|
+
throw new PactError('the Brand refused the personal agent’s identity or its delegation');
|
|
231
|
+
}
|
|
232
|
+
const body = await answer.json();
|
|
233
|
+
if (body.error) {
|
|
234
|
+
throw new PactError(`${body.error.details?.[0]?.reason ?? body.error.status}: ${body.error.message}`);
|
|
235
|
+
}
|
|
236
|
+
if (body.task) {
|
|
237
|
+
const meta = body.task.metadata ?? {};
|
|
238
|
+
return {
|
|
239
|
+
kind: 'step-up',
|
|
240
|
+
text: (body.task.status?.message?.parts ?? [])
|
|
241
|
+
.map((p) => p.text)
|
|
242
|
+
.join(' '),
|
|
243
|
+
contextId: body.task.contextId,
|
|
244
|
+
missing: meta['pact.missingScopes'] ?? [],
|
|
245
|
+
verificationUri: meta['pact.verificationUriComplete'] ?? '',
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
const reply = body.message;
|
|
249
|
+
let claims = {};
|
|
250
|
+
if (held) {
|
|
251
|
+
const receipt = reply.metadata?.['pact.receipt'];
|
|
252
|
+
if (!receipt) {
|
|
253
|
+
throw new PactError('a reply under delegation carries no receipt');
|
|
254
|
+
}
|
|
255
|
+
claims = await this.verifyReceipt(receipt);
|
|
256
|
+
await firstContribution(this.options.reactor, PactReceiptStores, 'PACT receipt store').keep(card.interfaceUrl, user, claims);
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
kind: 'reply',
|
|
260
|
+
text: (reply.parts ?? []).map((p) => p.text).join(' '),
|
|
261
|
+
contextId: reply.contextId,
|
|
262
|
+
scopesUsed: claims.scopesUsed ?? [],
|
|
263
|
+
actions: (claims.actions ?? []).map((a) => a.tool),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
async verifyReceipt(receipt) {
|
|
267
|
+
const card = this.card;
|
|
268
|
+
if (!this.jwks) {
|
|
269
|
+
const metadata = await (await this.fetch(card.delegation.metadataUrl)).json();
|
|
270
|
+
this.jwks = await (await this.fetch(metadata.jwks_uri)).json();
|
|
271
|
+
}
|
|
272
|
+
const kid = JSON.parse(atob(receipt.jws.split('.')[0].replace(/-/g, '+').replace(/_/g, '/'))).kid;
|
|
273
|
+
const jwk = this.jwks.keys.find(key => key.kid === kid);
|
|
274
|
+
if (!jwk) {
|
|
275
|
+
throw new PactError('no key of the Brand signs this receipt');
|
|
276
|
+
}
|
|
277
|
+
const { payload } = await compactVerify(receipt.jws, await importJWK(jwk, 'ES256'));
|
|
278
|
+
const claims = JSON.parse(new TextDecoder().decode(payload));
|
|
279
|
+
if (JSON.stringify(claims) !== JSON.stringify(receipt.claims)) {
|
|
280
|
+
throw new PactError('the receipt’s claims are not the ones it signs');
|
|
281
|
+
}
|
|
282
|
+
return claims;
|
|
283
|
+
}
|
|
284
|
+
async requestSignIn(user, scopes) {
|
|
285
|
+
const card = this.card ?? (await this.discover());
|
|
286
|
+
if (!card.delegation) {
|
|
287
|
+
throw new PactError('the Brand offers no delegation');
|
|
288
|
+
}
|
|
289
|
+
const offered = new Set(card.delegation.scopes.map(scope => scope.id));
|
|
290
|
+
const unknown = scopes.filter(scope => !offered.has(scope));
|
|
291
|
+
if (unknown.length) {
|
|
292
|
+
throw new PactError(`not on the card: ${unknown.join(', ')}`);
|
|
293
|
+
}
|
|
294
|
+
const answer = await this.fetch(card.delegation.deviceAuthorizationUrl, {
|
|
295
|
+
method: 'POST',
|
|
296
|
+
headers: {
|
|
297
|
+
Authorization: `Bearer ${await this.jwt(user)}`,
|
|
298
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
299
|
+
},
|
|
300
|
+
body: new URLSearchParams({
|
|
301
|
+
client_id: this.options.issuer,
|
|
302
|
+
scope: scopes.join(' '),
|
|
303
|
+
}).toString(),
|
|
304
|
+
});
|
|
305
|
+
if (answer.status !== 200) {
|
|
306
|
+
throw new PactError(`device authorization answered ${answer.status}`);
|
|
307
|
+
}
|
|
308
|
+
const body = await answer.json();
|
|
309
|
+
const reference = crypto.randomUUID();
|
|
310
|
+
this.pending.set(reference, body.device_code);
|
|
311
|
+
return {
|
|
312
|
+
verificationUri: body.verification_uri_complete ?? body.verification_uri,
|
|
313
|
+
userCode: body.user_code,
|
|
314
|
+
expiresIn: body.expires_in,
|
|
315
|
+
reference,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
/** Ask once whether the person approved: the scopes granted, or `undefined` while pending. */
|
|
319
|
+
async pollSignIn(user, signIn) {
|
|
320
|
+
const card = this.card;
|
|
321
|
+
const deviceCode = this.pending.get(signIn.reference);
|
|
322
|
+
if (!deviceCode) {
|
|
323
|
+
throw new PactError('no sign-in pending under this reference');
|
|
324
|
+
}
|
|
325
|
+
const answer = await this.fetch(card.delegation.tokenUrl, {
|
|
326
|
+
method: 'POST',
|
|
327
|
+
headers: {
|
|
328
|
+
Authorization: `Bearer ${await this.jwt(user)}`,
|
|
329
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
330
|
+
},
|
|
331
|
+
body: new URLSearchParams({
|
|
332
|
+
grant_type: DEVICE_CODE_GRANT,
|
|
333
|
+
device_code: deviceCode,
|
|
334
|
+
client_id: this.options.issuer,
|
|
335
|
+
}).toString(),
|
|
336
|
+
});
|
|
337
|
+
const body = await answer.json();
|
|
338
|
+
if (body.error === 'authorization_pending' || body.error === 'slow_down') {
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
341
|
+
if (body.error) {
|
|
342
|
+
this.pending.delete(signIn.reference);
|
|
343
|
+
throw new PactError(`the sign-in ended: ${body.error}`);
|
|
344
|
+
}
|
|
345
|
+
const granted = String(body.scope ?? '')
|
|
346
|
+
.split(' ')
|
|
347
|
+
.filter(Boolean);
|
|
348
|
+
await this.delegations().put(card.interfaceUrl, user, body.access_token, granted);
|
|
349
|
+
this.pending.delete(signIn.reference);
|
|
350
|
+
return granted;
|
|
351
|
+
}
|
|
352
|
+
/** Poll until the person approved; the scopes they actually granted. */
|
|
353
|
+
async finishSignIn(user, signIn, intervalMs = 5000, attempts = 120) {
|
|
354
|
+
for (let i = 0; i < attempts; i++) {
|
|
355
|
+
const granted = await this.pollSignIn(user, signIn);
|
|
356
|
+
if (granted) {
|
|
357
|
+
return granted;
|
|
358
|
+
}
|
|
359
|
+
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
|
360
|
+
}
|
|
361
|
+
throw new PactError('the person did not sign in in time');
|
|
362
|
+
}
|
|
363
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Universal Commerce Protocol, `2026-08-25`, as a Reactor Plugin and a
|
|
3
|
+
* shopping client (AGENT_PROTOCOLS PR-30, PR-31) — the twin of
|
|
4
|
+
* `agent_runtimes.protocols.ucp`.
|
|
5
|
+
*
|
|
6
|
+
* Checkouts are read through the protocol's own TypeScript SDK
|
|
7
|
+
* (`@ucp-js/sdk` 0.5.1, zod schemas generated from the same JSON Schemas).
|
|
8
|
+
* A purchase is an exact, revisioned proposal (`proposeCompletion`), approved
|
|
9
|
+
* by the person through the host's `ucp.approver`, paid with the host's
|
|
10
|
+
* `ucp.paymentInstruments`, completed once (`ucp.proposalStore`).
|
|
11
|
+
*
|
|
12
|
+
* @module protocols/ucp
|
|
13
|
+
*/
|
|
14
|
+
import { type ReactorPlatformView } from '@datalayer/reactor';
|
|
15
|
+
import { type CheckoutApprover, type CheckoutProposal, type ProposalStore } from '../commerce';
|
|
16
|
+
export declare const UCP_VERSION = "2026-08-25";
|
|
17
|
+
export declare const SHOPPING_SERVICE = "dev.ucp.shopping";
|
|
18
|
+
export declare const CHECKOUT_CAPABILITY = "dev.ucp.shopping.checkout";
|
|
19
|
+
export declare const PROFILE_PATH = "/.well-known/ucp";
|
|
20
|
+
export declare const UCP_PLUGIN = "agent-runtimes.protocols.ucp";
|
|
21
|
+
/** Each operation's action class (F-10), for Rules (R-05). */
|
|
22
|
+
export declare const UCP_ACTION_CLASSES: {
|
|
23
|
+
readonly search: "read";
|
|
24
|
+
readonly proposeCompletion: "read";
|
|
25
|
+
readonly getCheckout: "read";
|
|
26
|
+
readonly createCheckout: "write";
|
|
27
|
+
readonly updateCheckout: "write";
|
|
28
|
+
readonly cancelCheckout: "write";
|
|
29
|
+
readonly complete: "buy";
|
|
30
|
+
};
|
|
31
|
+
export { InMemoryProposalStore, proposalSummary, type CheckoutApprover, type CheckoutProposal, type ProposalStore, } from '../commerce';
|
|
32
|
+
export declare class UcpError extends Error {
|
|
33
|
+
}
|
|
34
|
+
type Json = Record<string, any>;
|
|
35
|
+
export interface PaymentInstruments {
|
|
36
|
+
instrument(proposal: CheckoutProposal, handlers: Json): Promise<Json>;
|
|
37
|
+
}
|
|
38
|
+
export interface UcpExtension {
|
|
39
|
+
capability: string;
|
|
40
|
+
version: string;
|
|
41
|
+
}
|
|
42
|
+
export declare const UcpApprovers: import("@datalayer/reactor").ContributionPoint<CheckoutApprover>;
|
|
43
|
+
export declare const UcpPaymentInstruments: import("@datalayer/reactor").ContributionPoint<PaymentInstruments>;
|
|
44
|
+
export declare const UcpProposalStores: import("@datalayer/reactor").ContributionPoint<ProposalStore>;
|
|
45
|
+
export declare const UcpFulfillmentExtensions: import("@datalayer/reactor").ContributionPoint<UcpExtension>;
|
|
46
|
+
export declare const UcpDiscountExtensions: import("@datalayer/reactor").ContributionPoint<UcpExtension>;
|
|
47
|
+
export declare const UcpBuyerConsentExtensions: import("@datalayer/reactor").ContributionPoint<UcpExtension>;
|
|
48
|
+
export declare const UcpPlugin: import("@datalayer/reactor").ReactorPlugin<Record<string, never>, unknown, unknown>;
|
|
49
|
+
/** The capabilities in use with a business: checkout, and the extensions both support. */
|
|
50
|
+
export declare function negotiateCapabilities(profile: Json, reactor: ReactorPlatformView): string[];
|
|
51
|
+
/** The revision of what a checkout buys; the same digest as Python's `revision_of`. */
|
|
52
|
+
export declare function revisionOf(checkout: Json): Promise<string>;
|
|
53
|
+
/** What an agent may see of a checkout: no payment credential, no token. */
|
|
54
|
+
export declare function checkoutSummary(checkout: Json): Json;
|
|
55
|
+
/** A platform's client of one business, over the REST binding. */
|
|
56
|
+
export declare class UcpClient {
|
|
57
|
+
private readonly business;
|
|
58
|
+
private readonly agentProfile;
|
|
59
|
+
private readonly fetchImpl;
|
|
60
|
+
private endpoint?;
|
|
61
|
+
private domain;
|
|
62
|
+
constructor(business: string, agentProfile: string, fetchImpl?: typeof fetch);
|
|
63
|
+
private headers;
|
|
64
|
+
/** Read the business's profile: the version, shopping over REST, checkout. */
|
|
65
|
+
discover(): Promise<Json>;
|
|
66
|
+
private call;
|
|
67
|
+
search(query: string, limit?: number): Promise<Json[]>;
|
|
68
|
+
createCheckout(lines: Array<{
|
|
69
|
+
id: string;
|
|
70
|
+
quantity?: number;
|
|
71
|
+
}>, buyer?: Json): Promise<Json>;
|
|
72
|
+
updateCheckout(id: string, update: Json): Promise<Json>;
|
|
73
|
+
getCheckout(id: string): Promise<Json>;
|
|
74
|
+
cancelCheckout(id: string): Promise<Json>;
|
|
75
|
+
/** The exact purchase a `ready_for_complete` checkout would make. */
|
|
76
|
+
proposeCompletion(id: string): Promise<CheckoutProposal>;
|
|
77
|
+
/** Complete an approved proposal, once: refused if unapproved, spent, changed or expired. */
|
|
78
|
+
complete(proposal: CheckoutProposal, reactor: ReactorPlatformView): Promise<Json>;
|
|
79
|
+
}
|