@fgv/ts-extras 5.1.0-42 → 5.1.0-44

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 (57) hide show
  1. package/dist/packlets/ai-assist/model.js.map +1 -1
  2. package/dist/packlets/ai-assist/registry.js +22 -18
  3. package/dist/packlets/ai-assist/registry.js.map +1 -1
  4. package/dist/packlets/crypto-utils/index.browser.js +1 -1
  5. package/dist/packlets/crypto-utils/index.browser.js.map +1 -1
  6. package/dist/packlets/crypto-utils/index.js +1 -1
  7. package/dist/packlets/crypto-utils/index.js.map +1 -1
  8. package/dist/packlets/crypto-utils/keystore/converters.js +8 -4
  9. package/dist/packlets/crypto-utils/keystore/converters.js.map +1 -1
  10. package/dist/packlets/crypto-utils/keystore/keyStore.js +101 -2
  11. package/dist/packlets/crypto-utils/keystore/keyStore.js.map +1 -1
  12. package/dist/packlets/crypto-utils/keystore/model.js +9 -4
  13. package/dist/packlets/crypto-utils/keystore/model.js.map +1 -1
  14. package/dist/packlets/crypto-utils/model.js.map +1 -1
  15. package/dist/packlets/crypto-utils/seedDerivedKeyPair.js +51 -38
  16. package/dist/packlets/crypto-utils/seedDerivedKeyPair.js.map +1 -1
  17. package/dist/packlets/crypto-utils/spkiHelpers.js +44 -0
  18. package/dist/packlets/crypto-utils/spkiHelpers.js.map +1 -1
  19. package/dist/ts-extras.d.ts +220 -38
  20. package/lib/packlets/ai-assist/model.d.ts +9 -8
  21. package/lib/packlets/ai-assist/model.d.ts.map +1 -1
  22. package/lib/packlets/ai-assist/model.js.map +1 -1
  23. package/lib/packlets/ai-assist/registry.d.ts.map +1 -1
  24. package/lib/packlets/ai-assist/registry.js +22 -18
  25. package/lib/packlets/ai-assist/registry.js.map +1 -1
  26. package/lib/packlets/crypto-utils/index.browser.d.ts +1 -1
  27. package/lib/packlets/crypto-utils/index.browser.d.ts.map +1 -1
  28. package/lib/packlets/crypto-utils/index.browser.js +3 -1
  29. package/lib/packlets/crypto-utils/index.browser.js.map +1 -1
  30. package/lib/packlets/crypto-utils/index.d.ts +1 -1
  31. package/lib/packlets/crypto-utils/index.d.ts.map +1 -1
  32. package/lib/packlets/crypto-utils/index.js +3 -1
  33. package/lib/packlets/crypto-utils/index.js.map +1 -1
  34. package/lib/packlets/crypto-utils/keystore/converters.d.ts +1 -1
  35. package/lib/packlets/crypto-utils/keystore/converters.d.ts.map +1 -1
  36. package/lib/packlets/crypto-utils/keystore/converters.js +8 -4
  37. package/lib/packlets/crypto-utils/keystore/converters.js.map +1 -1
  38. package/lib/packlets/crypto-utils/keystore/keyStore.d.ts +45 -1
  39. package/lib/packlets/crypto-utils/keystore/keyStore.d.ts.map +1 -1
  40. package/lib/packlets/crypto-utils/keystore/keyStore.js +101 -2
  41. package/lib/packlets/crypto-utils/keystore/keyStore.js.map +1 -1
  42. package/lib/packlets/crypto-utils/keystore/model.d.ts +57 -3
  43. package/lib/packlets/crypto-utils/keystore/model.d.ts.map +1 -1
  44. package/lib/packlets/crypto-utils/keystore/model.js +9 -4
  45. package/lib/packlets/crypto-utils/keystore/model.js.map +1 -1
  46. package/lib/packlets/crypto-utils/model.d.ts +65 -20
  47. package/lib/packlets/crypto-utils/model.d.ts.map +1 -1
  48. package/lib/packlets/crypto-utils/model.js.map +1 -1
  49. package/lib/packlets/crypto-utils/seedDerivedKeyPair.d.ts +11 -6
  50. package/lib/packlets/crypto-utils/seedDerivedKeyPair.d.ts.map +1 -1
  51. package/lib/packlets/crypto-utils/seedDerivedKeyPair.js +51 -38
  52. package/lib/packlets/crypto-utils/seedDerivedKeyPair.js.map +1 -1
  53. package/lib/packlets/crypto-utils/spkiHelpers.d.ts +26 -0
  54. package/lib/packlets/crypto-utils/spkiHelpers.d.ts.map +1 -1
  55. package/lib/packlets/crypto-utils/spkiHelpers.js +46 -0
  56. package/lib/packlets/crypto-utils/spkiHelpers.js.map +1 -1
  57. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"spkiHelpers.js","sourceRoot":"","sources":["../../../src/packlets/crypto-utils/spkiHelpers.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nimport { Result, fail, succeed } from '@fgv/ts-utils';\nimport { ICryptoProvider, KeyPairAlgorithm, MultibaseSpkiPublicKey } from './model';\n\n/**\n * Shared shape check for a base64url (no-padding) body: only base64url alphabet\n * characters (`A-Z`, `a-z`, `0-9`, `-`, `_`) and a length that is never `% 4 === 1`\n * (an impossible base64 remainder). Factored so the decoder and the\n * {@link isValidMultibaseSpkiPublicKey} guard agree on exactly one rule.\n */\nfunction isBase64UrlNoPadBody(body: string): boolean {\n return /^[A-Za-z0-9_-]*$/.test(body) && body.length % 4 !== 1;\n}\n\n/**\n * Encodes a `Uint8Array` as a base64url (no-padding) string (RFC 4648 §5).\n *\n * The body uses the base64url alphabet (`+` → `-`, `/` → `_`) and trailing `=`\n * padding is stripped. This is the bare primitive with no multibase prefix; use\n * {@link CryptoUtils.multibaseBase64UrlEncode} when a multibase-`'m'`-prefixed value is required.\n *\n * @param data - The binary data to encode.\n * @returns The base64url-no-pad string.\n * @public\n */\nexport function base64UrlNoPadEncode(data: Uint8Array): string {\n let base64: string;\n if (typeof Buffer !== 'undefined') {\n base64 = Buffer.from(data).toString('base64');\n /* c8 ignore start - browser-only: btoa path not available in Node tests */\n } else {\n let binary = '';\n for (let i = 0; i < data.length; i++) {\n binary += String.fromCharCode(data[i]);\n }\n base64 = btoa(binary);\n }\n /* c8 ignore stop */\n // Convert to base64url: + → -, / → _, strip = padding\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/**\n * Decodes a base64url (no-padding) string (RFC 4648 §5) back to a `Uint8Array`.\n *\n * This is the bare primitive with no multibase prefix; use\n * {@link CryptoUtils.multibaseBase64UrlDecode} to decode a multibase-`'m'`-prefixed value.\n *\n * @param encoded - The base64url-no-pad body to decode.\n * @returns `Success` with the decoded bytes, or `Failure` with error context.\n * @public\n */\nexport function base64UrlNoPadDecode(encoded: string): Result<Uint8Array> {\n if (!isBase64UrlNoPadBody(encoded)) {\n return fail(`base64UrlNoPadDecode: malformed base64url body`);\n }\n // Convert base64url back to standard base64 and restore padding\n const base64 = encoded.replace(/-/g, '+').replace(/_/g, '/');\n const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);\n try {\n let bytes: Uint8Array;\n if (typeof Buffer !== 'undefined') {\n bytes = new Uint8Array(Buffer.from(padded, 'base64'));\n /* c8 ignore start - browser-only: atob path not available in Node tests */\n } else {\n const binary = atob(padded);\n bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n }\n /* c8 ignore stop */\n return succeed(bytes);\n /* c8 ignore next 3 - defensive: shape check above prevents invalid chars from reaching here */\n } catch {\n return fail(`base64UrlNoPadDecode: malformed base64url body`);\n }\n}\n\n/**\n * The structural *shape* of a {@link CryptoUtils.MultibaseSpkiPublicKey}: the\n * multibase `'m'` prefix followed by a non-empty base64url-no-pad body\n * (`A-Z`, `a-z`, `0-9`, `-`, `_`).\n *\n * This is a shape prefilter, not full validation: it does not decode the DER\n * SPKI or verify the key material or algorithm (that happens in\n * {@link CryptoUtils.importPublicKeyFromMultibaseSpki}). The authoritative guard\n * is {@link CryptoUtils.isValidMultibaseSpkiPublicKey}, which enforces this\n * pattern **and** additionally rejects a body whose length is an impossible\n * base64 remainder. Exposed for callers that need the pattern directly (e.g. a\n * JSON-schema `pattern` field); prefer the guard/converter for validation.\n *\n * @public\n */\nexport const MultibaseSpkiPublicKeyRegExp: RegExp = /^m[A-Za-z0-9_-]+$/;\n\n/**\n * Type guard for {@link CryptoUtils.MultibaseSpkiPublicKey}: a string matching\n * {@link CryptoUtils.MultibaseSpkiPublicKeyRegExp} (multibase `'m'` prefix + a\n * non-empty base64url-no-pad body) whose body also satisfies the base64url-no-pad\n * length rule shared with {@link CryptoUtils.base64UrlNoPadDecode}. This is a\n * structural *shape* check, not full validation — it does not decode the DER SPKI\n * or verify the key material/algorithm (a malformed-but-well-shaped string fails\n * later, with clear context, in {@link CryptoUtils.importPublicKeyFromMultibaseSpki}).\n *\n * @param value - The value to test.\n * @returns `true` if `value` is a well-formed multibase SPKI public key string.\n * @public\n */\nexport function isValidMultibaseSpkiPublicKey(value: unknown): value is MultibaseSpkiPublicKey {\n return (\n typeof value === 'string' &&\n MultibaseSpkiPublicKeyRegExp.test(value) &&\n isBase64UrlNoPadBody(value.slice(1))\n );\n}\n\n/**\n * Encodes a `Uint8Array` as a multibase base64url (no-padding) string.\n *\n * The multibase prefix `'m'` identifies the encoding as RFC 4648 base64url\n * without padding. The body uses base64url alphabet: `+` → `-`, `/` → `_`,\n * and trailing `=` padding is stripped.\n *\n * @param data - The binary data to encode.\n * @returns A multibase-prefixed base64url string (`'m' + base64url-no-pad`).\n * @public\n */\nexport function multibaseBase64UrlEncode(data: Uint8Array): string {\n return 'm' + base64UrlNoPadEncode(data);\n}\n\n/**\n * Decodes a multibase base64url (no-padding) string back to a `Uint8Array`.\n *\n * Validates that the first character is `'m'` (the multibase prefix for\n * RFC 4648 base64url without padding), then decodes the remaining body.\n *\n * @param encoded - A multibase-prefixed base64url string.\n * @returns `Success` with the decoded bytes, or `Failure` with error context.\n * @public\n */\nexport function multibaseBase64UrlDecode(encoded: string): Result<Uint8Array> {\n if (!encoded.startsWith('m')) {\n return fail(\n `multibaseBase64UrlDecode: invalid multibase prefix '${\n encoded[0] ?? '(empty)'\n }' — expected 'm' (base64url)`\n );\n }\n // Intentionally pin the exact original message (the delegate has a single failure\n // mode) to keep this established public function byte-for-byte behavior-preserving\n // after the extract-and-delegate refactor — do not \"fix\" this into composing the\n // delegate's message without updating the delegation-equivalence tests.\n return base64UrlNoPadDecode(encoded.slice(1)).withErrorFormat(\n () => `multibaseBase64UrlDecode: malformed base64url body`\n );\n}\n\n/**\n * Exports a public `CryptoKey` as a multibase base64url-encoded SPKI blob.\n *\n * The SPKI (SubjectPublicKeyInfo) format is the standard DER-encoded structure\n * for public keys defined in RFC 5280, RFC 5480, and RFC 8410. It is\n * algorithm-agnostic and suitable for storage and transmission.\n *\n * @param key - The `CryptoKey` to export. Must have `key.type === 'public'`.\n * @param provider - The {@link CryptoUtils.ICryptoProvider} to use for the export operation.\n * @returns `Success` with the multibase SPKI string, or `Failure` with error context.\n * @public\n */\nexport async function exportPublicKeyAsMultibaseSpki(\n key: CryptoKey,\n provider: ICryptoProvider\n): Promise<Result<MultibaseSpkiPublicKey>> {\n return (await provider.exportPublicKeySpki(key))\n .withErrorFormat((e) => `exportPublicKeyAsMultibaseSpki: ${e}`)\n .onSuccess((buf) =>\n // The output is a freshly-built valid multibase SPKI string (`'m'` prefix +\n // base64url-no-pad body), so brand it at the construction site — no re-validation needed.\n succeed(multibaseBase64UrlEncode(buf) as MultibaseSpkiPublicKey)\n );\n}\n\n/**\n * Imports a public key from a multibase base64url-encoded SPKI blob.\n *\n * Decodes the multibase prefix, decodes the base64url body, then uses\n * the provider to import the key with the algorithm parameters from\n * {@link CryptoUtils.keyPairAlgorithmParams}.\n *\n * Accepts a plain `string` (not only a branded {@link CryptoUtils.MultibaseSpkiPublicKey}),\n * so callers holding an unbranded value read from storage or the wire can import\n * it directly; a malformed value fails with error context rather than throwing.\n *\n * @param encoded - A multibase SPKI string produced by {@link CryptoUtils.exportPublicKeyAsMultibaseSpki}.\n * @param algorithm - The {@link CryptoUtils.KeyPairAlgorithm} the key was generated for.\n * @param provider - The {@link CryptoUtils.ICryptoProvider} to use for the import operation.\n * @returns `Success` with the imported public `CryptoKey`, or `Failure` with error context.\n * @public\n */\nexport async function importPublicKeyFromMultibaseSpki(\n encoded: string,\n algorithm: KeyPairAlgorithm,\n provider: ICryptoProvider\n): Promise<Result<CryptoKey>> {\n const decodeResult = multibaseBase64UrlDecode(encoded);\n if (decodeResult.isFailure()) {\n return fail(`importPublicKeyFromMultibaseSpki: ${decodeResult.message}`);\n }\n return (await provider.importPublicKeySpki(decodeResult.value, algorithm)).withErrorFormat(\n (e) => `importPublicKeyFromMultibaseSpki: ${e}`\n );\n}\n\n// DER prefix for an X25519 SubjectPublicKeyInfo: SEQUENCE { SEQUENCE { OID id-X25519 }, BIT STRING }.\n// SEQUENCE(42) / SEQUENCE(5) / OID 1.3.101.110 (id-X25519) / BIT STRING(33, 0 unused bits).\nconst _X25519_SPKI_PREFIX: ReadonlyArray<number> = [\n 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x03, 0x21, 0x00\n];\nconst _X25519_SPKI_LENGTH: number = _X25519_SPKI_PREFIX.length + 32;\n\n/**\n * Strips the fixed DER prefix from an X25519 SubjectPublicKeyInfo blob, returning the raw\n * 32-byte public key.\n *\n * An X25519 SPKI is always exactly 44 bytes: a fixed 12-byte prefix (SEQUENCE / AlgorithmIdentifier\n * with OID 1.3.101.110 / BIT STRING) followed by the 32-byte raw key. Use this to convert a\n * SPKI-held recipient public key into the raw form accepted by {@link HpkeProvider.openBase}'s\n * `recipientPublicKey` parameter.\n *\n * @param spki - The DER-encoded X25519 SubjectPublicKeyInfo bytes.\n * @returns `Success` with the raw 32-byte public key, or `Failure` if `spki` is not a\n * well-formed 44-byte X25519 SPKI blob.\n * @public\n */\nexport function spkiToRawX25519(spki: Uint8Array): Result<Uint8Array> {\n if (spki.length !== _X25519_SPKI_LENGTH) {\n return fail(`spkiToRawX25519: expected ${_X25519_SPKI_LENGTH} bytes, got ${spki.length}`);\n }\n for (let i = 0; i < _X25519_SPKI_PREFIX.length; i++) {\n if (spki[i] !== _X25519_SPKI_PREFIX[i]) {\n return fail(`spkiToRawX25519: byte ${i} does not match the expected X25519 SPKI prefix`);\n }\n }\n return succeed(spki.slice(_X25519_SPKI_PREFIX.length));\n}\n"]}
