@simonpeacocks/peacockery-voice-client 0.1.2 → 0.1.4
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/README.md +43 -3
- package/dist/auth.d.ts +25 -0
- package/dist/auth.js +51 -0
- package/dist/index.d.ts +3 -20
- package/dist/index.js +4 -40
- package/dist/realtime.d.ts +57 -0
- package/dist/realtime.js +130 -0
- package/dist/schema.d.ts +55 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,8 +19,48 @@ const voice = createPeacockeryVoiceClient({
|
|
|
19
19
|
const { data, error } = await voice.GET("/v1/models");
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
For a device-bound child, pass `authorizationScheme: "DPoP"` and a platform proof provider. The callback receives the access token, HTTP method, canonical resource URI, and current server nonce; it returns one ES256 `dpop+jwt`. The wrapper automatically retries the first `use_dpop_nonce` challenge with a new proof. A trusted parent can
|
|
22
|
+
For a device-bound child, pass `authorizationScheme: "DPoP"` and a platform proof provider. The callback receives the access token, HTTP method, canonical resource URI, and current server nonce; it returns one ES256 `dpop+jwt`. The wrapper automatically retries the first `use_dpop_nonce` challenge with a new proof. A trusted parent can remain `Bearer` while supplying an `ath`-free `dpopBindingProof` to bind a newly minted child, keeping the parent token inside the trusted backend.
|
|
23
23
|
|
|
24
|
-
Private-key creation and signing stay in platform code.
|
|
24
|
+
Private-key creation and signing stay in platform code. Apple clients use Secure Enclave P-256 keys; Android clients should use a non-exportable Android Keystore P-256 key. The generated client handles public proofs while private-key state stays exclusively platform-managed.
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
For realtime PCM, build the authenticated upgrade request and parse every provider-neutral event through the package:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
const request = await createPeacockeryVoiceRealtimeRequest({
|
|
30
|
+
apiKey: childToken,
|
|
31
|
+
authorizationScheme: "DPoP",
|
|
32
|
+
dpopProof: signWithAndroidKeystore,
|
|
33
|
+
encoding: "linear16",
|
|
34
|
+
interimResults: true,
|
|
35
|
+
model: "deepgram-nova-3",
|
|
36
|
+
sampleRate: 16_000,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const socket = createNativeWebSocket(request.url, request.headers);
|
|
40
|
+
socket.addEventListener("message", ({ data }) => {
|
|
41
|
+
const event = parsePeacockeryVoiceRealtimeEvent(String(data));
|
|
42
|
+
if (event.type.startsWith("transcript.")) updateVisibleTranscript(event.text);
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Send raw PCM chunks as binary WebSocket messages. Send `{ "type": "close" }` after the final chunk and retain the terminal `session.completed` result. The React Native adapter owns the native WebSocket constructor and Android Keystore signer because those APIs live outside the generated HTTP package.
|
|
47
|
+
|
|
48
|
+
Run `pnpm run generate:sdk` from the repository root whenever the OpenAPI contract changes. Compiled JavaScript and declarations are checked in, and private Git installations consume them directly. Realtime WebSockets still use the protocol documented by `/v1/realtime`; `openapi-fetch` covers the HTTP surface.
|
|
49
|
+
|
|
50
|
+
## Release
|
|
51
|
+
|
|
52
|
+
Run one guarded command from a clean, synchronized `main` branch:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pnpm release:voice-client patch
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The command regenerates the SDK, validates the TypeScript client and Swift package when Swift is available, increments the requested semantic version, commits only generated client release files, creates the matching `voice-client-vX.Y.Z` tag, atomically pushes `main` and the tag, watches the GitHub Actions publication, and verifies the new npm version.
|
|
59
|
+
|
|
60
|
+
Use `minor`, `major`, or an explicit stable version in place of `patch`. Run the complete preflight while keeping the repository unchanged with:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pnpm release:voice-client patch --check
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
If GitHub Actions reports a provider-side failure after the atomic push, repair the publisher configuration and rerun that workflow. The existing version commit and release tag remain the canonical release identity.
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare const peacockeryVoiceBaseUrls: {
|
|
2
|
+
readonly lab: "https://voice-lab.peacockery.studio";
|
|
3
|
+
readonly production: "https://voice.peacockery.studio";
|
|
4
|
+
};
|
|
5
|
+
export type PeacockeryVoiceEnvironment = keyof typeof peacockeryVoiceBaseUrls;
|
|
6
|
+
export interface PeacockeryVoiceDpopProofInput {
|
|
7
|
+
readonly accessToken: string;
|
|
8
|
+
readonly htm: string;
|
|
9
|
+
readonly htu: string;
|
|
10
|
+
readonly nonce?: string;
|
|
11
|
+
}
|
|
12
|
+
export type PeacockeryVoiceDpopProofProvider = (input: PeacockeryVoiceDpopProofInput) => Promise<string>;
|
|
13
|
+
export type PeacockeryVoiceDpopBindingProofInput = Omit<PeacockeryVoiceDpopProofInput, "accessToken">;
|
|
14
|
+
export type PeacockeryVoiceDpopBindingProofProvider = (input: PeacockeryVoiceDpopBindingProofInput) => Promise<string>;
|
|
15
|
+
export interface PeacockeryVoiceClientOptions {
|
|
16
|
+
apiKey: string;
|
|
17
|
+
authorizationScheme?: "Bearer" | "DPoP";
|
|
18
|
+
baseUrl?: string;
|
|
19
|
+
dpopBindingProof?: PeacockeryVoiceDpopBindingProofProvider;
|
|
20
|
+
dpopProof?: PeacockeryVoiceDpopProofProvider;
|
|
21
|
+
environment?: PeacockeryVoiceEnvironment;
|
|
22
|
+
fetch?: typeof globalThis.fetch;
|
|
23
|
+
}
|
|
24
|
+
export declare const dpopHtu: (requestUrl: string) => string;
|
|
25
|
+
export declare const authenticatedFetch: (options: PeacockeryVoiceClientOptions) => typeof globalThis.fetch;
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export const peacockeryVoiceBaseUrls = {
|
|
2
|
+
lab: "https://voice-lab.peacockery.studio",
|
|
3
|
+
production: "https://voice.peacockery.studio",
|
|
4
|
+
};
|
|
5
|
+
export const dpopHtu = (requestUrl) => {
|
|
6
|
+
const url = new URL(requestUrl);
|
|
7
|
+
return `${url.origin}${url.pathname}`;
|
|
8
|
+
};
|
|
9
|
+
export const authenticatedFetch = (options) => {
|
|
10
|
+
const scheme = options.authorizationScheme ?? "Bearer";
|
|
11
|
+
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
12
|
+
const bindingProofProvider = options.dpopBindingProof;
|
|
13
|
+
const workloadProofProvider = options.dpopProof;
|
|
14
|
+
if (scheme === "DPoP" && !workloadProofProvider) {
|
|
15
|
+
throw new Error("DPoP authorization requires a proof provider");
|
|
16
|
+
}
|
|
17
|
+
const hasProofProvider = scheme === "DPoP"
|
|
18
|
+
? Boolean(workloadProofProvider)
|
|
19
|
+
: Boolean(bindingProofProvider);
|
|
20
|
+
let nonce;
|
|
21
|
+
return async (input, init) => {
|
|
22
|
+
const request = new Request(input, init);
|
|
23
|
+
const send = async () => {
|
|
24
|
+
const headers = new Headers(request.headers);
|
|
25
|
+
headers.set("Authorization", `${scheme} ${options.apiKey}`);
|
|
26
|
+
const proofInput = {
|
|
27
|
+
htm: request.method,
|
|
28
|
+
htu: dpopHtu(request.url),
|
|
29
|
+
...(nonce ? { nonce } : {}),
|
|
30
|
+
};
|
|
31
|
+
if (scheme === "DPoP" && workloadProofProvider) {
|
|
32
|
+
headers.set("DPoP", await workloadProofProvider({
|
|
33
|
+
accessToken: options.apiKey,
|
|
34
|
+
...proofInput,
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
else if (bindingProofProvider) {
|
|
38
|
+
headers.set("DPoP", await bindingProofProvider(proofInput));
|
|
39
|
+
}
|
|
40
|
+
return fetchImplementation(new Request(request.clone(), { headers }));
|
|
41
|
+
};
|
|
42
|
+
const response = await send();
|
|
43
|
+
const challengedNonce = response.headers.get("DPoP-Nonce")?.trim();
|
|
44
|
+
if (response.status !== 401 || !challengedNonce || !hasProofProvider) {
|
|
45
|
+
return response;
|
|
46
|
+
}
|
|
47
|
+
nonce = challengedNonce;
|
|
48
|
+
await response.body?.cancel();
|
|
49
|
+
return send();
|
|
50
|
+
};
|
|
51
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,24 +1,7 @@
|
|
|
1
|
+
import { type PeacockeryVoiceClientOptions } from "./auth.js";
|
|
1
2
|
import type { paths } from "./schema.js";
|
|
3
|
+
export { type PeacockeryVoiceClientOptions, type PeacockeryVoiceDpopBindingProofInput, type PeacockeryVoiceDpopBindingProofProvider, type PeacockeryVoiceDpopProofInput, type PeacockeryVoiceDpopProofProvider, type PeacockeryVoiceEnvironment, peacockeryVoiceBaseUrls, } from "./auth.js";
|
|
4
|
+
export { createPeacockeryVoiceRealtimeRequest, type PeacockeryVoiceRealtimeAudioReceivedEvent, type PeacockeryVoiceRealtimeEvent, type PeacockeryVoiceRealtimeOptions, type PeacockeryVoiceRealtimePongEvent, type PeacockeryVoiceRealtimeRequest, type PeacockeryVoiceRealtimeSessionStartedEvent, type PeacockeryVoiceRealtimeTranscriptEvent, parsePeacockeryVoiceRealtimeEvent, } from "./realtime.js";
|
|
2
5
|
export type { components, operations, paths } from "./schema.js";
|
|
3
|
-
export declare const peacockeryVoiceBaseUrls: {
|
|
4
|
-
readonly lab: "https://voice-lab.peacockery.studio";
|
|
5
|
-
readonly production: "https://voice.peacockery.studio";
|
|
6
|
-
};
|
|
7
|
-
export type PeacockeryVoiceEnvironment = keyof typeof peacockeryVoiceBaseUrls;
|
|
8
|
-
export interface PeacockeryVoiceDpopProofInput {
|
|
9
|
-
readonly accessToken: string;
|
|
10
|
-
readonly htm: string;
|
|
11
|
-
readonly htu: string;
|
|
12
|
-
readonly nonce?: string;
|
|
13
|
-
}
|
|
14
|
-
export type PeacockeryVoiceDpopProofProvider = (input: PeacockeryVoiceDpopProofInput) => Promise<string>;
|
|
15
|
-
export interface PeacockeryVoiceClientOptions {
|
|
16
|
-
apiKey: string;
|
|
17
|
-
authorizationScheme?: "Bearer" | "DPoP";
|
|
18
|
-
baseUrl?: string;
|
|
19
|
-
dpopProof?: PeacockeryVoiceDpopProofProvider;
|
|
20
|
-
environment?: PeacockeryVoiceEnvironment;
|
|
21
|
-
fetch?: typeof globalThis.fetch;
|
|
22
|
-
}
|
|
23
6
|
export declare const createPeacockeryVoiceClient: (options: PeacockeryVoiceClientOptions) => import("openapi-fetch").Client<paths, `${string}/${string}`>;
|
|
24
7
|
export type PeacockeryVoiceClient = ReturnType<typeof createPeacockeryVoiceClient>;
|
package/dist/index.js
CHANGED
|
@@ -1,44 +1,8 @@
|
|
|
1
1
|
import createClient from "openapi-fetch";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
};
|
|
6
|
-
const dpopHtu = (requestUrl) => {
|
|
7
|
-
const url = new URL(requestUrl);
|
|
8
|
-
return `${url.origin}${url.pathname}`;
|
|
9
|
-
};
|
|
10
|
-
const authenticatedFetch = (options) => {
|
|
11
|
-
const scheme = options.authorizationScheme ?? "Bearer";
|
|
12
|
-
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
13
|
-
if (scheme === "DPoP" && !options.dpopProof) {
|
|
14
|
-
throw new Error("DPoP authorization requires a proof provider");
|
|
15
|
-
}
|
|
16
|
-
let nonce;
|
|
17
|
-
return async (input, init) => {
|
|
18
|
-
const request = new Request(input, init);
|
|
19
|
-
const send = async () => {
|
|
20
|
-
const headers = new Headers(request.headers);
|
|
21
|
-
headers.set("Authorization", `${scheme} ${options.apiKey}`);
|
|
22
|
-
if (options.dpopProof) {
|
|
23
|
-
headers.set("DPoP", await options.dpopProof({
|
|
24
|
-
accessToken: options.apiKey,
|
|
25
|
-
htm: request.method,
|
|
26
|
-
htu: dpopHtu(request.url),
|
|
27
|
-
...(nonce ? { nonce } : {}),
|
|
28
|
-
}));
|
|
29
|
-
}
|
|
30
|
-
return fetchImplementation(new Request(request.clone(), { headers }));
|
|
31
|
-
};
|
|
32
|
-
const response = await send();
|
|
33
|
-
const challengedNonce = response.headers.get("DPoP-Nonce")?.trim();
|
|
34
|
-
if (response.status !== 401 || !challengedNonce || !options.dpopProof) {
|
|
35
|
-
return response;
|
|
36
|
-
}
|
|
37
|
-
nonce = challengedNonce;
|
|
38
|
-
await response.body?.cancel();
|
|
39
|
-
return send();
|
|
40
|
-
};
|
|
41
|
-
};
|
|
2
|
+
import { authenticatedFetch, peacockeryVoiceBaseUrls, } from "./auth.js";
|
|
3
|
+
// biome-ignore lint/performance/noBarrelFile: The public package entrypoint intentionally exposes the supported auth surface.
|
|
4
|
+
export { peacockeryVoiceBaseUrls, } from "./auth.js";
|
|
5
|
+
export { createPeacockeryVoiceRealtimeRequest, parsePeacockeryVoiceRealtimeEvent, } from "./realtime.js";
|
|
42
6
|
export const createPeacockeryVoiceClient = (options) => {
|
|
43
7
|
const environment = options.environment ?? "production";
|
|
44
8
|
return createClient({
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type PeacockeryVoiceClientOptions } from "./auth.js";
|
|
2
|
+
import type { components } from "./schema.js";
|
|
3
|
+
type RealtimeTerminalEvent = components["schemas"]["RealtimeSessionResult"];
|
|
4
|
+
type TranscriptionArtifact = components["schemas"]["TranscriptionArtifact"];
|
|
5
|
+
type TranscriptSegment = TranscriptionArtifact["content"]["segments"]["items"][number];
|
|
6
|
+
type TranscriptSpeakerTurn = TranscriptionArtifact["content"]["speaker_turns"]["items"][number];
|
|
7
|
+
type TranscriptWord = TranscriptionArtifact["content"]["words"]["items"][number];
|
|
8
|
+
export interface PeacockeryVoiceRealtimeOptions extends PeacockeryVoiceClientOptions {
|
|
9
|
+
channels?: number;
|
|
10
|
+
encoding?: string;
|
|
11
|
+
endpointing?: string;
|
|
12
|
+
interimResults?: boolean;
|
|
13
|
+
language?: string;
|
|
14
|
+
model: string;
|
|
15
|
+
sampleRate?: number;
|
|
16
|
+
targetStreamingDelayMs?: number;
|
|
17
|
+
utteranceEndMs?: number;
|
|
18
|
+
vadEvents?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface PeacockeryVoiceRealtimeRequest {
|
|
21
|
+
headers: Readonly<Record<string, string>>;
|
|
22
|
+
url: string;
|
|
23
|
+
}
|
|
24
|
+
interface RealtimeEventBase {
|
|
25
|
+
protocol_version: 1;
|
|
26
|
+
sequence: number;
|
|
27
|
+
session_id: string;
|
|
28
|
+
}
|
|
29
|
+
export interface PeacockeryVoiceRealtimeSessionStartedEvent extends RealtimeEventBase {
|
|
30
|
+
language: string | null;
|
|
31
|
+
model: string;
|
|
32
|
+
type: "session.started";
|
|
33
|
+
}
|
|
34
|
+
export interface PeacockeryVoiceRealtimeTranscriptEvent extends RealtimeEventBase {
|
|
35
|
+
segments: TranscriptSegment[];
|
|
36
|
+
speaker_turns: TranscriptSpeakerTurn[];
|
|
37
|
+
speech_final?: boolean;
|
|
38
|
+
text: string;
|
|
39
|
+
type: "transcript.committed" | "transcript.delta" | "transcript.interim";
|
|
40
|
+
words: TranscriptWord[];
|
|
41
|
+
}
|
|
42
|
+
export interface PeacockeryVoiceRealtimeAudioReceivedEvent {
|
|
43
|
+
audio_bytes: number;
|
|
44
|
+
chunk_bytes: number;
|
|
45
|
+
message_count: number;
|
|
46
|
+
session_id: string;
|
|
47
|
+
type: "audio.received";
|
|
48
|
+
}
|
|
49
|
+
export interface PeacockeryVoiceRealtimePongEvent {
|
|
50
|
+
message_count: number;
|
|
51
|
+
session_id: string;
|
|
52
|
+
type: "pong";
|
|
53
|
+
}
|
|
54
|
+
export type PeacockeryVoiceRealtimeEvent = PeacockeryVoiceRealtimeAudioReceivedEvent | PeacockeryVoiceRealtimePongEvent | PeacockeryVoiceRealtimeSessionStartedEvent | PeacockeryVoiceRealtimeTranscriptEvent | RealtimeTerminalEvent;
|
|
55
|
+
export declare const createPeacockeryVoiceRealtimeRequest: (options: PeacockeryVoiceRealtimeOptions) => Promise<PeacockeryVoiceRealtimeRequest>;
|
|
56
|
+
export declare const parsePeacockeryVoiceRealtimeEvent: (value: string | unknown) => PeacockeryVoiceRealtimeEvent;
|
|
57
|
+
export {};
|
package/dist/realtime.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { dpopHtu, peacockeryVoiceBaseUrls, } from "./auth.js";
|
|
2
|
+
const realtimeHttpUrl = (options) => {
|
|
3
|
+
const environment = options.environment ?? "production";
|
|
4
|
+
const url = new URL("/v1/realtime", options.baseUrl ?? peacockeryVoiceBaseUrls[environment]);
|
|
5
|
+
const query = {
|
|
6
|
+
channels: options.channels,
|
|
7
|
+
encoding: options.encoding,
|
|
8
|
+
endpointing: options.endpointing,
|
|
9
|
+
interim_results: options.interimResults,
|
|
10
|
+
language: options.language,
|
|
11
|
+
model: options.model,
|
|
12
|
+
sample_rate: options.sampleRate,
|
|
13
|
+
target_streaming_delay_ms: options.targetStreamingDelayMs,
|
|
14
|
+
utterance_end_ms: options.utteranceEndMs,
|
|
15
|
+
vad_events: options.vadEvents,
|
|
16
|
+
};
|
|
17
|
+
for (const [name, value] of Object.entries(query)) {
|
|
18
|
+
if (value !== undefined) {
|
|
19
|
+
url.searchParams.set(name, String(value));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return url;
|
|
23
|
+
};
|
|
24
|
+
const headersRecord = (headers) => {
|
|
25
|
+
const record = {};
|
|
26
|
+
for (const [name, value] of headers) {
|
|
27
|
+
record[name] = value;
|
|
28
|
+
}
|
|
29
|
+
return record;
|
|
30
|
+
};
|
|
31
|
+
export const createPeacockeryVoiceRealtimeRequest = async (options) => {
|
|
32
|
+
const scheme = options.authorizationScheme ?? "Bearer";
|
|
33
|
+
const url = realtimeHttpUrl(options);
|
|
34
|
+
const headers = new Headers({
|
|
35
|
+
Authorization: `${scheme} ${options.apiKey}`,
|
|
36
|
+
});
|
|
37
|
+
if (scheme === "DPoP") {
|
|
38
|
+
const proofProvider = options.dpopProof;
|
|
39
|
+
if (!proofProvider) {
|
|
40
|
+
throw new Error("DPoP authorization requires a proof provider");
|
|
41
|
+
}
|
|
42
|
+
let nonce;
|
|
43
|
+
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
44
|
+
const nonceUrl = new URL("/v1/auth/dpop-nonce", url);
|
|
45
|
+
const prime = async () => {
|
|
46
|
+
const primeHeaders = new Headers({
|
|
47
|
+
Authorization: `DPoP ${options.apiKey}`,
|
|
48
|
+
DPoP: await proofProvider({
|
|
49
|
+
accessToken: options.apiKey,
|
|
50
|
+
htm: "POST",
|
|
51
|
+
htu: dpopHtu(nonceUrl.href),
|
|
52
|
+
...(nonce ? { nonce } : {}),
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
return fetchImplementation(nonceUrl, {
|
|
56
|
+
headers: primeHeaders,
|
|
57
|
+
method: "POST",
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
let response = await prime();
|
|
61
|
+
const challengedNonce = response.headers.get("DPoP-Nonce")?.trim();
|
|
62
|
+
if (response.status === 401 && challengedNonce) {
|
|
63
|
+
nonce = challengedNonce;
|
|
64
|
+
await response.body?.cancel();
|
|
65
|
+
response = await prime();
|
|
66
|
+
}
|
|
67
|
+
if (response.status !== 204 || !nonce) {
|
|
68
|
+
throw new Error(`Peacockery Voice DPoP nonce bootstrap returned ${response.status}`);
|
|
69
|
+
}
|
|
70
|
+
await response.body?.cancel();
|
|
71
|
+
headers.set("DPoP", await proofProvider({
|
|
72
|
+
accessToken: options.apiKey,
|
|
73
|
+
htm: "GET",
|
|
74
|
+
htu: dpopHtu(url.href),
|
|
75
|
+
nonce,
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
url.protocol = url.protocol === "http:" ? "ws:" : "wss:";
|
|
79
|
+
return { headers: headersRecord(headers), url: url.href };
|
|
80
|
+
};
|
|
81
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
82
|
+
const isRealtimeEvent = (value) => {
|
|
83
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
if (value.type === "audio.received") {
|
|
87
|
+
return (typeof value.audio_bytes === "number" &&
|
|
88
|
+
typeof value.chunk_bytes === "number" &&
|
|
89
|
+
typeof value.message_count === "number" &&
|
|
90
|
+
typeof value.session_id === "string");
|
|
91
|
+
}
|
|
92
|
+
if (value.type === "pong") {
|
|
93
|
+
return (typeof value.message_count === "number" &&
|
|
94
|
+
typeof value.session_id === "string");
|
|
95
|
+
}
|
|
96
|
+
const hasProtocolBase = value.protocol_version === 1 &&
|
|
97
|
+
typeof value.sequence === "number" &&
|
|
98
|
+
typeof value.session_id === "string";
|
|
99
|
+
if (!hasProtocolBase) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
if (value.type === "session.started") {
|
|
103
|
+
return ((typeof value.language === "string" || value.language === null) &&
|
|
104
|
+
typeof value.model === "string");
|
|
105
|
+
}
|
|
106
|
+
if (value.type === "transcript.committed" ||
|
|
107
|
+
value.type === "transcript.delta" ||
|
|
108
|
+
value.type === "transcript.interim") {
|
|
109
|
+
return (Array.isArray(value.segments) &&
|
|
110
|
+
Array.isArray(value.speaker_turns) &&
|
|
111
|
+
typeof value.text === "string" &&
|
|
112
|
+
Array.isArray(value.words));
|
|
113
|
+
}
|
|
114
|
+
if (value.type === "session.completed") {
|
|
115
|
+
return value.status === "succeeded" && isRecord(value.result);
|
|
116
|
+
}
|
|
117
|
+
if (value.type === "session.failed") {
|
|
118
|
+
return (value.status === "failed" &&
|
|
119
|
+
isRecord(value.error) &&
|
|
120
|
+
isRecord(value.result));
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
123
|
+
};
|
|
124
|
+
export const parsePeacockeryVoiceRealtimeEvent = (value) => {
|
|
125
|
+
const parsed = typeof value === "string" ? JSON.parse(value) : value;
|
|
126
|
+
if (!isRealtimeEvent(parsed)) {
|
|
127
|
+
throw new Error("Peacockery Voice returned an invalid realtime event");
|
|
128
|
+
}
|
|
129
|
+
return parsed;
|
|
130
|
+
};
|
package/dist/schema.d.ts
CHANGED
|
@@ -1245,6 +1245,49 @@ export interface paths {
|
|
|
1245
1245
|
patch?: never;
|
|
1246
1246
|
trace?: never;
|
|
1247
1247
|
};
|
|
1248
|
+
"/v1/auth/dpop-nonce": {
|
|
1249
|
+
parameters: {
|
|
1250
|
+
query?: never;
|
|
1251
|
+
header?: never;
|
|
1252
|
+
path?: never;
|
|
1253
|
+
cookie?: never;
|
|
1254
|
+
};
|
|
1255
|
+
get?: never;
|
|
1256
|
+
put?: never;
|
|
1257
|
+
/** Prime a DPoP nonce before a WebSocket upgrade */
|
|
1258
|
+
post: {
|
|
1259
|
+
parameters: {
|
|
1260
|
+
query?: never;
|
|
1261
|
+
header?: never;
|
|
1262
|
+
path?: never;
|
|
1263
|
+
cookie?: never;
|
|
1264
|
+
};
|
|
1265
|
+
requestBody?: never;
|
|
1266
|
+
responses: {
|
|
1267
|
+
/** @description DPoP nonce is current for this credential. */
|
|
1268
|
+
204: {
|
|
1269
|
+
headers: {
|
|
1270
|
+
[name: string]: unknown;
|
|
1271
|
+
};
|
|
1272
|
+
content?: never;
|
|
1273
|
+
};
|
|
1274
|
+
/** @description Unauthorized. */
|
|
1275
|
+
401: {
|
|
1276
|
+
headers: {
|
|
1277
|
+
[name: string]: unknown;
|
|
1278
|
+
};
|
|
1279
|
+
content: {
|
|
1280
|
+
"application/json": components["schemas"]["ErrorResponse"];
|
|
1281
|
+
};
|
|
1282
|
+
};
|
|
1283
|
+
};
|
|
1284
|
+
};
|
|
1285
|
+
delete?: never;
|
|
1286
|
+
options?: never;
|
|
1287
|
+
head?: never;
|
|
1288
|
+
patch?: never;
|
|
1289
|
+
trace?: never;
|
|
1290
|
+
};
|
|
1248
1291
|
"/v1/jobs/{job_id}": {
|
|
1249
1292
|
parameters: {
|
|
1250
1293
|
query?: never;
|
|
@@ -2029,6 +2072,7 @@ export interface components {
|
|
|
2029
2072
|
completed_at: string | null;
|
|
2030
2073
|
created_at: string;
|
|
2031
2074
|
error: string | null;
|
|
2075
|
+
error_details: components["schemas"]["ProviderFailureDiagnostic"];
|
|
2032
2076
|
job_id: string;
|
|
2033
2077
|
/** @enum {string} */
|
|
2034
2078
|
kind: "transcription";
|
|
@@ -2040,6 +2084,17 @@ export interface components {
|
|
|
2040
2084
|
status: "pending" | "queued" | "running" | "succeeded" | "failed";
|
|
2041
2085
|
updated_at: string;
|
|
2042
2086
|
};
|
|
2087
|
+
ProviderFailureDiagnostic: {
|
|
2088
|
+
/** @enum {string} */
|
|
2089
|
+
category: "authentication" | "empty_output" | "invalid_request" | "network" | "rate_limited" | "timeout" | "unavailable" | "unknown";
|
|
2090
|
+
message: string;
|
|
2091
|
+
provider?: string;
|
|
2092
|
+
providerCode?: string;
|
|
2093
|
+
requestId?: string;
|
|
2094
|
+
retryAfterMs?: number;
|
|
2095
|
+
retryable: boolean;
|
|
2096
|
+
statusCode?: number;
|
|
2097
|
+
} | null;
|
|
2043
2098
|
TranscriptionArtifact: {
|
|
2044
2099
|
content: {
|
|
2045
2100
|
audio_events: {
|