@simonpeacocks/peacockery-voice-client 0.1.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 ADDED
@@ -0,0 +1,26 @@
1
+ # Peacockery Voice TypeScript client
2
+
3
+ The request and response types in `src/schema.ts` are generated from the service's checked-in `openapi.json`. The small runtime wrapper uses `openapi-fetch` and accepts either the `lab` or `production` environment.
4
+
5
+ Install it from the private repository:
6
+
7
+ ```bash
8
+ pnpm add '@chikingsley/peacockery-voice-client@github:chikingsley/peacockery-voice#main&path:/clients/typescript'
9
+ ```
10
+
11
+ ```ts
12
+ import { createPeacockeryVoiceClient } from "@chikingsley/peacockery-voice-client";
13
+
14
+ const voice = createPeacockeryVoiceClient({
15
+ apiKey: process.env.PEACOCKERY_VOICE_API_KEY!,
16
+ environment: "lab",
17
+ });
18
+
19
+ const { data, error } = await voice.GET("/v1/models");
20
+ ```
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 also remain `Bearer` while supplying `dpopProof` to bind a newly minted child.
23
+
24
+ Private-key creation and signing stay in platform code. On Apple hardware the intended provider uses a non-exportable Secure Enclave P-256 key; the generated client never serializes or persists that key.
25
+
26
+ Run `pnpm run generate:sdk` from the repository root whenever the OpenAPI contract changes. Compiled JavaScript and declarations are checked in so a private Git installation does not need to execute package lifecycle scripts. Realtime WebSockets still use the protocol documented by `/v1/realtime`; `openapi-fetch` covers the HTTP surface.
@@ -0,0 +1,24 @@
1
+ import type { paths } from "./schema.js";
2
+ 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
+ export declare const createPeacockeryVoiceClient: (options: PeacockeryVoiceClientOptions) => import("openapi-fetch").Client<paths, `${string}/${string}`>;
24
+ export type PeacockeryVoiceClient = ReturnType<typeof createPeacockeryVoiceClient>;
package/dist/index.js ADDED
@@ -0,0 +1,48 @@
1
+ import createClient from "openapi-fetch";
2
+ export const peacockeryVoiceBaseUrls = {
3
+ lab: "https://voice-lab.peacockery.studio",
4
+ production: "https://voice.peacockery.studio",
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
+ };
42
+ export const createPeacockeryVoiceClient = (options) => {
43
+ const environment = options.environment ?? "production";
44
+ return createClient({
45
+ baseUrl: options.baseUrl ?? peacockeryVoiceBaseUrls[environment],
46
+ fetch: authenticatedFetch(options),
47
+ });
48
+ };