@datalayer/agent-runtimes 1.5.8 → 1.5.10

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.
@@ -106,9 +106,9 @@ export declare function uiProblems(app: Pick<AppSpec, 'ui' | 'enabled'>): string
106
106
  * What is wrong with the protocols an application says it speaks
107
107
  * (AGENT_PROTOCOLS PS-02), as agentspecs says it: an id the catalogue does not
108
108
  * know or said twice, a planned protocol while it is offered, and a `pap`,
109
- * `pact` or `a2a` block configuring a protocol it does not list.
109
+ * `pact`, `a2a` or `signed_fetch` block configuring a protocol it does not list.
110
110
  */
111
- export declare function protocolsProblems(app: Pick<AppSpec, 'protocols' | 'enabled' | 'pap' | 'pact' | 'a2a' | 'commerce'>): string[];
111
+ export declare function protocolsProblems(app: Pick<AppSpec, 'protocols' | 'enabled' | 'pap' | 'pact' | 'a2a' | 'commerce' | 'signed_fetch'>): string[];
112
112
  /**
113
113
  * An application as the document its file holds.
114
114
  *
@@ -205,6 +205,8 @@ const KNOWN_KEYS = [
205
205
  'pap',
206
206
  'pact',
207
207
  'commerce',
208
+ 'signed_fetch',
209
+ 'pair',
208
210
  'a2a',
209
211
  'rules',
210
212
  'permissions',
@@ -304,12 +306,47 @@ function parsePap(data) {
304
306
  operations: records(company.operations).map(operation => ({
305
307
  action: text(operation.action),
306
308
  tool: text(operation.tool),
309
+ ...(isData(operation.allows)
310
+ ? { allows: parsePapStanding(operation.allows) }
311
+ : {}),
307
312
  summary: text(operation.summary),
308
313
  userApprovalRequired: flag(operation.user_approval_required, true),
309
314
  })),
310
315
  },
311
316
  };
312
317
  }
318
+ /** A standing permission an operation gives (PAP_DATALAYER R-07), as agentspecs reads it. */
319
+ function parsePapStanding(data) {
320
+ return {
321
+ connection: text(data.connection),
322
+ action: oneOf(data.action, ['write', 'buy'], 'buy'),
323
+ limits: records(data.limits).map(limit => ({
324
+ path: text(limit.path),
325
+ ...(typeof limit.maximum === 'string'
326
+ ? { maximum: text(limit.maximum) }
327
+ : {}),
328
+ ...(typeof limit.allowed === 'string'
329
+ ? { allowed: text(limit.allowed) }
330
+ : {}),
331
+ })),
332
+ uses: text(data.uses),
333
+ days: text(data.days),
334
+ };
335
+ }
336
+ /** A standing permission as its document says it: every field, none has a default. */
337
+ function dumpPapStanding(standing) {
338
+ return {
339
+ connection: standing.connection,
340
+ action: standing.action,
341
+ limits: standing.limits.map(limit => ({
342
+ path: limit.path,
343
+ ...(limit.maximum !== undefined ? { maximum: limit.maximum } : {}),
344
+ ...(limit.allowed !== undefined ? { allowed: limit.allowed } : {}),
345
+ })),
346
+ uses: standing.uses,
347
+ days: standing.days,
348
+ };
349
+ }
313
350
  /** Personal Agent Protocol as its document says it, nothing at its default. */
314
351
  function dumpPap(pap) {
315
352
  if (!pap)
@@ -337,7 +374,11 @@ function dumpPap(pap) {
337
374
  .list('registered_agents', pap.company.registeredAgents)
338
375
  .list('operations', pap.company.operations.map(operation => new Writer()
339
376
  .text('action', operation.action, '\u0000')
340
- .text('tool', operation.tool, '\u0000')
377
+ // An operation that allows a standing permission calls no tool (R-07).
378
+ .text('tool', operation.tool, operation.allows ? '' : '\u0000')
379
+ .present('allows', operation.allows
380
+ ? dumpPapStanding(operation.allows)
381
+ : undefined)
341
382
  .text('summary', operation.summary, '\u0000')
342
383
  .value('user_approval_required', operation.userApprovalRequired, true).data)).data).data;
343
384
  }
