@simonpeacocks/peacockery-voice-client 0.1.3 → 0.2.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/README.md +24 -2
- 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 +86 -0
- package/dist/realtime.js +149 -0
- package/dist/schema.d.ts +52 -4
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -19,9 +19,31 @@ 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
|
+
|
|
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.
|
|
25
47
|
|
|
26
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.
|
|
27
49
|
|
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, type PeacockeryVoiceRealtimeTurnEvent, 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,86 @@
|
|
|
1
|
+
import { type PeacockeryVoiceClientOptions } from "./auth.js";
|
|
2
|
+
import type { components } from "./schema.js";
|
|
3
|
+
type RealtimeTerminalEvent = components["schemas"]["RealtimeSessionResult"];
|
|
4
|
+
interface TranscriptSegment {
|
|
5
|
+
endSeconds: number | null;
|
|
6
|
+
scores?: {
|
|
7
|
+
confidence?: number;
|
|
8
|
+
logProbability?: number;
|
|
9
|
+
probability?: number;
|
|
10
|
+
score?: number;
|
|
11
|
+
speakerConfidence?: number;
|
|
12
|
+
};
|
|
13
|
+
speaker?: string | number;
|
|
14
|
+
startSeconds: number | null;
|
|
15
|
+
text: string;
|
|
16
|
+
}
|
|
17
|
+
type TranscriptSpeakerTurn = Omit<TranscriptSegment, "scores">;
|
|
18
|
+
type TranscriptWord = TranscriptSegment & {
|
|
19
|
+
startSeconds: number;
|
|
20
|
+
endSeconds: number;
|
|
21
|
+
};
|
|
22
|
+
export interface PeacockeryVoiceRealtimeOptions extends PeacockeryVoiceClientOptions {
|
|
23
|
+
channels?: number;
|
|
24
|
+
contextBias?: string[];
|
|
25
|
+
diarize?: boolean;
|
|
26
|
+
encoding?: string;
|
|
27
|
+
endpointing?: string;
|
|
28
|
+
interimResults?: boolean;
|
|
29
|
+
language?: string;
|
|
30
|
+
model: string;
|
|
31
|
+
sampleRate?: number;
|
|
32
|
+
speakerReferences?: Record<string, string>;
|
|
33
|
+
targetStreamingDelayMs?: number;
|
|
34
|
+
turnDetection?: {
|
|
35
|
+
start_threshold?: number;
|
|
36
|
+
eager_end_threshold?: number;
|
|
37
|
+
end_threshold?: number;
|
|
38
|
+
min_silence_ms?: number;
|
|
39
|
+
end_timeout_ms?: number;
|
|
40
|
+
};
|
|
41
|
+
utteranceEndMs?: number;
|
|
42
|
+
vadEvents?: boolean;
|
|
43
|
+
}
|
|
44
|
+
export interface PeacockeryVoiceRealtimeRequest {
|
|
45
|
+
headers: Readonly<Record<string, string>>;
|
|
46
|
+
url: string;
|
|
47
|
+
}
|
|
48
|
+
interface RealtimeEventBase {
|
|
49
|
+
protocol_version: 1;
|
|
50
|
+
sequence: number;
|
|
51
|
+
session_id: string;
|
|
52
|
+
}
|
|
53
|
+
export interface PeacockeryVoiceRealtimeSessionStartedEvent extends RealtimeEventBase {
|
|
54
|
+
language: string | null;
|
|
55
|
+
model: string;
|
|
56
|
+
type: "session.started";
|
|
57
|
+
}
|
|
58
|
+
export interface PeacockeryVoiceRealtimeTranscriptEvent extends RealtimeEventBase {
|
|
59
|
+
segments: TranscriptSegment[];
|
|
60
|
+
speaker_turns: TranscriptSpeakerTurn[];
|
|
61
|
+
speech_final?: boolean;
|
|
62
|
+
text: string;
|
|
63
|
+
type: "transcript.committed" | "transcript.delta" | "transcript.interim";
|
|
64
|
+
words: TranscriptWord[];
|
|
65
|
+
}
|
|
66
|
+
export interface PeacockeryVoiceRealtimeAudioReceivedEvent {
|
|
67
|
+
audio_bytes: number;
|
|
68
|
+
chunk_bytes: number;
|
|
69
|
+
message_count: number;
|
|
70
|
+
session_id: string;
|
|
71
|
+
type: "audio.received";
|
|
72
|
+
}
|
|
73
|
+
export interface PeacockeryVoiceRealtimePongEvent {
|
|
74
|
+
message_count: number;
|
|
75
|
+
session_id: string;
|
|
76
|
+
type: "pong";
|
|
77
|
+
}
|
|
78
|
+
export interface PeacockeryVoiceRealtimeTurnEvent extends RealtimeEventBase {
|
|
79
|
+
end_reason?: "threshold" | "timeout";
|
|
80
|
+
time_seconds: number;
|
|
81
|
+
type: "turn.start" | "turn.eager_end" | "turn.resume" | "turn.end";
|
|
82
|
+
}
|
|
83
|
+
export type PeacockeryVoiceRealtimeEvent = PeacockeryVoiceRealtimeTurnEvent | PeacockeryVoiceRealtimeAudioReceivedEvent | PeacockeryVoiceRealtimePongEvent | PeacockeryVoiceRealtimeSessionStartedEvent | PeacockeryVoiceRealtimeTranscriptEvent | RealtimeTerminalEvent;
|
|
84
|
+
export declare const createPeacockeryVoiceRealtimeRequest: (options: PeacockeryVoiceRealtimeOptions) => Promise<PeacockeryVoiceRealtimeRequest>;
|
|
85
|
+
export declare const parsePeacockeryVoiceRealtimeEvent: (value: string | unknown) => PeacockeryVoiceRealtimeEvent;
|
|
86
|
+
export {};
|
package/dist/realtime.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
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
|
+
diarize: options.diarize,
|
|
8
|
+
encoding: options.encoding,
|
|
9
|
+
endpointing: options.endpointing,
|
|
10
|
+
interim_results: options.interimResults,
|
|
11
|
+
language: options.language,
|
|
12
|
+
model: options.model,
|
|
13
|
+
sample_rate: options.sampleRate,
|
|
14
|
+
speaker_references: options.speakerReferences
|
|
15
|
+
? JSON.stringify(options.speakerReferences)
|
|
16
|
+
: undefined,
|
|
17
|
+
target_streaming_delay_ms: options.targetStreamingDelayMs,
|
|
18
|
+
turn_detection: options.turnDetection
|
|
19
|
+
? JSON.stringify(options.turnDetection)
|
|
20
|
+
: undefined,
|
|
21
|
+
utterance_end_ms: options.utteranceEndMs,
|
|
22
|
+
vad_events: options.vadEvents,
|
|
23
|
+
};
|
|
24
|
+
for (const [name, value] of Object.entries(query)) {
|
|
25
|
+
if (value !== undefined) {
|
|
26
|
+
url.searchParams.set(name, String(value));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
for (const term of options.contextBias ?? []) {
|
|
30
|
+
url.searchParams.append("context_bias", term);
|
|
31
|
+
}
|
|
32
|
+
return url;
|
|
33
|
+
};
|
|
34
|
+
const headersRecord = (headers) => {
|
|
35
|
+
const record = {};
|
|
36
|
+
for (const [name, value] of headers) {
|
|
37
|
+
record[name] = value;
|
|
38
|
+
}
|
|
39
|
+
return record;
|
|
40
|
+
};
|
|
41
|
+
export const createPeacockeryVoiceRealtimeRequest = async (options) => {
|
|
42
|
+
const scheme = options.authorizationScheme ?? "Bearer";
|
|
43
|
+
const url = realtimeHttpUrl(options);
|
|
44
|
+
const headers = new Headers({
|
|
45
|
+
Authorization: `${scheme} ${options.apiKey}`,
|
|
46
|
+
});
|
|
47
|
+
if (scheme === "DPoP") {
|
|
48
|
+
const proofProvider = options.dpopProof;
|
|
49
|
+
if (!proofProvider) {
|
|
50
|
+
throw new Error("DPoP authorization requires a proof provider");
|
|
51
|
+
}
|
|
52
|
+
let nonce;
|
|
53
|
+
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
54
|
+
const nonceUrl = new URL("/v1/auth/dpop-nonce", url);
|
|
55
|
+
const prime = async () => {
|
|
56
|
+
const primeHeaders = new Headers({
|
|
57
|
+
Authorization: `DPoP ${options.apiKey}`,
|
|
58
|
+
DPoP: await proofProvider({
|
|
59
|
+
accessToken: options.apiKey,
|
|
60
|
+
htm: "POST",
|
|
61
|
+
htu: dpopHtu(nonceUrl.href),
|
|
62
|
+
...(nonce ? { nonce } : {}),
|
|
63
|
+
}),
|
|
64
|
+
});
|
|
65
|
+
return fetchImplementation(nonceUrl, {
|
|
66
|
+
headers: primeHeaders,
|
|
67
|
+
method: "POST",
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
let response = await prime();
|
|
71
|
+
const challengedNonce = response.headers.get("DPoP-Nonce")?.trim();
|
|
72
|
+
if (response.status === 401 && challengedNonce) {
|
|
73
|
+
nonce = challengedNonce;
|
|
74
|
+
await response.body?.cancel();
|
|
75
|
+
response = await prime();
|
|
76
|
+
}
|
|
77
|
+
if (response.status !== 204 || !nonce) {
|
|
78
|
+
throw new Error(`Peacockery Voice DPoP nonce bootstrap returned ${response.status}`);
|
|
79
|
+
}
|
|
80
|
+
await response.body?.cancel();
|
|
81
|
+
headers.set("DPoP", await proofProvider({
|
|
82
|
+
accessToken: options.apiKey,
|
|
83
|
+
htm: "GET",
|
|
84
|
+
htu: dpopHtu(url.href),
|
|
85
|
+
nonce,
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
url.protocol = url.protocol === "http:" ? "ws:" : "wss:";
|
|
89
|
+
return { headers: headersRecord(headers), url: url.href };
|
|
90
|
+
};
|
|
91
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
92
|
+
const isTurnEvent = (value) => typeof value.time_seconds === "number" &&
|
|
93
|
+
Number.isFinite(value.time_seconds) &&
|
|
94
|
+
value.time_seconds >= 0 &&
|
|
95
|
+
(value.end_reason === undefined ||
|
|
96
|
+
value.end_reason === "threshold" ||
|
|
97
|
+
value.end_reason === "timeout");
|
|
98
|
+
const isRealtimeEvent = (value) => {
|
|
99
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
if (value.type === "audio.received") {
|
|
103
|
+
return (typeof value.audio_bytes === "number" &&
|
|
104
|
+
typeof value.chunk_bytes === "number" &&
|
|
105
|
+
typeof value.message_count === "number" &&
|
|
106
|
+
typeof value.session_id === "string");
|
|
107
|
+
}
|
|
108
|
+
if (value.type === "pong") {
|
|
109
|
+
return (typeof value.message_count === "number" &&
|
|
110
|
+
typeof value.session_id === "string");
|
|
111
|
+
}
|
|
112
|
+
const hasProtocolBase = value.protocol_version === 1 &&
|
|
113
|
+
typeof value.sequence === "number" &&
|
|
114
|
+
typeof value.session_id === "string";
|
|
115
|
+
if (!hasProtocolBase) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (["turn.start", "turn.eager_end", "turn.resume", "turn.end"].includes(value.type)) {
|
|
119
|
+
return isTurnEvent(value);
|
|
120
|
+
}
|
|
121
|
+
if (value.type === "session.started") {
|
|
122
|
+
return ((typeof value.language === "string" || value.language === null) &&
|
|
123
|
+
typeof value.model === "string");
|
|
124
|
+
}
|
|
125
|
+
if (value.type === "transcript.committed" ||
|
|
126
|
+
value.type === "transcript.delta" ||
|
|
127
|
+
value.type === "transcript.interim") {
|
|
128
|
+
return (Array.isArray(value.segments) &&
|
|
129
|
+
Array.isArray(value.speaker_turns) &&
|
|
130
|
+
typeof value.text === "string" &&
|
|
131
|
+
Array.isArray(value.words));
|
|
132
|
+
}
|
|
133
|
+
if (value.type === "session.completed") {
|
|
134
|
+
return value.status === "succeeded" && isRecord(value.result);
|
|
135
|
+
}
|
|
136
|
+
if (value.type === "session.failed") {
|
|
137
|
+
return (value.status === "failed" &&
|
|
138
|
+
isRecord(value.error) &&
|
|
139
|
+
isRecord(value.result));
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
};
|
|
143
|
+
export const parsePeacockeryVoiceRealtimeEvent = (value) => {
|
|
144
|
+
const parsed = typeof value === "string" ? JSON.parse(value) : value;
|
|
145
|
+
if (!isRealtimeEvent(parsed)) {
|
|
146
|
+
throw new Error("Peacockery Voice returned an invalid realtime event");
|
|
147
|
+
}
|
|
148
|
+
return parsed;
|
|
149
|
+
};
|
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;
|
|
@@ -1359,6 +1402,7 @@ export interface paths {
|
|
|
1359
1402
|
query?: {
|
|
1360
1403
|
audio_format?: string;
|
|
1361
1404
|
channels?: number;
|
|
1405
|
+
context_bias?: string;
|
|
1362
1406
|
detect_entities?: "true" | "false";
|
|
1363
1407
|
diarize?: "true" | "false";
|
|
1364
1408
|
diarize_model?: "latest" | "v1";
|
|
@@ -1382,8 +1426,12 @@ export interface paths {
|
|
|
1382
1426
|
sample_rate?: number;
|
|
1383
1427
|
search?: string;
|
|
1384
1428
|
smart_format?: "true" | "false";
|
|
1429
|
+
/** @description JSON object mapping speaker names to completed upload input_key values */
|
|
1430
|
+
speaker_references?: string;
|
|
1385
1431
|
tag?: string;
|
|
1386
1432
|
target_streaming_delay_ms?: number;
|
|
1433
|
+
/** @description JSON turn-detection configuration; {} uses provider defaults */
|
|
1434
|
+
turn_detection?: string;
|
|
1387
1435
|
utterance_end_ms?: number;
|
|
1388
1436
|
vad_events?: "true" | "false";
|
|
1389
1437
|
version?: string;
|
|
@@ -2071,7 +2119,7 @@ export interface components {
|
|
|
2071
2119
|
/** @enum {string|null} */
|
|
2072
2120
|
source: "derived" | "provider" | null;
|
|
2073
2121
|
items: {
|
|
2074
|
-
end_seconds: number;
|
|
2122
|
+
end_seconds: number | null;
|
|
2075
2123
|
scores: {
|
|
2076
2124
|
confidence: number | null;
|
|
2077
2125
|
log_probability: number | null;
|
|
@@ -2080,7 +2128,7 @@ export interface components {
|
|
|
2080
2128
|
speaker_confidence: number | null;
|
|
2081
2129
|
} | null;
|
|
2082
2130
|
speaker: string | number | null;
|
|
2083
|
-
start_seconds: number;
|
|
2131
|
+
start_seconds: number | null;
|
|
2084
2132
|
text: string;
|
|
2085
2133
|
}[];
|
|
2086
2134
|
};
|
|
@@ -2090,9 +2138,9 @@ export interface components {
|
|
|
2090
2138
|
/** @enum {string|null} */
|
|
2091
2139
|
source: "derived" | "provider" | null;
|
|
2092
2140
|
items: {
|
|
2093
|
-
end_seconds: number;
|
|
2141
|
+
end_seconds: number | null;
|
|
2094
2142
|
speaker: string | number | null;
|
|
2095
|
-
start_seconds: number;
|
|
2143
|
+
start_seconds: number | null;
|
|
2096
2144
|
text: string;
|
|
2097
2145
|
scores: null;
|
|
2098
2146
|
}[];
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simonpeacocks/peacockery-voice-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Generated TypeScript client for the Peacockery Voice API",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"license": "MIT",
|
|
7
5
|
"repository": {
|
|
8
6
|
"type": "git",
|
|
9
7
|
"url": "git+https://github.com/chikingsley/peacockery-voice.git",
|
|
10
8
|
"directory": "clients/typescript"
|
|
11
9
|
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"type": "module",
|
|
12
12
|
"exports": {
|
|
13
13
|
".": {
|
|
14
14
|
"types": "./dist/index.d.ts",
|
|
@@ -18,10 +18,6 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
"dist"
|
|
20
20
|
],
|
|
21
|
-
"publishConfig": {
|
|
22
|
-
"access": "public",
|
|
23
|
-
"provenance": false
|
|
24
|
-
},
|
|
25
21
|
"scripts": {
|
|
26
22
|
"build": "tsc",
|
|
27
23
|
"check": "tsc --noEmit",
|
|
@@ -32,5 +28,9 @@
|
|
|
32
28
|
},
|
|
33
29
|
"devDependencies": {
|
|
34
30
|
"typescript": "^7.0.2"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public",
|
|
34
|
+
"provenance": false
|
|
35
35
|
}
|
|
36
36
|
}
|