@datalayer/agent-runtimes 1.3.111 → 1.3.112
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 +139 -0
- package/lib/protocols/pact/index.js +357 -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 +2 -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,277 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The Universal Commerce Protocol, `2026-08-25`, as a Reactor Plugin and a
|
|
7
|
+
* shopping client (AGENT_PROTOCOLS PR-30, PR-31) — the twin of
|
|
8
|
+
* `agent_runtimes.protocols.ucp`.
|
|
9
|
+
*
|
|
10
|
+
* Checkouts are read through the protocol's own TypeScript SDK
|
|
11
|
+
* (`@ucp-js/sdk` 0.5.1, zod schemas generated from the same JSON Schemas).
|
|
12
|
+
* A purchase is an exact, revisioned proposal (`proposeCompletion`), approved
|
|
13
|
+
* by the person through the host's `ucp.approver`, paid with the host's
|
|
14
|
+
* `ucp.paymentInstruments`, completed once (`ucp.proposalStore`).
|
|
15
|
+
*
|
|
16
|
+
* @module protocols/ucp
|
|
17
|
+
*/
|
|
18
|
+
import { contribution, defineContributionPoint, definePlugin, } from '@datalayer/reactor';
|
|
19
|
+
import { ExtendedCheckoutResponseSchema } from '@ucp-js/sdk';
|
|
20
|
+
import { InMemoryProposalStore, digest, } from '../commerce';
|
|
21
|
+
import { DEFAULT_ORDER, ProtocolDescriptions, firstContribution, } from '../core';
|
|
22
|
+
export const UCP_VERSION = '2026-08-25';
|
|
23
|
+
export const SHOPPING_SERVICE = 'dev.ucp.shopping';
|
|
24
|
+
export const CHECKOUT_CAPABILITY = 'dev.ucp.shopping.checkout';
|
|
25
|
+
export const PROFILE_PATH = '/.well-known/ucp';
|
|
26
|
+
export const UCP_PLUGIN = 'agent-runtimes.protocols.ucp';
|
|
27
|
+
/** Each operation's action class (F-10), for Rules (R-05). */
|
|
28
|
+
export const UCP_ACTION_CLASSES = {
|
|
29
|
+
search: 'read',
|
|
30
|
+
proposeCompletion: 'read',
|
|
31
|
+
getCheckout: 'read',
|
|
32
|
+
createCheckout: 'write',
|
|
33
|
+
updateCheckout: 'write',
|
|
34
|
+
cancelCheckout: 'write',
|
|
35
|
+
complete: 'buy',
|
|
36
|
+
};
|
|
37
|
+
export { InMemoryProposalStore, proposalSummary, } from '../commerce';
|
|
38
|
+
export class UcpError extends Error {
|
|
39
|
+
}
|
|
40
|
+
export const UcpApprovers = defineContributionPoint('ucp.approver');
|
|
41
|
+
export const UcpPaymentInstruments = defineContributionPoint('ucp.paymentInstruments');
|
|
42
|
+
export const UcpProposalStores = defineContributionPoint('ucp.proposalStore');
|
|
43
|
+
export const UcpFulfillmentExtensions = defineContributionPoint('ucp.extension.fulfillment');
|
|
44
|
+
export const UcpDiscountExtensions = defineContributionPoint('ucp.extension.discount');
|
|
45
|
+
export const UcpBuyerConsentExtensions = defineContributionPoint('ucp.extension.buyer_consent');
|
|
46
|
+
const EXTENSION_POINTS = [
|
|
47
|
+
UcpFulfillmentExtensions,
|
|
48
|
+
UcpDiscountExtensions,
|
|
49
|
+
UcpBuyerConsentExtensions,
|
|
50
|
+
];
|
|
51
|
+
const UCP_POINTS = [
|
|
52
|
+
UcpApprovers,
|
|
53
|
+
UcpPaymentInstruments,
|
|
54
|
+
UcpProposalStores,
|
|
55
|
+
...EXTENSION_POINTS,
|
|
56
|
+
];
|
|
57
|
+
export const UcpPlugin = definePlugin({
|
|
58
|
+
name: UCP_PLUGIN,
|
|
59
|
+
version: '1.0.0',
|
|
60
|
+
description: `Universal Commerce Protocol ${UCP_VERSION}: shop, propose, buy once approved.`,
|
|
61
|
+
contributionPoints: UCP_POINTS,
|
|
62
|
+
contributes: [
|
|
63
|
+
contribution(ProtocolDescriptions, {
|
|
64
|
+
id: 'ucp',
|
|
65
|
+
name: 'Universal Commerce Protocol',
|
|
66
|
+
layer: 'commerce',
|
|
67
|
+
version: UCP_VERSION,
|
|
68
|
+
roles: ['shopper'],
|
|
69
|
+
status: 'partial',
|
|
70
|
+
specification: `https://ucp.dev/${UCP_VERSION}/specification/overview`,
|
|
71
|
+
plugin: UCP_PLUGIN,
|
|
72
|
+
points: UCP_POINTS.map(point => point.id),
|
|
73
|
+
}, { id: 'ucp', order: DEFAULT_ORDER }),
|
|
74
|
+
],
|
|
75
|
+
// One store per reactor, not one per module: a store is state.
|
|
76
|
+
register: ctx => {
|
|
77
|
+
ctx.contribute(UcpProposalStores, new InMemoryProposalStore(), {
|
|
78
|
+
id: 'memory',
|
|
79
|
+
order: DEFAULT_ORDER,
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
/** The capabilities in use with a business: checkout, and the extensions both support. */
|
|
84
|
+
export function negotiateCapabilities(profile, reactor) {
|
|
85
|
+
const offered = new Set(Object.keys(profile?.ucp?.capabilities ?? {}));
|
|
86
|
+
if (!offered.has(CHECKOUT_CAPABILITY)) {
|
|
87
|
+
throw new UcpError('the business offers no checkout');
|
|
88
|
+
}
|
|
89
|
+
const active = [CHECKOUT_CAPABILITY];
|
|
90
|
+
for (const point of EXTENSION_POINTS) {
|
|
91
|
+
const [first] = reactor.getContributions(point);
|
|
92
|
+
if (first && offered.has(first.value.capability)) {
|
|
93
|
+
active.push(first.value.capability);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return active;
|
|
97
|
+
}
|
|
98
|
+
/** The revision of what a checkout buys; the same digest as Python's `revision_of`. */
|
|
99
|
+
export function revisionOf(checkout) {
|
|
100
|
+
return digest({
|
|
101
|
+
id: checkout.id,
|
|
102
|
+
currency: checkout.currency,
|
|
103
|
+
line_items: (checkout.line_items ?? []).map((line) => ({
|
|
104
|
+
item: line.item?.id ?? null,
|
|
105
|
+
price: line.item?.price ?? null,
|
|
106
|
+
quantity: line.quantity,
|
|
107
|
+
})),
|
|
108
|
+
totals: (checkout.totals ?? []).map((total) => ({
|
|
109
|
+
type: total.type,
|
|
110
|
+
amount: total.amount,
|
|
111
|
+
})),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function readCheckout(body) {
|
|
115
|
+
if (body?.ucp?.status === 'error') {
|
|
116
|
+
const said = (body.messages ?? []).map((m) => m.content).join('; ');
|
|
117
|
+
throw new UcpError(`the business refused: ${said}`);
|
|
118
|
+
}
|
|
119
|
+
ExtendedCheckoutResponseSchema.parse(body);
|
|
120
|
+
return body;
|
|
121
|
+
}
|
|
122
|
+
/** What an agent may see of a checkout: no payment credential, no token. */
|
|
123
|
+
export function checkoutSummary(checkout) {
|
|
124
|
+
return {
|
|
125
|
+
id: checkout.id,
|
|
126
|
+
status: checkout.status,
|
|
127
|
+
currency: checkout.currency,
|
|
128
|
+
lines: (checkout.line_items ?? []).map((line) => ({
|
|
129
|
+
title: line.item?.title ?? line.item?.id,
|
|
130
|
+
quantity: line.quantity,
|
|
131
|
+
price: line.item?.price,
|
|
132
|
+
})),
|
|
133
|
+
totals: Object.fromEntries((checkout.totals ?? []).map((t) => [t.type, t.amount])),
|
|
134
|
+
messages: (checkout.messages ?? []).map((m) => m.content),
|
|
135
|
+
...(checkout.order ? { order: { id: checkout.order.id } } : {}),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/** A platform's client of one business, over the REST binding. */
|
|
139
|
+
export class UcpClient {
|
|
140
|
+
business;
|
|
141
|
+
agentProfile;
|
|
142
|
+
fetchImpl;
|
|
143
|
+
endpoint;
|
|
144
|
+
domain;
|
|
145
|
+
constructor(business, agentProfile, fetchImpl = (input, init) => fetch(input, init)) {
|
|
146
|
+
this.business = business;
|
|
147
|
+
this.agentProfile = agentProfile;
|
|
148
|
+
this.fetchImpl = fetchImpl;
|
|
149
|
+
this.domain = new URL(business).hostname;
|
|
150
|
+
}
|
|
151
|
+
headers(idempotencyKey) {
|
|
152
|
+
return {
|
|
153
|
+
'UCP-Agent': `profile="${this.agentProfile}"`,
|
|
154
|
+
'Request-Id': crypto.randomUUID(),
|
|
155
|
+
'Content-Type': 'application/json',
|
|
156
|
+
...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/** Read the business's profile: the version, shopping over REST, checkout. */
|
|
160
|
+
async discover() {
|
|
161
|
+
const answer = await this.fetchImpl(`${this.business.replace(/\/$/, '')}${PROFILE_PATH}`);
|
|
162
|
+
if (answer.status !== 200) {
|
|
163
|
+
throw new UcpError(`${PROFILE_PATH} answered ${answer.status}`);
|
|
164
|
+
}
|
|
165
|
+
const profile = await answer.json();
|
|
166
|
+
const ucp = profile.ucp ?? {};
|
|
167
|
+
const versions = new Set([
|
|
168
|
+
ucp.version,
|
|
169
|
+
...Object.keys(ucp.supported_versions ?? {}),
|
|
170
|
+
]);
|
|
171
|
+
if (!versions.has(UCP_VERSION)) {
|
|
172
|
+
throw new UcpError(`the business does not speak UCP ${UCP_VERSION}`);
|
|
173
|
+
}
|
|
174
|
+
const rest = (ucp.services?.[SHOPPING_SERVICE] ?? []).find((service) => service.transport === 'rest' && service.endpoint);
|
|
175
|
+
if (!rest) {
|
|
176
|
+
throw new UcpError('the business offers no shopping service over REST');
|
|
177
|
+
}
|
|
178
|
+
if (!ucp.capabilities?.[CHECKOUT_CAPABILITY]) {
|
|
179
|
+
throw new UcpError('the business offers no checkout');
|
|
180
|
+
}
|
|
181
|
+
this.endpoint = String(rest.endpoint).replace(/\/$/, '');
|
|
182
|
+
return profile;
|
|
183
|
+
}
|
|
184
|
+
async call(method, path, body, key) {
|
|
185
|
+
if (!this.endpoint) {
|
|
186
|
+
await this.discover();
|
|
187
|
+
}
|
|
188
|
+
const answer = await this.fetchImpl(`${this.endpoint}${path}`, {
|
|
189
|
+
method,
|
|
190
|
+
headers: this.headers(key),
|
|
191
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
192
|
+
});
|
|
193
|
+
if (answer.status === 409) {
|
|
194
|
+
throw new UcpError('the idempotency key was reused with another request');
|
|
195
|
+
}
|
|
196
|
+
if (answer.status >= 400) {
|
|
197
|
+
throw new UcpError(`${method} ${path} answered ${answer.status}`);
|
|
198
|
+
}
|
|
199
|
+
return answer.json();
|
|
200
|
+
}
|
|
201
|
+
async search(query, limit = 10) {
|
|
202
|
+
return (await this.call('POST', '/catalog/search', {
|
|
203
|
+
query,
|
|
204
|
+
pagination: { limit },
|
|
205
|
+
})).products;
|
|
206
|
+
}
|
|
207
|
+
async createCheckout(lines, buyer) {
|
|
208
|
+
return readCheckout(await this.call('POST', '/checkout-sessions', {
|
|
209
|
+
line_items: lines.map(line => ({
|
|
210
|
+
item: { id: line.id },
|
|
211
|
+
quantity: line.quantity ?? 1,
|
|
212
|
+
})),
|
|
213
|
+
...(buyer ? { buyer } : {}),
|
|
214
|
+
}, crypto.randomUUID()));
|
|
215
|
+
}
|
|
216
|
+
async updateCheckout(id, update) {
|
|
217
|
+
return readCheckout(await this.call('PUT', `/checkout-sessions/${id}`, update, crypto.randomUUID()));
|
|
218
|
+
}
|
|
219
|
+
async getCheckout(id) {
|
|
220
|
+
return readCheckout(await this.call('GET', `/checkout-sessions/${id}`));
|
|
221
|
+
}
|
|
222
|
+
async cancelCheckout(id) {
|
|
223
|
+
return readCheckout(await this.call('POST', `/checkout-sessions/${id}/cancel`, {}, crypto.randomUUID()));
|
|
224
|
+
}
|
|
225
|
+
/** The exact purchase a `ready_for_complete` checkout would make. */
|
|
226
|
+
async proposeCompletion(id) {
|
|
227
|
+
const checkout = await this.getCheckout(id);
|
|
228
|
+
if (checkout.status !== 'ready_for_complete') {
|
|
229
|
+
throw new UcpError(`the checkout is ${checkout.status}, not ready_for_complete`);
|
|
230
|
+
}
|
|
231
|
+
const total = checkout.totals.find(t => t.type === 'total');
|
|
232
|
+
if (!total) {
|
|
233
|
+
throw new UcpError('the checkout says no total');
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
id: crypto.randomUUID(),
|
|
237
|
+
merchant: this.domain,
|
|
238
|
+
checkoutId: checkout.id,
|
|
239
|
+
revision: await revisionOf(checkout),
|
|
240
|
+
currency: checkout.currency,
|
|
241
|
+
total: total.amount,
|
|
242
|
+
lines: checkout.line_items.map(line => ({
|
|
243
|
+
title: line.item.title ?? line.item.id,
|
|
244
|
+
quantity: line.quantity,
|
|
245
|
+
price: line.item.price ?? 0,
|
|
246
|
+
})),
|
|
247
|
+
expiresAt: checkout.expires_at,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
/** Complete an approved proposal, once: refused if unapproved, spent, changed or expired. */
|
|
251
|
+
async complete(proposal, reactor) {
|
|
252
|
+
if (proposal.expiresAt &&
|
|
253
|
+
new Date(proposal.expiresAt).getTime() <= Date.now()) {
|
|
254
|
+
throw new UcpError('the proposal has expired');
|
|
255
|
+
}
|
|
256
|
+
const approver = firstContribution(reactor, UcpApprovers, 'UCP approver');
|
|
257
|
+
const instruments = firstContribution(reactor, UcpPaymentInstruments, 'UCP payment instruments');
|
|
258
|
+
const store = firstContribution(reactor, UcpProposalStores, 'UCP proposal store');
|
|
259
|
+
// Spent before the person is asked: a proposal is put to them once, and a
|
|
260
|
+
// declined one is proposed again, never retried.
|
|
261
|
+
if (!(await store.take(proposal.id))) {
|
|
262
|
+
throw new UcpError('this proposal was used already');
|
|
263
|
+
}
|
|
264
|
+
if (!(await approver.approve(proposal))) {
|
|
265
|
+
throw new UcpError('the person did not approve this purchase');
|
|
266
|
+
}
|
|
267
|
+
const checkout = await this.getCheckout(proposal.checkoutId);
|
|
268
|
+
if (checkout.status !== 'ready_for_complete') {
|
|
269
|
+
throw new UcpError(`the checkout is ${checkout.status}, not ready_for_complete`);
|
|
270
|
+
}
|
|
271
|
+
if ((await revisionOf(checkout)) !== proposal.revision) {
|
|
272
|
+
throw new UcpError('the checkout changed since it was proposed: propose it again');
|
|
273
|
+
}
|
|
274
|
+
const instrument = await instruments.instrument(proposal, checkout.ucp?.payment_handlers ?? {});
|
|
275
|
+
return readCheckout(await this.call('POST', `/checkout-sessions/${proposal.checkoutId}/complete`, { payment: { instruments: [instrument] } }, proposal.id));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Bot Auth: an agent's requests signed, and verified
|
|
3
|
+
* (AGENT_PROTOCOLS PR-20 to PR-23).
|
|
4
|
+
*
|
|
5
|
+
* Implements `draft-ietf-webbotauth-httpsig-protocol-00` (2026-09-01) by
|
|
6
|
+
* wrapping Cloudflare's `web-bot-auth` 0.2.0, which reproduces the draft's
|
|
7
|
+
* Ed25519 vector E.2.1 byte for byte. What is added here is what an agent
|
|
8
|
+
* runtime needs around it:
|
|
9
|
+
*
|
|
10
|
+
* - a signer that is a callable the host owns (`protocols.httpSigner`; Vault
|
|
11
|
+
* transit in Datalayer): the key never reaches the runtime;
|
|
12
|
+
* - `signedFetch`, a `fetch` that signs every request it sends;
|
|
13
|
+
* - the key directory and its signed response (Appendix B);
|
|
14
|
+
* - verification with keys found by (agent URL, keyid), freshness and an
|
|
15
|
+
* atomic nonce check, answering `verified`, `invalid` or `unverified`.
|
|
16
|
+
*
|
|
17
|
+
* The Python package `agent_runtimes.protocols.web_bot_auth` is the same
|
|
18
|
+
* protocol, and both read `fixtures/vectors.json` (PR-51).
|
|
19
|
+
*
|
|
20
|
+
* @module protocols/web-bot-auth
|
|
21
|
+
*/
|
|
22
|
+
import { generateNonce, parseSignatureAgentHeader, type WebBotAlgorithm } from 'web-bot-auth';
|
|
23
|
+
import { type ReactorPlatformView } from '@datalayer/reactor';
|
|
24
|
+
/** The specification implemented. */
|
|
25
|
+
export declare const WEB_BOT_AUTH_SPECIFICATION = "draft-ietf-webbotauth-httpsig-protocol-00";
|
|
26
|
+
/** Where an agent's key directory is served. */
|
|
27
|
+
export declare const DIRECTORY_PATH = "/.well-known/http-message-signatures-directory";
|
|
28
|
+
/** The directory's media type. */
|
|
29
|
+
export declare const DIRECTORY_MEDIA_TYPE = "application/http-message-signatures-directory+json";
|
|
30
|
+
/** The tag of a directory response's signature. */
|
|
31
|
+
export declare const DIRECTORY_TAG = "http-message-signatures-directory";
|
|
32
|
+
export declare const TAG = "web-bot-auth";
|
|
33
|
+
export { generateNonce };
|
|
34
|
+
/** The base64url SHA-256 thumbprint of a public JWK (RFC 7638, RFC 8037): the draft's `keyid`. */
|
|
35
|
+
export declare function jwkThumbprint(jwk: JsonWebKey): Promise<string>;
|
|
36
|
+
/**
|
|
37
|
+
* Signs under one key the host holds. `sign` answers the raw signature
|
|
38
|
+
* (Ed25519's 64 bytes). The key is never passed to the runtime.
|
|
39
|
+
*/
|
|
40
|
+
export interface HttpSigner {
|
|
41
|
+
readonly keyid: string;
|
|
42
|
+
readonly algorithm: WebBotAlgorithm;
|
|
43
|
+
sign(data: Uint8Array): Promise<Uint8Array>;
|
|
44
|
+
}
|
|
45
|
+
/** A signer whose key is in this page or process: tests and examples only. */
|
|
46
|
+
export declare function localSigner(privateJwk: JsonWebKey): Promise<HttpSigner>;
|
|
47
|
+
/** A fresh Ed25519 key: its signer and its public JWK. Tests and examples only. */
|
|
48
|
+
/** A public JWK as a key directory lists it: its `kid` is its thumbprint. */
|
|
49
|
+
export type PublicJwk = JsonWebKey & {
|
|
50
|
+
kid: string;
|
|
51
|
+
};
|
|
52
|
+
export declare function generateLocalSigner(): Promise<{
|
|
53
|
+
signer: HttpSigner;
|
|
54
|
+
publicJwk: PublicJwk;
|
|
55
|
+
}>;
|
|
56
|
+
/** The public members of a JWK. */
|
|
57
|
+
export declare function publicJwkOf(jwk: JsonWebKey): JsonWebKey;
|
|
58
|
+
/** How a request is signed. */
|
|
59
|
+
export interface SignOptions {
|
|
60
|
+
/** Where the key directory is: the agent's origin, by default. */
|
|
61
|
+
agent: string;
|
|
62
|
+
/** `directory` (the default), `jwks_uri` or `cimd`. */
|
|
63
|
+
discovery?: 'directory' | 'jwks_uri' | 'cimd';
|
|
64
|
+
label?: string;
|
|
65
|
+
/** Seconds the signature lives; 60 by default, at most 24 hours. */
|
|
66
|
+
lifetime?: number;
|
|
67
|
+
created?: Date;
|
|
68
|
+
expires?: Date;
|
|
69
|
+
nonce?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Covered besides `@authority` and the `Signature-Agent` member (always
|
|
72
|
+
* covered): `@method` and `@path` by default.
|
|
73
|
+
*/
|
|
74
|
+
components?: string[];
|
|
75
|
+
}
|
|
76
|
+
/** The headers a signed request carries. */
|
|
77
|
+
export interface SignedHeaders {
|
|
78
|
+
'Signature-Agent': string;
|
|
79
|
+
'Signature-Input': string;
|
|
80
|
+
Signature: string;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Sign one request: the `Signature-Agent` member, `@authority`, and —
|
|
84
|
+
* narrower than the draft's minimum, as its §5.2 advises — `@method` and
|
|
85
|
+
* `@path`.
|
|
86
|
+
*/
|
|
87
|
+
export declare function signRequest(request: Request, signer: HttpSigner, options: SignOptions): Promise<SignedHeaders>;
|
|
88
|
+
/** A `fetch` that signs every request it sends. */
|
|
89
|
+
export declare function signedFetch(signer: HttpSigner, options: SignOptions, fetchImpl?: typeof fetch): typeof fetch;
|
|
90
|
+
/** The directory's body: public keys only, each `kid` its thumbprint. */
|
|
91
|
+
export declare function directoryBody(keys: JsonWebKey[]): Promise<string>;
|
|
92
|
+
/** The `Content-Digest` of a body, SHA-256 (RFC 9530). */
|
|
93
|
+
export declare function contentDigest(body: string | Uint8Array): Promise<string>;
|
|
94
|
+
/**
|
|
95
|
+
* Sign a directory response with every key it lists (Appendix B): the
|
|
96
|
+
* signature covers `"@authority";req` and `content-digest`.
|
|
97
|
+
*/
|
|
98
|
+
export declare function signDirectoryResponse(body: string, authorityUrl: string, signers: HttpSigner[], options?: {
|
|
99
|
+
created?: number;
|
|
100
|
+
expires?: number;
|
|
101
|
+
lifetime?: number;
|
|
102
|
+
}): Promise<Record<string, string>>;
|
|
103
|
+
/** What verifying a request found. */
|
|
104
|
+
export interface Verification {
|
|
105
|
+
outcome: 'verified' | 'invalid' | 'unverified';
|
|
106
|
+
reason?: string;
|
|
107
|
+
agent?: string;
|
|
108
|
+
keyid?: string;
|
|
109
|
+
created?: Date;
|
|
110
|
+
expires?: Date;
|
|
111
|
+
nonce?: string;
|
|
112
|
+
}
|
|
113
|
+
/** Where nonces are remembered until their signature expires. */
|
|
114
|
+
export interface NonceStore {
|
|
115
|
+
checkAndRecord(scope: string, nonce: string, until: Date): Promise<boolean>;
|
|
116
|
+
}
|
|
117
|
+
/** A nonce store for one page or process. */
|
|
118
|
+
export declare class InMemoryNonceStore implements NonceStore {
|
|
119
|
+
private seen;
|
|
120
|
+
checkAndRecord(scope: string, nonce: string, until: Date): Promise<boolean>;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* How an agent's keys are found for one `Signature-Agent` type: each type is
|
|
124
|
+
* an extension of the protocol, the point `web_bot_auth.extension.<type>`.
|
|
125
|
+
* Throws when the keys cannot be read.
|
|
126
|
+
*/
|
|
127
|
+
export interface KeyDiscovery {
|
|
128
|
+
keys(agent: string): Promise<JsonWebKey[]>;
|
|
129
|
+
}
|
|
130
|
+
/** `type=directory`: the origin's well-known directory, its Content-Digest checked. */
|
|
131
|
+
export declare function directoryDiscovery(fetchImpl?: typeof fetch, insecureHosts?: string[]): KeyDiscovery;
|
|
132
|
+
/** `type=jwks_uri`: a JWK Set at the URL itself. */
|
|
133
|
+
export declare function jwksUriDiscovery(fetchImpl?: typeof fetch, insecureHosts?: string[]): KeyDiscovery;
|
|
134
|
+
/** Public keys of an agent, by thumbprint; `undefined` when they could not be read. */
|
|
135
|
+
export type KeyResolver = (agent: string, discovery: string) => Promise<Map<string, JsonWebKey> | undefined>;
|
|
136
|
+
/** A resolver over one discovery per `Signature-Agent` type; a type with none answers nothing. */
|
|
137
|
+
export declare function keyResolver(discoveries: Record<string, KeyDiscovery>): KeyResolver;
|
|
138
|
+
/** The two discoveries this package implements, over one fetch. */
|
|
139
|
+
export declare function directoryResolver(fetchImpl?: typeof fetch, insecureHosts?: string[]): KeyResolver;
|
|
140
|
+
export declare const WEB_BOT_AUTH_PLUGIN = "agent-runtimes.protocols.web-bot-auth";
|
|
141
|
+
/** The host's signer: no default, the key is the host's. */
|
|
142
|
+
export declare const WebBotAuthSigners: import("@datalayer/reactor").ContributionPoint<HttpSigner>;
|
|
143
|
+
export declare const WebBotAuthNonceStores: import("@datalayer/reactor").ContributionPoint<NonceStore>;
|
|
144
|
+
export declare const WebBotAuthDirectoryDiscoveries: import("@datalayer/reactor").ContributionPoint<KeyDiscovery>;
|
|
145
|
+
export declare const WebBotAuthJwksUriDiscoveries: import("@datalayer/reactor").ContributionPoint<KeyDiscovery>;
|
|
146
|
+
export declare const WebBotAuthCimdDiscoveries: import("@datalayer/reactor").ContributionPoint<KeyDiscovery>;
|
|
147
|
+
export declare const WebBotAuthPlugin: import("@datalayer/reactor").ReactorPlugin<Record<string, never>, unknown, unknown>;
|
|
148
|
+
/** The host's signer; throws when the host contributed none. */
|
|
149
|
+
export declare function signerOf(reactor: ReactorPlatformView): HttpSigner;
|
|
150
|
+
/** A resolver over the discoveries contributed: one per type that has one. */
|
|
151
|
+
export declare function resolverOf(reactor: ReactorPlatformView): KeyResolver;
|
|
152
|
+
/**
|
|
153
|
+
* Verify the Web Bot Auth signature of one request. Keys are found by the
|
|
154
|
+
* pair (the covered `Signature-Agent` URL, `keyid`), never by `keyid` alone.
|
|
155
|
+
*/
|
|
156
|
+
export declare function verifyRequest(request: Request, resolveKeys: KeyResolver, options?: {
|
|
157
|
+
nonceStore?: NonceStore;
|
|
158
|
+
now?: Date;
|
|
159
|
+
clockSkew?: number;
|
|
160
|
+
}): Promise<Verification>;
|
|
161
|
+
export { parseSignatureAgentHeader };
|