@@ -404,6 +445,35 @@ function dumpCommerce(commerce) {
404
445
  }
405
446
  : {}).data)).data;
406
447
  }
448
+ /** Signed fetch (AGENT_PROTOCOLS EX-11): the exact sites it reads pages of, each request signed. */
449
+ function parseSignedFetch(data) {
450
+ return {
451
+ sites: records(data.sites).map(site => ({
452
+ id: text(site.id),
453
+ site: text(site.site),
454
+ })),
455
+ };
456
+ }
457
+ /** Signed fetch as its document says it, nothing at its default. */
458
+ function dumpSignedFetch(signedFetch) {
459
+ if (!signedFetch)
460
+ return {};
461
+ return new Writer().list('sites', signedFetch.sites.map(site => new Writer()
462
+ .text('id', site.id, '\u0000')
463
+ .text('site', site.site, '\u0000').data)).data;
464
+ }
465
+ /** One of a pair of examples (AGENT_PROTOCOLS PR-13): its id and its side, or a problem. */
466
+ function parsePair(data) {
467
+ const id = text(data.id);
468
+ const role = text(data.role);
469
+ if (!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(id)) {
470
+ return `A pair's id is lower-case letters, digits and hyphens: ${JSON.stringify(id)}.`;
471
+ }
472
+ if (role !== 'business' && role !== 'personal_agent') {
473
+ return `One of a pair is its business or its personal_agent, not ${JSON.stringify(role)}.`;
474
+ }
475
+ return { id, role };
476
+ }
407
477
  /** PACT as its document says it, nothing at its default. */