1
+ {"version":3,"file":"spkiHelpers.js","sourceRoot":"","sources":["../../../src/packlets/crypto-utils/spkiHelpers.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nimport { Result, fail, succeed } from '@fgv/ts-utils';\nimport { ICryptoProvider, KeyPairAlgorithm, MultibaseSpkiPublicKey } from './model';\n\n/**\n * Shared shape check for a base64url (no-padding) body: only base64url alphabet\n * characters (`A-Z`, `a-z`, `0-9`, `-`, `_`) and a length that is never `% 4 === 1`\n * (an impossible base64 remainder). Factored so the decoder and the\n * {@link isValidMultibaseSpkiPublicKey} guard agree on exactly one rule.\n */\nfunction isBase64UrlNoPadBody(body: string): boolean {\n return /^[A-Za-z0-9_-]*$/.test(body) && body.length % 4 !== 1;\n}\n\n/**\n * Encodes a `Uint8Array` as a base64url (no-padding) string (RFC 4648 §5).\n *\n * The body uses the base64url alphabet (`+` → `-`, `/` → `_`) and trailing `=`\n * padding is stripped. This is the bare primitive with no multibase prefix; use\n * {@link CryptoUtils.multibaseBase64UrlEncode} when a multibase-`'m'`-prefixed value is required.\n *\n * @param data - The binary data to encode.\n * @returns The base64url-no-pad string.\n * @public\n */\nexport function base64UrlNoPadEncode(data: Uint8Array): string {\n let base64: string;\n if (typeof Buffer !== 'undefined') {\n base64 = Buffer.from(data).toString('base64');\n /* c8 ignore start - browser-only: btoa path not available in Node tests */\n } else {\n let binary = '';\n for (let i = 0; i < data.length; i++) {\n binary += String.fromCharCode(data[i]);\n }\n base64 = btoa(binary);\n }\n /* c8 ignore stop */\n // Convert to base64url: + → -, / → _, strip = padding\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/**\n * Decodes a base64url (no-padding) string (RFC 4648 §5) back to a `Uint8Array`.\n *\n * This is the bare primitive with no multibase prefix; use\n * {@link CryptoUtils.multibaseBase64UrlDecode} to decode a multibase-`'m'`-prefixed value.\n *\n * @param encoded - The base64url-no-pad body to decode.\n * @returns `Success` with the decoded bytes, or `Failure` with error context.\n * @public\n */\nexport function base64UrlNoPadDecode(encoded: string): Result<Uint8Array> {\n if (!isBase64UrlNoPadBody(encoded)) {\n return fail(`base64UrlNoPadDecode: malformed base64url body`);\n }\n // Convert base64url back to standard base64 and restore padding\n const base64 = encoded.replace(/-/g, '+').replace(/_/g, '/');\n const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);\n try {\n let bytes: Uint8Array;\n if (typeof Buffer !== 'undefined') {\n bytes = new Uint8Array(Buffer.from(padded, 'base64'));\n /* c8 ignore start - browser-only: atob path not available in Node tests */\n } else {\n const binary = atob(padded);\n bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n }\n /* c8 ignore stop */\n return succeed(bytes);\n /* c8 ignore next 3 - defensive: shape check above prevents invalid chars from reaching here */\n } catch {\n return fail(`base64UrlNoPadDecode: malformed base64url body`);\n }\n}\n\n/**\n * Encodes a `Uint8Array` as a lowercase hexadecimal string with no `0x` prefix.\n *\n * Each byte becomes exactly two lowercase hex characters (zero-padded), so an\n * `n`-byte input yields a `2n`-character string. An empty input yields the empty\n * string. This is the inverse of {@link CryptoUtils.hexDecode}.\n *\n * @param data - The binary data to encode.\n * @returns The lowercase, unprefixed hex string.\n * @public\n */\nexport function hexEncode(data: Uint8Array): string {\n let hex = '';\n for (let i = 0; i < data.length; i++) {\n hex += data[i].toString(16).padStart(2, '0');\n }\n return hex;\n}\n\n/**\n * Decodes a hexadecimal string (no `0x` prefix) back to a `Uint8Array`.\n *\n * Liberal in what it accepts: both lowercase and uppercase hex digits are\n * allowed (whereas {@link CryptoUtils.hexEncode} always emits lowercase). The\n * input must have even length and contain only hex digits (`0-9`, `a-f`, `A-F`);\n * an empty string decodes to an empty `Uint8Array`. Odd length or any non-hex\n * character fails with error context.\n *\n * @param encoded - The hex string to decode.\n * @returns `Success` with the decoded bytes, or `Failure` with error context.\n * @public\n */\nexport function hexDecode(encoded: string): Result<Uint8Array> {\n if (encoded.length % 2 !== 0) {\n return fail(`hexDecode: odd-length hex string (length ${encoded.length})`);\n }\n if (!/^[0-9a-fA-F]*$/.test(encoded)) {\n return fail(`hexDecode: string contains non-hex characters`);\n }\n const bytes = new Uint8Array(encoded.length / 2);\n for (let i = 0; i < bytes.length; i++) {\n bytes[i] = parseInt(encoded.slice(i * 2, i * 2 + 2), 16);\n }\n return succeed(bytes);\n}\n\n/**\n * The structural *shape* of a {@link CryptoUtils.MultibaseSpkiPublicKey}: the\n * multibase `'m'` prefix followed by a non-empty base64url-no-pad body\n * (`A-Z`, `a-z`, `0-9`, `-`, `_`).\n *\n * This is a shape prefilter, not full validation: it does not decode the DER\n * SPKI or verify the key material or algorithm (that happens in\n * {@link CryptoUtils.importPublicKeyFromMultibaseSpki}). The authoritative guard\n * is {@link CryptoUtils.isValidMultibaseSpkiPublicKey}, which enforces this\n * pattern **and** additionally rejects a body whose length is an impossible\n * base64 remainder. Exposed for callers that need the pattern directly (e.g. a\n * JSON-schema `pattern` field); prefer the guard/converter for validation.\n *\n * @public\n */\nexport const MultibaseSpkiPublicKeyRegExp: RegExp = /^m[A-Za-z0-9_-]+$/;\n\n/**\n * Type guard for {@link CryptoUtils.MultibaseSpkiPublicKey}: a string matching\n * {@link CryptoUtils.MultibaseSpkiPublicKeyRegExp} (multibase `'m'` prefix + a\n * non-empty base64url-no-pad body) whose body also satisfies the base64url-no-pad\n * length rule shared with {@link CryptoUtils.base64UrlNoPadDecode}. This is a\n * structural *shape* check, not full validation — it does not decode the DER SPKI\n * or verify the key material/algorithm (a malformed-but-well-shaped string fails\n * later, with clear context, in {@link CryptoUtils.importPublicKeyFromMultibaseSpki}).\n *\n * @param value - The value to test.\n * @returns `true` if `value` is a well-formed multibase SPKI public key string.\n * @public\n */\nexport function isValidMultibaseSpkiPublicKey(value: unknown): value is MultibaseSpkiPublicKey {\n return (\n typeof value === 'string' &&\n MultibaseSpkiPublicKeyRegExp.test(value) &&\n isBase64UrlNoPadBody(value.slice(1))\n );\n}\n\n/**\n * Encodes a `Uint8Array` as a multibase base64url (no-padding) string.\n *\n * The multibase prefix `'m'` identifies the encoding as RFC 4648 base64url\n * without padding. The body uses base64url alphabet: `+` → `-`, `/` → `_`,\n * and trailing `=` padding is stripped.\n *\n * @param data - The binary data to encode.\n * @returns A multibase-prefixed base64url string (`'m' + base64url-no-pad`).\n * @public\n */\nexport function multibaseBase64UrlEncode(data: Uint8Array): string {\n return 'm' + base64UrlNoPadEncode(data);\n}\n\n/**\n * Decodes a multibase base64url (no-padding) string back to a `Uint8Array`.\n *\n * Validates that the first character is `'m'` (the multibase prefix for\n * RFC 4648 base64url without padding), then decodes the remaining body.\n *\n * @param encoded - A multibase-prefixed base64url string.\n * @returns `Success` with the decoded bytes, or `Failure` with error context.\n * @public\n */\nexport function multibaseBase64UrlDecode(encoded: string): Result<Uint8Array> {\n if (!encoded.startsWith('m')) {\n return fail(\n `multibaseBase64UrlDecode: invalid multibase prefix '${\n encoded[0] ?? '(empty)'\n }' — expected 'm' (base64url)`\n );\n }\n // Intentionally pin the exact original message (the delegate has a single failure\n // mode) to keep this established public function byte-for-byte behavior-preserving\n // after the extract-and-delegate refactor — do not \"fix\" this into composing the\n // delegate's message without updating the delegation-equivalence tests.\n return base64UrlNoPadDecode(encoded.slice(1)).withErrorFormat(\n () => `multibaseBase64UrlDecode: malformed base64url body`\n );\n}\n\n/**\n * Exports a public `CryptoKey` as a multibase base64url-encoded SPKI blob.\n *\n * The SPKI (SubjectPublicKeyInfo) format is the standard DER-encoded structure\n * for public keys defined in RFC 5280, RFC 5480, and RFC 8410. It is\n * algorithm-agnostic and suitable for storage and transmission.\n *\n * @param key - The `CryptoKey` to export. Must have `key.type === 'public'`.\n * @param provider - The {@link CryptoUtils.ICryptoProvider} to use for the export operation.\n * @returns `Success` with the multibase SPKI string, or `Failure` with error context.\n * @public\n */\nexport async function exportPublicKeyAsMultibaseSpki(\n key: CryptoKey,\n provider: ICryptoProvider\n): Promise<Result<MultibaseSpkiPublicKey>> {\n return (await provider.exportPublicKeySpki(key))\n .withErrorFormat((e) => `exportPublicKeyAsMultibaseSpki: ${e}`)\n .onSuccess((buf) =>\n // The output is a freshly-built valid multibase SPKI string (`'m'` prefix +\n // base64url-no-pad body), so brand it at the construction site — no re-validation needed.\n succeed(multibaseBase64UrlEncode(buf) as MultibaseSpkiPublicKey)\n );\n}\n\n/**\n * Imports a public key from a multibase base64url-encoded SPKI blob.\n *\n * Decodes the multibase prefix, decodes the base64url body, then uses\n * the provider to import the key with the algorithm parameters from\n * {@link CryptoUtils.keyPairAlgorithmParams}.\n *\n * Accepts a plain `string` (not only a branded {@link CryptoUtils.MultibaseSpkiPublicKey}),\n * so callers holding an unbranded value read from storage or the wire can import\n * it directly; a malformed value fails with error context rather than throwing.\n *\n * @param encoded - A multibase SPKI string produced by {@link CryptoUtils.exportPublicKeyAsMultibaseSpki}.\n * @param algorithm - The {@link CryptoUtils.KeyPairAlgorithm} the key was generated for.\n * @param provider - The {@link CryptoUtils.ICryptoProvider} to use for the import operation.\n * @returns `Success` with the imported public `CryptoKey`, or `Failure` with error context.\n * @public\n */\nexport async function importPublicKeyFromMultibaseSpki(\n encoded: string,\n algorithm: KeyPairAlgorithm,\n provider: ICryptoProvider\n): Promise<Result<CryptoKey>> {\n const decodeResult = multibaseBase64UrlDecode(encoded);\n if (decodeResult.isFailure()) {\n return fail(`importPublicKeyFromMultibaseSpki: ${decodeResult.message}`);\n }\n return (await provider.importPublicKeySpki(decodeResult.value, algorithm)).withErrorFormat(\n (e) => `importPublicKeyFromMultibaseSpki: ${e}`\n );\n}\n\n// DER prefix for an X25519 SubjectPublicKeyInfo: SEQUENCE { SEQUENCE { OID id-X25519 }, BIT STRING }.\n// SEQUENCE(42) / SEQUENCE(5) / OID 1.3.101.110 (id-X25519) / BIT STRING(33, 0 unused bits).\nconst _X25519_SPKI_PREFIX: ReadonlyArray<number> = [\n 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x03, 0x21, 0x00\n];\nconst _X25519_SPKI_LENGTH: number = _X25519_SPKI_PREFIX.length + 32;\n\n/**\n * Strips the fixed DER prefix from an X25519 SubjectPublicKeyInfo blob, returning the raw\n * 32-byte public key.\n *\n * An X25519 SPKI is always exactly 44 bytes: a fixed 12-byte prefix (SEQUENCE / AlgorithmIdentifier\n * with OID 1.3.101.110 / BIT STRING) followed by the 32-byte raw key. Use this to convert a\n * SPKI-held recipient public key into the raw form accepted by {@link HpkeProvider.openBase}'s\n * `recipientPublicKey` parameter.\n *\n * @param spki - The DER-encoded X25519 SubjectPublicKeyInfo bytes.\n * @returns `Success` with the raw 32-byte public key, or `Failure` if `spki` is not a\n * well-formed 44-byte X25519 SPKI blob.\n * @public\n */\nexport function spkiToRawX25519(spki: Uint8Array): Result<Uint8Array> {\n if (spki.length !== _X25519_SPKI_LENGTH) {\n return fail(`spkiToRawX25519: expected ${_X25519_SPKI_LENGTH} bytes, got ${spki.length}`);\n }\n for (let i = 0; i < _X25519_SPKI_PREFIX.length; i++) {\n if (spki[i] !== _X25519_SPKI_PREFIX[i]) {\n return fail(`spkiToRawX25519: byte ${i} does not match the expected X25519 SPKI prefix`);\n }\n }\n return succeed(spki.slice(_X25519_SPKI_PREFIX.length));\n}\n"]}
@@ -435,12 +435,13 @@ declare function anthropicEffortToBudgetTokens(effort: NonNullable<IAnthropicThi
435
435
  * Model IDs for Anthropic thinking-capable models.
436
436
  *
437
437
  * @remarks
438
- * Only thinking-capable lines are listed. The non-tier `@anthropic:haiku` / `@anthropic:fable`
439
- * aliases (reachable via `modelOverride` only) are deliberately omitted — they are not
440
- * documented as thinking-capable, so naming them in a thinking-model filter would be misleading.
438
+ * Only thinking-capable lines are listed. The non-tier `@anthropic:haiku` alias (reachable via
439
+ * `modelOverride` only) is deliberately omitted — it is not documented as thinking-capable, so
440
+ * naming it in a thinking-model filter would be misleading. `claude-fable-5` (the `@anthropic:fable`
441
+ * non-tier alias) is thinking-capable and reachable via `modelOverride`, so it is listed.
441
442
  * @public
442
443
  */
443
- declare type AnthropicThinkingModelNames = 'claude-sonnet-4-5' | 'claude-sonnet-4-6' | 'claude-sonnet-5' | 'claude-opus-4-6' | 'claude-opus-4-7' | 'claude-opus-4-8';
444
+ declare type AnthropicThinkingModelNames = 'claude-sonnet-4-5' | 'claude-sonnet-4-6' | 'claude-sonnet-5' | 'claude-opus-4-6' | 'claude-opus-4-7' | 'claude-opus-4-8' | 'claude-opus-5' | 'claude-fable-5';
444
445
 
445
446
  /**
446
447
  * Recommended OWASP 2023 minimum Argon2id parameters.
@@ -732,6 +733,8 @@ declare namespace CryptoUtils {
732
733
  base64UrlNoPadDecode,
733
734
  base64UrlNoPadEncode,
734
735
  exportPublicKeyAsMultibaseSpki,
736
+ hexDecode,
737
+ hexEncode,
735
738
  importPublicKeyFromMultibaseSpki,
736
739
  isValidMultibaseSpkiPublicKey,
737
740
  MultibaseSpkiPublicKeyRegExp,
@@ -759,6 +762,7 @@ declare namespace CryptoUtils {
759
762
  IArgon2idParams,
760
763
  ARGON2ID_OWASP_MIN,
761
764
  ARGON2ID_PASSPHRASE,
765
+ IArgon2idKeyingOptions,
762
766
  IArgon2idProvider,
763
767
  IEncryptedFile,
764
768
  ICryptoProvider,
@@ -851,25 +855,30 @@ declare const DEFAULT_SECRET_ITERATIONS: number;
851
855
  * {@link CryptoUtils.ICryptoProvider.importKeyPairFromSeed | importKeyPairFromSeed}
852
856
  * for the public contract.
853
857
  *
854
- * For `'ed25519'`: wraps the 32-byte seed in the RFC 8410 PKCS#8 envelope, then
855
- * runs a transient-extractable-then-derive dance so the public key is
856
- * recoverable even when the caller requests a non-extractable private key:
858
+ * For each supported algorithm: wraps the 32-byte seed in the RFC 8410 PKCS#8
859
+ * envelope, then runs a transient-extractable-then-derive dance so the public
860
+ * key is recoverable even when the caller requests a non-extractable private key:
857
861
  *
858
862
  * 1. Import the PKCS#8 as a *transient* extractable private key.
859
863
  * 2. Export it to JWK to read the deterministic public coordinate `x`.
860
- * 3. Import `{ kty: 'OKP', crv: 'Ed25519', x }` as the returned public key.
864
+ * 3. Import `{ kty: 'OKP', crv, x }` as the returned public key.
861
865
  * 4. For the returned private key: reuse the transient key when `extractable`
862
866
  * is `true`; otherwise re-import the same PKCS#8 as a non-extractable key.
863
867
  * The transient extractable key is never returned when `extractable` is
864
868
  * `false`, so seed material cannot escape through it.
865
869
  *
870
+ * The two algorithms differ only in the OID/name/curve/usages (see
871
+ * {@link ISeedAlgorithmParams}); `'x25519'` yields the DH key-agreement keypair
872
+ * (private `'deriveBits'`, public no-usage) that {@link CryptoUtils.HpkeProvider}
873
+ * consumes as its recipient key.
874
+ *
866
875
  * The seed bytes are copied into a freshly allocated PKCS#8 buffer, so the
867
876
  * `BufferSource` handed to WebCrypto is always backed by a plain `ArrayBuffer`
868
877
  * (side-stepping the Node-20 `SharedArrayBuffer`-view rejection) and the
869
878
  * caller's `seed` is never mutated.
870
879
  *
871
- * @param algorithm - The seed-derivable algorithm; only `'ed25519'` is
872
- * supported today. Any other value fails loudly rather than being mis-handled.
880
+ * @param algorithm - The seed-derivable algorithm (`'ed25519'` or `'x25519'`).
881
+ * Any other value fails loudly rather than being mis-handled.
873
882
  * @param seed - The 32-byte secret seed. Any other length fails loudly, before
874
883
  * any WebCrypto call.
875
884
  * @param extractable - Whether the returned private key may be exported.
@@ -1342,7 +1351,7 @@ declare const GCM_AUTH_TAG_SIZE: number;
1342
1351
  declare const GCM_IV_SIZE: number;
1343
1352
 
1344
1353
  /** Model names in the Gemini Flash Image family. @public */
1345
- declare type GeminiFlashImageModelNames = 'gemini-3.1-flash-image-preview';
1354
+ declare type GeminiFlashImageModelNames = 'gemini-3.1-flash-image';
1346
1355
 
1347
1356
  /**
1348
1357
  * Model IDs for Google Gemini thinking-capable models.
@@ -1387,7 +1396,7 @@ declare function getProviderDescriptor(id: string): Result<IAiProviderDescriptor
1387
1396
  declare function getProviderDescriptors(): ReadonlyArray<IAiProviderDescriptor>;
1388
1397
 
1389
1398
  /** Model names in the GPT Image family. @public */
1390
- declare type GptImageModelNames = 'gpt-image-1' | 'gpt-image-1.5';
1399
+ declare type GptImageModelNames = 'gpt-image-1' | 'gpt-image-1.5' | 'gpt-image-2';
1391
1400
 
1392
1401
  /** Quality values for gpt-image-1. @public */
1393
1402
  declare type GptImageQuality = 'low' | 'medium' | 'high' | 'auto';
@@ -1405,6 +1414,34 @@ declare namespace Hash {
1405
1414
  }
1406
1415
  export { Hash }
1407
1416
 
1417
+ /**
1418
+ * Decodes a hexadecimal string (no `0x` prefix) back to a `Uint8Array`.
1419
+ *
1420
+ * Liberal in what it accepts: both lowercase and uppercase hex digits are
1421
+ * allowed (whereas {@link CryptoUtils.hexEncode} always emits lowercase). The
1422
+ * input must have even length and contain only hex digits (`0-9`, `a-f`, `A-F`);
1423
+ * an empty string decodes to an empty `Uint8Array`. Odd length or any non-hex
1424
+ * character fails with error context.
1425
+ *
1426
+ * @param encoded - The hex string to decode.
1427
+ * @returns `Success` with the decoded bytes, or `Failure` with error context.
1428
+ * @public
1429
+ */
1430
+ declare function hexDecode(encoded: string): Result<Uint8Array>;
1431
+
1432
+ /**
1433
+ * Encodes a `Uint8Array` as a lowercase hexadecimal string with no `0x` prefix.
1434
+ *
1435
+ * Each byte becomes exactly two lowercase hex characters (zero-padded), so an
1436
+ * `n`-byte input yields a `2n`-character string. An empty input yields the empty
1437
+ * string. This is the inverse of {@link CryptoUtils.hexDecode}.
1438
+ *
1439
+ * @param data - The binary data to encode.
1440
+ * @returns The lowercase, unprefixed hex string.
1441
+ * @public
1442
+ */
1443
+ declare function hexEncode(data: Uint8Array): string;
1444
+
1408
1445
  /**
1409
1446
  * HPKE base mode (RFC 9180) — `DHKEM(X25519, HKDF-SHA256) + HKDF-SHA256 + AES-256-GCM`.
1410
1447
  *
@@ -2560,6 +2597,33 @@ declare interface IArgon2idKeyDerivationParams {
2560
2597
  readonly parallelism: number;
2561
2598
  }
2562
2599
 
2600
+ /**
2601
+ * Optional keyed-hashing inputs for {@link CryptoUtils.IArgon2idProvider.argon2id | argon2id}
2602
+ * (RFC 9106 §3.1). These are distinct from the cost parameters in
2603
+ * {@link CryptoUtils.IArgon2idParams} — they change the derived output but are
2604
+ * not tuning knobs. Both fields are optional; omitting a field (or passing an
2605
+ * empty `Uint8Array`) is a no-op that leaves the output byte-identical to a call
2606
+ * with no options at all.
2607
+ * @public
2608
+ */
2609
+ declare interface IArgon2idKeyingOptions {
2610
+ /**
2611
+ * Optional secret key K (RFC 9106 keyed hashing, sometimes called a "pepper").
2612
+ * When present and non-empty it is mixed into the hash so that the derived
2613
+ * output cannot be reproduced without also knowing K. Empty or omitted means
2614
+ * no secret. Honored by both the Node and browser backends.
2615
+ */
2616
+ readonly secret?: Uint8Array;
2617
+ /**
2618
+ * Optional associated data X (RFC 9106 §3.1). When present and non-empty it is
2619
+ * mixed into the hash. **Node-only:** the WASM (`hash-wasm`) browser backend
2620
+ * has no associated-data input, so `BrowserArgon2Provider` fails loudly rather
2621
+ * than silently dropping it (which would produce wrong bytes). Empty or omitted
2622
+ * means no associated data and is accepted by both backends.
2623
+ */
2624
+ readonly associatedData?: Uint8Array;
2625
+ }
2626
+
2563
2627
  /**
2564
2628
  * Parameters for Argon2id key derivation (RFC 9106).
2565
2629
  * All fields are required; fgv does not pick defaults silently.
@@ -2607,15 +2671,24 @@ declare interface IArgon2idProvider {
2607
2671
  /**
2608
2672
  * Derives key material from a password using Argon2id (RFC 9106 §3.1).
2609
2673
  *
2610
- * Returns the raw derived bytes as a `Uint8Array`. Both Node and browser
2611
- * implementations produce bit-identical output for identical inputs.
2674
+ * Returns the raw derived bytes as a `Uint8Array`. For the same inputs that
2675
+ * both backends support, the Node and browser implementations produce
2676
+ * byte-identical output. The optional `associatedData` in
2677
+ * {@link CryptoUtils.IArgon2idKeyingOptions} is the one exception: it is
2678
+ * Node-only (the browser WASM backend has no associated-data input), so a call
2679
+ * that supplies non-empty `associatedData` is not portable to the browser
2680
+ * backend. Every input the browser backend *does* support (including the
2681
+ * optional `secret`) is byte-identical across the two.
2612
2682
  *
2613
2683
  * @param password - Password or passphrase. Accepts string (UTF-8) or raw bytes.
2614
2684
  * @param salt - Salt bytes. Must be random and unique per credential (\>= 16 bytes recommended).
2615
2685
  * @param params - Argon2id parameters. Use `ARGON2ID_OWASP_MIN` as a starting point.
2686
+ * @param options - Optional {@link CryptoUtils.IArgon2idKeyingOptions | keyed-hashing inputs}
2687
+ * (secret K and/or associated data X). Omitting them (the default) leaves the
2688
+ * output byte-identical to prior behavior.
2616
2689
  * @returns Success with derived bytes, Failure with error context.
2617
2690
  */
2618
- argon2id(password: Uint8Array | string, salt: Uint8Array, params: IArgon2idParams): Promise<Result<Uint8Array>>;
2691
+ argon2id(password: Uint8Array | string, salt: Uint8Array, params: IArgon2idParams, options?: IArgon2idKeyingOptions): Promise<Result<Uint8Array>>;
2619
2692
  }
