@agent-cards/sdk 0.23.28 → 0.24.0

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.
Files changed (97) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/PREFLIGHT.md +7 -1
  3. package/README.md +137 -99
  4. package/dist/aci.d.ts +52 -22
  5. package/dist/aci.js +79 -15
  6. package/dist/adyen-merchant-hosted.generated.d.ts +1 -1
  7. package/dist/adyen-merchant-hosted.generated.js +459 -21
  8. package/dist/agent-amount.generated.js +1 -1
  9. package/dist/agentcard.d.ts +4 -5
  10. package/dist/agentcard.js +4 -5
  11. package/dist/amadeus.d.ts +9 -3
  12. package/dist/amadeus.js +33 -4
  13. package/dist/api-module.d.ts +1 -7
  14. package/dist/api-module.js +3 -5
  15. package/dist/{voice-retry.js → api-retry.js} +1 -1
  16. package/dist/api-types.d.ts +53 -0
  17. package/dist/{voice-types.js → api-types.js} +7 -2
  18. package/dist/approvals.d.ts +166 -0
  19. package/dist/approvals.js +213 -0
  20. package/dist/buy.d.ts +20 -101
  21. package/dist/buy.js +19 -74
  22. package/dist/capabilities.generated.js +1 -1
  23. package/dist/card-fields.generated.js +1398 -106
  24. package/dist/cdp.d.ts +3 -13
  25. package/dist/cdp.js +231 -175
  26. package/dist/client.d.ts +104 -22
  27. package/dist/client.js +215 -40
  28. package/dist/creditguard.generated.d.ts +4 -4
  29. package/dist/datatrans.generated.js +22 -1
  30. package/dist/elavon-converge.generated.js +5 -3
  31. package/dist/hold.d.ts +8 -0
  32. package/dist/hold.js +43 -2
  33. package/dist/hosted-entry.js +2 -0
  34. package/dist/index.d.ts +9 -15
  35. package/dist/index.js +6 -10
  36. package/dist/lifecycle.d.ts +9 -61
  37. package/dist/lifecycle.js +24 -10
  38. package/dist/merchant-card-endpoints.generated.d.ts +6 -1
  39. package/dist/merchant-card-endpoints.generated.js +344 -19
  40. package/dist/merchant-hosted.d.ts +8 -1
  41. package/dist/nike-order.d.ts +51 -0
  42. package/dist/nike-order.js +81 -0
  43. package/dist/oceanpayment.d.ts +38 -0
  44. package/dist/oceanpayment.js +70 -0
  45. package/dist/paddle-tokenex.generated.js +105 -10
  46. package/dist/payplug.d.ts +41 -0
  47. package/dist/payplug.js +71 -0
  48. package/dist/payrails.generated.js +27 -7
  49. package/dist/pre-claim.d.ts +9 -0
  50. package/dist/pre-claim.js +86 -7
  51. package/dist/preflight-capabilities.generated.d.ts +0 -4
  52. package/dist/preflight-capabilities.generated.js +0 -5
  53. package/dist/preflight-catalog.json +46 -5
  54. package/dist/preflight-playwright.d.ts +2 -2
  55. package/dist/preflight-playwright.js +63 -14
  56. package/dist/preflight-schemas.json +4 -0
  57. package/dist/preflight.generated.d.ts +0 -4
  58. package/dist/preflight.generated.js +24 -1
  59. package/dist/prepared-processor.d.ts +18 -5
  60. package/dist/prepared-processor.js +48 -8
  61. package/dist/primer.generated.js +1418 -120
  62. package/dist/processout.generated.d.ts +61 -0
  63. package/dist/processout.generated.js +577 -0
  64. package/dist/requests.js +1 -1
  65. package/dist/shiji-sts.generated.js +105 -10
  66. package/dist/stripe-checkout.generated.js +1405 -234
  67. package/dist/substitutions.generated.js +460 -26
  68. package/dist/tap-payments.generated.js +105 -10
  69. package/dist/twoc2p.generated.js +105 -10
  70. package/dist/url-masks/bluesnap.d.ts +2 -0
  71. package/dist/url-masks/bluesnap.js +15 -0
  72. package/dist/url-masks/oceanpayment.d.ts +2 -0
  73. package/dist/url-masks/oceanpayment.js +13 -0
  74. package/dist/url-masks/payplug.d.ts +2 -0
  75. package/dist/url-masks/payplug.js +13 -0
  76. package/dist/url-masks/vtex_payments.d.ts +2 -0
  77. package/dist/url-masks/vtex_payments.js +12 -0
  78. package/dist/url-masks.generated.d.ts +1 -1
  79. package/dist/url-masks.generated.js +6 -2
  80. package/dist/verifone.generated.js +42 -2
  81. package/dist/vtex-payments.generated.d.ts +127 -0
  82. package/dist/vtex-payments.generated.js +544 -0
  83. package/dist/webhooks.d.ts +2 -2
  84. package/dist/webhooks.js +2 -2
  85. package/examples/preflight/kernel-native/inventory.json +1 -1
  86. package/package.json +6 -8
  87. package/dist/languages.d.ts +0 -23
  88. package/dist/languages.js +0 -76
  89. package/dist/voice-pkce.d.ts +0 -49
  90. package/dist/voice-pkce.js +0 -147
  91. package/dist/voice-stream.generated.d.ts +0 -118
  92. package/dist/voice-stream.generated.js +0 -170
  93. package/dist/voice-types.d.ts +0 -735
  94. package/dist/voice.d.ts +0 -373
  95. package/dist/voice.js +0 -815
  96. package/readme.test.mjs +0 -53
  97. /package/dist/{voice-retry.d.ts → api-retry.d.ts} +0 -0