408
478
  function dumpPact(pact) {
409
479
  if (!pact)
@@ -1008,6 +1078,18 @@ export function parseAppspec(document) {
1008
1078
  if (isData(data.commerce)) {
1009
1079
  app.commerce = parseCommerce(data.commerce);
1010
1080
  }
1081
+ if (isData(data.signed_fetch)) {
1082
+ app.signed_fetch = parseSignedFetch(data.signed_fetch);
1083
+ }
1084
+ if (isData(data.pair)) {
1085
+ const pair = parsePair(data.pair);
1086
+ if (typeof pair === 'string') {
1087
+ problems.push(pair);
1088
+ }
1089
+ else {
1090
+ app.pair = pair;
1091
+ }
1092
+ }
1011
1093
  if (isData(data.a2a)) {
1012
1094
  app.a2a = parseA2A(data.a2a);
1013
1095
  }
@@ -1042,7 +1124,7 @@ export function uiProblems(app) {
1042
1124
  * What is wrong with the protocols an application says it speaks
1043
1125
  * (AGENT_PROTOCOLS PS-02), as agentspecs says it: an id the catalogue does not
1044
1126
  * know or said twice, a planned protocol while it is offered, and a `pap`,
1045
- * `pact` or `a2a` block configuring a protocol it does not list.
1127
+ * `pact`, `a2a` or `signed_fetch` block configuring a protocol it does not list.
1046
1128
  */
1047
1129
  export function protocolsProblems(app) {
1048
1130
  const problems = [];
@@ -1066,6 +1148,7 @@ export function protocolsProblems(app) {
1066
1148
  pact: Boolean(app.pact &&
1067
1149
  (app.pact.connections.length > 0 || app.pact.business?.enabled)),
1068
1150
  a2a: Boolean(app.a2a && app.a2a.extensions.length > 0),
1151
+ signed_fetch: Boolean(app.signed_fetch && app.signed_fetch.sites.length > 0),
1069
1152
  };
1070
1153
  for (const spec of Object.values(PROTOCOL_CATALOGUE)) {
1071
1154
  const block = spec.appspecBlock;
@@ -1347,6 +1430,8 @@ export function dumpAppspec(app) {
1347
1430
  .part('pap', dumpPap(app.pap))
1348
1431
  .part('pact', dumpPact(app.pact))
1349
1432
  .part('commerce', dumpCommerce(app.commerce))
1433
+ .part('signed_fetch', dumpSignedFetch(app.signed_fetch))
1434
+ .part('pair', app.pair ? { id: app.pair.id, role: app.pair.role } : {})
1350
1435
  .part('a2a', dumpA2A(app.a2a))
1351
1436
  .list('rules', app.rules.map(rule => ({
1352
1437
  action: rule.action,
@@ -0,0 +1,16 @@
1
+ /**
2
+ * What no file the embed ships may hold (PAP_DATALAYER B-02): the PAP SDK's
3
+ * emitted-bundle test (`src/__tests__/canaries.test.mjs`, `EMBEDDED_SECRETS`)
4
+ * extended to the embed, a browser artifact anybody's page loads. Its build
5
+ * fails on a file holding one (`vite.embed.config.ts`), naming the kind and
6
+ * the file, never what matched.
7
+ *
8
+ * Found 2026-10-13: the embed then served at datalayer.ai held the builder's
9
+ * Datalayer API key, because `import.meta.env` read whole (`env?.VITE_…`) is
10
+ * every `VITE_` variable of the environment it was built in.
11
+ *
12
+ * @module apps/embed/embeddedSecrets
13
+ */
14
+ export declare const EMBEDDED_SECRETS: ReadonlyArray<readonly [string, RegExp]>;
15
+ /** The kinds of secret a file's text holds, by name: empty when none. */
16
+ export declare function secretsIn(text: string): string[];
@@ -0,0 +1,34 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * What no file the embed ships may hold (PAP_DATALAYER B-02): the PAP SDK's
7
+ * emitted-bundle test (`src/__tests__/canaries.test.mjs`, `EMBEDDED_SECRETS`)
8
+ * extended to the embed, a browser artifact anybody's page loads. Its build
9
+ * fails on a file holding one (`vite.embed.config.ts`), naming the kind and
10
+ * the file, never what matched.
11
+ *
12
+ * Found 2026-10-13: the embed then served at datalayer.ai held the builder's
13
+ * Datalayer API key, because `import.meta.env` read whole (`env?.VITE_…`) is
14
+ * every `VITE_` variable of the environment it was built in.
15
+ *
16
+ * @module apps/embed/embeddedSecrets
17
+ */
18
+ export const EMBEDDED_SECRETS = [
19
+ // Key material after the header: a library may name the header to refuse it.
20
+ [
21
+ 'a PEM private key',
22
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----(?:\\n|\s)*[A-Za-z0-9+/=]{40,}/,
23
+ ],
24
+ ['a private JWK member', /["']d["']\s*:\s*["'][A-Za-z0-9_-]{32,}["']/],
25
+ [
26
+ 'a compact JWT',
27
+ /eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/,
28
+ ],
29
+ ['a Bearer or DPoP token', /(?:Bearer|DPoP) [A-Za-z0-9_-]{20,}/],
30
+ ];
31
+ /** The kinds of secret a file's text holds, by name: empty when none. */
32
+ export function secretsIn(text) {
33
+ return EMBEDDED_SECRETS.filter(([, pattern]) => pattern.test(text)).map(([kind]) => kind);
34
+ }
@@ -0,0 +1,73 @@
1
+ import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
2
+ import type { CallToolResult, GetPromptResult, ReadResourceResult, Tool } from '@modelcontextprotocol/sdk/types.js';
3
+ /** A listed tool as a client reads it. */
4
+ export interface McpToolReading {
5
+ name: string;
6
+ title: string | null;
7
+ description: string | null;
8
+ /** Its parameters' names, sorted. */
9
+ parameters: string[];
10
+ /** Those it requires, sorted. */
11
+ required: string[];
12
+ /** Whether it says an output schema (answers structured content). */
13
+ structured: boolean;
14
+ readOnly: boolean;
15
+ destructive: boolean;
16
+ idempotent: boolean;
17
+ openWorld: boolean;
18
+ }
19
+ /** A tool's answer as a client reads it. */
20
+ export interface McpCallReading {
21
+ isError: boolean;
22
+ /** Its text parts, joined by a newline. */
23
+ text: string;
24
+ structured: Record<string, unknown> | null;
25
+ }
26
+ /** A text resource's contents as a client reads them. */
27
+ export interface McpResourceReading {
28
+ uri: string;
29
+ mimeType: string | null;
30
+ text: string;
31
+ }
32
+ /** A prompt's message as a client reads it. */
33
+ export interface McpPromptReading {
34
+ role: 'user' | 'assistant';
35
+ text: string;
36
+ }
37
+ /** A listed tool: its parameters, whether it answers structured, its hints. */
38
+ export declare function toolReading(tool: Tool): McpToolReading;
39
+ /** A tool's answer: refused or not, its text parts joined, its structured content. */
40
+ export declare function callReading(result: CallToolResult): McpCallReading;
41
+ /** A resource's text contents; a binary one is refused, never decoded here. */
42
+ export declare function resourceReading(result: ReadResourceResult): McpResourceReading[];
43
+ /** A prompt's messages as text; a message of another kind is refused. */
44
+ export declare function promptReading(result: GetPromptResult): McpPromptReading[];
45
+ /** Where an `McpClient` connects: a Streamable HTTP endpoint, or any transport. */
46
+ export type McpServerAddress = {
47
+ url: string;
48
+ /** Headers sent with every request (a bearer, for one). */
49
+ headers?: Record<string, string>;
50
+ } | {
51
+ transport: Transport;
52
+ };
53
+ /**
54
+ * An MCP client: connects, lists and calls tools, reads resources, gets
55
+ * prompts, and answers each as the readings above. The SDK's `Client` does
56
+ * the protocol (the initialize handshake, the version negotiated, a
57
+ * structured answer checked against the tool's output schema).
58
+ */
59
+ export declare class McpClient {
60
+ private readonly client;
61
+ private readonly agreed;
62
+ private constructor();
63
+ /** Connected to `server`, said to it as `name` at `version`. */
64
+ static connect(server: McpServerAddress, name?: string, version?: string): Promise<McpClient>;
65
+ /** The protocol version agreed with the server. */
66
+ get protocolVersion(): string;
67
+ /** Every tool the server lists, through its pages. */
68
+ listTools(): Promise<McpToolReading[]>;
69
+ callTool(name: string, args: Record<string, unknown>): Promise<McpCallReading>;
70
+ readResource(uri: string): Promise<McpResourceReading[]>;
71
+ getPrompt(name: string, args: Record<string, string>): Promise<McpPromptReading[]>;
72
+ close(): Promise<void>;
73
+ }
@@ -0,0 +1,136 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * An MCP client in TypeScript, on the official `@modelcontextprotocol/sdk`
7
+ * (AGENT_PROTOCOLS PR-51, decided 2026-10-13), and what it reads from a
8
+ * server's answers — the twin of `agent_runtimes.protocols.mcp.reading`.
9
+ *
10
+ * The readings are the plain shape both languages agree on, held to
11
+ * `agent_runtimes/protocols/mcp/fixtures/session.json` by the pytest and the
12
+ * vitest. A tool's missing annotations are read with the specification's
13
+ * defaults (`readOnlyHint` false, `destructiveHint` true, `idempotentHint`
14
+ * false, `openWorldHint` true), so a server that says nothing is never read
15
+ * as harmless; `destructiveHint` means something only when the tool is not
16
+ * read-only, so a read-only tool is never destructive.
17
+ *
18
+ * @module protocols/mcp/client
19
+ */
20
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
21
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
22
+ const sorted = (values) => [...values].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
23
+ /** A listed tool: its parameters, whether it answers structured, its hints. */
24
+ export function toolReading(tool) {
25
+ const hints = tool.annotations;
26
+ const readOnly = hints?.readOnlyHint === true;
27
+ return {
28
+ name: tool.name,
29
+ title: tool.title ?? null,
30
+ description: tool.description ?? null,
31
+ parameters: sorted(Object.keys(tool.inputSchema.properties ?? {})),
32
+ required: sorted(tool.inputSchema.required ?? []),
33
+ structured: tool.outputSchema !== undefined,
34
+ readOnly,
35
+ destructive: !readOnly && hints?.destructiveHint !== false,
36
+ idempotent: hints?.idempotentHint === true,
37
+ openWorld: hints?.openWorldHint !== false,
38
+ };
39
+ }
40
+ /** A tool's answer: refused or not, its text parts joined, its structured content. */
41
+ export function callReading(result) {
42
+ return {
43
+ isError: result.isError === true,
44
+ text: result.content
45
+ .flatMap(part => (part.type === 'text' ? [part.text] : []))
46
+ .join('\n'),
47
+ structured: result.structuredContent ?? null,
48
+ };
49
+ }
50
+ /** A resource's text contents; a binary one is refused, never decoded here. */
51
+ export function resourceReading(result) {
52
+ return result.contents.map(part => {
53
+ if (!('text' in part) || typeof part.text !== 'string') {
54
+ throw new Error(`${part.uri} is not text.`);
55
+ }
56
+ return { uri: part.uri, mimeType: part.mimeType ?? null, text: part.text };
57
+ });
58
+ }
59
+ /** A prompt's messages as text; a message of another kind is refused. */
60
+ export function promptReading(result) {
61
+ return result.messages.map(message => {
62
+ if (message.content.type !== 'text') {
63
+ throw new Error(`A ${message.role} message is not text.`);
64
+ }
65
+ return { role: message.role, text: message.content.text };
66
+ });
67
+ }
68
+ /**
69
+ * An MCP client: connects, lists and calls tools, reads resources, gets
70
+ * prompts, and answers each as the readings above. The SDK's `Client` does
71
+ * the protocol (the initialize handshake, the version negotiated, a
72
+ * structured answer checked against the tool's output schema).
73
+ */
74
+ export class McpClient {
75
+ client;
76
+ agreed;
77
+ constructor(client, agreed) {
78
+ this.client = client;
79
+ this.agreed = agreed;
80
+ }
81
+ /** Connected to `server`, said to it as `name` at `version`. */
82
+ static async connect(server, name = 'agent-runtimes', version = '1.0.0') {
83
+ const transport = 'transport' in server
84
+ ? server.transport
85
+ : new StreamableHTTPClientTransport(new URL(server.url), {
86
+ requestInit: server.headers
87
+ ? { headers: server.headers }
88
+ : undefined,
89
+ });
90
+ // The SDK hands the version agreed at initialize to the transport alone.
91
+ let agreed;
92
+ const setProtocolVersion = transport.setProtocolVersion?.bind(transport);
93
+ transport.setProtocolVersion = (protocolVersion) => {
94
+ agreed = protocolVersion;
95
+ setProtocolVersion?.(protocolVersion);
96
+ };
97
+ const client = new Client({ name, version });
98
+ await client.connect(transport);
99
+ return new McpClient(client, () => agreed);
100
+ }
101
+ /** The protocol version agreed with the server. */
102
+ get protocolVersion() {
103
+ const version = this.agreed();
104
+ if (!version) {
105
+ throw new Error('The MCP client is not connected.');
106
+ }
107
+ return version;
108
+ }
109
+ /** Every tool the server lists, through its pages. */
110
+ async listTools() {
111
+ const tools = [];
112
+ let cursor;
113
+ do {
114
+ const page = await this.client.listTools(cursor ? { cursor } : {});
115
+ tools.push(...page.tools.map(toolReading));
116
+ cursor = page.nextCursor;
117
+ } while (cursor);
118
+ return tools;
119
+ }
120
+ async callTool(name, args) {
121
+ const result = await this.client.callTool({ name, arguments: args });
122
+ if (!Array.isArray(result.content)) {
123
+ throw new Error(`${name} answered in the form before MCP 2024-11-05.`);
124
+ }
125
+ return callReading(result);
126
+ }
127
+ async readResource(uri) {
128
+ return resourceReading(await this.client.readResource({ uri }));
129
+ }
130
+ async getPrompt(name, args) {
131
+ return promptReading(await this.client.getPrompt({ name, arguments: args }));
132
+ }
133
+ async close() {
134
+ await this.client.close();
135
+ }
136
+ }
@@ -2,3 +2,4 @@ export declare const MCP_PLUGIN = "agent-runtimes.protocols.mcp";
2
2
  /** The MCP specification version of the `@modelcontextprotocol/sdk` installed (1.32: `LATEST_PROTOCOL_VERSION`); Python's `mcp` 2.1 speaks 2026-07-28. */
3
3
  export declare const MCP_SPECIFICATION_VERSION = "2025-11-25";
4
4
  export declare const McpPlugin: import("@datalayer/reactor").ReactorPlugin<Record<string, never>, unknown, unknown>;
5
+ export * from './client';
@@ -7,7 +7,8 @@
7
7
  * declares no point yet: MCP's optional capabilities and extensions become
8
8
  * `mcp.extension.<name>` points when the MCP client and server read them
9
9
  * from contributions (AGENT_PROTOCOLS PR-01). The twin of
10
- * `agent_runtimes.protocols.mcp`.
10
+ * `agent_runtimes.protocols.mcp`. Its client, on the official
11
+ * `@modelcontextprotocol/sdk`, is `./client` (PR-51).
11
12
  *
12
13
  * @module protocols/mcp
13
14
  */
@@ -36,3 +37,4 @@ export const McpPlugin = definePlugin({
36
37
  }, { id: 'mcp', order: DEFAULT_ORDER }),
37
38
  ],
38
39
  });
40
+ export * from './client';
@@ -0,0 +1,56 @@
1
+ import { type HttpSigner, type NonceStore } from './index';
2
+ /** Draft until Visa confirms it. */
3
+ export declare const TAP_STATUS = "draft";
4
+ /** Where the profile was read, 2026-10-13. */
5
+ export declare const TAP_SOURCES: readonly ["https://developer.visa.com/capabilities/trusted-agent-protocol/trusted-agent-protocol-specifications", "https://developer.visa.com/capabilities/trusted-agent-protocol/overview", "https://github.com/visa/trusted-agent-protocol"];
6
+ /** Visa's key directory, as its page says it. */
7
+ export declare const VISA_JWKS = "https://mcp.visa.com/.well-known/jwks";
8
+ export declare const TAP_BROWSE_TAG = "agent-browser-auth";
9
+ export declare const TAP_PAY_TAG = "agent-payer-auth";
10
+ export type TapIntent = 'browse' | 'pay';
11
+ export declare const TAP_TAGS: Record<TapIntent, string>;
12
+ export declare const TAP_REQUIRED_COMPONENTS: readonly ["@authority", "@path"];
13
+ export declare const TAP_REQUIRED_PARAMS: readonly ["created", "keyid", "alg", "expires", "nonce", "tag"];
14
+ /** `alg` as Visa writes it. */
15
+ export declare const TAP_ALG = "Ed25519";
16
+ /** The longest window, and how long a nonce is remembered. */
17
+ export declare const TAP_MAX_WINDOW_SECONDS: number;
18
+ export interface TapSignOptions {
19
+ intent: TapIntent;
20
+ label?: string;
21
+ /** Covered: at least `@authority` and `@path` (the default). */
22
+ components?: string[];
23
+ /** Unix seconds; now by default. */
24
+ created?: number;
25
+ /** Seconds, at most 8 minutes (the default). */
26
+ lifetime?: number;
27
+ nonce?: string;
28
+ }
29
+ export interface TapSignedHeaders {
30
+ 'Signature-Input': string;
31
+ Signature: string;
32
+ }
33
+ /** What verifying a TAP signature found; `reason` when it does not hold. */
34
+ export interface TapVerification {
35
+ verified: boolean;
36
+ reason?: string;
37
+ intent?: TapIntent;
38
+ keyid?: string;
39
+ nonce?: string;
40
+ }
41
+ /** Sign one request as a TAP agent browsing or paying, under an Ed25519 key. */
42
+ export declare function signTapRequest(request: {
43
+ method: string;
44
+ url: string;
45
+ }, signer: HttpSigner, options: TapSignOptions): Promise<TapSignedHeaders>;
46
+ /**
47
+ * Verify the TAP signature of one request against Ed25519 keys known by
48
+ * `keyid` (Visa's directory, or a registry's). A request with no TAP tag is
49
+ * no trusted agent's; the merchant decides what to do with it.
50
+ */
51
+ export declare function verifyTapRequest(request: Request, keys: Map<string, JsonWebKey>, options: {
52
+ nonceStore: NonceStore;
53
+ now?: number;
54
+ }): Promise<TapVerification>;
55
+ /** The `keyid` of an Ed25519 public JWK, as a TAP key list is indexed when it says none. */
56
+ export declare function tapKeys(jwks: JsonWebKey[]): Promise<Map<string, JsonWebKey>>;