@oxy.so/contracts 1.1.1 → 1.3.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.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/identity.js +3 -2
- package/dist/cjs/identityMove.js +156 -0
- package/dist/cjs/identityProof.js +164 -0
- package/dist/cjs/identityRecovery.js +51 -0
- package/dist/cjs/index.js +75 -19
- package/dist/cjs/inference/catalogue.js +7 -6
- package/dist/cjs/inference/identifiers.js +3 -1
- package/dist/cjs/inference/providerConnection.js +1 -1
- package/dist/cjs/inference/request.js +15 -1
- package/dist/cjs/inference/streamEvents.js +24 -2
- package/dist/cjs/inference/version.js +1 -1
- package/dist/cjs/userResponse.js +82 -1
- package/dist/cjs/username.js +70 -2
- package/dist/cjs/webIdentityCarrier.js +181 -0
- package/dist/cjs/webauthn.js +14 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/identity.js +3 -2
- package/dist/esm/identityMove.js +148 -0
- package/dist/esm/identityProof.js +159 -0
- package/dist/esm/identityRecovery.js +48 -0
- package/dist/esm/index.js +16 -7
- package/dist/esm/inference/catalogue.js +7 -6
- package/dist/esm/inference/identifiers.js +2 -0
- package/dist/esm/inference/providerConnection.js +2 -2
- package/dist/esm/inference/request.js +14 -0
- package/dist/esm/inference/streamEvents.js +24 -2
- package/dist/esm/inference/version.js +1 -1
- package/dist/esm/userResponse.js +81 -0
- package/dist/esm/username.js +69 -1
- package/dist/esm/webIdentityCarrier.js +178 -0
- package/dist/esm/webauthn.js +14 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/accountGraph.d.ts +7 -7
- package/dist/types/agency.d.ts +24 -24
- package/dist/types/browserHub.d.ts +16 -16
- package/dist/types/deviceDirectory.d.ts +28 -28
- package/dist/types/externalIdentity.d.ts +21 -7
- package/dist/types/identity.d.ts +3 -2
- package/dist/types/identityMove.d.ts +185 -0
- package/dist/types/identityProof.d.ts +156 -0
- package/dist/types/identityRecovery.d.ts +246 -0
- package/dist/types/index.d.ts +14 -8
- package/dist/types/inference/catalogue.d.ts +21 -20
- package/dist/types/inference/embeddings.d.ts +12 -12
- package/dist/types/inference/errors.d.ts +4 -4
- package/dist/types/inference/identifiers.d.ts +2 -0
- package/dist/types/inference/inbox.d.ts +6 -6
- package/dist/types/inference/providerConnection.d.ts +40 -40
- package/dist/types/inference/request.d.ts +84 -36
- package/dist/types/inference/streamEvents.d.ts +69 -13
- package/dist/types/inference/usage.d.ts +8 -8
- package/dist/types/inference/version.d.ts +1 -1
- package/dist/types/keyRotation.d.ts +2 -2
- package/dist/types/oauth.d.ts +30 -30
- package/dist/types/sessionStatus.d.ts +6 -6
- package/dist/types/transparency.d.ts +10 -10
- package/dist/types/userResponse.d.ts +386 -18
- package/dist/types/username.d.ts +25 -2
- package/dist/types/webIdentityCarrier.d.ts +1130 -0
- package/dist/types/webauthn.d.ts +208 -0
- package/package.json +1 -1
- package/dist/cjs/devicePairing.js +0 -138
- package/dist/esm/devicePairing.js +0 -135
- package/dist/types/devicePairing.d.ts +0 -130
package/dist/esm/identity.js
CHANGED
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
* - DID = `did:web:oxy.so:u:<userId>` — anchored on the stable account id, NOT
|
|
15
15
|
* the keypair. The keypair is a *verification method* that maps 1:1 to the
|
|
16
16
|
* existing `authMethods[]`. Custodial (password-only) users get a DID
|
|
17
|
-
* controlled solely by Oxy (`OXY_DID`);
|
|
18
|
-
*
|
|
17
|
+
* controlled solely by Oxy (`OXY_DID`); linking a root makes them
|
|
18
|
+
* self-sovereign (`controller = [userDid]`, ADR 0024 D9). A root is never
|
|
19
|
+
* unlinked back into a custodial account.
|
|
19
20
|
* - Verification methods use the secp256k1 `EcdsaSecp256k1VerificationKey2019`
|
|
20
21
|
* type with `publicKeyHex` for now (a `Multikey`/`publicKeyMultibase` form may
|
|
21
22
|
* be added later — see the plan's open risks).
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity move contract — give a web root to Commons (add it, or keep it only in
|
|
3
|
+
* Commons; ADR 0024 D6).
|
|
4
|
+
*
|
|
5
|
+
* The web (holding the root) shows a QR carrying only a move id; Commons scans.
|
|
6
|
+
* The relay in between must not be able to take the root, so:
|
|
7
|
+
*
|
|
8
|
+
* 1. `POST /identity/move { initiatorCommitment }` — the web publishes only
|
|
9
|
+
* `H(initiatorKey, nonce)`, never the key.
|
|
10
|
+
* 2. `POST /identity/move/:moveId/join { responderEphemeralPublicKey }` —
|
|
11
|
+
* Commons, having read the commitment first, joins with its own key.
|
|
12
|
+
* 3. `POST /identity/move/:moveId/reveal` — only then does the web reveal its
|
|
13
|
+
* key and nonce; Commons checks them against the commitment it read.
|
|
14
|
+
* 4. Both screens show a 6-digit code over `(moveId, both keys, commitment)`;
|
|
15
|
+
* the person confirms on the web that they match.
|
|
16
|
+
* 5. `POST /identity/move/:moveId/seal` — the web seals the phrase entropy under
|
|
17
|
+
* `HKDF(ECDH(both keys), moveId)`, authorized by a one-use root proof over the
|
|
18
|
+
* exact sealed bytes.
|
|
19
|
+
* 6. `POST /identity/move/:moveId/receipt` — Commons stores the root, reads it
|
|
20
|
+
* back from its keychain, and signs a receipt over the move, the root, both
|
|
21
|
+
* keys and the digest of the ciphertext it opened. The server verifies it; the
|
|
22
|
+
* web verifies it again from what IT sealed before removing anything.
|
|
23
|
+
*
|
|
24
|
+
* Why the commitment: without it the relay sees both keys before committing to
|
|
25
|
+
* anything and can grind a substituted key until the two codes agree (~10⁶
|
|
26
|
+
* tries, seconds). With it, the relay must fix the key it shows Commons before
|
|
27
|
+
* learning Commons' key, and the key it shows the web before the web reveals.
|
|
28
|
+
*
|
|
29
|
+
* The server holds a commitment, two ephemeral public keys, an opaque ciphertext
|
|
30
|
+
* and a receipt — nothing that decrypts the ciphertext.
|
|
31
|
+
*
|
|
32
|
+
* Platform-agnostic — zod only, ESM-safe (no `require()`).
|
|
33
|
+
*/
|
|
34
|
+
import { z } from 'zod';
|
|
35
|
+
import { canonicalJson, identityProofSchema } from './identityProof.js';
|
|
36
|
+
/** A move lives this long: one interactive handoff. */
|
|
37
|
+
export const IDENTITY_MOVE_TTL_MS = 5 * 60 * 1000;
|
|
38
|
+
/** 128-bit move id, lowercase hex. */
|
|
39
|
+
export const identityMoveIdSchema = z
|
|
40
|
+
.string()
|
|
41
|
+
.trim()
|
|
42
|
+
.regex(/^[0-9a-f]{32}$/, 'moveId must be 32 lowercase hex characters');
|
|
43
|
+
/** An ephemeral secp256k1 public key, uncompressed lowercase hex. */
|
|
44
|
+
export const identityMoveEphemeralKeySchema = z
|
|
45
|
+
.string()
|
|
46
|
+
.trim()
|
|
47
|
+
.regex(/^04[0-9a-f]{128}$/, 'ephemeral public key must be uncompressed, lowercase hex');
|
|
48
|
+
export const IDENTITY_MOVE_STATUSES = ['pending', 'joined', 'sealed', 'completed', 'cancelled', 'expired'];
|
|
49
|
+
/** The QR payload Commons scans. Carries the move id only. */
|
|
50
|
+
export const IDENTITY_MOVE_QR_PREFIX = 'oxycommons://move?id=';
|
|
51
|
+
const hex64 = (label) => z.string().trim().regex(/^[0-9a-f]{64}$/, `${label} must be 64 lowercase hex characters`);
|
|
52
|
+
export const identityMoveCreateRequestSchema = z
|
|
53
|
+
.object({
|
|
54
|
+
/** `H(initiator ephemeral key, nonce)` — the key itself is revealed only after Commons joins. */
|
|
55
|
+
initiatorCommitment: hex64('initiatorCommitment'),
|
|
56
|
+
})
|
|
57
|
+
.strict();
|
|
58
|
+
export const identityMoveCreateResponseSchema = z.object({
|
|
59
|
+
moveId: identityMoveIdSchema,
|
|
60
|
+
expiresAt: z.string().datetime(),
|
|
61
|
+
});
|
|
62
|
+
export const identityMoveJoinRequestSchema = z.object({
|
|
63
|
+
responderEphemeralPublicKey: identityMoveEphemeralKeySchema,
|
|
64
|
+
});
|
|
65
|
+
/** After Commons joined: the committed key and its nonce. */
|
|
66
|
+
export const identityMoveRevealRequestSchema = z
|
|
67
|
+
.object({
|
|
68
|
+
initiatorEphemeralPublicKey: identityMoveEphemeralKeySchema,
|
|
69
|
+
commitmentNonce: hex64('commitmentNonce'),
|
|
70
|
+
})
|
|
71
|
+
.strict();
|
|
72
|
+
export const identityMoveSealRequestSchema = z
|
|
73
|
+
.object({
|
|
74
|
+
/** 24-byte XChaCha20-Poly1305 nonce, hex. */
|
|
75
|
+
nonce: z.string().trim().regex(/^[0-9a-f]{48}$/, 'nonce must be 48 lowercase hex characters'),
|
|
76
|
+
/** The 16–32-byte phrase entropy (12–24 words), tag appended, hex. */
|
|
77
|
+
ciphertext: z
|
|
78
|
+
.string()
|
|
79
|
+
.trim()
|
|
80
|
+
.regex(/^(?:[0-9a-f]{64}|[0-9a-f]{72}|[0-9a-f]{80}|[0-9a-f]{88}|[0-9a-f]{96})$/, 'ciphertext has an unsupported length'),
|
|
81
|
+
/**
|
|
82
|
+
* Root proof (`identity_move_seal`), payload `{ moveId, nonce, ciphertext }`,
|
|
83
|
+
* over a one-use challenge from `POST /identity/proof-challenge`.
|
|
84
|
+
*/
|
|
85
|
+
proof: identityProofSchema,
|
|
86
|
+
})
|
|
87
|
+
.strict();
|
|
88
|
+
export const identityMoveReceiptRequestSchema = z
|
|
89
|
+
.object({
|
|
90
|
+
/** Root signature over `buildMoveReceiptMessage(...)`. */
|
|
91
|
+
signature: z.string().trim().min(1).max(512),
|
|
92
|
+
})
|
|
93
|
+
.strict();
|
|
94
|
+
export const identityMoveStateSchema = z.object({
|
|
95
|
+
moveId: identityMoveIdSchema,
|
|
96
|
+
status: z.enum(IDENTITY_MOVE_STATUSES),
|
|
97
|
+
initiatorCommitment: z.string(),
|
|
98
|
+
initiatorCommitmentNonce: z.string().nullable(),
|
|
99
|
+
publicKey: z.string().regex(/^04[0-9a-f]{128}$/),
|
|
100
|
+
initiatorEphemeralPublicKey: identityMoveEphemeralKeySchema.nullable(),
|
|
101
|
+
responderEphemeralPublicKey: identityMoveEphemeralKeySchema.nullable(),
|
|
102
|
+
nonce: z.string().nullable(),
|
|
103
|
+
ciphertext: z.string().nullable(),
|
|
104
|
+
receiptSignature: z.string().nullable(),
|
|
105
|
+
expiresAt: z.string().datetime(),
|
|
106
|
+
});
|
|
107
|
+
/**
|
|
108
|
+
* The initiator's commitment input, `canonicalJson({ v, purpose, key, nonce })`,
|
|
109
|
+
* which the caller hashes with SHA-256.
|
|
110
|
+
*/
|
|
111
|
+
export function buildMoveCommitmentInput(initiatorEphemeralPublicKey, nonce) {
|
|
112
|
+
return canonicalJson({
|
|
113
|
+
v: 2,
|
|
114
|
+
purpose: 'oxy-identity-move-initiator-commitment',
|
|
115
|
+
initiatorEphemeralPublicKey: initiatorEphemeralPublicKey.toLowerCase(),
|
|
116
|
+
nonce: nonce.toLowerCase(),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/** The bytes both sides hash for the 6-digit code. */
|
|
120
|
+
export function buildMoveSasInput(input) {
|
|
121
|
+
return canonicalJson({
|
|
122
|
+
v: 'oxy-identity-transfer-sas-v2',
|
|
123
|
+
moveId: input.moveId.toLowerCase(),
|
|
124
|
+
initiator: input.initiatorEphemeralPublicKey.toLowerCase(),
|
|
125
|
+
responder: input.responderEphemeralPublicKey.toLowerCase(),
|
|
126
|
+
commitment: input.initiatorCommitment.toLowerCase(),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/** The ciphertext digest input the receipt binds: `canonicalJson({ nonce, ciphertext })`. */
|
|
130
|
+
export function buildMoveCiphertextDigestInput(sealed) {
|
|
131
|
+
return canonicalJson({ nonce: sealed.nonce.toLowerCase(), ciphertext: sealed.ciphertext.toLowerCase() });
|
|
132
|
+
}
|
|
133
|
+
/** The payload a seal proof digests. */
|
|
134
|
+
export function buildMoveSealPayload(moveId, sealed) {
|
|
135
|
+
return { moveId: moveId.toLowerCase(), nonce: sealed.nonce.toLowerCase(), ciphertext: sealed.ciphertext.toLowerCase() };
|
|
136
|
+
}
|
|
137
|
+
/** The exact bytes a receipt signs. */
|
|
138
|
+
export function buildMoveReceiptMessage(input) {
|
|
139
|
+
return canonicalJson({
|
|
140
|
+
v: 2,
|
|
141
|
+
domain: 'oxy-identity-move-receipt',
|
|
142
|
+
moveId: input.moveId.toLowerCase(),
|
|
143
|
+
rootPublicKey: input.rootPublicKey.toLowerCase(),
|
|
144
|
+
initiator: input.initiatorEphemeralPublicKey.toLowerCase(),
|
|
145
|
+
responder: input.responderEphemeralPublicKey.toLowerCase(),
|
|
146
|
+
ciphertextDigest: input.ciphertextDigest.toLowerCase(),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity proof contract — the ONE signed format for operations on a personal root.
|
|
3
|
+
*
|
|
4
|
+
* ADR 0024 D7. A proof is a signature by the root key over the canonical bytes
|
|
5
|
+
* of {@link IdentityProofClaims}. Every field the verifier cares about is IN the
|
|
6
|
+
* signed bytes, so a signature cannot be moved to another operation, account,
|
|
7
|
+
* root, payload, revision or audience, and the one-use `challenge` means it
|
|
8
|
+
* cannot be replayed either (a timestamp window is not replay protection).
|
|
9
|
+
*
|
|
10
|
+
* Both sides build the bytes with {@link buildIdentityProofMessage} and hash
|
|
11
|
+
* payloads with {@link canonicalJson}; neither writes its own JSON template, so
|
|
12
|
+
* the client and the verifier cannot drift apart.
|
|
13
|
+
*
|
|
14
|
+
* Platform-agnostic — zod only, ESM-safe (no `require()`), no hashing here (the
|
|
15
|
+
* caller hashes `canonicalJson(payload)` with SHA-256 using its platform's
|
|
16
|
+
* primitive and passes the hex digest in).
|
|
17
|
+
*/
|
|
18
|
+
import { z } from 'zod';
|
|
19
|
+
export const IDENTITY_PROOF_VERSION = 2;
|
|
20
|
+
export const IDENTITY_PROOF_DOMAIN = 'oxy-identity-proof';
|
|
21
|
+
/** The audience every API-verified identity proof names. */
|
|
22
|
+
export const IDENTITY_PROOF_AUDIENCE = 'oxy-api/identity';
|
|
23
|
+
/** How long a proof challenge lives: one interactive ceremony. */
|
|
24
|
+
export const IDENTITY_PROOF_CHALLENGE_TTL_MS = 5 * 60 * 1000;
|
|
25
|
+
/**
|
|
26
|
+
* Everything a root proof may authorize. A challenge is minted for exactly one
|
|
27
|
+
* action and spent only by a proof for that action.
|
|
28
|
+
*/
|
|
29
|
+
export const IDENTITY_PROOF_ACTIONS = {
|
|
30
|
+
/** A keyless account's FIRST root, stored with its web envelope. */
|
|
31
|
+
establish: 'web_envelope_establish',
|
|
32
|
+
/** Replace the web envelope (add or remove a wrap, re-seal). */
|
|
33
|
+
put: 'web_envelope_put',
|
|
34
|
+
/** Record that the recovery material is written down. */
|
|
35
|
+
phraseConfirmed: 'web_envelope_phrase_confirmed',
|
|
36
|
+
/** Record that the recovery material re-derived the root. */
|
|
37
|
+
recoveryVerified: 'web_envelope_recovery_verified',
|
|
38
|
+
/** Remove the web holder. */
|
|
39
|
+
delete: 'web_envelope_delete',
|
|
40
|
+
/** Link a keyless account's first root without a web envelope (`POST /auth/link`). */
|
|
41
|
+
link: 'link_identity',
|
|
42
|
+
/** Create a personal account together with its root (passkey sign-up). */
|
|
43
|
+
enroll: 'enroll_identity',
|
|
44
|
+
/** Prove the root to start signed-out recovery. */
|
|
45
|
+
recoverStart: 'recover_account_start',
|
|
46
|
+
/** Bind the new passkey and envelope when completing signed-out recovery. */
|
|
47
|
+
recoverComplete: 'recover_account_complete',
|
|
48
|
+
/** Seal the root for the Commons device that joined a move (payload: move id + sealed bytes). */
|
|
49
|
+
moveSeal: 'identity_move_seal',
|
|
50
|
+
};
|
|
51
|
+
export const IDENTITY_PROOF_ACTION_VALUES = Object.values(IDENTITY_PROOF_ACTIONS);
|
|
52
|
+
const HEX_DIGEST = /^[0-9a-f]{64}$/;
|
|
53
|
+
const ROOT_KEY = /^04[0-9a-f]{128}$/;
|
|
54
|
+
const CHALLENGE = /^[0-9a-f]{64}$/;
|
|
55
|
+
/**
|
|
56
|
+
* Canonical JSON: object keys sorted by UTF-16 code unit, no whitespace,
|
|
57
|
+
* `undefined` members omitted, arrays in order. Numbers must be finite. This is
|
|
58
|
+
* the ONLY serializer for anything digested into a proof.
|
|
59
|
+
*/
|
|
60
|
+
export function canonicalJson(value) {
|
|
61
|
+
if (value === null)
|
|
62
|
+
return 'null';
|
|
63
|
+
switch (typeof value) {
|
|
64
|
+
case 'string':
|
|
65
|
+
case 'boolean':
|
|
66
|
+
return JSON.stringify(value);
|
|
67
|
+
case 'number':
|
|
68
|
+
if (!Number.isFinite(value))
|
|
69
|
+
throw new Error('canonicalJson: non-finite number');
|
|
70
|
+
return JSON.stringify(value);
|
|
71
|
+
case 'object': {
|
|
72
|
+
if (Array.isArray(value)) {
|
|
73
|
+
return `[${value.map((entry) => (entry === undefined ? 'null' : canonicalJson(entry))).join(',')}]`;
|
|
74
|
+
}
|
|
75
|
+
const record = value;
|
|
76
|
+
const keys = Object.keys(record)
|
|
77
|
+
.filter((key) => record[key] !== undefined)
|
|
78
|
+
.sort();
|
|
79
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(',')}}`;
|
|
80
|
+
}
|
|
81
|
+
default:
|
|
82
|
+
throw new Error(`canonicalJson: unsupported ${typeof value}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The exact bytes a root signs. Throws on a malformed claim rather than signing
|
|
87
|
+
* (or verifying) something ambiguous.
|
|
88
|
+
*/
|
|
89
|
+
export function buildIdentityProofMessage(claims) {
|
|
90
|
+
if (!IDENTITY_PROOF_ACTION_VALUES.includes(claims.action))
|
|
91
|
+
throw new Error('identity proof: unknown action');
|
|
92
|
+
if (!claims.subject || !claims.actor)
|
|
93
|
+
throw new Error('identity proof: subject and actor are required');
|
|
94
|
+
if (!ROOT_KEY.test(claims.rootPublicKey))
|
|
95
|
+
throw new Error('identity proof: rootPublicKey must be canonical');
|
|
96
|
+
if (claims.payloadDigest !== null && !HEX_DIGEST.test(claims.payloadDigest)) {
|
|
97
|
+
throw new Error('identity proof: payloadDigest must be a lowercase SHA-256 hex digest');
|
|
98
|
+
}
|
|
99
|
+
if (claims.expectedRevision !== null && (!Number.isSafeInteger(claims.expectedRevision) || claims.expectedRevision < 0)) {
|
|
100
|
+
throw new Error('identity proof: expectedRevision must be a non-negative integer');
|
|
101
|
+
}
|
|
102
|
+
if (!CHALLENGE.test(claims.challenge))
|
|
103
|
+
throw new Error('identity proof: challenge must be 64 lowercase hex characters');
|
|
104
|
+
if (!Number.isSafeInteger(claims.expiresAt) || claims.expiresAt <= 0)
|
|
105
|
+
throw new Error('identity proof: expiresAt must be unix milliseconds');
|
|
106
|
+
return canonicalJson({
|
|
107
|
+
v: IDENTITY_PROOF_VERSION,
|
|
108
|
+
domain: IDENTITY_PROOF_DOMAIN,
|
|
109
|
+
action: claims.action,
|
|
110
|
+
subject: claims.subject,
|
|
111
|
+
actor: claims.actor,
|
|
112
|
+
rootPublicKey: claims.rootPublicKey,
|
|
113
|
+
payloadDigest: claims.payloadDigest,
|
|
114
|
+
expectedRevision: claims.expectedRevision,
|
|
115
|
+
audience: claims.audience,
|
|
116
|
+
challenge: claims.challenge,
|
|
117
|
+
expiresAt: claims.expiresAt,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/** The proof as it travels: the signature plus the two claims the verifier cannot derive. */
|
|
121
|
+
export const identityProofSchema = z.object({
|
|
122
|
+
v: z.literal(IDENTITY_PROOF_VERSION),
|
|
123
|
+
challenge: z.string().trim().regex(CHALLENGE, 'challenge must be 64 lowercase hex characters'),
|
|
124
|
+
expiresAt: z.number().int().positive(),
|
|
125
|
+
signature: z.string().trim().min(1).max(512),
|
|
126
|
+
});
|
|
127
|
+
/** `POST /identity/proof-challenge` */
|
|
128
|
+
export const identityProofChallengeRequestSchema = z.object({
|
|
129
|
+
action: z.enum(IDENTITY_PROOF_ACTION_VALUES),
|
|
130
|
+
});
|
|
131
|
+
export const identityProofChallengeResponseSchema = z.object({
|
|
132
|
+
challenge: z.string().regex(CHALLENGE),
|
|
133
|
+
expiresAt: z.number().int().positive(),
|
|
134
|
+
audience: z.string().min(1),
|
|
135
|
+
});
|
|
136
|
+
/**
|
|
137
|
+
* Stable error codes the root routes answer with (`error.code` in the API error
|
|
138
|
+
* body). Clients map these through their localization, never the English message.
|
|
139
|
+
*/
|
|
140
|
+
export const IDENTITY_ERROR_CODES = {
|
|
141
|
+
proofInvalid: 'IDENTITY_PROOF_INVALID',
|
|
142
|
+
revisionConflict: 'IDENTITY_ENVELOPE_REVISION_CONFLICT',
|
|
143
|
+
rootAlreadyLinked: 'IDENTITY_ROOT_ALREADY_LINKED',
|
|
144
|
+
rootLinkedElsewhere: 'IDENTITY_ROOT_LINKED_ELSEWHERE',
|
|
145
|
+
noRoot: 'IDENTITY_NO_ROOT',
|
|
146
|
+
freshFactorRequired: 'IDENTITY_FRESH_FACTOR_REQUIRED',
|
|
147
|
+
lastWebHolder: 'IDENTITY_LAST_WEB_HOLDER',
|
|
148
|
+
enrollmentRequired: 'IDENTITY_ENROLLMENT_REQUIRED',
|
|
149
|
+
enrollmentInvalid: 'IDENTITY_ENROLLMENT_INVALID',
|
|
150
|
+
notPersonal: 'IDENTITY_NOT_PERSONAL_ACCOUNT',
|
|
151
|
+
recoveryFailed: 'IDENTITY_RECOVERY_FAILED',
|
|
152
|
+
};
|
|
153
|
+
export const identityRootStatusSchema = z.object({
|
|
154
|
+
rootLinked: z.boolean(),
|
|
155
|
+
webHolder: z.object({ passkeys: z.number().int().nonnegative(), verifiedPasskeys: z.number().int().nonnegative() }).nullable(),
|
|
156
|
+
hasPhrase: z.boolean().nullable(),
|
|
157
|
+
phraseConfirmedAt: z.string().datetime().nullable(),
|
|
158
|
+
recoveryVerifiedAt: z.string().datetime().nullable(),
|
|
159
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signed-out recovery contract (ADR 0024 D5) — get an EXISTING account back from
|
|
3
|
+
* its root alone, with no passkey, session or email.
|
|
4
|
+
*
|
|
5
|
+
* 1. `POST /identity/recovery/challenge` → a one-use challenge. It says nothing
|
|
6
|
+
* about any account.
|
|
7
|
+
* 2. The holder derives the root locally from recovery material (12/24 words or a
|
|
8
|
+
* raw key) and signs `recover_account_start` over that challenge:
|
|
9
|
+
* `POST /identity/recovery/start { publicKey, proof }`. Only a valid proof
|
|
10
|
+
* learns which account the root belongs to; the response carries passkey
|
|
11
|
+
* registration options for THAT account and a short-lived ticket.
|
|
12
|
+
* 3. The holder creates the passkey, seals the root under it (PRF), and signs
|
|
13
|
+
* `recover_account_complete` over the registration challenge and the envelope
|
|
14
|
+
* digest: `POST /identity/recovery/complete`. The passkey, the envelope and a
|
|
15
|
+
* session are created in one transaction.
|
|
16
|
+
*
|
|
17
|
+
* The recovery material and the root never leave the holder. Platform-agnostic —
|
|
18
|
+
* zod only, ESM-safe.
|
|
19
|
+
*/
|
|
20
|
+
import { z } from 'zod';
|
|
21
|
+
import { identityProofSchema } from './identityProof.js';
|
|
22
|
+
import { webIdentityEnvelopeSchema, webIdentityPublicKeySchema } from './webIdentityCarrier.js';
|
|
23
|
+
/** A recovery attempt lives this long between steps. */
|
|
24
|
+
export const IDENTITY_RECOVERY_TTL_MS = 5 * 60 * 1000;
|
|
25
|
+
export const identityRecoveryChallengeResponseSchema = z.object({
|
|
26
|
+
challenge: z.string().regex(/^[0-9a-f]{64}$/),
|
|
27
|
+
expiresAt: z.number().int().positive(),
|
|
28
|
+
});
|
|
29
|
+
export const identityRecoveryStartRequestSchema = z
|
|
30
|
+
.object({
|
|
31
|
+
publicKey: webIdentityPublicKeySchema,
|
|
32
|
+
/** `recover_account_start`, subject `root:<publicKey>`, actor `anonymous`. */
|
|
33
|
+
proof: identityProofSchema,
|
|
34
|
+
})
|
|
35
|
+
.strict();
|
|
36
|
+
export const identityRecoveryCompleteRequestSchema = z
|
|
37
|
+
.object({
|
|
38
|
+
ticket: z.string().regex(/^[0-9a-f]{64}$/),
|
|
39
|
+
/** The browser `RegistrationResponseJSON`, verified by the API's WebAuthn library. */
|
|
40
|
+
response: z.record(z.string(), z.unknown()),
|
|
41
|
+
/** The root sealed under the new passkey — exactly one wrap, for that credential. */
|
|
42
|
+
envelope: webIdentityEnvelopeSchema,
|
|
43
|
+
/** `recover_account_complete`, subject = account id, actor `credential:<id>`, challenge = registration challenge (hex). */
|
|
44
|
+
proof: identityProofSchema,
|
|
45
|
+
deviceName: z.string().trim().max(100).optional(),
|
|
46
|
+
deviceFingerprint: z.string().trim().max(512).optional(),
|
|
47
|
+
})
|
|
48
|
+
.strict();
|
package/dist/esm/index.js
CHANGED
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
* expo, no `require()` in the ESM build.
|
|
11
11
|
*/
|
|
12
12
|
export { ACCOUNT_KINDS, accountKindSchema, CHILD_ACCOUNT_KINDS, childAccountKindSchema, isAccountKind, isDelegatedActAsEligibleKind, isOperatorSwitchTargetKind, ACCOUNT_CATEGORY_IDS, ACCOUNT_CATEGORY_KINDS, accountCategoriesSchema, accountCategoryIdSchema, isSelectableAccountCategoryId, kindAcceptsAccountCategories, MAX_ACCOUNT_CATEGORIES, newlyAddedRetiredCategories, RETIRED_ACCOUNT_CATEGORY_IDS, SELECTABLE_ACCOUNT_CATEGORY_IDS, createAccountRequestSchema, } from './accountGraph.js';
|
|
13
|
-
export { usernameSchema, usernameSchemaForAccountKind, isValidUsername, stripDisallowedUsernameCharacters, applyBotUsernameSuffix, USERNAME_MIN_LENGTH, USERNAME_MAX_LENGTH, USERNAME_INVALID_MESSAGE, BOT_USERNAME_INVALID_MESSAGE, } from './username.js';
|
|
13
|
+
export { usernameSchema, usernameSchemaForAccountKind, isValidUsername, stripDisallowedUsernameCharacters, applyBotUsernameSuffix, USERNAME_MIN_LENGTH, USERNAME_MAX_LENGTH, USERNAME_INVALID_MESSAGE, BOT_USERNAME_INVALID_MESSAGE, RESERVED_USERNAME_MESSAGE, NUMERIC_USERNAME_MESSAGE, } from './username.js';
|
|
14
14
|
export {
|
|
15
15
|
// Schemas
|
|
16
|
-
userNameSchema, userRelationshipSchema, themePreferenceSchema, userResponseSchema, userProfileUpdateSchema, currentUserResponseSchema, deviceLinkedSessionSchema, deviceLinkedSessionsResponseSchema,
|
|
16
|
+
userNameSchema, userRelationshipSchema, themePreferenceSchema, dateOfBirthSchema, userResponseSchema, userProfileUpdateSchema, currentUserResponseSchema, deviceLinkedSessionSchema, deviceLinkedSessionsResponseSchema,
|
|
17
17
|
// Helpers
|
|
18
18
|
resolveUserId, safeParseContract, } from './userResponse.js';
|
|
19
19
|
export {
|
|
@@ -81,6 +81,18 @@ export {
|
|
|
81
81
|
// Schemas — encrypted off-device identity backup (b3 Feature 1)
|
|
82
82
|
backupLookupIdSchema, encryptedBackupEnvelopeSchema, backupUploadRequestSchema, backupStatusResponseSchema, } from './keyRecovery.js';
|
|
83
83
|
export {
|
|
84
|
+
// Schemas — web identity carrier (one identity, two carriers)
|
|
85
|
+
WEB_IDENTITY_ENVELOPE_VERSION, WEB_IDENTITY_SECRET_KINDS, webIdentityPublicKeySchema, webauthnCredentialIdSchema, webauthnRpIdSchema, webIdentityWrapSchema, webIdentityEnvelopeSchema, webIdentityEnvelopeUploadSchema, webIdentityHolderSchema, webIdentityEnvelopeResponseSchema, webIdentityEnvelopeProofFieldsSchema, webIdentityEnvelopeActionSchema, webIdentityEnvelopePutSchema, webauthnAssertionResponseSchema, webIdentityEnvelopeEstablishSchema, } from './webIdentityCarrier.js';
|
|
86
|
+
export {
|
|
87
|
+
// Identity proofs (ADR 0024 D7) — the one signed format for root operations
|
|
88
|
+
IDENTITY_PROOF_VERSION, IDENTITY_PROOF_DOMAIN, IDENTITY_PROOF_AUDIENCE, IDENTITY_PROOF_CHALLENGE_TTL_MS, IDENTITY_PROOF_ACTIONS, IDENTITY_PROOF_ACTION_VALUES, IDENTITY_ERROR_CODES, canonicalJson, buildIdentityProofMessage, identityProofSchema, identityProofChallengeRequestSchema, identityProofChallengeResponseSchema, identityRootStatusSchema, } from './identityProof.js';
|
|
89
|
+
export {
|
|
90
|
+
// Signed-out recovery (ADR 0024 D5)
|
|
91
|
+
IDENTITY_RECOVERY_TTL_MS, identityRecoveryChallengeResponseSchema, identityRecoveryStartRequestSchema, identityRecoveryCompleteRequestSchema, } from './identityRecovery.js';
|
|
92
|
+
export {
|
|
93
|
+
// Schemas — moving a web identity into Commons
|
|
94
|
+
IDENTITY_MOVE_TTL_MS, IDENTITY_MOVE_STATUSES, IDENTITY_MOVE_QR_PREFIX, identityMoveRevealRequestSchema, buildMoveCommitmentInput, buildMoveSasInput, buildMoveSealPayload, buildMoveCiphertextDigestInput, buildMoveReceiptMessage, identityMoveIdSchema, identityMoveEphemeralKeySchema, identityMoveCreateRequestSchema, identityMoveCreateResponseSchema, identityMoveJoinRequestSchema, identityMoveSealRequestSchema, identityMoveReceiptRequestSchema, identityMoveStateSchema, } from './identityMove.js';
|
|
95
|
+
export {
|
|
84
96
|
// Shared primitives
|
|
85
97
|
updatePlatformSchema, updateStatusSchema, updateAssetStatusSchema, sha256HexSchema, channelNameSchema, runtimeVersionSchema, rolloutPercentSchema,
|
|
86
98
|
// Assets: init + complete
|
|
@@ -95,9 +107,6 @@ export {
|
|
|
95
107
|
// Schemas
|
|
96
108
|
webauthnRegisterOptionsRequestSchema, webauthnLoginOptionsRequestSchema, webauthnRegisterVerifyRequestSchema, webauthnLoginVerifyRequestSchema, } from './webauthn.js';
|
|
97
109
|
export {
|
|
98
|
-
// Schemas
|
|
99
|
-
devicePairingStatusSchema, deviceTransferInitRequestSchema, deviceTransferInitResponseSchema, deviceTransferInfoResponseSchema, deviceTransferApproveRequestSchema, deviceTransferApproveResponseSchema, deviceTransferDenyResponseSchema, } from './devicePairing.js';
|
|
100
|
-
export {
|
|
101
110
|
// Schemas — transparency log (checkpoints + inclusion proofs)
|
|
102
111
|
transparencyCheckpointSignatureSchema, transparencyAnchorSchema, transparencyCheckpointSchema, transparencyInclusionProofSchema, transparencyCheckpointListSchema, } from './transparency.js';
|
|
103
112
|
/* -------------------------------------------------------------------------- */
|
|
@@ -146,10 +155,10 @@ export {
|
|
|
146
155
|
modelDistributionMethodSchema, modelSystemicRiskTierSchema, trainingComputeFlopsSchema, SYSTEMIC_RISK_COMPUTE_THRESHOLD_FLOPS, modelDownstreamDocumentationSchema, modelGpaiDocumentationSchema, modelLineDeclarationSchema, modelReleaseIngestionRequestSchema, modelReleaseIngestionResultSchema, modelDocumentationSchema, } from './inference/modelDocumentation.js';
|
|
147
156
|
export {
|
|
148
157
|
// The normalized Oxy→data-plane request envelope.
|
|
149
|
-
inferenceContentSourceSchema, inferenceContentPartSchema, inferenceToolCallSchema, inferenceMessageRoleSchema, inferenceMessageSchema, inferenceInputSchema, samplingParametersSchema, toolDefinitionSchema, toolChoiceSchema, responseFormatSchema, clientRequestMetadataSchema, inferenceRequestSchema, } from './inference/request.js';
|
|
158
|
+
inferenceContentSourceSchema, inferenceContentPartSchema, inferenceToolCallSchema, inferenceMessageRoleSchema, inferenceMessageSchema, inferenceInputSchema, samplingParametersSchema, toolDefinitionSchema, toolChoiceSchema, responseFormatSchema, clientRequestMetadataSchema, inferenceRequestSchema, inferenceSpeechParametersSchema, } from './inference/request.js';
|
|
150
159
|
export {
|
|
151
160
|
// Normalized SSE events.
|
|
152
|
-
inferenceStreamStartEventSchema, inferenceStreamDeltaEventSchema, inferenceStreamToolCallEventSchema, inferenceStreamUsageEventSchema, inferenceRouteSwitchDetailSchema, inferenceRouteSwitchReasonSchema, inferenceStreamRouteSwitchEventSchema, inferenceStreamErrorEventSchema, inferenceFinishReasonSchema, inferenceStreamDoneEventSchema, inferenceStreamEventSchema, } from './inference/streamEvents.js';
|
|
161
|
+
inferenceStreamStartEventSchema, inferenceStreamDeltaEventSchema, inferenceAudioMediaTypeSchema, MAX_INFERENCE_AUDIO_BYTES, inferenceStreamAudioEventSchema, inferenceStreamToolCallEventSchema, inferenceStreamUsageEventSchema, inferenceRouteSwitchDetailSchema, inferenceRouteSwitchReasonSchema, inferenceStreamRouteSwitchEventSchema, inferenceStreamErrorEventSchema, inferenceFinishReasonSchema, inferenceStreamDoneEventSchema, inferenceStreamEventSchema, } from './inference/streamEvents.js';
|
|
153
162
|
export {
|
|
154
163
|
// Reserve → settle → refund.
|
|
155
164
|
usageReservationRequestSchema, usageReservationStatusSchema, usageReservationSchema, inferenceRequestOutcomeSchema, normalizedUsageReportSchema, usageReceiptSchema, usageRefundSubjectSchema, usageRefundReasonSchema, usageRefundSchema, } from './inference/usage.js';
|
|
@@ -134,12 +134,13 @@ export const inferenceDataPolicySchema = z
|
|
|
134
134
|
}
|
|
135
135
|
});
|
|
136
136
|
/**
|
|
137
|
-
* Who a route may be served to. Availability
|
|
138
|
-
* permission to resell the same provider/model publicly, which is why
|
|
139
|
-
* an explicit scope on the route rather than a boolean derived from
|
|
137
|
+
* Who a route may be served to. Availability to an official Oxy product never
|
|
138
|
+
* implies permission to resell the same provider/model publicly, which is why
|
|
139
|
+
* this is an explicit scope on the route rather than a boolean derived from
|
|
140
|
+
* "it works".
|
|
140
141
|
*/
|
|
141
142
|
export const availabilityScopeSchema = z.enum([
|
|
142
|
-
'
|
|
143
|
+
'platform_internal',
|
|
143
144
|
'public_payg',
|
|
144
145
|
'enterprise',
|
|
145
146
|
'byok_only',
|
|
@@ -332,7 +333,7 @@ export const inferenceProviderSchema = z.object({
|
|
|
332
333
|
export const modelDeploymentSchema = z
|
|
333
334
|
.object({
|
|
334
335
|
/** See `version.ts`: exchanged with the data plane on its own. */
|
|
335
|
-
schemaVersion: z.literal(
|
|
336
|
+
schemaVersion: z.literal(2),
|
|
336
337
|
deploymentId: deploymentIdSchema,
|
|
337
338
|
provider: inferenceProviderSlugSchema,
|
|
338
339
|
/** Always revision-pinned: a deployment serves specific weights. */
|
|
@@ -455,7 +456,7 @@ export const catalogueServingProviderSummarySchema = z
|
|
|
455
456
|
*/
|
|
456
457
|
export const modelCatalogueEntrySchema = z.object({
|
|
457
458
|
/** See `version.ts`: this is the public catalogue response shape. */
|
|
458
|
-
schemaVersion: z.literal(
|
|
459
|
+
schemaVersion: z.literal(3),
|
|
459
460
|
modelId: modelIdSchema,
|
|
460
461
|
publisher: cataloguePublisherSummarySchema,
|
|
461
462
|
displayName: z.string().min(1).max(200),
|
|
@@ -126,6 +126,8 @@ export const inferenceHttpsUrlSchema = z
|
|
|
126
126
|
export const sha256DigestSchema = z
|
|
127
127
|
.string()
|
|
128
128
|
.regex(/^sha256:[a-f0-9]{64}$/, 'digest must be sha256:<64 lowercase hex>');
|
|
129
|
+
/** Standard base64 in whole, padded 4-character groups. */
|
|
130
|
+
export const PADDED_BASE64_PATTERN = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
129
131
|
/* -------------------------------------------------------------------------- */
|
|
130
132
|
/* Catalogue references */
|
|
131
133
|
/* -------------------------------------------------------------------------- */
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* Decided in: issue #972 workstream 10.
|
|
25
25
|
*/
|
|
26
26
|
import { z } from 'zod';
|
|
27
|
-
import { deploymentIdSchema, inferenceEnvironmentSchema, inferenceProviderSlugSchema, inferenceTimestampSchema, oxyAccountIdSchema, oxyApplicationIdSchema, } from './identifiers.js';
|
|
27
|
+
import { deploymentIdSchema, inferenceEnvironmentSchema, inferenceProviderSlugSchema, inferenceTimestampSchema, oxyAccountIdSchema, oxyApplicationIdSchema, PADDED_BASE64_PATTERN, } from './identifiers.js';
|
|
28
28
|
/**
|
|
29
29
|
* How widely a connection applies.
|
|
30
30
|
*
|
|
@@ -111,7 +111,7 @@ const kaanaCredentialSecretBase64Schema = z
|
|
|
111
111
|
.string()
|
|
112
112
|
.min(1)
|
|
113
113
|
.max(8192)
|
|
114
|
-
.regex(
|
|
114
|
+
.regex(PADDED_BASE64_PATTERN)
|
|
115
115
|
.refine(isVisibleASCIIProviderCredential, {
|
|
116
116
|
message: 'a decoded provider credential is 1-4096 visible ASCII bytes',
|
|
117
117
|
});
|
|
@@ -311,6 +311,12 @@ const modelLineOf = (reference) => {
|
|
|
311
311
|
const at = reference.indexOf("@");
|
|
312
312
|
return at === -1 ? reference : reference.slice(0, at);
|
|
313
313
|
};
|
|
314
|
+
/** Parameters for text-to-speech, preserved in the signed request. */
|
|
315
|
+
export const inferenceSpeechParametersSchema = z.object({
|
|
316
|
+
voice: z.string().min(1).max(64),
|
|
317
|
+
responseFormat: z.enum(["mp3", "opus", "aac", "flac", "wav", "pcm"]),
|
|
318
|
+
speed: z.number().min(0.25).max(4).optional(),
|
|
319
|
+
}).strict();
|
|
314
320
|
/**
|
|
315
321
|
* The canonical internal request Oxy forwards to the data plane.
|
|
316
322
|
*
|
|
@@ -330,6 +336,7 @@ export const inferenceRequestSchema = z
|
|
|
330
336
|
stream: z.boolean(),
|
|
331
337
|
maxOutputTokens: z.number().int().positive().safe().optional(),
|
|
332
338
|
sampling: samplingParametersSchema,
|
|
339
|
+
speech: inferenceSpeechParametersSchema.optional(),
|
|
333
340
|
tools: z.array(toolDefinitionSchema).default([]),
|
|
334
341
|
toolChoice: toolChoiceSchema.optional(),
|
|
335
342
|
responseFormat: responseFormatSchema.optional(),
|
|
@@ -372,6 +379,13 @@ export const inferenceRequestSchema = z
|
|
|
372
379
|
authorizedRoutes: z.array(authorizedRouteSchema).min(1).optional(),
|
|
373
380
|
})
|
|
374
381
|
.superRefine((request, ctx) => {
|
|
382
|
+
const isSpeech = request.client.apiFormat === "audio_speech";
|
|
383
|
+
if (isSpeech && request.speech !== undefined && (request.modality !== "audio" || request.input.format !== "text" || request.stream)) {
|
|
384
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["speech"], message: "speech requires audio modality, text input, parameters and non-streaming output" });
|
|
385
|
+
}
|
|
386
|
+
if (!isSpeech && request.speech !== undefined) {
|
|
387
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["speech"], message: "speech parameters require the audio_speech API format" });
|
|
388
|
+
}
|
|
375
389
|
if (request.toolChoice !== undefined && request.tools.length === 0) {
|
|
376
390
|
ctx.addIssue({
|
|
377
391
|
code: z.ZodIssueCode.custom,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Normalized stream events — what the data plane emits and the Oxy edge
|
|
3
3
|
* forwards as SSE.
|
|
4
4
|
*
|
|
5
|
-
* One discriminated union,
|
|
5
|
+
* One discriminated union, eight shapes, all carrying `requestId` and a
|
|
6
6
|
* monotonic `sequence`. `requestId` is on EVERY event rather than only the
|
|
7
7
|
* first because a proxy that re-frames or a client that reconnects would
|
|
8
8
|
* otherwise be holding events it cannot attribute; `sequence` is what makes a
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* Decided in: docs/adr/0010-public-api-compatibility.md, docs/adr/0008-catalogue-concept-separation.md.
|
|
22
22
|
*/
|
|
23
23
|
import { z } from 'zod';
|
|
24
|
-
import { deploymentIdSchema, generationIdSchema, inferenceProviderSlugSchema, inferenceTimestampSchema, modelIdSchema, modelReferenceSchema, requestIdSchema, } from './identifiers.js';
|
|
24
|
+
import { deploymentIdSchema, generationIdSchema, inferenceProviderSlugSchema, inferenceTimestampSchema, modelIdSchema, modelReferenceSchema, PADDED_BASE64_PATTERN, requestIdSchema, } from './identifiers.js';
|
|
25
25
|
import { inferenceErrorSchema } from './errors.js';
|
|
26
26
|
import { usageQuantitySchema, usageSourceSchema } from './money.js';
|
|
27
27
|
/**
|
|
@@ -62,6 +62,27 @@ export const inferenceStreamDeltaEventSchema = z.object({
|
|
|
62
62
|
channel: z.enum(['output_text', 'reasoning', 'refusal']),
|
|
63
63
|
text: z.string(),
|
|
64
64
|
});
|
|
65
|
+
/** The encodings an audio output may carry. */
|
|
66
|
+
export const inferenceAudioMediaTypeSchema = z.enum([
|
|
67
|
+
'audio/mpeg',
|
|
68
|
+
'audio/wav',
|
|
69
|
+
'audio/ogg',
|
|
70
|
+
'audio/aac',
|
|
71
|
+
'audio/flac',
|
|
72
|
+
'audio/pcm',
|
|
73
|
+
]);
|
|
74
|
+
/** The most bytes one folded audio output may hold, at the edge and in the SDK. */
|
|
75
|
+
export const MAX_INFERENCE_AUDIO_BYTES = 20 * 1024 * 1024;
|
|
76
|
+
/** A bounded, independently base64-encoded chunk of one audio output. */
|
|
77
|
+
export const inferenceStreamAudioEventSchema = z.object({
|
|
78
|
+
schemaVersion: z.literal(1),
|
|
79
|
+
type: z.literal('audio'),
|
|
80
|
+
requestId: requestIdSchema,
|
|
81
|
+
sequence: z.number().int().nonnegative().safe(),
|
|
82
|
+
outputIndex: z.number().int().nonnegative().safe(),
|
|
83
|
+
mediaType: inferenceAudioMediaTypeSchema,
|
|
84
|
+
data: z.string().min(4).max(65536).regex(PADDED_BASE64_PATTERN),
|
|
85
|
+
});
|
|
65
86
|
/**
|
|
66
87
|
* A tool call being streamed.
|
|
67
88
|
*
|
|
@@ -247,6 +268,7 @@ export const inferenceStreamDoneEventSchema = z.object({
|
|
|
247
268
|
export const inferenceStreamEventSchema = z.discriminatedUnion('type', [
|
|
248
269
|
inferenceStreamStartEventSchema,
|
|
249
270
|
inferenceStreamDeltaEventSchema,
|
|
271
|
+
inferenceStreamAudioEventSchema,
|
|
250
272
|
inferenceStreamToolCallEventSchema,
|
|
251
273
|
inferenceStreamUsageEventSchema,
|
|
252
274
|
inferenceStreamRouteSwitchEventSchema,
|
|
@@ -99,4 +99,4 @@
|
|
|
99
99
|
* change to, say, the catalogue reject every in-flight inference request; the
|
|
100
100
|
* per-shape `schemaVersion` is what a message is validated against.
|
|
101
101
|
*/
|
|
102
|
-
export const INFERENCE_CONTRACT_VERSION = '
|
|
102
|
+
export const INFERENCE_CONTRACT_VERSION = '3.0.0';
|