@@ -1,23 +0,0 @@
1
- /** A language tag as BCP 47 spells it: `en`, `es`, `pt-BR`. */
2
- export type Language = string;
3
- /** One language Agentcard speaks, as the API lists it. */
4
- export interface LanguageInfo {
5
- /** The BCP 47 tag your calls name (`en`). */
6
- tag: Language;
7
- /** The language's own name for itself (`English`, `Español`). */
8
- name: string;
9
- }
10
- export declare class Languages {
11
- private readonly request;
12
- private cached;
13
- private inflight;
14
- constructor(request: <T>(path: string) => Promise<T>);
15
- /**
16
- * The languages Agentcard speaks, read once and kept for ten minutes. `null`
17
- * when the API does not serve the list (an API older than the route), in
18
- * which case tags are passed through unchecked and the API judges them.
19
- */
20
- list(): Promise<LanguageInfo[] | null>;
21
- /** Refuses a tag the API did not list (`unsupported_language`) before any call leaves; a well-formed tag passes when the API serves no list. */
22
- check(tag: Language | undefined): Promise<void>;
23
- }
package/dist/languages.js DELETED
@@ -1,76 +0,0 @@
1
- /**
2
- * Languages are data, not types. A language is a BCP 47 tag (`en`, `es`,
3
- * `pt-BR`), and the list Agentcard speaks is read from the API
4
- * (`GET /api/v2/voice/languages`), so a language added on Agentcard's side
5
- * needs no release of this package. Every call that names a `lang` asks this
6
- * guard first and is refused with `unsupported_language` before anything is
7
- * sent when the tag is not on the list.
8
- */
9
- import { AgentcardError } from './voice-types.js';
10
- /** The list out of either wire shape: `data` on the list envelope, else `languages`; null when neither is a list. */
11
- function listOf(body) {
12
- if (Array.isArray(body?.data))
13
- return body.data;
14
- if (Array.isArray(body?.languages))
15
- return body.languages;
16
- return null;
17
- }
18
- /** A tag's shape, before the list is consulted: a primary subtag and optional subtags, letters and digits only. */
19
- const TAG_SHAPE = /^[A-Za-z]{2,3}(-[A-Za-z0-9]{1,8})*$/;
20
- /** How long a read list is trusted before it is read again. */
21
- const LIST_TTL_MS = 10 * 60_000;
22
- export class Languages {
23
- request;
24
- cached = null;
25
- inflight = null;
26
- constructor(request) {
27
- this.request = request;
28
- }
29
- /**
30
- * The languages Agentcard speaks, read once and kept for ten minutes. `null`
31
- * when the API does not serve the list (an API older than the route), in
32
- * which case tags are passed through unchecked and the API judges them.
33
- */
34
- async list() {
35
- if (this.cached && Date.now() - this.cached.at < LIST_TTL_MS)
36
- return this.cached.list;
37
- if (this.inflight)
38
- return this.inflight;
39
- this.inflight = (async () => {
40
- let list;
41
- try {
42
- const body = await this.request('/api/v2/voice/languages');
43
- const wire = listOf(body);
44
- list = wire
45
- ? wire.flatMap((l) => (l && typeof l === 'object' && typeof l.tag === 'string'
46
- ? [{ tag: l.tag, name: typeof l.name === 'string' ? l.name : l.tag }]
47
- : []))
48
- : null;
49
- }
50
- catch (err) {
51
- // An API that has no list yet answers 404; the tag then travels unchecked. Anything else is the caller's to see.
52
- if (err instanceof AgentcardError && (err.status === 404 || err.code === 'not_found'))
53
- list = null;
54
- else
55
- throw err;
56
- }
57
- this.cached = { list, at: Date.now() };
58
- return list;
59
- })().finally(() => { this.inflight = null; });
60
- return this.inflight;
61
- }
62
- /** Refuses a tag the API did not list (`unsupported_language`) before any call leaves; a well-formed tag passes when the API serves no list. */
63
- async check(tag) {
64
- if (tag === undefined)
65
- return;
66
- if (typeof tag !== 'string' || !TAG_SHAPE.test(tag)) {
67
- throw new AgentcardError('unsupported_language', `'${String(tag)}' is not a language tag (BCP 47: en, es, pt-BR). Refused before any call.`);
68
- }
69
- const list = await this.list();
70
- if (list === null)
71
- return;
72
- if (!list.some((l) => l.tag.toLowerCase() === tag.toLowerCase())) {
73
- throw new AgentcardError('unsupported_language', `Agentcard does not speak '${tag}' yet. It speaks ${list.map((l) => l.tag).join(', ') || 'no language on this API'}; read voice.languages() for the list. Refused before any call.`, undefined, { tag, languages: list.map((l) => l.tag) });
74
- }
75
- }
76
- }
@@ -1,49 +0,0 @@
1
- /**
2
- * The grant's client half: a PKCE verifier and its S256
3
- * challenge (RFC 7636), and the opaque `state` the return link carries back.
4
- * Random bytes come from the platform's WebCrypto; SHA-256 from WebCrypto
5
- * where it exists (Node, browsers, workers), else from the small
6
- * implementation below, so the module keeps zero dependencies on a phone
7
- * runtime with no `crypto.subtle`. A runtime with no secure random source
8
- * names one through `VoiceOptions.pkce.randomBytes`; nothing here ever
9
- * falls back to `Math.random`.
10
- */
11
- import { type ReturnLink } from './voice-types.js';
12
- /** Seams for a runtime without WebCrypto (a React Native app running the demo's adapter). */
13
- export interface PkceCrypto {
14
- /** `n` cryptographically random bytes. Default: `crypto.getRandomValues`. */
15
- randomBytes?: (n: number) => Uint8Array;
16
- /** SHA-256 of the bytes. Default: `crypto.subtle.digest`, else the built-in implementation. */
17
- sha256?: (bytes: Uint8Array) => Promise<Uint8Array> | Uint8Array;
18
- }
19
- export declare const PKCE_UNAVAILABLE_MESSAGE = "This runtime has no crypto.getRandomValues, so the client cannot mint a PKCE verifier. Name voice: { pkce: { randomBytes } } with a secure source, or pass pkce: false to connect (the polling shape, deprecated).";
20
- export declare function randomBytes(n: number, seam: PkceCrypto | undefined): Uint8Array;
21
- export declare function sha256(bytes: Uint8Array, seam: PkceCrypto | undefined): Promise<Uint8Array>;
22
- /** RFC 4648 §5, no padding: what a verifier, a challenge and a state are made of. */
23
- export declare function base64url(bytes: Uint8Array): string;
24
- /** The ASCII bytes of a verifier (it is base64url, so ASCII by construction). */
25
- export declare function asciiBytes(s: string): Uint8Array;
26
- export interface PkcePair {
27
- /** 43 characters of base64url (32 random bytes): within RFC 7636's 43–128. */
28
- code_verifier: string;
29
- /** base64url(SHA-256(code_verifier)). */
30
- code_challenge: string;
31
- code_challenge_method: 'S256';
32
- /** 22 characters of base64url (16 random bytes), opaque. */
33
- state: string;
34
- }
35
- export declare function mintPkce(seam: PkceCrypto | undefined): Promise<PkcePair>;
36
- /** The `code` and `state` a return link carries, read from its query alone; the scheme may be an app's own. */
37
- export declare function grantFromReturnLink(url: string): {
38
- code: string | null;
39
- state: string | null;
40
- };
41
- /**
42
- * A return link, read from its query alone (the scheme may be an app's own, `yourapp://connected?…`): the
43
- * grant's `code` and `state` (null on a link minted to poll: a reset, an action, `pkce: false`) and the
44
- * `voice_reset` outcome the Vault appends when the visit reset the person's voice key (`done`, `unchanged`,
45
- * `failed`; null when the link says nothing about a reset, and for any other word). A pure function with no
46
- * client behind it: the app half reads the sheet's result with it too.
47
- */
48
- export declare function readReturnLink(url: string): ReturnLink;
49
- export declare function sha256Pure(input: Uint8Array): Uint8Array;
@@ -1,147 +0,0 @@
1
- /**
2
- * The grant's client half: a PKCE verifier and its S256
3
- * challenge (RFC 7636), and the opaque `state` the return link carries back.
4
- * Random bytes come from the platform's WebCrypto; SHA-256 from WebCrypto
5
- * where it exists (Node, browsers, workers), else from the small
6
- * implementation below, so the module keeps zero dependencies on a phone
7
- * runtime with no `crypto.subtle`. A runtime with no secure random source
8
- * names one through `VoiceOptions.pkce.randomBytes`; nothing here ever
9
- * falls back to `Math.random`.
10
- */
11
- import { AgentcardError } from './voice-types.js';
12
- export const PKCE_UNAVAILABLE_MESSAGE = 'This runtime has no crypto.getRandomValues, so the client cannot mint a PKCE verifier. Name voice: { pkce: { randomBytes } } with a secure source, or pass pkce: false to connect (the polling shape, deprecated).';
13
- export function randomBytes(n, seam) {
14
- if (seam?.randomBytes) {
15
- const out = seam.randomBytes(n);
16
- if (!(out instanceof Uint8Array) || out.length !== n)
17
- throw new AgentcardError('pkce_unavailable', `voice.pkce.randomBytes must return ${n} bytes.`);
18
- return out;
19
- }
20
- const c = globalThis.crypto;
21
- if (!c?.getRandomValues)
22
- throw new AgentcardError('pkce_unavailable', PKCE_UNAVAILABLE_MESSAGE);
23
- return c.getRandomValues(new Uint8Array(n));
24
- }
25
- export async function sha256(bytes, seam) {
26
- if (seam?.sha256)
27
- return seam.sha256(bytes);
28
- const subtle = globalThis.crypto?.subtle;
29
- if (subtle?.digest)
30
- return new Uint8Array(await subtle.digest('SHA-256', bytes));
31
- return sha256Pure(bytes);
32
- }
33
- /** RFC 4648 §5, no padding: what a verifier, a challenge and a state are made of. */
34
- export function base64url(bytes) {
35
- const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
36
- let out = '';
37
- for (let i = 0; i < bytes.length; i += 3) {
38
- const a = bytes[i], b = bytes[i + 1], c = bytes[i + 2];
39
- const n = (a << 16) | ((b ?? 0) << 8) | (c ?? 0);
40
- out += alphabet[(n >>> 18) & 63] + alphabet[(n >>> 12) & 63];
41
- if (b !== undefined)
42
- out += alphabet[(n >>> 6) & 63];
43
- if (c !== undefined)
44
- out += alphabet[n & 63];
45
- }
46
- return out;
47
- }
48
- /** The ASCII bytes of a verifier (it is base64url, so ASCII by construction). */
49
- export function asciiBytes(s) {
50
- const out = new Uint8Array(s.length);
51
- for (let i = 0; i < s.length; i++)
52
- out[i] = s.charCodeAt(i) & 0x7f;
53
- return out;
54
- }
55
- export async function mintPkce(seam) {
56
- const code_verifier = base64url(randomBytes(32, seam));
57
- const code_challenge = base64url(await sha256(asciiBytes(code_verifier), seam));
58
- const state = base64url(randomBytes(16, seam));
59
- return { code_verifier, code_challenge, code_challenge_method: 'S256', state };
60
- }
61
- /** The `code` and `state` a return link carries, read from its query alone; the scheme may be an app's own. */
62
- export function grantFromReturnLink(url) {
63
- const { code, state } = readReturnLink(url);
64
- return { code, state };
65
- }
66
- /**
67
- * A return link, read from its query alone (the scheme may be an app's own, `yourapp://connected?…`): the
68
- * grant's `code` and `state` (null on a link minted to poll: a reset, an action, `pkce: false`) and the
69
- * `voice_reset` outcome the Vault appends when the visit reset the person's voice key (`done`, `unchanged`,
70
- * `failed`; null when the link says nothing about a reset, and for any other word). A pure function with no
71
- * client behind it: the app half reads the sheet's result with it too.
72
- */
73
- export function readReturnLink(url) {
74
- const q = url.indexOf('?');
75
- if (q < 0)
76
- return { code: null, state: null, voice_reset: null };
77
- const hash = url.indexOf('#', q);
78
- const params = new URLSearchParams(url.slice(q + 1, hash < 0 ? undefined : hash));
79
- const reset = params.get('voice_reset');
80
- return {
81
- code: params.get('code'),
82
- state: params.get('state'),
83
- voice_reset: reset === 'done' || reset === 'unchanged' || reset === 'failed' ? reset : null,
84
- };
85
- }
86
- // ─── SHA-256, FIPS 180-4, for a runtime without crypto.subtle ───────────────
87
- const K = new Uint32Array([
88
- 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
89
- 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
90
- 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
91
- 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
92
- 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
93
- 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
94
- 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
95
- 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
96
- ]);
97
- export function sha256Pure(input) {
98
- const bitLength = input.length * 8;
99
- const padded = new Uint8Array(((input.length + 9 + 63) >> 6) << 6);
100
- padded.set(input);
101
- padded[input.length] = 0x80;
102
- const view = new DataView(padded.buffer);
103
- view.setUint32(padded.length - 8, Math.floor(bitLength / 0x100000000));
104
- view.setUint32(padded.length - 4, bitLength >>> 0);
105
- const h = new Uint32Array([0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]);
106
- const w = new Uint32Array(64);
107
- const rotr = (x, n) => (x >>> n) | (x << (32 - n));
108
- for (let off = 0; off < padded.length; off += 64) {
109
- for (let i = 0; i < 16; i++)
110
- w[i] = view.getUint32(off + i * 4);
111
- for (let i = 16; i < 64; i++) {
112
- const s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ (w[i - 15] >>> 3);
113
- const s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ (w[i - 2] >>> 10);
114
- w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0;
115
- }
116
- let [a, b, c, d, e, f, g, hh] = h;
117
- for (let i = 0; i < 64; i++) {
118
- const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
119
- const ch = (e & f) ^ (~e & g);
120
- const t1 = (hh + S1 + ch + K[i] + w[i]) >>> 0;
121
- const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
122
- const maj = (a & b) ^ (a & c) ^ (b & c);
123
- const t2 = (S0 + maj) >>> 0;
124
- hh = g;
125
- g = f;
126
- f = e;
127
- e = (d + t1) >>> 0;
128
- d = c;
129
- c = b;
130
- b = a;
131
- a = (t1 + t2) >>> 0;
132
- }
133
- h[0] = (h[0] + a) >>> 0;
134
- h[1] = (h[1] + b) >>> 0;
135
- h[2] = (h[2] + c) >>> 0;
136
- h[3] = (h[3] + d) >>> 0;
137
- h[4] = (h[4] + e) >>> 0;
138
- h[5] = (h[5] + f) >>> 0;
139
- h[6] = (h[6] + g) >>> 0;
140
- h[7] = (h[7] + hh) >>> 0;
141
- }
142
- const out = new Uint8Array(32);
143
- const ov = new DataView(out.buffer);
144
- for (let i = 0; i < 8; i++)
145
- ov.setUint32(i * 4, h[i]);
146
- return out;
147
- }
@@ -1,118 +0,0 @@
1
- /**
2
- * The streaming door, from either side: the person's
3
- * answer goes to the verifier as it is spoken, over one WebSocket, and the
4
- * verifier says `hearing` the moment the key word is out and `verdict` when it
5
- * has closed the span. One call serves a live microphone and a finished
6
- * recording alike: a buffer pushed in one go and ended is a short stream.
7
- *
8
- * Uses the platform's WebSocket (Node 22, React Native, the browser). The
9
- * credential rides in the first frame, never in a header, so every platform
10
- * behaves the same.
11
- */
12
- import { AgentcardError, type Lang, type Verdict } from './voice-types.js';
13
- /**
14
- * How the bytes are encoded. `pcm16-16k`: little-endian 16-bit mono at
15
- * 16 kHz, judged live. `mulaw-8k`: a telephone band, judged on the words
16
- * only. `opus`: an Ogg or WebM Opus stream, judged live. A media type such
17
- * as `audio/m4a` or `audio/wav`: a finished recording pushed in one go, held
18
- * until `end`, then judged exactly as an upload of the same bytes.
19
- */
20
- export type StreamFormat = 'pcm16-16k' | 'mulaw-8k' | 'opus' | `audio/${string}`;
21
- /**
22
- * How the take is heard: the phone's own microphone, a wired headset, a Bluetooth one, or
23
- * unknown when the platform says nothing. The verifier scores the voice on that route's cohort when it has one
24
- * and records a change from the enrolment's route as evidence; only the kind travels, never a device name.
25
- */
26
- export type ChannelKind = 'builtin' | 'wired' | 'bluetooth' | 'unknown';
27
- export declare const CHANNEL_KINDS: readonly ChannelKind[];
28
- /** The key word (and, on a phone's stream, the price) is out: a bot may start its next line now. `heard_count` is how many words landed; the words themselves never cross (WP115). */
29
- export interface Hearing {
30
- heard_count: number;
31
- }
32
- /** The verifier's verdict on the stream: the upload verdict plus what the live door knows. */
33
- /**
34
- * The verifier's signed word on a take (WP182): `payload` is the exact JSON string the signature covers (the challenge
35
- * id, `accepted`, the template hash it matched, `z`, `cos`, `spoof_bits`, `issued_at`), `signature` the raw ECDSA
36
- * `r || s` bytes base64url under the receipt key the Vault's page stamped on the listen, `kid` that key's name. The
37
- * host relays it to the frame as `host-judged` once the proof landed; the frame checks the signature itself.
38
- */
39
- export interface VerdictReceipt {
40
- payload: string;
41
- signature: string;
42
- alg: string;
43
- kid: string;
44
- }
45
- export interface StreamVerdict extends Verdict {
46
- reason: string;
47
- bits: {
48
- words: number;
49
- voice: number;
50
- total: number;
51
- };
52
- cos: number | null;
53
- z: number | null;
54
- speech_s: number;
55
- /** From the first byte to the verdict. */
56
- ms: number;
57
- /** From the caller's last byte (or `end`) to the verdict: what streaming saves. */
58
- since_end_ms: number;
59
- /** Why the verifier closed the span: `all_words`, `quiet`, `ended`, `stalled`, `span_cap`, `wall_cap`. */
60
- closed_by: string;
61
- /** The verifier's signed receipt for this take (WP182), when it sent one. */
62
- receipt?: VerdictReceipt;
63
- }
64
- export interface StreamInput {
65
- /** The verifier's streaming address, `wss://…/v1/stream` (`ws://` against a local verifier). */
66
- stream_url: string;
67
- authorization_id: string;
68
- challenge_id: string;
69
- lang: Lang;
70
- format: StreamFormat;
71
- /** The credential for this answer: the challenge's `answer_token` on the app side, the business's bearer on the server side. */
72
- token: string;
73
- /** The channel the take is heard on, when known at the open; `handle.channel(kind)` names it later. */
74
- channel?: ChannelKind;
75
- /** Test seam: the WebSocket constructor; defaults to the platform's. */
76
- WebSocket?: typeof WebSocket;
77
- }
78
- type Listener<T> = (value: T) => void;
79
- export interface StreamHandle {
80
- readonly format: StreamFormat;
81
- /** Queue bytes; they go the moment the socket is open. */
82
- push(chunk: ArrayBuffer | Uint8Array): void;
83
- /** Nothing more is coming: the verifier closes the span with what it has. */
84
- end(): void;
85
- /** How the take is heard, once the microphone is open and the device is known; before the verdict. */
86
- channel(kind: ChannelKind): void;
87
- /**
88
- * The audio that follows is sealed (WP115): the take's ephemeral key goes ahead of it as the stream's `sealed`
89
- * frame, once, before the first push. The verifier opens every later chunk with it; this side reads none of them.
90
- * `kid` names the verifier key the take was sealed to (the first sixteen hex characters of SHA-256 over its SPKI,
91
- * as the frame computes it): after a key rotation the verifier opens the take under the key it names, inside the
92
- * previous key's grace window, instead of assuming the current one and refusing. A head with no `kid` means the
93
- * current key, as a frame from before the field sealed to it.
94
- */
95
- seal(head: {
96
- alg: string;
97
- eph: string;
98
- kid?: string;
99
- }): void;
100
- on(event: 'hearing', fn: Listener<Hearing>): StreamHandle;
101
- on(event: 'verdict', fn: Listener<StreamVerdict>): StreamHandle;
102
- on(event: 'error', fn: Listener<AgentcardError>): StreamHandle;
103
- /** Resolves with the verdict; rejects with an `AgentcardError` carrying the verifier's code. */
104
- verdict(): Promise<StreamVerdict>;
105
- /** Drops the socket without a verdict (the person walked away). */
106
- close(): void;
107
- }
108
- /** Opens the door and hands back the handle. The first frame names the challenge; bytes follow. */
109
- export declare function openStream(input: StreamInput): StreamHandle;
110
- /**
111
- * A finished recording through the same door. For `pcm16-16k` with `chunkMs`
112
- * the bytes go in timed chunks, as a microphone would send them; otherwise
113
- * the whole buffer goes at once. Ends the stream and resolves the verdict.
114
- */
115
- export declare function streamBuffer(handle: StreamHandle, bytes: ArrayBuffer | Uint8Array, chunkMs?: number): Promise<StreamVerdict>;
116
- /** The verifier's streaming address from its origin (`/v1/stream` appended) or a full address, as given. */
117
- export declare function streamAddress(verifierUrl: string): string;
118
- export {};
@@ -1,170 +0,0 @@
1
- // GENERATED by scripts/generate-voice-stream.mjs from packages/voice/src/core/stream.ts. Do not edit; edit the source and rebuild.
2
- /**
3
- * The streaming door, from either side: the person's
4
- * answer goes to the verifier as it is spoken, over one WebSocket, and the
5
- * verifier says `hearing` the moment the key word is out and `verdict` when it
6
- * has closed the span. One call serves a live microphone and a finished
7
- * recording alike: a buffer pushed in one go and ended is a short stream.
8
- *
9
- * Uses the platform's WebSocket (Node 22, React Native, the browser). The
10
- * credential rides in the first frame, never in a header, so every platform
11
- * behaves the same.
12
- */
13
- import { AgentcardError } from './voice-types.js';
14
- export const CHANNEL_KINDS = ['builtin', 'wired', 'bluetooth', 'unknown'];
15
- /** The receipt as the verifier sends it, every field a string, or null for anything else: a receipt with a piece missing cannot be checked, so it is not carried. */
16
- function receiptOf(r) {
17
- if (!r || typeof r !== 'object')
18
- return null;
19
- const { payload, signature, alg, kid } = r;
20
- return typeof payload === 'string' && typeof signature === 'string' && typeof alg === 'string' && typeof kid === 'string' ? { payload, signature, alg, kid } : null;
21
- }
22
- function toArrayBuffer(chunk) {
23
- if (chunk instanceof ArrayBuffer)
24
- return chunk;
25
- return chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength);
26
- }
27
- /** Opens the door and hands back the handle. The first frame names the challenge; bytes follow. */
28
- export function openStream(input) {
29
- const WS = input.WebSocket ?? globalThis.WebSocket;
30
- if (!WS)
31
- throw new AgentcardError('no_websocket', 'This runtime has no WebSocket; the streaming door needs one.');
32
- const ws = new WS(input.stream_url);
33
- ws.binaryType = 'arraybuffer';
34
- const listeners = { hearing: [], verdict: [], error: [] };
35
- // Audio chunks and, ahead of the first sealed chunk, the `sealed` text frame: sent in the order they were queued.
36
- const queue = [];
37
- let open = false, ended = false, settled = false, sealed = false;
38
- let channelKind = input.channel && CHANNEL_KINDS.includes(input.channel) ? input.channel : undefined;
39
- // A channel named after the open goes as its own text frame; one named before rides the first frame.
40
- let channelPending = null;
41
- let resolveVerdict, rejectVerdict;
42
- const verdictPromise = new Promise((resolve, reject) => { resolveVerdict = resolve; rejectVerdict = reject; });
43
- verdictPromise.catch(() => { });
44
- const fail = (err) => {
45
- if (settled)
46
- return;
47
- settled = true;
48
- for (const fn of listeners.error)
49
- fn(err);
50
- rejectVerdict(err);
51
- };
52
- const flush = () => { while (open && queue.length)
53
- ws.send(queue.shift()); };
54
- ws.onopen = () => {
55
- // No token on the frame when the caller has none: a fronting door may carry the credential on the upgrade instead.
56
- ws.send(JSON.stringify({ authorization_id: input.authorization_id, challenge_id: input.challenge_id, lang: input.lang, format: input.format, ...(input.token ? { token: input.token } : {}), ...(channelKind ? { channel: channelKind } : {}) }));
57
- open = true;
58
- if (channelPending) {
59
- ws.send(JSON.stringify({ type: 'channel', kind: channelPending }));
60
- channelPending = null;
61
- }
62
- flush();
63
- if (ended)
64
- ws.send(JSON.stringify({ type: 'end' }));
65
- };
66
- ws.onmessage = (ev) => {
67
- if (typeof ev.data !== 'string')
68
- return;
69
- let msg;
70
- try {
71
- msg = JSON.parse(ev.data);
72
- }
73
- catch {
74
- return;
75
- }
76
- if (msg.type === 'hearing') {
77
- const h = { heard_count: Math.max(0, Math.floor(Number(msg.heard_count)) || 0) };
78
- for (const fn of listeners.hearing)
79
- fn(h);
80
- }
81
- else if (msg.type === 'verdict') {
82
- if (settled)
83
- return;
84
- settled = true;
85
- const receipt = receiptOf(msg.receipt);
86
- const v = {
87
- accepted: msg.accepted === true, spent: true,
88
- approved_with: typeof msg.approved_with === 'string' ? msg.approved_with : null,
89
- reason: String(msg.reason ?? ''), bits: msg.bits ?? { words: 0, voice: 0, total: 0 },
90
- cos: typeof msg.cos === 'number' ? msg.cos : null, z: typeof msg.z === 'number' ? msg.z : null,
91
- speech_s: Number(msg.speech_s ?? 0), ms: Number(msg.ms ?? 0), since_end_ms: Number(msg.since_end_ms ?? 0), closed_by: String(msg.closed_by ?? ''),
92
- ...(receipt ? { receipt } : {}),
93
- };
94
- for (const fn of listeners.verdict)
95
- fn(v);
96
- resolveVerdict(v);
97
- }
98
- else if (msg.type === 'error') {
99
- fail(new AgentcardError(String(msg.code ?? 'verifier_error'), String(msg.message ?? 'The verifier refused the stream.')));
100
- }
101
- };
102
- ws.onerror = () => fail(new AgentcardError('unreachable', 'The verifier could not be reached.'));
103
- ws.onclose = () => fail(new AgentcardError('stream_closed', 'The verifier closed the stream without a verdict.'));
104
- const handle = {
105
- format: input.format,
106
- push(chunk) { if (ended || settled)
107
- return; queue.push(toArrayBuffer(chunk)); flush(); },
108
- end() { if (ended)
109
- return; ended = true; if (open) {
110
- flush();
111
- ws.send(JSON.stringify({ type: 'end' }));
112
- } },
113
- channel(kind) {
114
- if (settled || ended || !CHANNEL_KINDS.includes(kind))
115
- return;
116
- if (!open) {
117
- channelKind = kind;
118
- channelPending = null;
119
- return;
120
- }
121
- channelKind = kind;
122
- ws.send(JSON.stringify({ type: 'channel', kind }));
123
- },
124
- seal(head) {
125
- if (settled || ended || sealed || typeof head.eph !== 'string' || !head.eph)
126
- return;
127
- sealed = true;
128
- const kid = typeof head.kid === 'string' && head.kid ? head.kid : null;
129
- queue.push(JSON.stringify({ type: 'sealed', alg: head.alg, eph: head.eph, ...(kid ? { kid } : {}) }));
130
- flush();
131
- },
132
- on(event, fn) { listeners[event].push(fn); return handle; },
133
- verdict: () => verdictPromise,
134
- close() { if (!settled) {
135
- settled = true;
136
- rejectVerdict(new AgentcardError('stream_closed', 'The stream was closed before a verdict.'));
137
- } try {
138
- ws.close();
139
- }
140
- catch { /* already closed */ } },
141
- };
142
- return handle;
143
- }
144
- const PCM16_16K_BYTES_PER_MS = 32;
145
- /**
146
- * A finished recording through the same door. For `pcm16-16k` with `chunkMs`
147
- * the bytes go in timed chunks, as a microphone would send them; otherwise
148
- * the whole buffer goes at once. Ends the stream and resolves the verdict.
149
- */
150
- export async function streamBuffer(handle, bytes, chunkMs) {
151
- const u8 = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
152
- if (chunkMs && chunkMs > 0 && handle.format === 'pcm16-16k') {
153
- const step = PCM16_16K_BYTES_PER_MS * chunkMs;
154
- for (let i = 0; i < u8.length; i += step) {
155
- handle.push(u8.subarray(i, Math.min(u8.length, i + step)));
156
- if (i + step < u8.length)
157
- await new Promise((r) => setTimeout(r, chunkMs));
158
- }
159
- }
160
- else {
161
- handle.push(u8);
162
- }
163
- handle.end();
164
- return handle.verdict();
165
- }
166
- /** The verifier's streaming address from its origin (`/v1/stream` appended) or a full address, as given. */
167
- export function streamAddress(verifierUrl) {
168
- const trimmed = verifierUrl.replace(/\/+$/, '');
169
- return /\/v1\/stream$/.test(trimmed) ? trimmed : trimmed + '/v1/stream';
170
- }