2620
2693
 
2621
2694
  /**
@@ -2881,16 +2954,19 @@ declare interface ICryptoProvider {
2881
2954
  * material (key escrow, HD-style derivation, deterministic test vectors) rather
2882
2955
  * than freshly sampled by {@link CryptoUtils.ICryptoProvider.generateKeyPair | generateKeyPair}.
2883
2956
  *
2884
- * For `'ed25519'` the private key *is* its 32-byte seed and the public key is a
2885
- * deterministic function of that seed (RFC 8032), so the returned public key is
2886
- * recovered even when the caller requests a non-extractable private key. The
2887
- * transient extractable key used internally to recover the public half is never
2888
- * returned or logged when `extractable` is `false`.
2889
- * @param algorithm - The {@link CryptoUtils.SeedDerivableAlgorithm | seed-derivable algorithm}.
2890
- * Only `'ed25519'` is supported today; any other value fails loudly with context.
2891
- * @param seed - The secret seed. For `'ed25519'` it must be exactly 32 bytes;
2892
- * any other length fails loudly, before any WebCrypto call. The bytes are copied,
2893
- * not retained or mutated.
2957
+ * For both `'ed25519'` and `'x25519'` the private key *is* its 32-byte seed and
2958
+ * the public key is a deterministic function of that seed (RFC 8032 / RFC 7748),
2959
+ * so the returned public key is recovered even when the caller requests a
2960
+ * non-extractable private key. The transient extractable key used internally to
2961
+ * recover the public half is never returned or logged when `extractable` is
2962
+ * `false`. The returned keys carry the algorithm's usages: `'ed25519'` →
2963
+ * private `'sign'` / public `'verify'`; `'x25519'` → private `'deriveBits'` /
2964
+ * public no-usage (ready to hand to {@link CryptoUtils.HpkeProvider}).
2965
+ * @param algorithm - The {@link CryptoUtils.SeedDerivableAlgorithm | seed-derivable algorithm}
2966
+ * (`'ed25519'` or `'x25519'`); any other value fails loudly with context.
2967
+ * @param seed - The secret seed. It must be exactly 32 bytes for both supported
2968
+ * algorithms; any other length fails loudly, before any WebCrypto call. The
2969
+ * bytes are copied, not retained or mutated.
2894
2970
  * @param extractable - Whether the returned private key may be exported. The
2895
2971
  * returned public key is identical for a given seed regardless of this flag.
2896
2972
  * @returns Success with the derived `CryptoKeyPair`, or Failure with error context.
@@ -3570,6 +3646,19 @@ declare interface IImportKeyOptions extends IImportSecretOptions {
3570
3646
  readonly type?: KeyStoreSymmetricSecretType;
3571
3647
  }
3572
3648
 
3649
+ /**
3650
+ * Options for importing raw opaque bytes via {@link KeyStore.importSecretBytes}.
3651
+ * Extends {@link IImportSecretOptions} with optional initial metadata.
3652
+ * @public
3653
+ */
3654
+ declare interface IImportSecretBytesOptions extends IImportSecretOptions {
3655
+ /**
3656
+ * Optional initial mutable metadata to store with the entry. Can also be set
3657
+ * or replaced later via {@link KeyStore.setSecretMetadata}.
3658
+ */
3659
+ readonly metadata?: JsonValue;
3660
+ }
3661
+
3573
3662
  /**
3574
3663
  * Options for importing a secret.
3575
3664
  * @public
@@ -3850,16 +3939,36 @@ declare interface IKeyStoreSymmetricEntry {
3850
3939
  * The secret data.
3851
3940
  * - For `'encryption-key'`: 32-byte AES-256 key.
3852
3941
  * - For `'api-key'`: UTF-8 encoded API key string (arbitrary length).
3942
+ * - For `'opaque'`: arbitrary raw bytes, stored and returned verbatim.
3853
3943
  */
