@ai-ecoverse/pi-audio 1.4.0 → 1.6.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 +8 -0
- package/dist/audio.js +25 -0
- package/dist/audio.js.map +3 -3
- package/dist/faux/realtime.js +57 -12
- package/dist/faux/realtime.js.map +2 -2
- package/dist/index.js +5 -0
- package/dist/index.js.map +2 -2
- package/dist/openai/catalog.js +126 -0
- package/dist/openai/catalog.js.map +7 -0
- package/dist/openai/index.js +5 -0
- package/dist/openai/index.js.map +7 -0
- package/dist/openai/listen.js +200 -0
- package/dist/openai/listen.js.map +7 -0
- package/dist/openai/speak.js +91 -0
- package/dist/openai/speak.js.map +7 -0
- package/dist/openai/transcribe.js +112 -0
- package/dist/openai/transcribe.js.map +7 -0
- package/dist/openai/wire.js +201 -0
- package/dist/openai/wire.js.map +7 -0
- package/dist/realtime.js +93 -0
- package/dist/realtime.js.map +3 -3
- package/dist/registry.js +9 -0
- package/dist/registry.js.map +2 -2
- package/dist/types/audio.d.ts +2 -0
- package/dist/types/faux/realtime.d.ts +6 -2
- package/dist/types/index.d.ts +2 -1
- package/dist/types/openai/catalog.d.ts +1 -0
- package/dist/types/openai/index.d.ts +1 -0
- package/dist/types/openai/listen.d.ts +4 -0
- package/dist/types/openai/speak.d.ts +4 -0
- package/dist/types/openai/transcribe.d.ts +4 -0
- package/dist/types/openai/wire.d.ts +52 -0
- package/dist/types/realtime.d.ts +38 -2
- package/package.json +5 -1
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { encodeAudioBase64 } from "../convert.js";
|
|
2
|
+
import { VoiceRequestError } from "../errors.js";
|
|
3
|
+
const boundary = "pi-audio-boundary";
|
|
4
|
+
const mimeFiles = {
|
|
5
|
+
"audio/wav": { format: "wav", filename: "audio.wav" },
|
|
6
|
+
"audio/wave": { format: "wav", filename: "audio.wav" },
|
|
7
|
+
"audio/x-wav": { format: "wav", filename: "audio.wav" },
|
|
8
|
+
"audio/mpeg": { format: "mp3", filename: "audio.mp3" },
|
|
9
|
+
"audio/mp3": { format: "mp3", filename: "audio.mp3" },
|
|
10
|
+
"audio/flac": { format: "flac", filename: "audio.flac" },
|
|
11
|
+
"audio/ogg": { format: "ogg", filename: "audio.ogg" },
|
|
12
|
+
"audio/opus": { format: "ogg", filename: "audio.ogg" },
|
|
13
|
+
"audio/webm": { format: "webm", filename: "audio.webm" },
|
|
14
|
+
"audio/mp4": { format: "m4a", filename: "audio.m4a" },
|
|
15
|
+
"audio/m4a": { format: "m4a", filename: "audio.m4a" },
|
|
16
|
+
"audio/aac": { format: "aac", filename: "audio.aac" },
|
|
17
|
+
"audio/pcm": { format: "pcm", filename: "audio.pcm" }
|
|
18
|
+
};
|
|
19
|
+
function isOpenRouter(model) {
|
|
20
|
+
return model.provider === "openrouter" || model.baseUrl.includes("openrouter.ai");
|
|
21
|
+
}
|
|
22
|
+
function endpoint(baseUrl, path) {
|
|
23
|
+
return `${baseUrl.replace(/\/$/, "")}/${path}`;
|
|
24
|
+
}
|
|
25
|
+
function realtimeUrl(baseUrl) {
|
|
26
|
+
const url = new URL(baseUrl);
|
|
27
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
28
|
+
url.pathname = `${url.pathname.replace(/\/$/, "")}/realtime`;
|
|
29
|
+
url.search = "intent=transcription";
|
|
30
|
+
return url.toString();
|
|
31
|
+
}
|
|
32
|
+
function audioFile(mimeType) {
|
|
33
|
+
const found = mimeFiles[mimeType.toLowerCase()];
|
|
34
|
+
if (!found) throw new Error(`Unsupported audio type ${mimeType}`);
|
|
35
|
+
return found;
|
|
36
|
+
}
|
|
37
|
+
function isRecord(value) {
|
|
38
|
+
return typeof value === "object" && value !== null;
|
|
39
|
+
}
|
|
40
|
+
function stringField(record, key) {
|
|
41
|
+
const value = record[key];
|
|
42
|
+
return typeof value === "string" ? value : void 0;
|
|
43
|
+
}
|
|
44
|
+
function numberField(record, key) {
|
|
45
|
+
const value = record[key];
|
|
46
|
+
return typeof value === "number" ? value : void 0;
|
|
47
|
+
}
|
|
48
|
+
function asVoiceError(error) {
|
|
49
|
+
if (error instanceof VoiceRequestError) return { reason: error.reason, error: error.message };
|
|
50
|
+
if (error instanceof Error && error.name === "AbortError")
|
|
51
|
+
return { reason: "aborted", error: "aborted" };
|
|
52
|
+
if (error instanceof Error) return { reason: "error", error: error.message };
|
|
53
|
+
return { reason: "error", error: String(error) };
|
|
54
|
+
}
|
|
55
|
+
function headerValue(headers, name) {
|
|
56
|
+
if (!headers) return void 0;
|
|
57
|
+
const expected = name.toLowerCase();
|
|
58
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
59
|
+
if (key.toLowerCase() === expected && value !== null && value.trim() !== "") return value;
|
|
60
|
+
}
|
|
61
|
+
return void 0;
|
|
62
|
+
}
|
|
63
|
+
function hasAuth(headers) {
|
|
64
|
+
return headerValue(headers, "authorization") !== void 0 || headerValue(headers, "cf-aig-authorization") !== void 0;
|
|
65
|
+
}
|
|
66
|
+
function requestHeaders(model, options) {
|
|
67
|
+
const headers = { ...model.headers };
|
|
68
|
+
const extra = options?.headers;
|
|
69
|
+
if (extra) {
|
|
70
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
71
|
+
if (value === null) delete headers[key];
|
|
72
|
+
else headers[key] = value;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (!hasAuth(headers)) {
|
|
76
|
+
const apiKey = options?.apiKey;
|
|
77
|
+
if (!apiKey) throw new Error(`No API key for provider: ${model.provider}`);
|
|
78
|
+
headers.Authorization = `Bearer ${apiKey}`;
|
|
79
|
+
}
|
|
80
|
+
return headers;
|
|
81
|
+
}
|
|
82
|
+
function requestSignal(options) {
|
|
83
|
+
const signals = [];
|
|
84
|
+
if (options?.signal) signals.push(options.signal);
|
|
85
|
+
const timeout = options?.timeoutMs;
|
|
86
|
+
if (timeout !== void 0 && timeout > 0) signals.push(AbortSignal.timeout(timeout));
|
|
87
|
+
if (signals.length === 0) return void 0;
|
|
88
|
+
if (signals.length === 1) return signals[0];
|
|
89
|
+
return AbortSignal.any(signals);
|
|
90
|
+
}
|
|
91
|
+
function encodeMultipart(file, fields) {
|
|
92
|
+
const chunks = [];
|
|
93
|
+
const encoder = new TextEncoder();
|
|
94
|
+
const write = (value) => chunks.push(encoder.encode(value));
|
|
95
|
+
write(
|
|
96
|
+
`--${boundary}\r
|
|
97
|
+
Content-Disposition: form-data; name="file"; filename="${file.filename}"\r
|
|
98
|
+
Content-Type: ${file.mimeType}\r
|
|
99
|
+
\r
|
|
100
|
+
`
|
|
101
|
+
);
|
|
102
|
+
chunks.push(file.data);
|
|
103
|
+
write("\r\n");
|
|
104
|
+
for (const [name, value] of Object.entries(fields)) {
|
|
105
|
+
write(`--${boundary}\r
|
|
106
|
+
Content-Disposition: form-data; name="${name}"\r
|
|
107
|
+
\r
|
|
108
|
+
${value}\r
|
|
109
|
+
`);
|
|
110
|
+
}
|
|
111
|
+
write(`--${boundary}--\r
|
|
112
|
+
`);
|
|
113
|
+
const body = new Uint8Array(chunks.reduce((sum, chunk) => sum + chunk.length, 0));
|
|
114
|
+
let offset = 0;
|
|
115
|
+
for (const chunk of chunks) {
|
|
116
|
+
body.set(chunk, offset);
|
|
117
|
+
offset += chunk.length;
|
|
118
|
+
}
|
|
119
|
+
return body;
|
|
120
|
+
}
|
|
121
|
+
function multipartType() {
|
|
122
|
+
return `multipart/form-data; boundary=${boundary}`;
|
|
123
|
+
}
|
|
124
|
+
async function readAudio(input, fetchImpl, signal) {
|
|
125
|
+
if (!("url" in input)) return { data: input.data, mimeType: input.mimeType };
|
|
126
|
+
const response = await fetchImpl(input.url, { signal });
|
|
127
|
+
if (!response.ok) throw new Error(`Audio URL failed: HTTP ${response.status}`);
|
|
128
|
+
const mime = response.headers.get("content-type");
|
|
129
|
+
const mimeType = mime === null || mime.trim() === "" ? "audio/wav" : mime.split(";")[0].trim();
|
|
130
|
+
return { data: new Uint8Array(await response.arrayBuffer()), mimeType };
|
|
131
|
+
}
|
|
132
|
+
function audioPayload(file, openRouter, modelId) {
|
|
133
|
+
if (openRouter) {
|
|
134
|
+
return {
|
|
135
|
+
model: modelId,
|
|
136
|
+
input_audio: { data: encodeAudioBase64(file.data), format: file.format }
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return { model: modelId, file };
|
|
140
|
+
}
|
|
141
|
+
async function applyPayload(payload, model, options) {
|
|
142
|
+
const hook = options?.onPayload;
|
|
143
|
+
const replaced = await hook?.(payload, model);
|
|
144
|
+
return replaced === void 0 ? payload : replaced;
|
|
145
|
+
}
|
|
146
|
+
function headerRecord(headers) {
|
|
147
|
+
const record = {};
|
|
148
|
+
headers.forEach((value, key) => {
|
|
149
|
+
record[key] = value;
|
|
150
|
+
});
|
|
151
|
+
return record;
|
|
152
|
+
}
|
|
153
|
+
async function voiceFetch(model, options, url, body, contentType) {
|
|
154
|
+
const headers = requestHeaders(model, options);
|
|
155
|
+
headers["Content-Type"] = contentType;
|
|
156
|
+
const fetchImpl = options?.fetch ?? globalThis.fetch;
|
|
157
|
+
const response = await fetchImpl(url, {
|
|
158
|
+
method: "POST",
|
|
159
|
+
headers,
|
|
160
|
+
body,
|
|
161
|
+
signal: requestSignal(options)
|
|
162
|
+
});
|
|
163
|
+
await options?.onResponse?.(
|
|
164
|
+
{ status: response.status, headers: headerRecord(response.headers) },
|
|
165
|
+
model
|
|
166
|
+
);
|
|
167
|
+
if (!response.ok) {
|
|
168
|
+
const text = await response.text();
|
|
169
|
+
throw new Error(text === "" ? `HTTP ${response.status}` : text);
|
|
170
|
+
}
|
|
171
|
+
return response;
|
|
172
|
+
}
|
|
173
|
+
async function readJson(response) {
|
|
174
|
+
const text = await response.text();
|
|
175
|
+
try {
|
|
176
|
+
return JSON.parse(text);
|
|
177
|
+
} catch {
|
|
178
|
+
throw new Error("Transcription response was not JSON");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
export {
|
|
182
|
+
applyPayload,
|
|
183
|
+
asVoiceError,
|
|
184
|
+
audioFile,
|
|
185
|
+
audioPayload,
|
|
186
|
+
encodeMultipart,
|
|
187
|
+
endpoint,
|
|
188
|
+
hasAuth,
|
|
189
|
+
isOpenRouter,
|
|
190
|
+
isRecord,
|
|
191
|
+
multipartType,
|
|
192
|
+
numberField,
|
|
193
|
+
readAudio,
|
|
194
|
+
readJson,
|
|
195
|
+
realtimeUrl,
|
|
196
|
+
requestHeaders,
|
|
197
|
+
requestSignal,
|
|
198
|
+
stringField,
|
|
199
|
+
voiceFetch
|
|
200
|
+
};
|
|
201
|
+
//# sourceMappingURL=wire.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/openai/wire.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ProviderRequestOptions } from '@earendil-works/pi-ai';\nimport { encodeAudioBase64 } from '../convert.ts';\nimport { VoiceRequestError } from '../errors.ts';\n\nconst boundary = 'pi-audio-boundary';\n\nconst mimeFiles: Record<string, { format: string; filename: string }> = {\n 'audio/wav': { format: 'wav', filename: 'audio.wav' },\n 'audio/wave': { format: 'wav', filename: 'audio.wav' },\n 'audio/x-wav': { format: 'wav', filename: 'audio.wav' },\n 'audio/mpeg': { format: 'mp3', filename: 'audio.mp3' },\n 'audio/mp3': { format: 'mp3', filename: 'audio.mp3' },\n 'audio/flac': { format: 'flac', filename: 'audio.flac' },\n 'audio/ogg': { format: 'ogg', filename: 'audio.ogg' },\n 'audio/opus': { format: 'ogg', filename: 'audio.ogg' },\n 'audio/webm': { format: 'webm', filename: 'audio.webm' },\n 'audio/mp4': { format: 'm4a', filename: 'audio.m4a' },\n 'audio/m4a': { format: 'm4a', filename: 'audio.m4a' },\n 'audio/aac': { format: 'aac', filename: 'audio.aac' },\n 'audio/pcm': { format: 'pcm', filename: 'audio.pcm' },\n};\n\nexport interface AudioFile {\n data: Uint8Array;\n mimeType: string;\n filename: string;\n format: string;\n}\n\nexport interface JsonBody {\n [key: string]: unknown;\n}\n\ninterface HeaderModel {\n provider: string;\n headers?: Record<string, string>;\n}\n\nexport function isOpenRouter(model: { provider: string; baseUrl: string }): boolean {\n return model.provider === 'openrouter' || model.baseUrl.includes('openrouter.ai');\n}\n\nexport function endpoint(baseUrl: string, path: string): string {\n return `${baseUrl.replace(/\\/$/, '')}/${path}`;\n}\n\nexport function realtimeUrl(baseUrl: string): string {\n const url = new URL(baseUrl);\n url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';\n url.pathname = `${url.pathname.replace(/\\/$/, '')}/realtime`;\n url.search = 'intent=transcription';\n return url.toString();\n}\n\nexport function audioFile(mimeType: string): { format: string; filename: string } {\n const found = mimeFiles[mimeType.toLowerCase()];\n if (!found) throw new Error(`Unsupported audio type ${mimeType}`);\n return found;\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nexport function stringField(record: Record<string, unknown>, key: string): string | undefined {\n const value = record[key];\n return typeof value === 'string' ? value : undefined;\n}\n\nexport function numberField(record: Record<string, unknown>, key: string): number | undefined {\n const value = record[key];\n return typeof value === 'number' ? value : undefined;\n}\n\nexport function asVoiceError(error: unknown): { reason: 'aborted' | 'error'; error: string } {\n if (error instanceof VoiceRequestError) return { reason: error.reason, error: error.message };\n if (error instanceof Error && error.name === 'AbortError')\n return { reason: 'aborted', error: 'aborted' };\n if (error instanceof Error) return { reason: 'error', error: error.message };\n return { reason: 'error', error: String(error) };\n}\n\nfunction headerValue(\n headers: Record<string, string | null> | undefined,\n name: string\n): string | undefined {\n if (!headers) return undefined;\n const expected = name.toLowerCase();\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() === expected && value !== null && value.trim() !== '') return value;\n }\n return undefined;\n}\n\nexport function hasAuth(headers: Record<string, string | null> | undefined): boolean {\n return (\n headerValue(headers, 'authorization') !== undefined ||\n headerValue(headers, 'cf-aig-authorization') !== undefined\n );\n}\n\nexport function requestHeaders(\n model: HeaderModel,\n options: ProviderRequestOptions | undefined\n): Record<string, string> {\n const headers: Record<string, string> = { ...model.headers };\n const extra = options?.headers;\n if (extra) {\n for (const [key, value] of Object.entries(extra)) {\n if (value === null) delete headers[key];\n else headers[key] = value;\n }\n }\n if (!hasAuth(headers)) {\n const apiKey = options?.apiKey;\n if (!apiKey) throw new Error(`No API key for provider: ${model.provider}`);\n headers.Authorization = `Bearer ${apiKey}`;\n }\n return headers;\n}\n\nexport function requestSignal(\n options: ProviderRequestOptions | undefined\n): AbortSignal | undefined {\n const signals: AbortSignal[] = [];\n if (options?.signal) signals.push(options.signal);\n const timeout = options?.timeoutMs;\n if (timeout !== undefined && timeout > 0) signals.push(AbortSignal.timeout(timeout));\n if (signals.length === 0) return undefined;\n if (signals.length === 1) return signals[0];\n return AbortSignal.any(signals);\n}\n\nexport function encodeMultipart(file: AudioFile, fields: Record<string, string>): Uint8Array {\n const chunks: Uint8Array[] = [];\n const encoder = new TextEncoder();\n const write = (value: string) => chunks.push(encoder.encode(value));\n write(\n `--${boundary}\\r\\nContent-Disposition: form-data; name=\"file\"; filename=\"${file.filename}\"\\r\\nContent-Type: ${file.mimeType}\\r\\n\\r\\n`\n );\n chunks.push(file.data);\n write('\\r\\n');\n for (const [name, value] of Object.entries(fields)) {\n write(`--${boundary}\\r\\nContent-Disposition: form-data; name=\"${name}\"\\r\\n\\r\\n${value}\\r\\n`);\n }\n write(`--${boundary}--\\r\\n`);\n const body = new Uint8Array(chunks.reduce((sum, chunk) => sum + chunk.length, 0));\n let offset = 0;\n for (const chunk of chunks) {\n body.set(chunk, offset);\n offset += chunk.length;\n }\n return body;\n}\n\nexport function multipartType(): string {\n return `multipart/form-data; boundary=${boundary}`;\n}\n\nexport async function readAudio(\n input: { data: Uint8Array; mimeType: string } | { url: string },\n fetchImpl: typeof fetch,\n signal: AbortSignal | undefined\n): Promise<{ data: Uint8Array; mimeType: string }> {\n if (!('url' in input)) return { data: input.data, mimeType: input.mimeType };\n const response = await fetchImpl(input.url, { signal });\n if (!response.ok) throw new Error(`Audio URL failed: HTTP ${response.status}`);\n const mime = response.headers.get('content-type');\n const mimeType = mime === null || mime.trim() === '' ? 'audio/wav' : mime.split(';')[0].trim();\n return { data: new Uint8Array(await response.arrayBuffer()), mimeType };\n}\n\nexport function audioPayload(file: AudioFile, openRouter: boolean, modelId: string): JsonBody {\n if (openRouter) {\n return {\n model: modelId,\n input_audio: { data: encodeAudioBase64(file.data), format: file.format },\n };\n }\n return { model: modelId, file };\n}\n\nexport async function applyPayload(\n payload: unknown,\n model: unknown,\n options: ProviderRequestOptions | undefined\n): Promise<unknown> {\n const hook = options?.onPayload as\n | ((body: unknown, voiceModel: unknown) => unknown | undefined | Promise<unknown | undefined>)\n | undefined;\n const replaced = await hook?.(payload, model);\n return replaced === undefined ? payload : replaced;\n}\n\nfunction headerRecord(headers: Headers): Record<string, string> {\n const record: Record<string, string> = {};\n headers.forEach((value, key) => {\n record[key] = value;\n });\n return record;\n}\n\nexport async function voiceFetch(\n model: HeaderModel & { id: string },\n options: ProviderRequestOptions | undefined,\n url: string,\n body: BodyInit,\n contentType: string\n): Promise<Response> {\n const headers = requestHeaders(model, options);\n headers['Content-Type'] = contentType;\n const fetchImpl = options?.fetch ?? globalThis.fetch;\n const response = await fetchImpl(url, {\n method: 'POST',\n headers,\n body,\n signal: requestSignal(options),\n });\n await options?.onResponse?.(\n { status: response.status, headers: headerRecord(response.headers) },\n model as never\n );\n if (!response.ok) {\n const text = await response.text();\n throw new Error(text === '' ? `HTTP ${response.status}` : text);\n }\n return response;\n}\n\nexport async function readJson(response: Response): Promise<unknown> {\n const text = await response.text();\n try {\n return JSON.parse(text);\n } catch {\n throw new Error('Transcription response was not JSON');\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/realtime.js
CHANGED
|
@@ -1 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
alignmentSeconds,
|
|
3
|
+
chunkSeconds
|
|
4
|
+
} from "./audio.js";
|
|
5
|
+
import { VoiceRequestError } from "./errors.js";
|
|
6
|
+
import { priceAudio } from "./usage.js";
|
|
7
|
+
function planDelivery(delivery, requested) {
|
|
8
|
+
const support = delivery[requested];
|
|
9
|
+
if (support !== "native" && support !== "approximate") {
|
|
10
|
+
throw new VoiceRequestError("error", `unsupported delivery: ${requested}`);
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
delivery: requested,
|
|
14
|
+
support,
|
|
15
|
+
waitForIdle: requested === "when_idle" && support === "approximate"
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function planTruncation(truncation, heard) {
|
|
19
|
+
if (truncation === "client" && heard !== void 0) {
|
|
20
|
+
return { item: { itemId: heard.itemId, audioEndMs: heard.heardMs } };
|
|
21
|
+
}
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
function realtimeSocket(url, options, protocols = []) {
|
|
25
|
+
const gateway = options?.gateway;
|
|
26
|
+
if (gateway === void 0) return { url, protocols: [...protocols] };
|
|
27
|
+
return {
|
|
28
|
+
url: gateway.url,
|
|
29
|
+
protocols: [`cf-aig-authorization.${gateway.token}`, ...protocols]
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
class RealtimeLedger {
|
|
33
|
+
#inputSeconds = 0;
|
|
34
|
+
#outputSeconds = 0;
|
|
35
|
+
#textMessages = 0;
|
|
36
|
+
#busy = false;
|
|
37
|
+
#held = [];
|
|
38
|
+
get inputSeconds() {
|
|
39
|
+
return this.#inputSeconds;
|
|
40
|
+
}
|
|
41
|
+
get outputSeconds() {
|
|
42
|
+
return this.#outputSeconds;
|
|
43
|
+
}
|
|
44
|
+
get textMessages() {
|
|
45
|
+
return this.#textMessages;
|
|
46
|
+
}
|
|
47
|
+
get busy() {
|
|
48
|
+
return this.#busy;
|
|
49
|
+
}
|
|
50
|
+
addInput(chunk) {
|
|
51
|
+
this.#inputSeconds += chunkSeconds(chunk);
|
|
52
|
+
}
|
|
53
|
+
addOutput(chunk, alignment) {
|
|
54
|
+
const seconds = chunkSeconds(chunk);
|
|
55
|
+
this.#outputSeconds += seconds > 0 ? seconds : alignmentSeconds(alignment);
|
|
56
|
+
}
|
|
57
|
+
addText() {
|
|
58
|
+
this.#textMessages += 1;
|
|
59
|
+
}
|
|
60
|
+
observe(event) {
|
|
61
|
+
if (event.type === "output_audio") this.addOutput(event.chunk, event.alignment);
|
|
62
|
+
if (event.type === "output_audio" || event.type === "output_transcript") this.#busy = true;
|
|
63
|
+
if (event.type === "response_done") this.#release();
|
|
64
|
+
}
|
|
65
|
+
submit(delivery, requested, run) {
|
|
66
|
+
const plan = planDelivery(delivery, requested);
|
|
67
|
+
if (plan.waitForIdle && this.#busy) this.#held.push(run);
|
|
68
|
+
else run();
|
|
69
|
+
return plan;
|
|
70
|
+
}
|
|
71
|
+
settle() {
|
|
72
|
+
this.#release();
|
|
73
|
+
}
|
|
74
|
+
usage(rates) {
|
|
75
|
+
return priceAudio(rates, {
|
|
76
|
+
inputSeconds: this.#inputSeconds,
|
|
77
|
+
outputSeconds: this.#outputSeconds,
|
|
78
|
+
textMessages: this.#textMessages
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
#release() {
|
|
82
|
+
this.#busy = false;
|
|
83
|
+
const held = this.#held;
|
|
84
|
+
this.#held = [];
|
|
85
|
+
for (const run of held) run();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export {
|
|
89
|
+
RealtimeLedger,
|
|
90
|
+
planDelivery,
|
|
91
|
+
planTruncation,
|
|
92
|
+
realtimeSocket
|
|
93
|
+
};
|
|
1
94
|
//# sourceMappingURL=realtime.js.map
|
package/dist/realtime.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": [],
|
|
4
|
-
"sourcesContent": [],
|
|
5
|
-
"mappings": "",
|
|
3
|
+
"sources": ["../src/realtime.ts"],
|
|
4
|
+
"sourcesContent": ["import type {\n Message,\n ProviderRequestOptions,\n Tool,\n ToolCall,\n ToolResultMessage,\n Usage,\n} from '@earendil-works/pi-ai';\nimport {\n type AudioChunk,\n alignmentSeconds,\n type CharAlignment,\n chunkSeconds,\n type TextSpan,\n} from './audio.ts';\nimport { VoiceRequestError } from './errors.ts';\nimport type { Delivery, DeliverySupport, RealtimeModel } from './models.ts';\nimport { priceAudio, type RealtimeAudioCost } from './usage.ts';\n\nexport interface RealtimeContext {\n instructions: string;\n tools: Tool[];\n voice: string;\n history?: Message[];\n}\n\nexport interface RealtimeToken {\n value: string;\n expiresAt: number;\n model: string;\n}\n\nexport interface RealtimeOptions extends ProviderRequestOptions {\n token?: RealtimeToken;\n transport?: 'websocket' | 'webrtc';\n gateway?: { url: string; token: string };\n resumeHandle?: string;\n}\n\nexport type RealtimeEvent =\n | { type: 'session_start'; sessionId: string }\n | { type: 'input_speech_start' }\n | { type: 'input_transcript'; itemId: string; text: string; final: boolean }\n | {\n type: 'output_audio';\n itemId: string;\n chunk: AudioChunk;\n span?: TextSpan;\n alignment?: CharAlignment;\n }\n | { type: 'output_transcript'; itemId: string; text: string; final: boolean }\n | { type: 'tool_call'; call: ToolCall; blocking: boolean }\n | { type: 'tool_call_cancelled'; callId: string }\n | { type: 'response_done'; itemId: string; interrupted: boolean; usage: Usage }\n | { type: 'go_away'; timeLeftMs: number }\n | { type: 'resumption'; handle: string }\n | { type: 'error'; error: string; fatal: boolean };\n\nexport interface RealtimeSession extends AsyncIterable<RealtimeEvent> {\n readonly model: RealtimeModel<string>;\n appendAudio(chunk: AudioChunk): void;\n appendVideoFrame?(jpeg: Uint8Array): void;\n sendText(text: string): void;\n inject(text: string, delivery: Delivery): void;\n respond(callId: string, result: ToolResultMessage, delivery?: Delivery): void;\n interrupt(heard?: { itemId: string; heardMs: number }): void;\n update(patch: Partial<Pick<RealtimeContext, 'instructions' | 'tools' | 'voice'>>): void;\n close(): Promise<{ usage: Usage }>;\n}\n\nexport interface RealtimeApiProvider<TApi extends string = string> {\n api: TApi;\n connect: (\n model: RealtimeModel<TApi>,\n context: RealtimeContext,\n options?: RealtimeOptions\n ) => Promise<RealtimeSession>;\n}\n\nexport interface DeliveryPlan {\n delivery: Delivery;\n support: DeliverySupport;\n waitForIdle: boolean;\n}\n\nexport interface TruncationPlan {\n item?: { itemId: string; audioEndMs: number };\n}\n\nexport interface RealtimeSocket {\n url: string;\n protocols: string[];\n}\n\nexport function planDelivery(\n delivery: Record<Delivery, DeliverySupport>,\n requested: Delivery\n): DeliveryPlan {\n const support = delivery[requested];\n if (support !== 'native' && support !== 'approximate') {\n throw new VoiceRequestError('error', `unsupported delivery: ${requested}`);\n }\n return {\n delivery: requested,\n support,\n waitForIdle: requested === 'when_idle' && support === 'approximate',\n };\n}\n\nexport function planTruncation(\n truncation: RealtimeModel<string>['truncation'],\n heard?: { itemId: string; heardMs: number }\n): TruncationPlan {\n if (truncation === 'client' && heard !== undefined) {\n return { item: { itemId: heard.itemId, audioEndMs: heard.heardMs } };\n }\n return {};\n}\n\nexport function realtimeSocket(\n url: string,\n options?: Pick<RealtimeOptions, 'gateway'>,\n protocols: readonly string[] = []\n): RealtimeSocket {\n const gateway = options?.gateway;\n if (gateway === undefined) return { url, protocols: [...protocols] };\n return {\n url: gateway.url,\n protocols: [`cf-aig-authorization.${gateway.token}`, ...protocols],\n };\n}\n\nexport class RealtimeLedger {\n #inputSeconds = 0;\n #outputSeconds = 0;\n #textMessages = 0;\n #busy = false;\n #held: Array<() => void> = [];\n\n get inputSeconds(): number {\n return this.#inputSeconds;\n }\n\n get outputSeconds(): number {\n return this.#outputSeconds;\n }\n\n get textMessages(): number {\n return this.#textMessages;\n }\n\n get busy(): boolean {\n return this.#busy;\n }\n\n addInput(chunk: AudioChunk): void {\n this.#inputSeconds += chunkSeconds(chunk);\n }\n\n addOutput(chunk: AudioChunk, alignment?: CharAlignment): void {\n const seconds = chunkSeconds(chunk);\n this.#outputSeconds += seconds > 0 ? seconds : alignmentSeconds(alignment);\n }\n\n addText(): void {\n this.#textMessages += 1;\n }\n\n observe(event: RealtimeEvent): void {\n if (event.type === 'output_audio') this.addOutput(event.chunk, event.alignment);\n if (event.type === 'output_audio' || event.type === 'output_transcript') this.#busy = true;\n if (event.type === 'response_done') this.#release();\n }\n\n submit(\n delivery: Record<Delivery, DeliverySupport>,\n requested: Delivery,\n run: () => void\n ): DeliveryPlan {\n const plan = planDelivery(delivery, requested);\n if (plan.waitForIdle && this.#busy) this.#held.push(run);\n else run();\n return plan;\n }\n\n settle(): void {\n this.#release();\n }\n\n usage(rates: RealtimeAudioCost): Usage {\n return priceAudio(rates, {\n inputSeconds: this.#inputSeconds,\n outputSeconds: this.#outputSeconds,\n textMessages: this.#textMessages,\n });\n }\n\n #release(): void {\n this.#busy = false;\n const held = this.#held;\n this.#held = [];\n for (const run of held) run();\n }\n}\n"],
|
|
5
|
+
"mappings": "AAQA;AAAA,EAEE;AAAA,EAEA;AAAA,OAEK;AACP,SAAS,yBAAyB;AAElC,SAAS,kBAA0C;AA6E5C,SAAS,aACd,UACA,WACc;AACd,QAAM,UAAU,SAAS,SAAS;AAClC,MAAI,YAAY,YAAY,YAAY,eAAe;AACrD,UAAM,IAAI,kBAAkB,SAAS,yBAAyB,SAAS,EAAE;AAAA,EAC3E;AACA,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,IACA,aAAa,cAAc,eAAe,YAAY;AAAA,EACxD;AACF;AAEO,SAAS,eACd,YACA,OACgB;AAChB,MAAI,eAAe,YAAY,UAAU,QAAW;AAClD,WAAO,EAAE,MAAM,EAAE,QAAQ,MAAM,QAAQ,YAAY,MAAM,QAAQ,EAAE;AAAA,EACrE;AACA,SAAO,CAAC;AACV;AAEO,SAAS,eACd,KACA,SACA,YAA+B,CAAC,GAChB;AAChB,QAAM,UAAU,SAAS;AACzB,MAAI,YAAY,OAAW,QAAO,EAAE,KAAK,WAAW,CAAC,GAAG,SAAS,EAAE;AACnE,SAAO;AAAA,IACL,KAAK,QAAQ;AAAA,IACb,WAAW,CAAC,wBAAwB,QAAQ,KAAK,IAAI,GAAG,SAAS;AAAA,EACnE;AACF;AAEO,MAAM,eAAe;AAAA,EAC1B,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,QAAQ;AAAA,EACR,QAA2B,CAAC;AAAA,EAE5B,IAAI,eAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,gBAAwB;AAC1B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,eAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,OAAgB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,SAAS,OAAyB;AAChC,SAAK,iBAAiB,aAAa,KAAK;AAAA,EAC1C;AAAA,EAEA,UAAU,OAAmB,WAAiC;AAC5D,UAAM,UAAU,aAAa,KAAK;AAClC,SAAK,kBAAkB,UAAU,IAAI,UAAU,iBAAiB,SAAS;AAAA,EAC3E;AAAA,EAEA,UAAgB;AACd,SAAK,iBAAiB;AAAA,EACxB;AAAA,EAEA,QAAQ,OAA4B;AAClC,QAAI,MAAM,SAAS,eAAgB,MAAK,UAAU,MAAM,OAAO,MAAM,SAAS;AAC9E,QAAI,MAAM,SAAS,kBAAkB,MAAM,SAAS,oBAAqB,MAAK,QAAQ;AACtF,QAAI,MAAM,SAAS,gBAAiB,MAAK,SAAS;AAAA,EACpD;AAAA,EAEA,OACE,UACA,WACA,KACc;AACd,UAAM,OAAO,aAAa,UAAU,SAAS;AAC7C,QAAI,KAAK,eAAe,KAAK,MAAO,MAAK,MAAM,KAAK,GAAG;AAAA,QAClD,KAAI;AACT,WAAO;AAAA,EACT;AAAA,EAEA,SAAe;AACb,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,MAAM,OAAiC;AACrC,WAAO,WAAW,OAAO;AAAA,MACvB,cAAc,KAAK;AAAA,MACnB,eAAe,KAAK;AAAA,MACpB,cAAc,KAAK;AAAA,IACrB,CAAC;AAAA,EACH;AAAA,EAEA,WAAiB;AACf,SAAK,QAAQ;AACb,UAAM,OAAO,KAAK;AAClB,SAAK,QAAQ,CAAC;AACd,eAAW,OAAO,KAAM,KAAI;AAAA,EAC9B;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/registry.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { speakInSentences } from "./chunk-speech.js";
|
|
2
|
+
import { VoiceRequestError } from "./errors.js";
|
|
2
3
|
import { listenWithVad } from "./vad-listen.js";
|
|
3
4
|
const transcriptionApis = /* @__PURE__ */ new Map();
|
|
4
5
|
const speechApis = /* @__PURE__ */ new Map();
|
|
@@ -45,6 +46,14 @@ function speak(model, input, options) {
|
|
|
45
46
|
return speakModel(model, input, options);
|
|
46
47
|
}
|
|
47
48
|
function connectRealtime(model, context, options) {
|
|
49
|
+
if (options?.transport !== void 0 && !model.transports.includes(options.transport)) {
|
|
50
|
+
return Promise.reject(
|
|
51
|
+
new VoiceRequestError("error", `unsupported transport: ${options.transport}`)
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
if (options?.resumeHandle !== void 0 && !model.resumable) {
|
|
55
|
+
return Promise.reject(new VoiceRequestError("error", "session is not resumable"));
|
|
56
|
+
}
|
|
48
57
|
const connect = realtimeApis.get(model.api)?.provider.connect;
|
|
49
58
|
if (!connect) return Promise.reject(missing(model.api));
|
|
50
59
|
return connect(model, context, options);
|
package/dist/registry.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/registry.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ProviderRequestOptions } from '@earendil-works/pi-ai';\nimport type { AudioSource } from './audio.ts';\nimport { speakInSentences } from './chunk-speech.ts';\nimport type { RealtimeModel, SpeechModel, TranscriptionModel } from './models.ts';\nimport type {\n RealtimeApiProvider,\n RealtimeContext,\n RealtimeOptions,\n RealtimeSession,\n} from './realtime.ts';\nimport type { SpeechApiProvider, SpeechEventStream, SpeechInput } from './speech.ts';\nimport type {\n AudioInput,\n ListenOptions,\n Transcript,\n TranscriptionApiProvider,\n TranscriptionEventStream,\n} from './transcription.ts';\nimport { listenWithVad } from './vad-listen.ts';\n\ninterface TranscriptionEntry {\n provider: TranscriptionApiProvider;\n}\n\ninterface SpeechEntry {\n provider: SpeechApiProvider;\n}\n\ninterface RealtimeEntry {\n provider: RealtimeApiProvider;\n}\n\nconst transcriptionApis = new Map<string, TranscriptionEntry>();\nconst speechApis = new Map<string, SpeechEntry>();\nconst realtimeApis = new Map<string, RealtimeEntry>();\n\nfunction missing(api: string): Error {\n return new Error(`No API provider registered for api: ${api}`);\n}\n\nexport function registerTranscriptionApiProvider(provider: TranscriptionApiProvider): () => void {\n const entry = { provider };\n transcriptionApis.set(provider.api, entry);\n return () => {\n if (transcriptionApis.get(provider.api) === entry) transcriptionApis.delete(provider.api);\n };\n}\n\nexport function registerSpeechApiProvider(provider: SpeechApiProvider): () => void {\n const entry = { provider };\n speechApis.set(provider.api, entry);\n return () => {\n if (speechApis.get(provider.api) === entry) speechApis.delete(provider.api);\n };\n}\n\nexport function registerRealtimeApiProvider(provider: RealtimeApiProvider): () => void {\n const entry = { provider };\n realtimeApis.set(provider.api, entry);\n return () => {\n if (realtimeApis.get(provider.api) === entry) realtimeApis.delete(provider.api);\n };\n}\n\nexport function transcribe(\n model: TranscriptionModel<string>,\n input: AudioInput,\n options?: ProviderRequestOptions\n): Promise<Transcript> {\n const transcribeModel = transcriptionApis.get(model.api)?.provider.transcribe;\n if (!transcribeModel) return Promise.reject(missing(model.api));\n return transcribeModel(model, input, options);\n}\n\nexport function listen(\n model: TranscriptionModel<string>,\n audio: AudioSource,\n options?: ListenOptions\n): TranscriptionEventStream {\n const listenModel = transcriptionApis.get(model.api)?.provider.listen;\n if (!listenModel) throw missing(model.api);\n if (model.turnDetection === 'none') return listenWithVad(listenModel, model, audio, options);\n return listenModel(model, audio, options);\n}\n\nexport function speak(\n model: SpeechModel<string>,\n input: SpeechInput,\n options?: ProviderRequestOptions\n): SpeechEventStream {\n const speakModel = speechApis.get(model.api)?.provider.speak;\n if (!speakModel) throw missing(model.api);\n if (!model.textStreaming) return speakInSentences(speakModel, model, input, options);\n return speakModel(model, input, options);\n}\n\nexport function connectRealtime(\n model: RealtimeModel<string>,\n context: RealtimeContext,\n options?: RealtimeOptions\n): Promise<RealtimeSession> {\n const connect = realtimeApis.get(model.api)?.provider.connect;\n if (!connect) return Promise.reject(missing(model.api));\n return connect(model, context, options);\n}\n"],
|
|
5
|
-
"mappings": "AAEA,SAAS,wBAAwB;
|
|
4
|
+
"sourcesContent": ["import type { ProviderRequestOptions } from '@earendil-works/pi-ai';\nimport type { AudioSource } from './audio.ts';\nimport { speakInSentences } from './chunk-speech.ts';\nimport { VoiceRequestError } from './errors.ts';\nimport type { RealtimeModel, SpeechModel, TranscriptionModel } from './models.ts';\nimport type {\n RealtimeApiProvider,\n RealtimeContext,\n RealtimeOptions,\n RealtimeSession,\n} from './realtime.ts';\nimport type { SpeechApiProvider, SpeechEventStream, SpeechInput } from './speech.ts';\nimport type {\n AudioInput,\n ListenOptions,\n Transcript,\n TranscriptionApiProvider,\n TranscriptionEventStream,\n} from './transcription.ts';\nimport { listenWithVad } from './vad-listen.ts';\n\ninterface TranscriptionEntry {\n provider: TranscriptionApiProvider;\n}\n\ninterface SpeechEntry {\n provider: SpeechApiProvider;\n}\n\ninterface RealtimeEntry {\n provider: RealtimeApiProvider;\n}\n\nconst transcriptionApis = new Map<string, TranscriptionEntry>();\nconst speechApis = new Map<string, SpeechEntry>();\nconst realtimeApis = new Map<string, RealtimeEntry>();\n\nfunction missing(api: string): Error {\n return new Error(`No API provider registered for api: ${api}`);\n}\n\nexport function registerTranscriptionApiProvider(provider: TranscriptionApiProvider): () => void {\n const entry = { provider };\n transcriptionApis.set(provider.api, entry);\n return () => {\n if (transcriptionApis.get(provider.api) === entry) transcriptionApis.delete(provider.api);\n };\n}\n\nexport function registerSpeechApiProvider(provider: SpeechApiProvider): () => void {\n const entry = { provider };\n speechApis.set(provider.api, entry);\n return () => {\n if (speechApis.get(provider.api) === entry) speechApis.delete(provider.api);\n };\n}\n\nexport function registerRealtimeApiProvider(provider: RealtimeApiProvider): () => void {\n const entry = { provider };\n realtimeApis.set(provider.api, entry);\n return () => {\n if (realtimeApis.get(provider.api) === entry) realtimeApis.delete(provider.api);\n };\n}\n\nexport function transcribe(\n model: TranscriptionModel<string>,\n input: AudioInput,\n options?: ProviderRequestOptions\n): Promise<Transcript> {\n const transcribeModel = transcriptionApis.get(model.api)?.provider.transcribe;\n if (!transcribeModel) return Promise.reject(missing(model.api));\n return transcribeModel(model, input, options);\n}\n\nexport function listen(\n model: TranscriptionModel<string>,\n audio: AudioSource,\n options?: ListenOptions\n): TranscriptionEventStream {\n const listenModel = transcriptionApis.get(model.api)?.provider.listen;\n if (!listenModel) throw missing(model.api);\n if (model.turnDetection === 'none') return listenWithVad(listenModel, model, audio, options);\n return listenModel(model, audio, options);\n}\n\nexport function speak(\n model: SpeechModel<string>,\n input: SpeechInput,\n options?: ProviderRequestOptions\n): SpeechEventStream {\n const speakModel = speechApis.get(model.api)?.provider.speak;\n if (!speakModel) throw missing(model.api);\n if (!model.textStreaming) return speakInSentences(speakModel, model, input, options);\n return speakModel(model, input, options);\n}\n\nexport function connectRealtime(\n model: RealtimeModel<string>,\n context: RealtimeContext,\n options?: RealtimeOptions\n): Promise<RealtimeSession> {\n if (options?.transport !== undefined && !model.transports.includes(options.transport)) {\n return Promise.reject(\n new VoiceRequestError('error', `unsupported transport: ${options.transport}`)\n );\n }\n if (options?.resumeHandle !== undefined && !model.resumable) {\n return Promise.reject(new VoiceRequestError('error', 'session is not resumable'));\n }\n const connect = realtimeApis.get(model.api)?.provider.connect;\n if (!connect) return Promise.reject(missing(model.api));\n return connect(model, context, options);\n}\n"],
|
|
5
|
+
"mappings": "AAEA,SAAS,wBAAwB;AACjC,SAAS,yBAAyB;AAgBlC,SAAS,qBAAqB;AAc9B,MAAM,oBAAoB,oBAAI,IAAgC;AAC9D,MAAM,aAAa,oBAAI,IAAyB;AAChD,MAAM,eAAe,oBAAI,IAA2B;AAEpD,SAAS,QAAQ,KAAoB;AACnC,SAAO,IAAI,MAAM,uCAAuC,GAAG,EAAE;AAC/D;AAEO,SAAS,iCAAiC,UAAgD;AAC/F,QAAM,QAAQ,EAAE,SAAS;AACzB,oBAAkB,IAAI,SAAS,KAAK,KAAK;AACzC,SAAO,MAAM;AACX,QAAI,kBAAkB,IAAI,SAAS,GAAG,MAAM,MAAO,mBAAkB,OAAO,SAAS,GAAG;AAAA,EAC1F;AACF;AAEO,SAAS,0BAA0B,UAAyC;AACjF,QAAM,QAAQ,EAAE,SAAS;AACzB,aAAW,IAAI,SAAS,KAAK,KAAK;AAClC,SAAO,MAAM;AACX,QAAI,WAAW,IAAI,SAAS,GAAG,MAAM,MAAO,YAAW,OAAO,SAAS,GAAG;AAAA,EAC5E;AACF;AAEO,SAAS,4BAA4B,UAA2C;AACrF,QAAM,QAAQ,EAAE,SAAS;AACzB,eAAa,IAAI,SAAS,KAAK,KAAK;AACpC,SAAO,MAAM;AACX,QAAI,aAAa,IAAI,SAAS,GAAG,MAAM,MAAO,cAAa,OAAO,SAAS,GAAG;AAAA,EAChF;AACF;AAEO,SAAS,WACd,OACA,OACA,SACqB;AACrB,QAAM,kBAAkB,kBAAkB,IAAI,MAAM,GAAG,GAAG,SAAS;AACnE,MAAI,CAAC,gBAAiB,QAAO,QAAQ,OAAO,QAAQ,MAAM,GAAG,CAAC;AAC9D,SAAO,gBAAgB,OAAO,OAAO,OAAO;AAC9C;AAEO,SAAS,OACd,OACA,OACA,SAC0B;AAC1B,QAAM,cAAc,kBAAkB,IAAI,MAAM,GAAG,GAAG,SAAS;AAC/D,MAAI,CAAC,YAAa,OAAM,QAAQ,MAAM,GAAG;AACzC,MAAI,MAAM,kBAAkB,OAAQ,QAAO,cAAc,aAAa,OAAO,OAAO,OAAO;AAC3F,SAAO,YAAY,OAAO,OAAO,OAAO;AAC1C;AAEO,SAAS,MACd,OACA,OACA,SACmB;AACnB,QAAM,aAAa,WAAW,IAAI,MAAM,GAAG,GAAG,SAAS;AACvD,MAAI,CAAC,WAAY,OAAM,QAAQ,MAAM,GAAG;AACxC,MAAI,CAAC,MAAM,cAAe,QAAO,iBAAiB,YAAY,OAAO,OAAO,OAAO;AACnF,SAAO,WAAW,OAAO,OAAO,OAAO;AACzC;AAEO,SAAS,gBACd,OACA,SACA,SAC0B;AAC1B,MAAI,SAAS,cAAc,UAAa,CAAC,MAAM,WAAW,SAAS,QAAQ,SAAS,GAAG;AACrF,WAAO,QAAQ;AAAA,MACb,IAAI,kBAAkB,SAAS,0BAA0B,QAAQ,SAAS,EAAE;AAAA,IAC9E;AAAA,EACF;AACA,MAAI,SAAS,iBAAiB,UAAa,CAAC,MAAM,WAAW;AAC3D,WAAO,QAAQ,OAAO,IAAI,kBAAkB,SAAS,0BAA0B,CAAC;AAAA,EAClF;AACA,QAAM,UAAU,aAAa,IAAI,MAAM,GAAG,GAAG,SAAS;AACtD,MAAI,CAAC,QAAS,QAAO,QAAQ,OAAO,QAAQ,MAAM,GAAG,CAAC;AACtD,SAAO,QAAQ,OAAO,SAAS,OAAO;AACxC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/types/audio.d.ts
CHANGED
|
@@ -34,3 +34,5 @@ export interface AudioSink {
|
|
|
34
34
|
readonly playedMs: number;
|
|
35
35
|
onPlayback(listener: (tag: AudioTag, offsetMs: number) => void): () => void;
|
|
36
36
|
}
|
|
37
|
+
export declare function chunkSeconds(chunk: AudioChunk): number;
|
|
38
|
+
export declare function alignmentSeconds(alignment: CharAlignment | undefined): number;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AudioChunk } from '../audio.ts';
|
|
2
2
|
import { type Delivery, type DeliverySupport, type RealtimeModel } from '../models.ts';
|
|
3
|
-
import type
|
|
4
|
-
import {
|
|
3
|
+
import { type RealtimeEvent, type RealtimeSession, type RealtimeSocket, type TruncationPlan } from '../realtime.ts';
|
|
4
|
+
import type { RealtimeAudioCost } from '../usage.ts';
|
|
5
5
|
export type FauxRealtimeCall = {
|
|
6
6
|
method: 'appendAudio';
|
|
7
7
|
chunk: AudioChunk;
|
|
@@ -15,17 +15,20 @@ export type FauxRealtimeCall = {
|
|
|
15
15
|
method: 'inject';
|
|
16
16
|
text: string;
|
|
17
17
|
delivery: Delivery;
|
|
18
|
+
support: DeliverySupport;
|
|
18
19
|
} | {
|
|
19
20
|
method: 'respond';
|
|
20
21
|
callId: string;
|
|
21
22
|
result: Parameters<RealtimeSession['respond']>[1];
|
|
22
23
|
delivery?: Delivery;
|
|
24
|
+
support?: DeliverySupport;
|
|
23
25
|
} | {
|
|
24
26
|
method: 'interrupt';
|
|
25
27
|
heard?: {
|
|
26
28
|
itemId: string;
|
|
27
29
|
heardMs: number;
|
|
28
30
|
};
|
|
31
|
+
plan: TruncationPlan;
|
|
29
32
|
} | {
|
|
30
33
|
method: 'update';
|
|
31
34
|
patch: Parameters<RealtimeSession['update']>[0];
|
|
@@ -48,6 +51,7 @@ export interface FauxRealtimeOptions extends FauxRealtimeFlags {
|
|
|
48
51
|
export interface FauxRealtime {
|
|
49
52
|
model: RealtimeModel<string>;
|
|
50
53
|
calls: FauxRealtimeCall[];
|
|
54
|
+
readonly socket: RealtimeSocket | undefined;
|
|
51
55
|
emit(event: RealtimeEvent): void;
|
|
52
56
|
setFlags(flags: FauxRealtimeFlags): void;
|
|
53
57
|
unregister(): void;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -5,7 +5,8 @@ export { convertAudio, convertAudioSource, decodeAudioBase64, encodeAudioBase64,
|
|
|
5
5
|
export { VoiceRequestError } from './errors.ts';
|
|
6
6
|
export type { Delivery, DeliverySupport, KnownRealtimeApi, KnownSpeechApi, KnownTranscriptionApi, RealtimeApi, RealtimeModel, SpeechApi, SpeechModel, TranscriptionApi, TranscriptionModel, VoiceInfo, VoiceModel, VoiceModelType, } from './models.ts';
|
|
7
7
|
export { getVoiceModel, listVoiceModels, registerVoiceModel, } from './models.ts';
|
|
8
|
-
export type { RealtimeApiProvider, RealtimeContext, RealtimeEvent, RealtimeOptions, RealtimeSession, RealtimeToken, } from './realtime.ts';
|
|
8
|
+
export type { DeliveryPlan, RealtimeApiProvider, RealtimeContext, RealtimeEvent, RealtimeOptions, RealtimeSession, RealtimeSocket, RealtimeToken, TruncationPlan, } from './realtime.ts';
|
|
9
|
+
export { planDelivery, planTruncation, RealtimeLedger, realtimeSocket } from './realtime.ts';
|
|
9
10
|
export { connectRealtime, listen, registerRealtimeApiProvider, registerSpeechApiProvider, registerTranscriptionApiProvider, speak, transcribe, } from './registry.ts';
|
|
10
11
|
export type { SpeechApiProvider, SpeechEvent, SpeechInput } from './speech.ts';
|
|
11
12
|
export { SpeechEventStream } from './speech.ts';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function registerOpenAI(): () => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { registerOpenAI } from './catalog.ts';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AudioChunk } from '../audio.ts';
|
|
2
|
+
import type { TranscriptionModel } from '../models.ts';
|
|
3
|
+
import { type ListenOptions, TranscriptionEventStream } from '../transcription.ts';
|
|
4
|
+
export declare function listenOpenAI(model: TranscriptionModel<string>, audio: AsyncIterable<AudioChunk>, options?: ListenOptions): TranscriptionEventStream;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ProviderRequestOptions } from '@earendil-works/pi-ai';
|
|
2
|
+
import type { SpeechModel } from '../models.ts';
|
|
3
|
+
import { SpeechEventStream, type SpeechInput } from '../speech.ts';
|
|
4
|
+
export declare function speakOpenAI(model: SpeechModel<string>, input: SpeechInput, options?: ProviderRequestOptions): SpeechEventStream;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ProviderRequestOptions } from '@earendil-works/pi-ai';
|
|
2
|
+
import type { TranscriptionModel } from '../models.ts';
|
|
3
|
+
import type { AudioInput, Transcript } from '../transcription.ts';
|
|
4
|
+
export declare function transcribeOpenAI(model: TranscriptionModel<string>, input: AudioInput, options?: ProviderRequestOptions): Promise<Transcript>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ProviderRequestOptions } from '@earendil-works/pi-ai';
|
|
2
|
+
export interface AudioFile {
|
|
3
|
+
data: Uint8Array;
|
|
4
|
+
mimeType: string;
|
|
5
|
+
filename: string;
|
|
6
|
+
format: string;
|
|
7
|
+
}
|
|
8
|
+
export interface JsonBody {
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
interface HeaderModel {
|
|
12
|
+
provider: string;
|
|
13
|
+
headers?: Record<string, string>;
|
|
14
|
+
}
|
|
15
|
+
export declare function isOpenRouter(model: {
|
|
16
|
+
provider: string;
|
|
17
|
+
baseUrl: string;
|
|
18
|
+
}): boolean;
|
|
19
|
+
export declare function endpoint(baseUrl: string, path: string): string;
|
|
20
|
+
export declare function realtimeUrl(baseUrl: string): string;
|
|
21
|
+
export declare function audioFile(mimeType: string): {
|
|
22
|
+
format: string;
|
|
23
|
+
filename: string;
|
|
24
|
+
};
|
|
25
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
26
|
+
export declare function stringField(record: Record<string, unknown>, key: string): string | undefined;
|
|
27
|
+
export declare function numberField(record: Record<string, unknown>, key: string): number | undefined;
|
|
28
|
+
export declare function asVoiceError(error: unknown): {
|
|
29
|
+
reason: 'aborted' | 'error';
|
|
30
|
+
error: string;
|
|
31
|
+
};
|
|
32
|
+
export declare function hasAuth(headers: Record<string, string | null> | undefined): boolean;
|
|
33
|
+
export declare function requestHeaders(model: HeaderModel, options: ProviderRequestOptions | undefined): Record<string, string>;
|
|
34
|
+
export declare function requestSignal(options: ProviderRequestOptions | undefined): AbortSignal | undefined;
|
|
35
|
+
export declare function encodeMultipart(file: AudioFile, fields: Record<string, string>): Uint8Array;
|
|
36
|
+
export declare function multipartType(): string;
|
|
37
|
+
export declare function readAudio(input: {
|
|
38
|
+
data: Uint8Array;
|
|
39
|
+
mimeType: string;
|
|
40
|
+
} | {
|
|
41
|
+
url: string;
|
|
42
|
+
}, fetchImpl: typeof fetch, signal: AbortSignal | undefined): Promise<{
|
|
43
|
+
data: Uint8Array;
|
|
44
|
+
mimeType: string;
|
|
45
|
+
}>;
|
|
46
|
+
export declare function audioPayload(file: AudioFile, openRouter: boolean, modelId: string): JsonBody;
|
|
47
|
+
export declare function applyPayload(payload: unknown, model: unknown, options: ProviderRequestOptions | undefined): Promise<unknown>;
|
|
48
|
+
export declare function voiceFetch(model: HeaderModel & {
|
|
49
|
+
id: string;
|
|
50
|
+
}, options: ProviderRequestOptions | undefined, url: string, body: BodyInit, contentType: string): Promise<Response>;
|
|
51
|
+
export declare function readJson(response: Response): Promise<unknown>;
|
|
52
|
+
export {};
|
package/dist/types/realtime.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Message, ProviderRequestOptions, Tool, ToolCall, ToolResultMessage, Usage } from '@earendil-works/pi-ai';
|
|
2
|
-
import type
|
|
3
|
-
import type { Delivery, RealtimeModel } from './models.ts';
|
|
2
|
+
import { type AudioChunk, type CharAlignment, type TextSpan } from './audio.ts';
|
|
3
|
+
import type { Delivery, DeliverySupport, RealtimeModel } from './models.ts';
|
|
4
|
+
import { type RealtimeAudioCost } from './usage.ts';
|
|
4
5
|
export interface RealtimeContext {
|
|
5
6
|
instructions: string;
|
|
6
7
|
tools: Tool[];
|
|
@@ -85,3 +86,38 @@ export interface RealtimeApiProvider<TApi extends string = string> {
|
|
|
85
86
|
api: TApi;
|
|
86
87
|
connect: (model: RealtimeModel<TApi>, context: RealtimeContext, options?: RealtimeOptions) => Promise<RealtimeSession>;
|
|
87
88
|
}
|
|
89
|
+
export interface DeliveryPlan {
|
|
90
|
+
delivery: Delivery;
|
|
91
|
+
support: DeliverySupport;
|
|
92
|
+
waitForIdle: boolean;
|
|
93
|
+
}
|
|
94
|
+
export interface TruncationPlan {
|
|
95
|
+
item?: {
|
|
96
|
+
itemId: string;
|
|
97
|
+
audioEndMs: number;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export interface RealtimeSocket {
|
|
101
|
+
url: string;
|
|
102
|
+
protocols: string[];
|
|
103
|
+
}
|
|
104
|
+
export declare function planDelivery(delivery: Record<Delivery, DeliverySupport>, requested: Delivery): DeliveryPlan;
|
|
105
|
+
export declare function planTruncation(truncation: RealtimeModel<string>['truncation'], heard?: {
|
|
106
|
+
itemId: string;
|
|
107
|
+
heardMs: number;
|
|
108
|
+
}): TruncationPlan;
|
|
109
|
+
export declare function realtimeSocket(url: string, options?: Pick<RealtimeOptions, 'gateway'>, protocols?: readonly string[]): RealtimeSocket;
|
|
110
|
+
export declare class RealtimeLedger {
|
|
111
|
+
#private;
|
|
112
|
+
get inputSeconds(): number;
|
|
113
|
+
get outputSeconds(): number;
|
|
114
|
+
get textMessages(): number;
|
|
115
|
+
get busy(): boolean;
|
|
116
|
+
addInput(chunk: AudioChunk): void;
|
|
117
|
+
addOutput(chunk: AudioChunk, alignment?: CharAlignment): void;
|
|
118
|
+
addText(): void;
|
|
119
|
+
observe(event: RealtimeEvent): void;
|
|
120
|
+
submit(delivery: Record<Delivery, DeliverySupport>, requested: Delivery, run: () => void): DeliveryPlan;
|
|
121
|
+
settle(): void;
|
|
122
|
+
usage(rates: RealtimeAudioCost): Usage;
|
|
123
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/pi-audio",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Voice bindings for pi: speech-to-text, text-to-speech and realtime speech for pi-coding-agent plugins and slicc-agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -33,6 +33,10 @@
|
|
|
33
33
|
"./worklet": {
|
|
34
34
|
"types": "./dist/types/worklet/convert.d.ts",
|
|
35
35
|
"default": "./dist/worklet/convert.js"
|
|
36
|
+
},
|
|
37
|
+
"./openai": {
|
|
38
|
+
"types": "./dist/types/openai/index.d.ts",
|
|
39
|
+
"default": "./dist/openai/index.js"
|
|
36
40
|
}
|
|
37
41
|
},
|
|
38
42
|
"files": [
|