@ai-ecoverse/pi-audio 0.0.0 → 1.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/LICENSE +191 -0
- package/README.md +27 -5
- package/dist/audio.js +1 -0
- package/dist/audio.js.map +7 -0
- package/dist/auth.js +7 -0
- package/dist/auth.js.map +7 -0
- package/dist/blank-usage.js +14 -0
- package/dist/blank-usage.js.map +7 -0
- package/dist/errors.js +12 -0
- package/dist/errors.js.map +7 -0
- package/dist/faux/index.js +15 -0
- package/dist/faux/index.js.map +7 -0
- package/dist/faux/realtime.js +137 -0
- package/dist/faux/realtime.js.map +7 -0
- package/dist/faux/shared.js +37 -0
- package/dist/faux/shared.js.map +7 -0
- package/dist/faux/speech.js +108 -0
- package/dist/faux/speech.js.map +7 -0
- package/dist/faux/transcription.js +152 -0
- package/dist/faux/transcription.js.map +7 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +7 -0
- package/dist/models.js +29 -0
- package/dist/models.js.map +7 -0
- package/dist/realtime.js +1 -0
- package/dist/realtime.js.map +7 -0
- package/dist/registry.js +57 -0
- package/dist/registry.js.map +7 -0
- package/dist/speech.js +31 -0
- package/dist/speech.js.map +7 -0
- package/dist/stream-result.js +28 -0
- package/dist/stream-result.js.map +7 -0
- package/dist/transcription.js +37 -0
- package/dist/transcription.js.map +7 -0
- package/dist/types/audio.d.ts +36 -0
- package/dist/types/auth.d.ts +3 -0
- package/dist/types/blank-usage.d.ts +2 -0
- package/dist/types/errors.d.ts +4 -0
- package/dist/types/faux/index.d.ts +3 -0
- package/dist/types/faux/realtime.d.ts +53 -0
- package/dist/types/faux/shared.d.ts +16 -0
- package/dist/types/faux/speech.d.ts +18 -0
- package/dist/types/faux/transcription.d.ts +27 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/models.d.ts +73 -0
- package/dist/types/realtime.d.ts +87 -0
- package/dist/types/registry.d.ts +13 -0
- package/dist/types/speech.d.ts +38 -0
- package/dist/types/stream-result.d.ts +6 -0
- package/dist/types/transcription.d.ts +62 -0
- package/package.json +56 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/faux/speech.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ProviderRequestOptions } from '@earendil-works/pi-ai';\nimport type { AudioFormat, CharAlignment } from '../audio.ts';\nimport { blankUsage } from '../blank-usage.ts';\nimport { VoiceRequestError } from '../errors.ts';\nimport { registerVoiceModel, type SpeechModel } from '../models.ts';\nimport { registerSpeechApiProvider } from '../registry.ts';\nimport { SpeechEventStream, type SpeechInput } from '../speech.ts';\nimport { encodeChars, errorMessage, modelKey, pcm, shareApi, zeroCost } from './shared.ts';\n\nexport interface FauxSpeechOptions {\n api?: string;\n provider?: string;\n id?: string;\n name?: string;\n alignment?: boolean;\n chunkChars?: number;\n textStreaming?: boolean;\n}\n\nexport interface FauxSpeech {\n model: SpeechModel<string>;\n calls: SpeechInput[];\n setAlignment(alignment: boolean): void;\n unregister(): void;\n}\n\ninterface SpeechState {\n model: SpeechModel<string>;\n alignment: boolean;\n chunkChars: number;\n calls: SpeechInput[];\n}\n\nconst states = new Map<string, SpeechState>();\nconst apis = new Map<string, () => void>();\n\nexport function registerFauxSpeech(options: FauxSpeechOptions = {}): FauxSpeech {\n const model: SpeechModel<string> = {\n id: options.id ?? 'faux-speech',\n name: options.name ?? 'Faux speech',\n api: options.api ?? 'faux-speech',\n provider: options.provider ?? 'faux',\n baseUrl: 'http://localhost:0',\n type: 'speech',\n textStreaming: options.textStreaming ?? true,\n outputFormats: [pcm()],\n voices: [{ id: 'faux', name: 'Faux', languages: ['en'] }],\n cloning: false,\n cost: zeroCost,\n };\n const state: SpeechState = {\n model,\n alignment: options.alignment ?? false,\n chunkChars: Math.max(1, options.chunkChars ?? 4),\n calls: [],\n };\n states.set(modelKey(model), state);\n const unregisterModel = registerVoiceModel(model);\n const release = shareApi(\n apis,\n model.api,\n () => registerSpeechApiProvider({ api: model.api, speak: speakFaux }),\n () => [...states.values()].some((entry) => entry.model.api === model.api)\n );\n return {\n model,\n calls: state.calls,\n setAlignment(alignment) {\n state.alignment = alignment;\n },\n unregister() {\n if (states.get(modelKey(model)) === state) states.delete(modelKey(model));\n unregisterModel();\n release();\n },\n };\n}\n\nfunction speakFaux(\n model: SpeechModel<string>,\n input: SpeechInput,\n options?: ProviderRequestOptions\n): SpeechEventStream {\n const stream = new SpeechEventStream();\n const state = states.get(modelKey(model));\n if (!state) {\n stream.push({\n type: 'error',\n reason: 'error',\n error: `Unknown faux speech model ${model.provider}/${model.id}`,\n });\n return stream;\n }\n state.calls.push(input);\n void pumpSpeech(state, input, options, stream);\n return stream;\n}\n\nasync function readText(input: SpeechInput, signal: AbortSignal | undefined): Promise<string> {\n if (typeof input.text === 'string') return input.text;\n let text = '';\n for await (const part of input.text) {\n if (signal?.aborted) throw new VoiceRequestError('aborted', 'aborted');\n text += part;\n }\n return text;\n}\n\nfunction alignmentFor(start: number, length: number): CharAlignment {\n const startMs = [];\n const durationMs = [];\n for (let offset = 0; offset < length; offset++) {\n startMs.push((start + offset) * 20);\n durationMs.push(20);\n }\n return { startMs, durationMs };\n}\n\nasync function pumpSpeech(\n state: SpeechState,\n input: SpeechInput,\n options: ProviderRequestOptions | undefined,\n stream: SpeechEventStream\n) {\n try {\n if (options?.signal?.aborted) throw new VoiceRequestError('aborted', 'aborted');\n const text = await readText(input, options?.signal);\n const format: AudioFormat | undefined = input.format ?? state.model.outputFormats[0];\n if (!format) throw new Error('No output format');\n stream.push({ type: 'start', format });\n for (let start = 0; start < text.length; start += state.chunkChars) {\n const slice = text.slice(start, start + state.chunkChars);\n stream.push({\n type: 'audio',\n chunk: { data: encodeChars(slice), format, timestamp: start * 20 },\n span: { itemId: state.model.id, start, end: start + slice.length },\n ...(state.alignment ? { alignment: alignmentFor(start, slice.length) } : {}),\n });\n }\n stream.push({ type: 'done', usage: blankUsage() });\n } catch (error) {\n const reason = error instanceof VoiceRequestError ? error.reason : 'error';\n stream.push({ type: 'error', reason, error: errorMessage(error) });\n }\n}\n"],
|
|
5
|
+
"mappings": "AAEA,SAAS,kBAAkB;AAC3B,SAAS,yBAAyB;AAClC,SAAS,0BAA4C;AACrD,SAAS,iCAAiC;AAC1C,SAAS,yBAA2C;AACpD,SAAS,aAAa,cAAc,UAAU,KAAK,UAAU,gBAAgB;AA0B7E,MAAM,SAAS,oBAAI,IAAyB;AAC5C,MAAM,OAAO,oBAAI,IAAwB;AAElC,SAAS,mBAAmB,UAA6B,CAAC,GAAe;AAC9E,QAAM,QAA6B;AAAA,IACjC,IAAI,QAAQ,MAAM;AAAA,IAClB,MAAM,QAAQ,QAAQ;AAAA,IACtB,KAAK,QAAQ,OAAO;AAAA,IACpB,UAAU,QAAQ,YAAY;AAAA,IAC9B,SAAS;AAAA,IACT,MAAM;AAAA,IACN,eAAe,QAAQ,iBAAiB;AAAA,IACxC,eAAe,CAAC,IAAI,CAAC;AAAA,IACrB,QAAQ,CAAC,EAAE,IAAI,QAAQ,MAAM,QAAQ,WAAW,CAAC,IAAI,EAAE,CAAC;AAAA,IACxD,SAAS;AAAA,IACT,MAAM;AAAA,EACR;AACA,QAAM,QAAqB;AAAA,IACzB;AAAA,IACA,WAAW,QAAQ,aAAa;AAAA,IAChC,YAAY,KAAK,IAAI,GAAG,QAAQ,cAAc,CAAC;AAAA,IAC/C,OAAO,CAAC;AAAA,EACV;AACA,SAAO,IAAI,SAAS,KAAK,GAAG,KAAK;AACjC,QAAM,kBAAkB,mBAAmB,KAAK;AAChD,QAAM,UAAU;AAAA,IACd;AAAA,IACA,MAAM;AAAA,IACN,MAAM,0BAA0B,EAAE,KAAK,MAAM,KAAK,OAAO,UAAU,CAAC;AAAA,IACpE,MAAM,CAAC,GAAG,OAAO,OAAO,CAAC,EAAE,KAAK,CAAC,UAAU,MAAM,MAAM,QAAQ,MAAM,GAAG;AAAA,EAC1E;AACA,SAAO;AAAA,IACL;AAAA,IACA,OAAO,MAAM;AAAA,IACb,aAAa,WAAW;AACtB,YAAM,YAAY;AAAA,IACpB;AAAA,IACA,aAAa;AACX,UAAI,OAAO,IAAI,SAAS,KAAK,CAAC,MAAM,MAAO,QAAO,OAAO,SAAS,KAAK,CAAC;AACxE,sBAAgB;AAChB,cAAQ;AAAA,IACV;AAAA,EACF;AACF;AAEA,SAAS,UACP,OACA,OACA,SACmB;AACnB,QAAM,SAAS,IAAI,kBAAkB;AACrC,QAAM,QAAQ,OAAO,IAAI,SAAS,KAAK,CAAC;AACxC,MAAI,CAAC,OAAO;AACV,WAAO,KAAK;AAAA,MACV,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,OAAO,6BAA6B,MAAM,QAAQ,IAAI,MAAM,EAAE;AAAA,IAChE,CAAC;AACD,WAAO;AAAA,EACT;AACA,QAAM,MAAM,KAAK,KAAK;AACtB,OAAK,WAAW,OAAO,OAAO,SAAS,MAAM;AAC7C,SAAO;AACT;AAEA,eAAe,SAAS,OAAoB,QAAkD;AAC5F,MAAI,OAAO,MAAM,SAAS,SAAU,QAAO,MAAM;AACjD,MAAI,OAAO;AACX,mBAAiB,QAAQ,MAAM,MAAM;AACnC,QAAI,QAAQ,QAAS,OAAM,IAAI,kBAAkB,WAAW,SAAS;AACrE,YAAQ;AAAA,EACV;AACA,SAAO;AACT;AAEA,SAAS,aAAa,OAAe,QAA+B;AAClE,QAAM,UAAU,CAAC;AACjB,QAAM,aAAa,CAAC;AACpB,WAAS,SAAS,GAAG,SAAS,QAAQ,UAAU;AAC9C,YAAQ,MAAM,QAAQ,UAAU,EAAE;AAClC,eAAW,KAAK,EAAE;AAAA,EACpB;AACA,SAAO,EAAE,SAAS,WAAW;AAC/B;AAEA,eAAe,WACb,OACA,OACA,SACA,QACA;AACA,MAAI;AACF,QAAI,SAAS,QAAQ,QAAS,OAAM,IAAI,kBAAkB,WAAW,SAAS;AAC9E,UAAM,OAAO,MAAM,SAAS,OAAO,SAAS,MAAM;AAClD,UAAM,SAAkC,MAAM,UAAU,MAAM,MAAM,cAAc,CAAC;AACnF,QAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,kBAAkB;AAC/C,WAAO,KAAK,EAAE,MAAM,SAAS,OAAO,CAAC;AACrC,aAAS,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS,MAAM,YAAY;AAClE,YAAM,QAAQ,KAAK,MAAM,OAAO,QAAQ,MAAM,UAAU;AACxD,aAAO,KAAK;AAAA,QACV,MAAM;AAAA,QACN,OAAO,EAAE,MAAM,YAAY,KAAK,GAAG,QAAQ,WAAW,QAAQ,GAAG;AAAA,QACjE,MAAM,EAAE,QAAQ,MAAM,MAAM,IAAI,OAAO,KAAK,QAAQ,MAAM,OAAO;AAAA,QACjE,GAAI,MAAM,YAAY,EAAE,WAAW,aAAa,OAAO,MAAM,MAAM,EAAE,IAAI,CAAC;AAAA,MAC5E,CAAC;AAAA,IACH;AACA,WAAO,KAAK,EAAE,MAAM,QAAQ,OAAO,WAAW,EAAE,CAAC;AAAA,EACnD,SAAS,OAAO;AACd,UAAM,SAAS,iBAAiB,oBAAoB,MAAM,SAAS;AACnE,WAAO,KAAK,EAAE,MAAM,SAAS,QAAQ,OAAO,aAAa,KAAK,EAAE,CAAC;AAAA,EACnE;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { blankUsage } from "../blank-usage.js";
|
|
2
|
+
import { VoiceRequestError } from "../errors.js";
|
|
3
|
+
import { registerVoiceModel } from "../models.js";
|
|
4
|
+
import { registerTranscriptionApiProvider } from "../registry.js";
|
|
5
|
+
import {
|
|
6
|
+
TranscriptionEventStream
|
|
7
|
+
} from "../transcription.js";
|
|
8
|
+
import { decodeBytes, errorMessage, modelKey, pcm, shareApi, zeroCost } from "./shared.js";
|
|
9
|
+
const states = /* @__PURE__ */ new Map();
|
|
10
|
+
const apis = /* @__PURE__ */ new Map();
|
|
11
|
+
function requireState(model) {
|
|
12
|
+
const state = states.get(modelKey(model));
|
|
13
|
+
if (!state) throw new Error(`Unknown faux transcription model ${model.provider}/${model.id}`);
|
|
14
|
+
return state;
|
|
15
|
+
}
|
|
16
|
+
function registerFauxTranscription(options = {}) {
|
|
17
|
+
const model = {
|
|
18
|
+
id: options.id ?? "faux-transcription",
|
|
19
|
+
name: options.name ?? "Faux transcription",
|
|
20
|
+
api: options.api ?? "faux-transcription",
|
|
21
|
+
provider: options.provider ?? "faux",
|
|
22
|
+
baseUrl: "http://localhost:0",
|
|
23
|
+
type: "transcription",
|
|
24
|
+
streaming: true,
|
|
25
|
+
turnDetection: options.turnDetection ?? "vad",
|
|
26
|
+
inputFormats: [pcm(16e3)],
|
|
27
|
+
languages: ["en"],
|
|
28
|
+
diarization: false,
|
|
29
|
+
cost: zeroCost
|
|
30
|
+
};
|
|
31
|
+
const state = { model, cues: options.cues, calls: [] };
|
|
32
|
+
states.set(modelKey(model), state);
|
|
33
|
+
const unregisterModel = registerVoiceModel(model);
|
|
34
|
+
const release = shareApi(
|
|
35
|
+
apis,
|
|
36
|
+
model.api,
|
|
37
|
+
() => registerTranscriptionApiProvider({
|
|
38
|
+
api: model.api,
|
|
39
|
+
transcribe: transcribeFaux,
|
|
40
|
+
listen: listenFaux
|
|
41
|
+
}),
|
|
42
|
+
() => [...states.values()].some((entry) => entry.model.api === model.api)
|
|
43
|
+
);
|
|
44
|
+
return {
|
|
45
|
+
model,
|
|
46
|
+
calls: state.calls,
|
|
47
|
+
setCues(cues) {
|
|
48
|
+
state.cues = cues;
|
|
49
|
+
},
|
|
50
|
+
unregister() {
|
|
51
|
+
if (states.get(modelKey(model)) === state) states.delete(modelKey(model));
|
|
52
|
+
unregisterModel();
|
|
53
|
+
release();
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
async function transcribeFaux(model, input, options) {
|
|
58
|
+
if (options?.signal?.aborted) throw new VoiceRequestError("aborted", "aborted");
|
|
59
|
+
const state = requireState(model);
|
|
60
|
+
state.calls.push({ kind: "transcribe", input, chunks: [] });
|
|
61
|
+
const text = "url" in input ? input.url : decodeBytes(input.data);
|
|
62
|
+
return { text, usage: blankUsage() };
|
|
63
|
+
}
|
|
64
|
+
function listenFaux(model, audio, options) {
|
|
65
|
+
const stream = new TranscriptionEventStream();
|
|
66
|
+
let state;
|
|
67
|
+
try {
|
|
68
|
+
state = requireState(model);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
stream.push({ type: "error", reason: "error", error: errorMessage(error) });
|
|
71
|
+
return stream;
|
|
72
|
+
}
|
|
73
|
+
void pumpListen(state, audio, options, stream);
|
|
74
|
+
return stream;
|
|
75
|
+
}
|
|
76
|
+
function createPump(stream) {
|
|
77
|
+
const pump = {
|
|
78
|
+
stream,
|
|
79
|
+
finished: false,
|
|
80
|
+
push(event) {
|
|
81
|
+
if (pump.finished) return;
|
|
82
|
+
if (event.type === "turn_end") pump.turnText = event.text;
|
|
83
|
+
stream.push(event);
|
|
84
|
+
if (event.type === "done" || event.type === "error") pump.finished = true;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
return pump;
|
|
88
|
+
}
|
|
89
|
+
function cuesAt(cues, at) {
|
|
90
|
+
return cues.filter((cue) => cue.at === at).flatMap((cue) => cue.events);
|
|
91
|
+
}
|
|
92
|
+
function endCues(cues, count) {
|
|
93
|
+
return cues.filter((cue) => cue.at === "end" || typeof cue.at === "number" && cue.at > count).flatMap((cue) => cue.events);
|
|
94
|
+
}
|
|
95
|
+
function finishScript(pump, cues, text, at, count) {
|
|
96
|
+
if (pump.finished) return;
|
|
97
|
+
if (cues) for (const event of endCues(cues, count)) pump.push(event);
|
|
98
|
+
else {
|
|
99
|
+
pump.push({ type: "final", segmentId: "0", text });
|
|
100
|
+
pump.push({ type: "turn_end", text, at });
|
|
101
|
+
}
|
|
102
|
+
if (!pump.finished) {
|
|
103
|
+
pump.push({
|
|
104
|
+
type: "done",
|
|
105
|
+
transcript: { text: pump.turnText ?? text, usage: blankUsage() }
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function pumpListen(state, audio, options, stream) {
|
|
110
|
+
const pump = createPump(stream);
|
|
111
|
+
const call = { kind: "listen", chunks: [] };
|
|
112
|
+
state.calls.push(call);
|
|
113
|
+
let text = "";
|
|
114
|
+
let at = 0;
|
|
115
|
+
let count = 0;
|
|
116
|
+
try {
|
|
117
|
+
if (options?.signal?.aborted) {
|
|
118
|
+
pump.push({ type: "error", reason: "aborted", error: "aborted" });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
for (const event of cuesAt(state.cues ?? [], 0)) pump.push(event);
|
|
122
|
+
if (!pump.finished) {
|
|
123
|
+
for await (const chunk of audio) {
|
|
124
|
+
if (pump.finished || options?.signal?.aborted) break;
|
|
125
|
+
call.chunks.push(chunk);
|
|
126
|
+
count += 1;
|
|
127
|
+
at = chunk.timestamp;
|
|
128
|
+
text += decodeBytes(chunk.data);
|
|
129
|
+
deliverChunk(pump, state.cues, chunk, count, text);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (options?.signal?.aborted && !pump.finished) {
|
|
133
|
+
pump.push({ type: "error", reason: "aborted", error: "aborted" });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
finishScript(pump, state.cues, text, at, count);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
pump.push({ type: "error", reason: "error", error: errorMessage(error) });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function deliverChunk(pump, cues, chunk, count, text) {
|
|
142
|
+
if (cues) {
|
|
143
|
+
for (const event of cuesAt(cues, count)) pump.push(event);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (count === 1) pump.push({ type: "speech_start", at: chunk.timestamp });
|
|
147
|
+
pump.push({ type: "partial", segmentId: "0", text });
|
|
148
|
+
}
|
|
149
|
+
export {
|
|
150
|
+
registerFauxTranscription
|
|
151
|
+
};
|
|
152
|
+
//# sourceMappingURL=transcription.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/faux/transcription.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ProviderRequestOptions } from '@earendil-works/pi-ai';\nimport type { AudioChunk, AudioSource } from '../audio.ts';\nimport { blankUsage } from '../blank-usage.ts';\nimport { VoiceRequestError } from '../errors.ts';\nimport { registerVoiceModel, type TranscriptionModel } from '../models.ts';\nimport { registerTranscriptionApiProvider } from '../registry.ts';\nimport {\n type AudioInput,\n type ListenOptions,\n type TranscriptionEvent,\n TranscriptionEventStream,\n} from '../transcription.ts';\nimport { decodeBytes, errorMessage, modelKey, pcm, shareApi, zeroCost } from './shared.ts';\n\nexport interface FauxTranscriptionCue {\n at: number | 'end';\n events: TranscriptionEvent[];\n}\n\nexport interface FauxTranscriptionOptions {\n api?: string;\n provider?: string;\n id?: string;\n name?: string;\n turnDetection?: TranscriptionModel<string>['turnDetection'];\n cues?: FauxTranscriptionCue[];\n}\n\nexport interface FauxTranscriptionCall {\n kind: 'transcribe' | 'listen';\n input?: AudioInput;\n chunks: AudioChunk[];\n}\n\nexport interface FauxTranscription {\n model: TranscriptionModel<string>;\n calls: FauxTranscriptionCall[];\n setCues(cues: FauxTranscriptionCue[] | undefined): void;\n unregister(): void;\n}\n\ninterface TranscriptionState {\n model: TranscriptionModel<string>;\n cues?: FauxTranscriptionCue[];\n calls: FauxTranscriptionCall[];\n}\n\nconst states = new Map<string, TranscriptionState>();\nconst apis = new Map<string, () => void>();\n\nfunction requireState(model: TranscriptionModel<string>): TranscriptionState {\n const state = states.get(modelKey(model));\n if (!state) throw new Error(`Unknown faux transcription model ${model.provider}/${model.id}`);\n return state;\n}\n\nexport function registerFauxTranscription(\n options: FauxTranscriptionOptions = {}\n): FauxTranscription {\n const model: TranscriptionModel<string> = {\n id: options.id ?? 'faux-transcription',\n name: options.name ?? 'Faux transcription',\n api: options.api ?? 'faux-transcription',\n provider: options.provider ?? 'faux',\n baseUrl: 'http://localhost:0',\n type: 'transcription',\n streaming: true,\n turnDetection: options.turnDetection ?? 'vad',\n inputFormats: [pcm(16000)],\n languages: ['en'],\n diarization: false,\n cost: zeroCost,\n };\n const state: TranscriptionState = { model, cues: options.cues, calls: [] };\n states.set(modelKey(model), state);\n const unregisterModel = registerVoiceModel(model);\n const release = shareApi(\n apis,\n model.api,\n () =>\n registerTranscriptionApiProvider({\n api: model.api,\n transcribe: transcribeFaux,\n listen: listenFaux,\n }),\n () => [...states.values()].some((entry) => entry.model.api === model.api)\n );\n return {\n model,\n calls: state.calls,\n setCues(cues) {\n state.cues = cues;\n },\n unregister() {\n if (states.get(modelKey(model)) === state) states.delete(modelKey(model));\n unregisterModel();\n release();\n },\n };\n}\n\nasync function transcribeFaux(\n model: TranscriptionModel<string>,\n input: AudioInput,\n options?: ProviderRequestOptions\n) {\n if (options?.signal?.aborted) throw new VoiceRequestError('aborted', 'aborted');\n const state = requireState(model);\n state.calls.push({ kind: 'transcribe', input, chunks: [] });\n const text = 'url' in input ? input.url : decodeBytes(input.data);\n return { text, usage: blankUsage() };\n}\n\nfunction listenFaux(\n model: TranscriptionModel<string>,\n audio: AudioSource,\n options?: ListenOptions\n): TranscriptionEventStream {\n const stream = new TranscriptionEventStream();\n let state: TranscriptionState;\n try {\n state = requireState(model);\n } catch (error) {\n stream.push({ type: 'error', reason: 'error', error: errorMessage(error) });\n return stream;\n }\n void pumpListen(state, audio, options, stream);\n return stream;\n}\n\ninterface Pump {\n stream: TranscriptionEventStream;\n finished: boolean;\n turnText?: string;\n push(event: TranscriptionEvent): void;\n}\n\nfunction createPump(stream: TranscriptionEventStream): Pump {\n const pump: Pump = {\n stream,\n finished: false,\n push(event) {\n if (pump.finished) return;\n if (event.type === 'turn_end') pump.turnText = event.text;\n stream.push(event);\n if (event.type === 'done' || event.type === 'error') pump.finished = true;\n },\n };\n return pump;\n}\n\nfunction cuesAt(cues: FauxTranscriptionCue[], at: number | 'end'): TranscriptionEvent[] {\n return cues.filter((cue) => cue.at === at).flatMap((cue) => cue.events);\n}\n\nfunction endCues(cues: FauxTranscriptionCue[], count: number): TranscriptionEvent[] {\n return cues\n .filter((cue) => cue.at === 'end' || (typeof cue.at === 'number' && cue.at > count))\n .flatMap((cue) => cue.events);\n}\n\nfunction finishScript(\n pump: Pump,\n cues: FauxTranscriptionCue[] | undefined,\n text: string,\n at: number,\n count: number\n) {\n if (pump.finished) return;\n if (cues) for (const event of endCues(cues, count)) pump.push(event);\n else {\n pump.push({ type: 'final', segmentId: '0', text });\n pump.push({ type: 'turn_end', text, at });\n }\n if (!pump.finished) {\n pump.push({\n type: 'done',\n transcript: { text: pump.turnText ?? text, usage: blankUsage() },\n });\n }\n}\n\nasync function pumpListen(\n state: TranscriptionState,\n audio: AudioSource,\n options: ListenOptions | undefined,\n stream: TranscriptionEventStream\n) {\n const pump = createPump(stream);\n const call: FauxTranscriptionCall = { kind: 'listen', chunks: [] };\n state.calls.push(call);\n let text = '';\n let at = 0;\n let count = 0;\n try {\n if (options?.signal?.aborted) {\n pump.push({ type: 'error', reason: 'aborted', error: 'aborted' });\n return;\n }\n for (const event of cuesAt(state.cues ?? [], 0)) pump.push(event);\n if (!pump.finished) {\n for await (const chunk of audio) {\n if (pump.finished || options?.signal?.aborted) break;\n call.chunks.push(chunk);\n count += 1;\n at = chunk.timestamp;\n text += decodeBytes(chunk.data);\n deliverChunk(pump, state.cues, chunk, count, text);\n }\n }\n if (options?.signal?.aborted && !pump.finished) {\n pump.push({ type: 'error', reason: 'aborted', error: 'aborted' });\n return;\n }\n finishScript(pump, state.cues, text, at, count);\n } catch (error) {\n pump.push({ type: 'error', reason: 'error', error: errorMessage(error) });\n }\n}\n\nfunction deliverChunk(\n pump: Pump,\n cues: FauxTranscriptionCue[] | undefined,\n chunk: AudioChunk,\n count: number,\n text: string\n) {\n if (cues) {\n for (const event of cuesAt(cues, count)) pump.push(event);\n return;\n }\n if (count === 1) pump.push({ type: 'speech_start', at: chunk.timestamp });\n pump.push({ type: 'partial', segmentId: '0', text });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { resolveVoiceAuth } from "./auth.js";
|
|
2
|
+
import { blankUsage } from "./blank-usage.js";
|
|
3
|
+
import { VoiceRequestError } from "./errors.js";
|
|
4
|
+
import {
|
|
5
|
+
getVoiceModel,
|
|
6
|
+
listVoiceModels,
|
|
7
|
+
registerVoiceModel
|
|
8
|
+
} from "./models.js";
|
|
9
|
+
import {
|
|
10
|
+
connectRealtime,
|
|
11
|
+
listen,
|
|
12
|
+
registerRealtimeApiProvider,
|
|
13
|
+
registerSpeechApiProvider,
|
|
14
|
+
registerTranscriptionApiProvider,
|
|
15
|
+
speak,
|
|
16
|
+
transcribe
|
|
17
|
+
} from "./registry.js";
|
|
18
|
+
import { SpeechEventStream } from "./speech.js";
|
|
19
|
+
import { TranscriptionEventStream } from "./transcription.js";
|
|
20
|
+
export {
|
|
21
|
+
SpeechEventStream,
|
|
22
|
+
TranscriptionEventStream,
|
|
23
|
+
VoiceRequestError,
|
|
24
|
+
blankUsage,
|
|
25
|
+
connectRealtime,
|
|
26
|
+
getVoiceModel,
|
|
27
|
+
listVoiceModels,
|
|
28
|
+
listen,
|
|
29
|
+
registerRealtimeApiProvider,
|
|
30
|
+
registerSpeechApiProvider,
|
|
31
|
+
registerTranscriptionApiProvider,
|
|
32
|
+
registerVoiceModel,
|
|
33
|
+
resolveVoiceAuth,
|
|
34
|
+
speak,
|
|
35
|
+
transcribe
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts"],
|
|
4
|
+
"sourcesContent": ["export type {\n AudioChunk,\n AudioEncoding,\n AudioFormat,\n AudioSink,\n AudioSource,\n AudioTag,\n CharAlignment,\n TextSpan,\n} from './audio.ts';\nexport { resolveVoiceAuth } from './auth.ts';\nexport { blankUsage } from './blank-usage.ts';\nexport { VoiceRequestError } from './errors.ts';\nexport type {\n Delivery,\n DeliverySupport,\n KnownRealtimeApi,\n KnownSpeechApi,\n KnownTranscriptionApi,\n RealtimeApi,\n RealtimeModel,\n SpeechApi,\n SpeechModel,\n TranscriptionApi,\n TranscriptionModel,\n VoiceInfo,\n VoiceModel,\n VoiceModelType,\n} from './models.ts';\nexport {\n getVoiceModel,\n listVoiceModels,\n registerVoiceModel,\n} from './models.ts';\nexport type {\n RealtimeApiProvider,\n RealtimeContext,\n RealtimeEvent,\n RealtimeOptions,\n RealtimeSession,\n RealtimeToken,\n} from './realtime.ts';\nexport {\n connectRealtime,\n listen,\n registerRealtimeApiProvider,\n registerSpeechApiProvider,\n registerTranscriptionApiProvider,\n speak,\n transcribe,\n} from './registry.ts';\nexport type { SpeechApiProvider, SpeechEvent, SpeechInput } from './speech.ts';\nexport { SpeechEventStream } from './speech.ts';\nexport type {\n AudioInput,\n ListenOptions,\n Transcript,\n TranscriptionApiProvider,\n TranscriptionEvent,\n TranscriptSegment,\n} from './transcription.ts';\nexport { TranscriptionEventStream } from './transcription.ts';\n"],
|
|
5
|
+
"mappings": "AAUA,SAAS,wBAAwB;AACjC,SAAS,kBAAkB;AAC3B,SAAS,yBAAyB;AAiBlC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AASP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,yBAAyB;AASlC,SAAS,gCAAgC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/models.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const catalog = /* @__PURE__ */ new Map();
|
|
2
|
+
function catalogKey(type, provider, id) {
|
|
3
|
+
return `${type}\0${provider}\0${id}`;
|
|
4
|
+
}
|
|
5
|
+
function registerVoiceModel(model) {
|
|
6
|
+
const key = catalogKey(model.type, model.provider, model.id);
|
|
7
|
+
catalog.set(key, model);
|
|
8
|
+
return () => {
|
|
9
|
+
if (catalog.get(key) === model) catalog.delete(key);
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function getVoiceModel(type, provider, id) {
|
|
13
|
+
return catalog.get(catalogKey(type, provider, id));
|
|
14
|
+
}
|
|
15
|
+
function listVoiceModels(type, provider) {
|
|
16
|
+
const found = [];
|
|
17
|
+
for (const model of catalog.values()) {
|
|
18
|
+
if (model.type !== type) continue;
|
|
19
|
+
if (provider !== void 0 && model.provider !== provider) continue;
|
|
20
|
+
found.push(model);
|
|
21
|
+
}
|
|
22
|
+
return found;
|
|
23
|
+
}
|
|
24
|
+
export {
|
|
25
|
+
getVoiceModel,
|
|
26
|
+
listVoiceModels,
|
|
27
|
+
registerVoiceModel
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=models.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/models.ts"],
|
|
4
|
+
"sourcesContent": ["import type { BaseModel } from '@earendil-works/pi-ai';\nimport type { AudioFormat } from './audio.ts';\n\nexport type KnownTranscriptionApi =\n | 'openai-transcriptions'\n | 'openai-realtime-transcription'\n | 'xai-stt'\n | 'elevenlabs-scribe'\n | 'mistral-transcriptions'\n | 'fish-asr'\n | 'cloudflare-ai-run';\n\nexport type TranscriptionApi = KnownTranscriptionApi | (string & {});\n\nexport type KnownSpeechApi =\n | 'openai-speech'\n | 'xai-tts'\n | 'elevenlabs-tts'\n | 'mistral-speech'\n | 'fish-tts'\n | 'google-tts'\n | 'cloudflare-ai-run'\n | 'wavespeed';\n\nexport type SpeechApi = KnownSpeechApi | (string & {});\n\nexport type KnownRealtimeApi =\n | 'openai-realtime'\n | 'xai-realtime'\n | 'gemini-live'\n | 'elevenlabs-agents'\n | 'fish-agents';\n\nexport type RealtimeApi = KnownRealtimeApi | (string & {});\n\nexport type Delivery = 'interrupt' | 'when_idle' | 'silent';\n\nexport type DeliverySupport = 'native' | 'approximate' | 'unsupported';\n\nexport interface VoiceInfo {\n id: string;\n name: string;\n languages?: string[];\n}\n\nexport interface TranscriptionModel<TApi extends TranscriptionApi>\n extends Omit<BaseModel<TApi>, 'input'> {\n type: 'transcription';\n streaming: boolean;\n turnDetection: 'none' | 'vad' | 'semantic';\n inputFormats: AudioFormat[];\n languages?: string[];\n diarization?: boolean;\n}\n\nexport interface SpeechModel<TApi extends SpeechApi> extends Omit<BaseModel<TApi>, 'input'> {\n type: 'speech';\n textStreaming: boolean;\n outputFormats: AudioFormat[];\n voices?: VoiceInfo[];\n cloning?: boolean;\n}\n\nexport interface RealtimeModel<TApi extends RealtimeApi> extends Omit<BaseModel<TApi>, 'input'> {\n type: 'realtime';\n transports: ('websocket' | 'webrtc')[];\n toolCalls: 'blocking' | 'async';\n inputFormat: AudioFormat;\n outputFormat: AudioFormat;\n video?: { maxFps: number };\n maxSessionMs?: number;\n resumable: boolean;\n ephemeralAuth: boolean;\n voices: VoiceInfo[];\n cascade?: { stt: string; llm: string; tts: string };\n provisioning: 'none' | 'agent';\n delivery: Record<Delivery, DeliverySupport>;\n truncation: 'client' | 'server' | 'none';\n liveToolUpdate: boolean;\n toolTimeoutMs?: { min: number; max: number };\n}\n\ndeclare module '@earendil-works/pi-ai' {\n interface ModelTypeMap {\n transcription: TranscriptionModel<TranscriptionApi>;\n speech: SpeechModel<SpeechApi>;\n realtime: RealtimeModel<RealtimeApi>;\n }\n}\n\nexport type VoiceModel =\n | TranscriptionModel<TranscriptionApi>\n | SpeechModel<SpeechApi>\n | RealtimeModel<RealtimeApi>;\n\nexport type VoiceModelType = VoiceModel['type'];\n\nconst catalog = new Map<string, VoiceModel>();\n\nfunction catalogKey(type: string, provider: string, id: string): string {\n return `${type}\\0${provider}\\0${id}`;\n}\n\nexport function registerVoiceModel(model: VoiceModel): () => void {\n const key = catalogKey(model.type, model.provider, model.id);\n catalog.set(key, model);\n return () => {\n if (catalog.get(key) === model) catalog.delete(key);\n };\n}\n\nexport function getVoiceModel<TType extends VoiceModelType>(\n type: TType,\n provider: string,\n id: string\n): Extract<VoiceModel, { type: TType }> | undefined {\n return catalog.get(catalogKey(type, provider, id)) as\n | Extract<VoiceModel, { type: TType }>\n | undefined;\n}\n\nexport function listVoiceModels<TType extends VoiceModelType>(\n type: TType,\n provider?: string\n): Extract<VoiceModel, { type: TType }>[] {\n const found: Extract<VoiceModel, { type: TType }>[] = [];\n for (const model of catalog.values()) {\n if (model.type !== type) continue;\n if (provider !== undefined && model.provider !== provider) continue;\n found.push(model as Extract<VoiceModel, { type: TType }>);\n }\n return found;\n}\n"],
|
|
5
|
+
"mappings": "AAiGA,MAAM,UAAU,oBAAI,IAAwB;AAE5C,SAAS,WAAW,MAAc,UAAkB,IAAoB;AACtE,SAAO,GAAG,IAAI,KAAK,QAAQ,KAAK,EAAE;AACpC;AAEO,SAAS,mBAAmB,OAA+B;AAChE,QAAM,MAAM,WAAW,MAAM,MAAM,MAAM,UAAU,MAAM,EAAE;AAC3D,UAAQ,IAAI,KAAK,KAAK;AACtB,SAAO,MAAM;AACX,QAAI,QAAQ,IAAI,GAAG,MAAM,MAAO,SAAQ,OAAO,GAAG;AAAA,EACpD;AACF;AAEO,SAAS,cACd,MACA,UACA,IACkD;AAClD,SAAO,QAAQ,IAAI,WAAW,MAAM,UAAU,EAAE,CAAC;AAGnD;AAEO,SAAS,gBACd,MACA,UACwC;AACxC,QAAM,QAAgD,CAAC;AACvD,aAAW,SAAS,QAAQ,OAAO,GAAG;AACpC,QAAI,MAAM,SAAS,KAAM;AACzB,QAAI,aAAa,UAAa,MAAM,aAAa,SAAU;AAC3D,UAAM,KAAK,KAA6C;AAAA,EAC1D;AACA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/realtime.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=realtime.js.map
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const transcriptionApis = /* @__PURE__ */ new Map();
|
|
2
|
+
const speechApis = /* @__PURE__ */ new Map();
|
|
3
|
+
const realtimeApis = /* @__PURE__ */ new Map();
|
|
4
|
+
function missing(api) {
|
|
5
|
+
return new Error(`No API provider registered for api: ${api}`);
|
|
6
|
+
}
|
|
7
|
+
function registerTranscriptionApiProvider(provider) {
|
|
8
|
+
const entry = { provider };
|
|
9
|
+
transcriptionApis.set(provider.api, entry);
|
|
10
|
+
return () => {
|
|
11
|
+
if (transcriptionApis.get(provider.api) === entry) transcriptionApis.delete(provider.api);
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function registerSpeechApiProvider(provider) {
|
|
15
|
+
const entry = { provider };
|
|
16
|
+
speechApis.set(provider.api, entry);
|
|
17
|
+
return () => {
|
|
18
|
+
if (speechApis.get(provider.api) === entry) speechApis.delete(provider.api);
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function registerRealtimeApiProvider(provider) {
|
|
22
|
+
const entry = { provider };
|
|
23
|
+
realtimeApis.set(provider.api, entry);
|
|
24
|
+
return () => {
|
|
25
|
+
if (realtimeApis.get(provider.api) === entry) realtimeApis.delete(provider.api);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function transcribe(model, input, options) {
|
|
29
|
+
const transcribeModel = transcriptionApis.get(model.api)?.provider.transcribe;
|
|
30
|
+
if (!transcribeModel) return Promise.reject(missing(model.api));
|
|
31
|
+
return transcribeModel(model, input, options);
|
|
32
|
+
}
|
|
33
|
+
function listen(model, audio, options) {
|
|
34
|
+
const listenModel = transcriptionApis.get(model.api)?.provider.listen;
|
|
35
|
+
if (!listenModel) throw missing(model.api);
|
|
36
|
+
return listenModel(model, audio, options);
|
|
37
|
+
}
|
|
38
|
+
function speak(model, input, options) {
|
|
39
|
+
const speakModel = speechApis.get(model.api)?.provider.speak;
|
|
40
|
+
if (!speakModel) throw missing(model.api);
|
|
41
|
+
return speakModel(model, input, options);
|
|
42
|
+
}
|
|
43
|
+
function connectRealtime(model, context, options) {
|
|
44
|
+
const connect = realtimeApis.get(model.api)?.provider.connect;
|
|
45
|
+
if (!connect) return Promise.reject(missing(model.api));
|
|
46
|
+
return connect(model, context, options);
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
connectRealtime,
|
|
50
|
+
listen,
|
|
51
|
+
registerRealtimeApiProvider,
|
|
52
|
+
registerSpeechApiProvider,
|
|
53
|
+
registerTranscriptionApiProvider,
|
|
54
|
+
speak,
|
|
55
|
+
transcribe
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/registry.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ProviderRequestOptions } from '@earendil-works/pi-ai';\nimport type { AudioSource } from './audio.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';\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 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 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": "AA8BA,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,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,SAAO,WAAW,OAAO,OAAO,OAAO;AACzC;AAEO,SAAS,gBACd,OACA,SACA,SAC0B;AAC1B,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
|
+
"names": []
|
|
7
|
+
}
|
package/dist/speech.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { EventStream } from "@earendil-works/pi-ai";
|
|
2
|
+
import { blankUsage } from "./blank-usage.js";
|
|
3
|
+
import { VoiceRequestError } from "./errors.js";
|
|
4
|
+
import { streamResult } from "./stream-result.js";
|
|
5
|
+
class SpeechEventStream extends EventStream {
|
|
6
|
+
#settled = streamResult();
|
|
7
|
+
constructor() {
|
|
8
|
+
super(
|
|
9
|
+
(event) => event.type === "done" || event.type === "error",
|
|
10
|
+
(event) => event.type === "done" ? event.usage : blankUsage()
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
push(event) {
|
|
14
|
+
super.push(event);
|
|
15
|
+
if (event.type === "done") this.#settled.finish(event.usage);
|
|
16
|
+
else if (event.type === "error")
|
|
17
|
+
this.#settled.fail(new VoiceRequestError(event.reason, event.error));
|
|
18
|
+
}
|
|
19
|
+
end(result) {
|
|
20
|
+
super.end(result);
|
|
21
|
+
if (result !== void 0) this.#settled.finish(result);
|
|
22
|
+
else this.#settled.fail(new VoiceRequestError("error", "Speech ended without usage"));
|
|
23
|
+
}
|
|
24
|
+
result() {
|
|
25
|
+
return this.#settled.result;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
SpeechEventStream
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=speech.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/speech.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ProviderRequestOptions, Usage } from '@earendil-works/pi-ai';\nimport { EventStream } from '@earendil-works/pi-ai';\nimport type { AudioChunk, AudioFormat, CharAlignment, TextSpan } from './audio.ts';\nimport { blankUsage } from './blank-usage.ts';\nimport { VoiceRequestError } from './errors.ts';\nimport type { SpeechModel } from './models.ts';\nimport { streamResult } from './stream-result.ts';\n\nexport interface SpeechInput {\n text: string | AsyncIterable<string>;\n voice: string;\n language?: string;\n instructions?: string;\n format?: AudioFormat;\n}\n\nexport type SpeechEvent =\n | { type: 'start'; format: AudioFormat }\n | { type: 'audio'; chunk: AudioChunk; span: TextSpan; alignment?: CharAlignment }\n | { type: 'done'; usage: Usage }\n | { type: 'error'; reason: 'aborted' | 'error'; error: string };\n\nexport class SpeechEventStream extends EventStream<SpeechEvent, Usage> {\n readonly #settled = streamResult<Usage>();\n\n constructor() {\n super(\n (event) => event.type === 'done' || event.type === 'error',\n (event) => (event.type === 'done' ? event.usage : blankUsage())\n );\n }\n\n override push(event: SpeechEvent): void {\n super.push(event);\n if (event.type === 'done') this.#settled.finish(event.usage);\n else if (event.type === 'error')\n this.#settled.fail(new VoiceRequestError(event.reason, event.error));\n }\n\n override end(result?: Usage): void {\n super.end(result);\n if (result !== undefined) this.#settled.finish(result);\n else this.#settled.fail(new VoiceRequestError('error', 'Speech ended without usage'));\n }\n\n override result(): Promise<Usage> {\n return this.#settled.result;\n }\n}\n\nexport interface SpeechApiProvider<TApi extends string = string> {\n api: TApi;\n speak: (\n model: SpeechModel<TApi>,\n input: SpeechInput,\n options?: ProviderRequestOptions\n ) => SpeechEventStream;\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,mBAAmB;AAE5B,SAAS,kBAAkB;AAC3B,SAAS,yBAAyB;AAElC,SAAS,oBAAoB;AAgBtB,MAAM,0BAA0B,YAAgC;AAAA,EAC5D,WAAW,aAAoB;AAAA,EAExC,cAAc;AACZ;AAAA,MACE,CAAC,UAAU,MAAM,SAAS,UAAU,MAAM,SAAS;AAAA,MACnD,CAAC,UAAW,MAAM,SAAS,SAAS,MAAM,QAAQ,WAAW;AAAA,IAC/D;AAAA,EACF;AAAA,EAES,KAAK,OAA0B;AACtC,UAAM,KAAK,KAAK;AAChB,QAAI,MAAM,SAAS,OAAQ,MAAK,SAAS,OAAO,MAAM,KAAK;AAAA,aAClD,MAAM,SAAS;AACtB,WAAK,SAAS,KAAK,IAAI,kBAAkB,MAAM,QAAQ,MAAM,KAAK,CAAC;AAAA,EACvE;AAAA,EAES,IAAI,QAAsB;AACjC,UAAM,IAAI,MAAM;AAChB,QAAI,WAAW,OAAW,MAAK,SAAS,OAAO,MAAM;AAAA,QAChD,MAAK,SAAS,KAAK,IAAI,kBAAkB,SAAS,4BAA4B,CAAC;AAAA,EACtF;AAAA,EAES,SAAyB;AAChC,WAAO,KAAK,SAAS;AAAA,EACvB;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
function streamResult() {
|
|
2
|
+
let settled = false;
|
|
3
|
+
let resolve;
|
|
4
|
+
let reject;
|
|
5
|
+
const result = new Promise((res, rej) => {
|
|
6
|
+
resolve = res;
|
|
7
|
+
reject = rej;
|
|
8
|
+
});
|
|
9
|
+
void result.catch(() => {
|
|
10
|
+
});
|
|
11
|
+
return {
|
|
12
|
+
result,
|
|
13
|
+
finish(value) {
|
|
14
|
+
if (settled) return;
|
|
15
|
+
settled = true;
|
|
16
|
+
resolve(value);
|
|
17
|
+
},
|
|
18
|
+
fail(error) {
|
|
19
|
+
if (settled) return;
|
|
20
|
+
settled = true;
|
|
21
|
+
reject(error);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
streamResult
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=stream-result.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/stream-result.ts"],
|
|
4
|
+
"sourcesContent": ["export interface StreamResult<T> {\n readonly result: Promise<T>;\n finish(value: T): void;\n fail(error: Error): void;\n}\n\nexport function streamResult<T>(): StreamResult<T> {\n let settled = false;\n let resolve!: (value: T) => void;\n let reject!: (error: Error) => void;\n const result = new Promise<T>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n void result.catch(() => {});\n return {\n result,\n finish(value) {\n if (settled) return;\n settled = true;\n resolve(value);\n },\n fail(error) {\n if (settled) return;\n settled = true;\n reject(error);\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "AAMO,SAAS,eAAmC;AACjD,MAAI,UAAU;AACd,MAAI;AACJ,MAAI;AACJ,QAAM,SAAS,IAAI,QAAW,CAAC,KAAK,QAAQ;AAC1C,cAAU;AACV,aAAS;AAAA,EACX,CAAC;AACD,OAAK,OAAO,MAAM,MAAM;AAAA,EAAC,CAAC;AAC1B,SAAO;AAAA,IACL;AAAA,IACA,OAAO,OAAO;AACZ,UAAI,QAAS;AACb,gBAAU;AACV,cAAQ,KAAK;AAAA,IACf;AAAA,IACA,KAAK,OAAO;AACV,UAAI,QAAS;AACb,gBAAU;AACV,aAAO,KAAK;AAAA,IACd;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { EventStream } from "@earendil-works/pi-ai";
|
|
2
|
+
import { blankUsage } from "./blank-usage.js";
|
|
3
|
+
import { VoiceRequestError } from "./errors.js";
|
|
4
|
+
import { streamResult } from "./stream-result.js";
|
|
5
|
+
function blankTranscript() {
|
|
6
|
+
return { text: "", usage: blankUsage() };
|
|
7
|
+
}
|
|
8
|
+
class TranscriptionEventStream extends EventStream {
|
|
9
|
+
#settled = streamResult();
|
|
10
|
+
constructor() {
|
|
11
|
+
super(
|
|
12
|
+
(event) => event.type === "done" || event.type === "error",
|
|
13
|
+
(event) => event.type === "done" ? event.transcript : blankTranscript()
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
push(event) {
|
|
17
|
+
super.push(event);
|
|
18
|
+
if (event.type === "done") this.#settled.finish(event.transcript);
|
|
19
|
+
else if (event.type === "error")
|
|
20
|
+
this.#settled.fail(new VoiceRequestError(event.reason, event.error));
|
|
21
|
+
}
|
|
22
|
+
end(result) {
|
|
23
|
+
super.end(result);
|
|
24
|
+
if (result !== void 0) this.#settled.finish(result);
|
|
25
|
+
else
|
|
26
|
+
this.#settled.fail(
|
|
27
|
+
new VoiceRequestError("error", "Transcription ended without a transcript")
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
result() {
|
|
31
|
+
return this.#settled.result;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
TranscriptionEventStream
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=transcription.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/transcription.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ProviderRequestOptions, Usage } from '@earendil-works/pi-ai';\nimport { EventStream } from '@earendil-works/pi-ai';\nimport type { AudioSource } from './audio.ts';\nimport { blankUsage } from './blank-usage.ts';\nimport { VoiceRequestError } from './errors.ts';\nimport type { TranscriptionModel } from './models.ts';\nimport { streamResult } from './stream-result.ts';\n\nexport type AudioInput = { data: Uint8Array; mimeType: string } | { url: string };\n\nexport interface TranscriptSegment {\n text: string;\n start: number;\n end: number;\n speaker?: string;\n}\n\nexport interface Transcript {\n text: string;\n language?: string;\n segments?: TranscriptSegment[];\n usage: Usage;\n}\n\nexport type TranscriptionEvent =\n | { type: 'speech_start'; at: number }\n | { type: 'partial'; segmentId: string; text: string }\n | { type: 'final'; segmentId: string; text: string; confidence?: number }\n | { type: 'turn_end'; text: string; at: number }\n | { type: 'done'; transcript: Transcript }\n | { type: 'error'; reason: 'aborted' | 'error'; error: string };\n\nexport interface ListenOptions extends ProviderRequestOptions {\n language?: string;\n vocabulary?: string[];\n}\n\nfunction blankTranscript(): Transcript {\n return { text: '', usage: blankUsage() };\n}\n\nexport class TranscriptionEventStream extends EventStream<TranscriptionEvent, Transcript> {\n readonly #settled = streamResult<Transcript>();\n\n constructor() {\n super(\n (event) => event.type === 'done' || event.type === 'error',\n (event) => (event.type === 'done' ? event.transcript : blankTranscript())\n );\n }\n\n override push(event: TranscriptionEvent): void {\n super.push(event);\n if (event.type === 'done') this.#settled.finish(event.transcript);\n else if (event.type === 'error')\n this.#settled.fail(new VoiceRequestError(event.reason, event.error));\n }\n\n override end(result?: Transcript): void {\n super.end(result);\n if (result !== undefined) this.#settled.finish(result);\n else\n this.#settled.fail(\n new VoiceRequestError('error', 'Transcription ended without a transcript')\n );\n }\n\n override result(): Promise<Transcript> {\n return this.#settled.result;\n }\n}\n\nexport interface TranscriptionApiProvider<TApi extends string = string> {\n api: TApi;\n transcribe?: (\n model: TranscriptionModel<TApi>,\n input: AudioInput,\n options?: ProviderRequestOptions\n ) => Promise<Transcript>;\n listen?: (\n model: TranscriptionModel<TApi>,\n audio: AudioSource,\n options?: ListenOptions\n ) => TranscriptionEventStream;\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,mBAAmB;AAE5B,SAAS,kBAAkB;AAC3B,SAAS,yBAAyB;AAElC,SAAS,oBAAoB;AA+B7B,SAAS,kBAA8B;AACrC,SAAO,EAAE,MAAM,IAAI,OAAO,WAAW,EAAE;AACzC;AAEO,MAAM,iCAAiC,YAA4C;AAAA,EAC/E,WAAW,aAAyB;AAAA,EAE7C,cAAc;AACZ;AAAA,MACE,CAAC,UAAU,MAAM,SAAS,UAAU,MAAM,SAAS;AAAA,MACnD,CAAC,UAAW,MAAM,SAAS,SAAS,MAAM,aAAa,gBAAgB;AAAA,IACzE;AAAA,EACF;AAAA,EAES,KAAK,OAAiC;AAC7C,UAAM,KAAK,KAAK;AAChB,QAAI,MAAM,SAAS,OAAQ,MAAK,SAAS,OAAO,MAAM,UAAU;AAAA,aACvD,MAAM,SAAS;AACtB,WAAK,SAAS,KAAK,IAAI,kBAAkB,MAAM,QAAQ,MAAM,KAAK,CAAC;AAAA,EACvE;AAAA,EAES,IAAI,QAA2B;AACtC,UAAM,IAAI,MAAM;AAChB,QAAI,WAAW,OAAW,MAAK,SAAS,OAAO,MAAM;AAAA;AAEnD,WAAK,SAAS;AAAA,QACZ,IAAI,kBAAkB,SAAS,0CAA0C;AAAA,MAC3E;AAAA,EACJ;AAAA,EAES,SAA8B;AACrC,WAAO,KAAK,SAAS;AAAA,EACvB;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type AudioEncoding = 'pcm16' | 'pcmf32' | 'mulaw' | 'alaw' | 'opus' | 'mp3' | 'wav' | 'flac';
|
|
2
|
+
export interface AudioFormat {
|
|
3
|
+
encoding: AudioEncoding;
|
|
4
|
+
sampleRate: number;
|
|
5
|
+
channels: 1 | 2;
|
|
6
|
+
}
|
|
7
|
+
export interface AudioChunk {
|
|
8
|
+
data: Uint8Array;
|
|
9
|
+
format: AudioFormat;
|
|
10
|
+
timestamp: number;
|
|
11
|
+
}
|
|
12
|
+
export type AudioSource = AsyncIterable<AudioChunk>;
|
|
13
|
+
export type TextSpan = {
|
|
14
|
+
entryId: string;
|
|
15
|
+
contentIndex: number;
|
|
16
|
+
start: number;
|
|
17
|
+
end: number;
|
|
18
|
+
} | {
|
|
19
|
+
itemId: string;
|
|
20
|
+
start: number;
|
|
21
|
+
end: number;
|
|
22
|
+
};
|
|
23
|
+
export interface CharAlignment {
|
|
24
|
+
startMs: number[];
|
|
25
|
+
durationMs: number[];
|
|
26
|
+
}
|
|
27
|
+
export interface AudioTag {
|
|
28
|
+
span: TextSpan;
|
|
29
|
+
alignment?: CharAlignment;
|
|
30
|
+
}
|
|
31
|
+
export interface AudioSink {
|
|
32
|
+
write(chunk: AudioChunk, tag?: AudioTag): void;
|
|
33
|
+
flush(): void;
|
|
34
|
+
readonly playedMs: number;
|
|
35
|
+
onPlayback(listener: (tag: AudioTag, offsetMs: number) => void): () => void;
|
|
36
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { type FauxRealtime, type FauxRealtimeCall, type FauxRealtimeFlags, type FauxRealtimeOptions, registerFauxRealtime, } from './realtime.ts';
|
|
2
|
+
export { type FauxSpeech, type FauxSpeechOptions, registerFauxSpeech, } from './speech.ts';
|
|
3
|
+
export { type FauxTranscription, type FauxTranscriptionCall, type FauxTranscriptionCue, type FauxTranscriptionOptions, registerFauxTranscription, } from './transcription.ts';
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { AudioChunk } from '../audio.ts';
|
|
2
|
+
import { type Delivery, type DeliverySupport, type RealtimeModel } from '../models.ts';
|
|
3
|
+
import type { RealtimeEvent, RealtimeSession } from '../realtime.ts';
|
|
4
|
+
export type FauxRealtimeCall = {
|
|
5
|
+
method: 'appendAudio';
|
|
6
|
+
chunk: AudioChunk;
|
|
7
|
+
} | {
|
|
8
|
+
method: 'appendVideoFrame';
|
|
9
|
+
jpeg: Uint8Array;
|
|
10
|
+
} | {
|
|
11
|
+
method: 'sendText';
|
|
12
|
+
text: string;
|
|
13
|
+
} | {
|
|
14
|
+
method: 'inject';
|
|
15
|
+
text: string;
|
|
16
|
+
delivery: Delivery;
|
|
17
|
+
} | {
|
|
18
|
+
method: 'respond';
|
|
19
|
+
callId: string;
|
|
20
|
+
result: Parameters<RealtimeSession['respond']>[1];
|
|
21
|
+
delivery?: Delivery;
|
|
22
|
+
} | {
|
|
23
|
+
method: 'interrupt';
|
|
24
|
+
heard?: {
|
|
25
|
+
itemId: string;
|
|
26
|
+
heardMs: number;
|
|
27
|
+
};
|
|
28
|
+
} | {
|
|
29
|
+
method: 'update';
|
|
30
|
+
patch: Parameters<RealtimeSession['update']>[0];
|
|
31
|
+
} | {
|
|
32
|
+
method: 'close';
|
|
33
|
+
};
|
|
34
|
+
export interface FauxRealtimeFlags {
|
|
35
|
+
toolCalls?: RealtimeModel<string>['toolCalls'];
|
|
36
|
+
delivery?: Record<Delivery, DeliverySupport>;
|
|
37
|
+
truncation?: RealtimeModel<string>['truncation'];
|
|
38
|
+
}
|
|
39
|
+
export interface FauxRealtimeOptions extends FauxRealtimeFlags {
|
|
40
|
+
api?: string;
|
|
41
|
+
provider?: string;
|
|
42
|
+
id?: string;
|
|
43
|
+
name?: string;
|
|
44
|
+
events?: RealtimeEvent[];
|
|
45
|
+
}
|
|
46
|
+
export interface FauxRealtime {
|
|
47
|
+
model: RealtimeModel<string>;
|
|
48
|
+
calls: FauxRealtimeCall[];
|
|
49
|
+
emit(event: RealtimeEvent): void;
|
|
50
|
+
setFlags(flags: FauxRealtimeFlags): void;
|
|
51
|
+
unregister(): void;
|
|
52
|
+
}
|
|
53
|
+
export declare function registerFauxRealtime(options?: FauxRealtimeOptions): FauxRealtime;
|