3854
3944
  readonly key: Uint8Array;
3855
3945
  /**
3856
3946
  * Optional description for this secret.
3857
3947
  */
3858
3948
  readonly description?: string;
3949
+ /**
3950
+ * Optional mutable, non-secret-by-contract metadata for this entry. Set via
3951
+ * {@link CryptoUtils.KeyStore.KeyStore.setSecretMetadata} without touching the
3952
+ * secret `key` bytes.
3953
+ *
3954
+ * "Non-secret by contract" means the field is intended for lifecycle metadata
3955
+ * (rotation timestamps, labels, provenance) rather than secret material.
3956
+ * PHYSICALLY it lives inside the vault's AES-GCM ciphertext alongside the key
3957
+ * bytes — the vault has no plaintext index — so it is protected at rest by the
3958
+ * master password; the "non-secret" designation is a usage contract, not a
3959
+ * weaker custody class.
3960
+ */
3961
+ readonly metadata?: JsonValue;
3859
3962
  /**
3860
3963
  * When this secret was added (ISO 8601).
3861
3964
  */
3862
3965
  readonly createdAt: string;
3966
+ /**
3967
+ * When this entry was last mutated (ISO 8601). Stamped on any metadata
3968
+ * mutation (see {@link CryptoUtils.KeyStore.KeyStore.setSecretMetadata}).
3969
+ * Absent on entries that have never been mutated since creation.
3970
+ */
3971
+ readonly updatedAt?: string;
3863
3972
  }
3864
3973
 
3865
3974
  /**
@@ -3899,10 +4008,22 @@ declare interface IKeyStoreSymmetricEntryJson {
3899
4008
  * Optional description.
3900
4009
  */
3901
4010
  readonly description?: string;
4011
+ /**
4012
+ * Optional mutable, non-secret-by-contract metadata for this entry. Present
4013
+ * only on `'keystore-v3'` vaults whose entry carried metadata; a v1/v2 vault
4014
+ * omits the field. Physically carried inside the vault ciphertext (the vault
4015
+ * has no plaintext index).
4016
+ */
4017
+ readonly metadata?: JsonValue;
3902
4018
  /**
3903
4019
  * When this secret was added (ISO 8601).
3904
4020
  */
3905
4021
  readonly createdAt: string;
4022
+ /**
4023
+ * When this entry was last mutated (ISO 8601). Present only on `'keystore-v3'`
4024
+ * vaults whose entry has been mutated since creation.
4025
+ */
4026
+ readonly updatedAt?: string;
3906
4027
  }
3907
4028
 
3908
4029
  /**
@@ -3977,7 +4098,7 @@ declare type IModelFamilyConfig = IGptImageModelOptions | IGrokImagineModelOptio
3977
4098
  * const simple: ModelSpec = 'grok-4.3';
3978
4099
  *
3979
4100
  * // Context-aware — different model per quality tier:
3980
- * const split: ModelSpec = { base: 'gpt-5.4-mini', advanced: 'gpt-5.5', frontier: 'gpt-5.5-pro' };
4101
+ * const split: ModelSpec = { base: 'gpt-5.6-luna', advanced: 'gpt-5.6-terra', frontier: 'gpt-5.6-sol' };
3981
4102
  *
3982
4103
  * // Nested — a tier branch is itself a spec:
3983
4104
  * const nested: ModelSpec = { base: 'grok-fast', advanced: { base: 'grok-r', image: 'grok-v' } };
@@ -4859,6 +4980,7 @@ declare namespace KeyStore {
4859
4980
  IAddSecretOptions,
4860
4981
  IImportSecretOptions,
4861
4982
  IImportKeyOptions,
4983
+ IImportSecretBytesOptions,
4862
4984
  IAddSecretFromPasswordOptions,
4863
4985
  DEFAULT_SECRET_ITERATIONS,
4864
4986
  IAddSecretFromPasswordResult,
@@ -5179,6 +5301,50 @@ declare class KeyStore_2 implements IEncryptionProvider {
5179
5301
  * @public
5180
5302
  */
5181
5303
  getApiKey(name: string): Result<string>;
5304
+ /**
5305
+ * Imports arbitrary raw bytes into the vault with type `'opaque'`.
5306
+ *
5307
+ * Unlike {@link KeyStore.importSecret} (which enforces a 32-byte AES-256 key)
5308
+ * and {@link KeyStore.importApiKey} (which UTF-8 encodes a string), this
5309
+ * accepts a byte buffer of any length and stores it verbatim — including
5310
+ * embedded NUL bytes. Use it for byte blobs that are neither a fixed-size key
5311
+ * nor a UTF-8 string (e.g. a serialized credential bundle), so they are not
5312
+ * mistyped as `'api-key'`.
5313
+ *
5314
+ * @param name - Unique name for the secret
5315
+ * @param bytes - The raw bytes to store
5316
+ * @param options - Optional description, initial metadata, whether to replace existing
5317
+ * @returns Success with entry, Failure if locked, empty, or exists and !replace
5318
+ * @public
5319
+ */
5320
+ importSecretBytes(name: string, bytes: Uint8Array, options?: IImportSecretBytesOptions): Promise<Result<IAddSecretResult>>;
5321
+ /**
5322
+ * Retrieves the raw stored bytes for a symmetric entry, returned verbatim
5323
+ * (never UTF-8 decoded). Intended for `'opaque'` entries but works for any
5324
+ * symmetric type; asymmetric-keypair entries carry no raw key material and
5325
+ * are rejected.
5326
+ *
5327
+ * @param name - Name of the secret
5328
+ * @returns Success with a copy of the stored bytes, Failure if not found,
5329
+ * locked, or the entry is asymmetric
5330
+ * @public
5331
+ */
5332
+ getSecretBytes(name: string): Result<Uint8Array>;
5333
+ /**
5334
+ * Replaces the mutable metadata on a symmetric entry and stamps `updatedAt`
5335
+ * with the current timestamp. Does NOT touch the secret `key` bytes — the
5336
+ * material reads back identically afterward.
5337
+ *
5338
+ * Metadata is non-secret by contract but physically lives inside the vault
5339
+ * ciphertext (see {@link CryptoUtils.KeyStore.IKeyStoreSymmetricEntry.metadata}).
5340
+ *
5341
+ * @param name - Name of the secret to update
5342
+ * @param value - New metadata value (replaces any prior metadata)
5343
+ * @returns Success with the updated entry, Failure if not found, locked, or
5344
+ * the entry is asymmetric
5345
+ * @public
5346
+ */
5347
+ setSecretMetadata(name: string, value: JsonValue): Result<IKeyStoreSymmetricEntry>;
5182
5348
  /**
5183
5349
  * Adds a new asymmetric keypair to the vault. Storage-first: the private key
5184
5350
  * is stored under a freshly-minted `id` before the public-key vault entry is
@@ -5364,7 +5530,7 @@ declare class KeyStore_2 implements IEncryptionProvider {
5364
5530
  }
5365
5531
 
5366
5532
  /**
5367
- * Current format version constant. New vaults are written as `'keystore-v2'`.
5533
+ * Current format version constant. New vaults are written as `'keystore-v3'`.
5368
5534
  * @public
5369
5535
  */
5370
5536
  declare const KEYSTORE_FORMAT: KeyStoreFormat;
@@ -5408,9 +5574,14 @@ declare const keystoreFile: Converter<IKeyStoreFile>;
5408
5574
  * asymmetric-keypair entries. A strict superset of v1 — the field is
5409
5575
  * optional, so a v2 reader opens a v1 vault with no special-casing, and a
5410
5576
  * v1 vault opened and re-saved is silently upgraded to v2.
5577
+ * - `'keystore-v3'`: adds the `'opaque'` symmetric secret type and the optional
5578
+ * `metadata` / `updatedAt` fields on symmetric entries. A strict superset of
5579
+ * v1/v2 — the additions are optional (and `'opaque'` is simply a new enum
5580
+ * value), so a v3 reader opens a v1/v2 vault with no special-casing, and a
5581
+ * v1/v2 vault opened and re-saved is silently upgraded to v3.
5411
5582
  * @public
5412
5583
  */
5413
- declare type KeyStoreFormat = 'keystore-v1' | 'keystore-v2';
5584
+ declare type KeyStoreFormat = 'keystore-v1' | 'keystore-v2' | 'keystore-v3';
5414
5585
 
5415
5586
  /**
5416
5587
  * Converter for {@link CryptoUtils.KeyStore.KeyStoreFormat | key store format} version.
@@ -5468,13 +5639,17 @@ declare const keystoreSymmetricEntryJson: Converter<IKeyStoreSymmetricEntryJson>
5468
5639
  * Discriminator for symmetric secret types stored in the vault.
5469
5640
  * - `'encryption-key'`: A 32-byte AES-256 encryption key.
5470
5641
  * - `'api-key'`: An arbitrary-length API key string (UTF-8 encoded).
5642
+ * - `'opaque'`: Arbitrary raw bytes with no encoding contract — stored and
5643
+ * returned verbatim (never UTF-8 decoded). Use for byte blobs that are
5644
+ * neither a fixed-size AES key nor a UTF-8 string (e.g. a serialized
5645
+ * credential bundle), so they are not mistyped as `'api-key'`.
5471
5646
  * @public
5472
5647
  */
5473
- declare type KeyStoreSymmetricSecretType = 'encryption-key' | 'api-key';
5648
+ declare type KeyStoreSymmetricSecretType = 'encryption-key' | 'api-key' | 'opaque';
5474
5649
 
5475
5650
  /**
5476
5651
  * Converter for {@link CryptoUtils.KeyStore.KeyStoreSymmetricSecretType | symmetric secret type} discriminator.
5477
- * Accepts only `'encryption-key'` and `'api-key'`.
5652
+ * Accepts `'encryption-key'`, `'api-key'`, and `'opaque'`.
5478
5653
  * @public
5479
5654
  */
5480
5655
  declare const keystoreSymmetricSecretType: Converter<KeyStoreSymmetricSecretType>;
@@ -5931,7 +6106,7 @@ declare const nodeCryptoProvider: NodeCryptoProvider;
5931
6106
  * Model IDs for OpenAI thinking-capable models.
5932
6107
  * @public
5933
6108
  */
5934
- declare type OpenAiThinkingModelNames = 'o3' | 'o4-mini' | 'o3-deep-research' | 'o4-mini-deep-research' | 'gpt-5' | 'gpt-5.1' | 'gpt-5.2' | 'gpt-5.4-mini' | 'gpt-5.5' | 'gpt-5.5-pro' | 'gpt-5-pro';
6109
+ declare type OpenAiThinkingModelNames = 'o3' | 'o4-mini' | 'o3-deep-research' | 'o4-mini-deep-research' | 'gpt-5' | 'gpt-5.1' | 'gpt-5.2' | 'gpt-5.4-mini' | 'gpt-5.5' | 'gpt-5.5-pro' | 'gpt-5.6-sol' | 'gpt-5.6-terra' | 'gpt-5.6-luna' | 'gpt-5-pro';
5935
6110
 
5936
6111
  /**
5937
6112
  * Parses CSV data from a string.
@@ -6286,15 +6461,22 @@ declare type SecretProvider = (secretName: string) => Promise<Result<Uint8Array>
6286
6461
  * derived *deterministically* from a fixed secret seed, for use with
6287
6462
  * {@link CryptoUtils.ICryptoProvider.importKeyPairFromSeed | importKeyPairFromSeed}.
6288
6463
  *
6289
- * Only `'ed25519'` is supported today: an Ed25519 private key *is* a 32-byte
6290
- * seed and its public key is a deterministic function of that seed (RFC 8032),
6291
- * so the same seed always yields the same keypair on every runtime. The type is
6292
- * a proper subset because algorithms like RSA or the NIST curves are not
6293
- * recoverable from a bare seed. It is intentionally left open to grow (e.g.
6294
- * `'x25519'`) without a breaking change.
6464
+ * Two algorithms are supported, both of whose 32-byte private scalar *is* the
6465
+ * seed and whose public key is a deterministic function of it, so the same seed
6466
+ * always yields the same keypair on every runtime:
6467
+ * - `'ed25519'` — signing keypair (RFC 8032). Private usage `'sign'`, public
6468
+ * usage `'verify'`.
6469
+ * - `'x25519'` — Diffie-Hellman key-agreement keypair (RFC 7748), the recipient
6470
+ * keypair consumed by {@link CryptoUtils.HpkeProvider}. Private usage
6471
+ * `'deriveBits'`, public key imported with no usages. Deriving a fixed X25519
6472
+ * recipient keypair from a checked-in seed is what makes a deterministic HPKE
6473
+ * seal/open round-trip vector possible.
6474
+ *
6475
+ * The type is a proper subset of {@link CryptoUtils.KeyPairAlgorithm} because
6476
+ * algorithms like RSA or the NIST curves are not recoverable from a bare seed.
6295
6477
  * @public
6296
6478
  */
6297
- declare type SeedDerivableAlgorithm = 'ed25519';
6479
+ declare type SeedDerivableAlgorithm = 'ed25519' | 'x25519';
6298
6480
 
6299
6481
  /**
6300
6482
  * Default system-prompt suffix appended when {@link AiAssist.IGenerateJsonCompletionParams.promptHint}
@@ -415,7 +415,7 @@ export declare const MODEL_SPEC_BASE_KEY: ModelSpecKey;
415
415
  * const simple: ModelSpec = 'grok-4.3';
416
416
  *
417
417
  * // Context-aware — different model per quality tier:
418
- * const split: ModelSpec = { base: 'gpt-5.4-mini', advanced: 'gpt-5.5', frontier: 'gpt-5.5-pro' };
418
+ * const split: ModelSpec = { base: 'gpt-5.6-luna', advanced: 'gpt-5.6-terra', frontier: 'gpt-5.6-sol' };
419
419
  *
420
420
  * // Nested — a tier branch is itself a spec:
421
421
  * const nested: ModelSpec = { base: 'grok-fast', advanced: { base: 'grok-r', image: 'grok-v' } };
@@ -938,11 +938,11 @@ export type GptImageQuality = 'low' | 'medium' | 'high' | 'auto';
938
938
  /** All accepted quality strings across all providers. @public */
939
939
  export type AiImageQuality = GptImageQuality;
940
940
  /** Model names in the GPT Image family. @public */
941
- export type GptImageModelNames = 'gpt-image-1' | 'gpt-image-1.5';
941
+ export type GptImageModelNames = 'gpt-image-1' | 'gpt-image-1.5' | 'gpt-image-2';
942
942
  /** Model names in the xAI Grok Imagine family. @public */
943
943
  export type GrokImagineModelNames = 'grok-imagine-image' | 'grok-imagine-image-quality';
944
944
  /** Model names in the Gemini Flash Image family. @public */
945
- export type GeminiFlashImageModelNames = 'gemini-3.1-flash-image-preview';
945
+ export type GeminiFlashImageModelNames = 'gemini-3.1-flash-image';
946
946
  /**
947
947
  * Provider-specific config for gpt-image-1.
948
948
  * @public
@@ -1186,17 +1186,18 @@ export interface IAiImageGenerationResponse {
1186
1186
  * Model IDs for Anthropic thinking-capable models.
1187
1187
  *
1188
1188
  * @remarks
1189
- * Only thinking-capable lines are listed. The non-tier `@anthropic:haiku` / `@anthropic:fable`
1190
- * aliases (reachable via `modelOverride` only) are deliberately omitted — they are not
1191
- * documented as thinking-capable, so naming them in a thinking-model filter would be misleading.
1189
+ * Only thinking-capable lines are listed. The non-tier `@anthropic:haiku` alias (reachable via
1190
+ * `modelOverride` only) is deliberately omitted — it is not documented as thinking-capable, so
1191
+ * naming it in a thinking-model filter would be misleading. `claude-fable-5` (the `@anthropic:fable`
1192
+ * non-tier alias) is thinking-capable and reachable via `modelOverride`, so it is listed.
1192
1193
  * @public
1193
1194
  */
1194
- export type AnthropicThinkingModelNames = 'claude-sonnet-4-5' | 'claude-sonnet-4-6' | 'claude-sonnet-5' | 'claude-opus-4-6' | 'claude-opus-4-7' | 'claude-opus-4-8';
1195
+ export type AnthropicThinkingModelNames = 'claude-sonnet-4-5' | 'claude-sonnet-4-6' | 'claude-sonnet-5' | 'claude-opus-4-6' | 'claude-opus-4-7' | 'claude-opus-4-8' | 'claude-opus-5' | 'claude-fable-5';
1195
1196
  /**
1196
1197
  * Model IDs for OpenAI thinking-capable models.
1197
1198
  * @public
1198
1199
  */
1199
- export type OpenAiThinkingModelNames = 'o3' | 'o4-mini' | 'o3-deep-research' | 'o4-mini-deep-research' | 'gpt-5' | 'gpt-5.1' | 'gpt-5.2' | 'gpt-5.4-mini' | 'gpt-5.5' | 'gpt-5.5-pro' | 'gpt-5-pro';
1200
+ export type OpenAiThinkingModelNames = 'o3' | 'o4-mini' | 'o3-deep-research' | 'o4-mini-deep-research' | 'gpt-5' | 'gpt-5.1' | 'gpt-5.2' | 'gpt-5.4-mini' | 'gpt-5.5' | 'gpt-5.5-pro' | 'gpt-5.6-sol' | 'gpt-5.6-terra' | 'gpt-5.6-luna' | 'gpt-5-pro';
1200
1201
  /**
1201
1202
  * Model IDs for Google Gemini thinking-capable models.
1202
1203
  * @public