@ai-ecoverse/pi-audio 1.20.1 → 1.22.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 +5 -1
- package/dist/cloudflare/catalog.js +165 -0
- package/dist/cloudflare/catalog.js.map +7 -0
- package/dist/cloudflare/index.js +5 -0
- package/dist/cloudflare/index.js.map +7 -0
- package/dist/cloudflare/listen.js +133 -0
- package/dist/cloudflare/listen.js.map +7 -0
- package/dist/cloudflare/speak.js +224 -0
- package/dist/cloudflare/speak.js.map +7 -0
- package/dist/cloudflare/transcribe.js +155 -0
- package/dist/cloudflare/transcribe.js.map +7 -0
- package/dist/cloudflare/wire.js +166 -0
- package/dist/cloudflare/wire.js.map +7 -0
- package/dist/fish-agents/events.js +94 -0
- package/dist/fish-agents/events.js.map +7 -0
- package/dist/fish-agents/index.js +57 -0
- package/dist/fish-agents/index.js.map +7 -0
- package/dist/fish-agents/provision.js +178 -0
- package/dist/fish-agents/provision.js.map +7 -0
- package/dist/fish-agents/room.js +80 -0
- package/dist/fish-agents/room.js.map +7 -0
- package/dist/fish-agents/session.js +304 -0
- package/dist/fish-agents/session.js.map +7 -0
- package/dist/realtime.js.map +2 -2
- package/dist/tokens.js +11 -1
- package/dist/tokens.js.map +2 -2
- package/dist/types/cloudflare/catalog.d.ts +1 -0
- package/dist/types/cloudflare/index.d.ts +1 -0
- package/dist/types/cloudflare/listen.d.ts +4 -0
- package/dist/types/cloudflare/speak.d.ts +4 -0
- package/dist/types/cloudflare/transcribe.d.ts +4 -0
- package/dist/types/cloudflare/wire.d.ts +27 -0
- package/dist/types/fish-agents/events.d.ts +21 -0
- package/dist/types/fish-agents/index.d.ts +11 -0
- package/dist/types/fish-agents/provision.d.ts +11 -0
- package/dist/types/fish-agents/room.d.ts +46 -0
- package/dist/types/fish-agents/session.d.ts +19 -0
- package/dist/types/realtime.d.ts +1 -0
- package/package.json +9 -1
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { encodeAudioBase64 } from "../convert.js";
|
|
2
|
+
import { VoiceRequestError } from "../errors.js";
|
|
3
|
+
import {
|
|
4
|
+
applyPayload,
|
|
5
|
+
asVoiceError,
|
|
6
|
+
isRecord,
|
|
7
|
+
numberField,
|
|
8
|
+
readAudio,
|
|
9
|
+
requestSignal,
|
|
10
|
+
stringField
|
|
11
|
+
} from "../openai/wire.js";
|
|
12
|
+
import { priceAudio } from "../usage.js";
|
|
13
|
+
import { errorText, resultOf, runUrl, session } from "./wire.js";
|
|
14
|
+
function novaEncoding(mimeType) {
|
|
15
|
+
const mime = mimeType.toLowerCase();
|
|
16
|
+
if (mime.includes("mulaw") || mime === "audio/basic") return "mulaw";
|
|
17
|
+
if (mime.includes("flac")) return "flac";
|
|
18
|
+
if (mime.includes("opus")) return "opus";
|
|
19
|
+
if (mime.includes("pcm") || mime.includes("l16") || mime.includes("wav")) return "linear16";
|
|
20
|
+
return void 0;
|
|
21
|
+
}
|
|
22
|
+
function requestBody(modelId, audio, mimeType, language) {
|
|
23
|
+
if (modelId.includes("whisper")) {
|
|
24
|
+
const body2 = { audio };
|
|
25
|
+
if (language !== void 0) body2.language = language;
|
|
26
|
+
return body2;
|
|
27
|
+
}
|
|
28
|
+
const body = { audio: { body: audio, contentType: mimeType } };
|
|
29
|
+
const encoding = novaEncoding(mimeType);
|
|
30
|
+
if (encoding !== void 0) body.encoding = encoding;
|
|
31
|
+
if (language !== void 0) body.language = language;
|
|
32
|
+
return body;
|
|
33
|
+
}
|
|
34
|
+
function speakerOf(record) {
|
|
35
|
+
const value = record.speaker;
|
|
36
|
+
if (typeof value === "string" && value !== "") return value;
|
|
37
|
+
if (typeof value === "number") return String(value);
|
|
38
|
+
return void 0;
|
|
39
|
+
}
|
|
40
|
+
function segmentText(record) {
|
|
41
|
+
return stringField(record, "text") ?? stringField(record, "word") ?? "";
|
|
42
|
+
}
|
|
43
|
+
function segmentsFrom(value) {
|
|
44
|
+
if (!Array.isArray(value)) return [];
|
|
45
|
+
const segments = [];
|
|
46
|
+
for (const item of value) {
|
|
47
|
+
if (!isRecord(item) || Array.isArray(item)) continue;
|
|
48
|
+
const text = segmentText(item);
|
|
49
|
+
if (text === "") continue;
|
|
50
|
+
const segment = {
|
|
51
|
+
text,
|
|
52
|
+
start: numberField(item, "start") ?? 0,
|
|
53
|
+
end: numberField(item, "end") ?? 0
|
|
54
|
+
};
|
|
55
|
+
const speaker = speakerOf(item);
|
|
56
|
+
if (speaker !== void 0) segment.speaker = speaker;
|
|
57
|
+
segments.push(segment);
|
|
58
|
+
}
|
|
59
|
+
return segments;
|
|
60
|
+
}
|
|
61
|
+
function alternative(body) {
|
|
62
|
+
const results = body.results;
|
|
63
|
+
if (!isRecord(results) || Array.isArray(results)) return void 0;
|
|
64
|
+
const channels = results.channels;
|
|
65
|
+
if (!Array.isArray(channels)) return void 0;
|
|
66
|
+
const first = channels[0];
|
|
67
|
+
if (!isRecord(first) || Array.isArray(first)) return void 0;
|
|
68
|
+
const alternatives = first.alternatives;
|
|
69
|
+
if (!Array.isArray(alternatives)) return void 0;
|
|
70
|
+
const alt = alternatives[0];
|
|
71
|
+
if (!isRecord(alt) || Array.isArray(alt)) return void 0;
|
|
72
|
+
return alt;
|
|
73
|
+
}
|
|
74
|
+
function durationSeconds(body) {
|
|
75
|
+
const direct = numberField(body, "duration");
|
|
76
|
+
if (direct !== void 0) return direct;
|
|
77
|
+
const metadata = body.metadata;
|
|
78
|
+
if (!isRecord(metadata) || Array.isArray(metadata)) return 0;
|
|
79
|
+
const nested = numberField(metadata, "duration");
|
|
80
|
+
if (nested === void 0) return 0;
|
|
81
|
+
return nested;
|
|
82
|
+
}
|
|
83
|
+
function optionLanguage(options) {
|
|
84
|
+
const record = options;
|
|
85
|
+
if (record === void 0) return void 0;
|
|
86
|
+
if (typeof record.language !== "string") return void 0;
|
|
87
|
+
return record.language;
|
|
88
|
+
}
|
|
89
|
+
function transcriptFrom(body, model) {
|
|
90
|
+
const result = resultOf(body);
|
|
91
|
+
if (!isRecord(result) || Array.isArray(result))
|
|
92
|
+
throw new Error("Transcription response had no text");
|
|
93
|
+
const alt = alternative(result);
|
|
94
|
+
const nova = alt === void 0 ? void 0 : stringField(alt, "transcript");
|
|
95
|
+
const text = nova ?? stringField(result, "text");
|
|
96
|
+
if (text === void 0) throw new Error("Transcription response had no text");
|
|
97
|
+
const words = alt === void 0 ? void 0 : alt.words;
|
|
98
|
+
const segments = segmentsFrom(words ?? result.segments);
|
|
99
|
+
const seconds = durationSeconds(result);
|
|
100
|
+
const meters = {};
|
|
101
|
+
if (seconds > 0) meters.inputSeconds = seconds;
|
|
102
|
+
const transcript = { text, usage: priceAudio(model.audioCost, meters) };
|
|
103
|
+
const language = stringField(result, "language");
|
|
104
|
+
if (language !== void 0) transcript.language = language;
|
|
105
|
+
if (segments.length > 0) transcript.segments = segments;
|
|
106
|
+
return transcript;
|
|
107
|
+
}
|
|
108
|
+
async function postTranscription(model, body, options) {
|
|
109
|
+
const route = session(model, options);
|
|
110
|
+
const headers = route.headers;
|
|
111
|
+
headers["Content-Type"] = "application/json";
|
|
112
|
+
const fetchImpl = options?.fetch ?? globalThis.fetch;
|
|
113
|
+
const response = await fetchImpl(runUrl(model.baseUrl, model.id, route.account), {
|
|
114
|
+
method: "POST",
|
|
115
|
+
headers,
|
|
116
|
+
body: JSON.stringify(body),
|
|
117
|
+
signal: requestSignal(options)
|
|
118
|
+
});
|
|
119
|
+
if (!response.ok) {
|
|
120
|
+
const text2 = await response.text();
|
|
121
|
+
throw new Error(errorText(text2, response.status));
|
|
122
|
+
}
|
|
123
|
+
const text = await response.text();
|
|
124
|
+
if (text === "") throw new Error("Transcription response had no text");
|
|
125
|
+
try {
|
|
126
|
+
return JSON.parse(text);
|
|
127
|
+
} catch {
|
|
128
|
+
return text;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function transcribeCloudflare(model, input, options) {
|
|
132
|
+
try {
|
|
133
|
+
if (model.id.includes("/flux")) throw new Error("Cloudflare flux is websocket only");
|
|
134
|
+
if (options?.signal?.aborted) throw new DOMException("aborted", "AbortError");
|
|
135
|
+
const file = await readAudio(input, options?.fetch ?? globalThis.fetch, requestSignal(options));
|
|
136
|
+
const payload = requestBody(
|
|
137
|
+
model.id,
|
|
138
|
+
encodeAudioBase64(file.data),
|
|
139
|
+
file.mimeType,
|
|
140
|
+
optionLanguage(options)
|
|
141
|
+
);
|
|
142
|
+
const body = await applyPayload(payload, model, options);
|
|
143
|
+
if (!isRecord(body) || Array.isArray(body))
|
|
144
|
+
throw new Error("Transcription payload was not JSON");
|
|
145
|
+
const parsed = await postTranscription(model, body, options);
|
|
146
|
+
return transcriptFrom(parsed, model);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
const voice = asVoiceError(error);
|
|
149
|
+
throw new VoiceRequestError(voice.reason, voice.error);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
export {
|
|
153
|
+
transcribeCloudflare
|
|
154
|
+
};
|
|
155
|
+
//# sourceMappingURL=transcribe.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/cloudflare/transcribe.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ProviderRequestOptions } from '@earendil-works/pi-ai';\nimport { encodeAudioBase64 } from '../convert.ts';\nimport { VoiceRequestError } from '../errors.ts';\nimport type { TranscriptionModel } from '../models.ts';\nimport {\n applyPayload,\n asVoiceError,\n isRecord,\n numberField,\n readAudio,\n requestSignal,\n stringField,\n} from '../openai/wire.ts';\nimport type { AudioInput, Transcript, TranscriptSegment } from '../transcription.ts';\nimport { type AudioCounters, priceAudio } from '../usage.ts';\nimport { errorText, resultOf, runUrl, session } from './wire.ts';\n\nfunction novaEncoding(mimeType: string): string | undefined {\n const mime = mimeType.toLowerCase();\n if (mime.includes('mulaw') || mime === 'audio/basic') return 'mulaw';\n if (mime.includes('flac')) return 'flac';\n if (mime.includes('opus')) return 'opus';\n if (mime.includes('pcm') || mime.includes('l16') || mime.includes('wav')) return 'linear16';\n return undefined;\n}\n\nfunction requestBody(\n modelId: string,\n audio: string,\n mimeType: string,\n language: string | undefined\n): Record<string, unknown> {\n if (modelId.includes('whisper')) {\n const body: Record<string, unknown> = { audio };\n if (language !== undefined) body.language = language;\n return body;\n }\n const body: Record<string, unknown> = { audio: { body: audio, contentType: mimeType } };\n const encoding = novaEncoding(mimeType);\n if (encoding !== undefined) body.encoding = encoding;\n if (language !== undefined) body.language = language;\n return body;\n}\n\nfunction speakerOf(record: Record<string, unknown>): string | undefined {\n const value = record.speaker;\n if (typeof value === 'string' && value !== '') return value;\n if (typeof value === 'number') return String(value);\n return undefined;\n}\n\nfunction segmentText(record: Record<string, unknown>): string {\n return stringField(record, 'text') ?? stringField(record, 'word') ?? '';\n}\n\nfunction segmentsFrom(value: unknown): TranscriptSegment[] {\n if (!Array.isArray(value)) return [];\n const segments: TranscriptSegment[] = [];\n for (const item of value) {\n if (!isRecord(item) || Array.isArray(item)) continue;\n const text = segmentText(item);\n if (text === '') continue;\n const segment: TranscriptSegment = {\n text,\n start: numberField(item, 'start') ?? 0,\n end: numberField(item, 'end') ?? 0,\n };\n const speaker = speakerOf(item);\n if (speaker !== undefined) segment.speaker = speaker;\n segments.push(segment);\n }\n return segments;\n}\n\nfunction alternative(body: Record<string, unknown>): Record<string, unknown> | undefined {\n const results = body.results;\n if (!isRecord(results) || Array.isArray(results)) return undefined;\n const channels = results.channels;\n if (!Array.isArray(channels)) return undefined;\n const first = channels[0];\n if (!isRecord(first) || Array.isArray(first)) return undefined;\n const alternatives = first.alternatives;\n if (!Array.isArray(alternatives)) return undefined;\n const alt = alternatives[0];\n if (!isRecord(alt) || Array.isArray(alt)) return undefined;\n return alt;\n}\n\nfunction durationSeconds(body: Record<string, unknown>): number {\n const direct = numberField(body, 'duration');\n if (direct !== undefined) return direct;\n const metadata = body.metadata;\n if (!isRecord(metadata) || Array.isArray(metadata)) return 0;\n const nested = numberField(metadata, 'duration');\n if (nested === undefined) return 0;\n return nested;\n}\n\nfunction optionLanguage(options: ProviderRequestOptions | undefined): string | undefined {\n const record = options as { language?: unknown } | undefined;\n if (record === undefined) return undefined;\n if (typeof record.language !== 'string') return undefined;\n return record.language;\n}\n\nfunction transcriptFrom(body: unknown, model: TranscriptionModel<string>): Transcript {\n const result = resultOf(body);\n if (!isRecord(result) || Array.isArray(result))\n throw new Error('Transcription response had no text');\n const alt = alternative(result);\n const nova = alt === undefined ? undefined : stringField(alt, 'transcript');\n const text = nova ?? stringField(result, 'text');\n if (text === undefined) throw new Error('Transcription response had no text');\n const words = alt === undefined ? undefined : alt.words;\n const segments = segmentsFrom(words ?? result.segments);\n const seconds = durationSeconds(result);\n const meters: AudioCounters = {};\n if (seconds > 0) meters.inputSeconds = seconds;\n const transcript: Transcript = { text, usage: priceAudio(model.audioCost, meters) };\n const language = stringField(result, 'language');\n if (language !== undefined) transcript.language = language;\n if (segments.length > 0) transcript.segments = segments;\n return transcript;\n}\n\nasync function postTranscription(\n model: TranscriptionModel<string>,\n body: unknown,\n options: ProviderRequestOptions | undefined\n): Promise<unknown> {\n const route = session(model, options);\n const headers = route.headers;\n headers['Content-Type'] = 'application/json';\n const fetchImpl = options?.fetch ?? globalThis.fetch;\n const response = await fetchImpl(runUrl(model.baseUrl, model.id, route.account), {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n signal: requestSignal(options),\n });\n if (!response.ok) {\n const text = await response.text();\n throw new Error(errorText(text, response.status));\n }\n const text = await response.text();\n if (text === '') throw new Error('Transcription response had no text');\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\nexport async function transcribeCloudflare(\n model: TranscriptionModel<string>,\n input: AudioInput,\n options?: ProviderRequestOptions\n): Promise<Transcript> {\n try {\n if (model.id.includes('/flux')) throw new Error('Cloudflare flux is websocket only');\n if (options?.signal?.aborted) throw new DOMException('aborted', 'AbortError');\n const file = await readAudio(input, options?.fetch ?? globalThis.fetch, requestSignal(options));\n const payload = requestBody(\n model.id,\n encodeAudioBase64(file.data),\n file.mimeType,\n optionLanguage(options)\n );\n const body = await applyPayload(payload, model, options);\n if (!isRecord(body) || Array.isArray(body))\n throw new Error('Transcription payload was not JSON');\n const parsed = await postTranscription(model, body, options);\n return transcriptFrom(parsed, model);\n } catch (error) {\n const voice = asVoiceError(error);\n throw new VoiceRequestError(voice.reason, voice.error);\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["body", "text"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { isRecord, requestHeaders, stringField } from "../openai/wire.js";
|
|
2
|
+
const placeholder = "{account_id}";
|
|
3
|
+
const speechEncodings = {
|
|
4
|
+
pcm16: "linear16",
|
|
5
|
+
mp3: "mp3",
|
|
6
|
+
flac: "flac",
|
|
7
|
+
mulaw: "mulaw",
|
|
8
|
+
alaw: "alaw",
|
|
9
|
+
opus: "opus"
|
|
10
|
+
};
|
|
11
|
+
function headerValue(headers, name) {
|
|
12
|
+
const expected = name.toLowerCase();
|
|
13
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
14
|
+
if (key.toLowerCase() === expected && value.trim() !== "") return value;
|
|
15
|
+
}
|
|
16
|
+
return void 0;
|
|
17
|
+
}
|
|
18
|
+
function takeHeader(headers, name) {
|
|
19
|
+
const expected = name.toLowerCase();
|
|
20
|
+
let found;
|
|
21
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
22
|
+
if (key.toLowerCase() !== expected) continue;
|
|
23
|
+
if (found === void 0 && value.trim() !== "") found = value.trim();
|
|
24
|
+
delete headers[key];
|
|
25
|
+
}
|
|
26
|
+
return found;
|
|
27
|
+
}
|
|
28
|
+
function accountFromUrl(baseUrl) {
|
|
29
|
+
const match = /\/accounts\/([^/]+)\/ai\/run/.exec(baseUrl);
|
|
30
|
+
const found = match?.[1];
|
|
31
|
+
if (found === void 0 || found === placeholder) return void 0;
|
|
32
|
+
return found;
|
|
33
|
+
}
|
|
34
|
+
function gatewayId(headers) {
|
|
35
|
+
const value = headerValue(headers, "cf-aig-gateway-id");
|
|
36
|
+
if (value === void 0) return "default";
|
|
37
|
+
return value.trim();
|
|
38
|
+
}
|
|
39
|
+
function stampGatewayAuth(headers) {
|
|
40
|
+
const gateway = headerValue(headers, "cf-aig-authorization");
|
|
41
|
+
const authorization = headerValue(headers, "authorization");
|
|
42
|
+
if (gateway === void 0 && authorization !== void 0 && authorization.toLowerCase().startsWith("bearer ")) {
|
|
43
|
+
headers["cf-aig-authorization"] = authorization.slice(authorization.indexOf(" ") + 1).trim();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (gateway !== void 0 && authorization === void 0)
|
|
47
|
+
headers.Authorization = `Bearer ${gateway}`;
|
|
48
|
+
}
|
|
49
|
+
function session(model, options) {
|
|
50
|
+
const headers = requestHeaders(model, options);
|
|
51
|
+
const fromHeader = takeHeader(headers, "cf-account-id");
|
|
52
|
+
const account = fromHeader ?? accountFromUrl(model.baseUrl);
|
|
53
|
+
if (account === void 0) throw new Error("No Cloudflare account id");
|
|
54
|
+
const gateway = gatewayId(headers);
|
|
55
|
+
stampGatewayAuth(headers);
|
|
56
|
+
return { headers, account, gateway };
|
|
57
|
+
}
|
|
58
|
+
function runUrl(baseUrl, modelId, account) {
|
|
59
|
+
const base = baseUrl.replaceAll(placeholder, account).replace(/\/$/, "");
|
|
60
|
+
return `${base}/${modelId}`;
|
|
61
|
+
}
|
|
62
|
+
function workersAiSocket(account, gateway, modelId, query) {
|
|
63
|
+
const extra = query === "" ? "" : `&${query}`;
|
|
64
|
+
return `wss://gateway.ai.cloudflare.com/v1/${account}/${gateway}/workers-ai?model=${encodeURIComponent(modelId)}${extra}`;
|
|
65
|
+
}
|
|
66
|
+
function auraEncoding(format) {
|
|
67
|
+
const name = speechEncodings[format.encoding];
|
|
68
|
+
if (!name) throw new Error(`Unsupported speech format ${format.encoding}`);
|
|
69
|
+
return name;
|
|
70
|
+
}
|
|
71
|
+
function listenQuery(modelId, format) {
|
|
72
|
+
if (format.encoding !== "pcm16" && format.encoding !== "mulaw") {
|
|
73
|
+
throw new Error(`Unsupported audio format ${format.encoding}`);
|
|
74
|
+
}
|
|
75
|
+
if (modelId.includes("/flux") && format.encoding !== "pcm16") {
|
|
76
|
+
throw new Error(`Unsupported audio format ${format.encoding}`);
|
|
77
|
+
}
|
|
78
|
+
const encoding = format.encoding === "mulaw" ? "mulaw" : "linear16";
|
|
79
|
+
const shared = `encoding=${encoding}&sample_rate=${format.sampleRate}`;
|
|
80
|
+
if (modelId.includes("/flux")) return shared;
|
|
81
|
+
return `${shared}&interim_results=true&vad_events=true`;
|
|
82
|
+
}
|
|
83
|
+
function sameFormat(left, right) {
|
|
84
|
+
return left.encoding === right.encoding && left.sampleRate === right.sampleRate && left.channels === right.channels;
|
|
85
|
+
}
|
|
86
|
+
function connectSocket(url, headers) {
|
|
87
|
+
const Socket = WebSocket;
|
|
88
|
+
return new Socket(url, { headers });
|
|
89
|
+
}
|
|
90
|
+
function awaitOpen(socket, label, fail) {
|
|
91
|
+
return new Promise((resolve) => {
|
|
92
|
+
let opened = false;
|
|
93
|
+
socket.addEventListener("open", () => {
|
|
94
|
+
opened = true;
|
|
95
|
+
resolve();
|
|
96
|
+
});
|
|
97
|
+
socket.addEventListener("error", () => {
|
|
98
|
+
fail(`${label} socket failed`);
|
|
99
|
+
if (!opened) resolve();
|
|
100
|
+
});
|
|
101
|
+
socket.addEventListener("close", () => {
|
|
102
|
+
if (!opened) {
|
|
103
|
+
fail(`${label} socket closed`);
|
|
104
|
+
resolve();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function audioFrame(data) {
|
|
110
|
+
const copy = new Uint8Array(data.byteLength);
|
|
111
|
+
copy.set(data);
|
|
112
|
+
return copy.buffer;
|
|
113
|
+
}
|
|
114
|
+
async function messageBytes(data) {
|
|
115
|
+
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
116
|
+
if (data instanceof Uint8Array) return data;
|
|
117
|
+
if (typeof Blob !== "undefined" && data instanceof Blob)
|
|
118
|
+
return new Uint8Array(await data.arrayBuffer());
|
|
119
|
+
return void 0;
|
|
120
|
+
}
|
|
121
|
+
function failureMessage(body, fallback) {
|
|
122
|
+
const errors = body.errors;
|
|
123
|
+
if (!Array.isArray(errors)) return fallback;
|
|
124
|
+
const first = errors[0];
|
|
125
|
+
if (!isRecord(first) || Array.isArray(first)) return fallback;
|
|
126
|
+
const message = stringField(first, "message");
|
|
127
|
+
if (message === void 0 || message === "") return fallback;
|
|
128
|
+
return message;
|
|
129
|
+
}
|
|
130
|
+
function resultOf(body) {
|
|
131
|
+
if (!isRecord(body) || Array.isArray(body)) return body;
|
|
132
|
+
if (body.success === false) throw new Error(failureMessage(body, "Cloudflare request failed"));
|
|
133
|
+
if (!Object.hasOwn(body, "result")) return body;
|
|
134
|
+
return body.result;
|
|
135
|
+
}
|
|
136
|
+
function errorText(text, status) {
|
|
137
|
+
if (text === "") return `HTTP ${status}`;
|
|
138
|
+
let parsed;
|
|
139
|
+
try {
|
|
140
|
+
parsed = JSON.parse(text);
|
|
141
|
+
} catch {
|
|
142
|
+
return text;
|
|
143
|
+
}
|
|
144
|
+
if (!isRecord(parsed) || Array.isArray(parsed)) return text;
|
|
145
|
+
if (Array.isArray(parsed.errors)) return failureMessage(parsed, text);
|
|
146
|
+
const message = stringField(parsed, "message");
|
|
147
|
+
if (message !== void 0) return message;
|
|
148
|
+
return text;
|
|
149
|
+
}
|
|
150
|
+
export {
|
|
151
|
+
audioFrame,
|
|
152
|
+
auraEncoding,
|
|
153
|
+
awaitOpen,
|
|
154
|
+
connectSocket,
|
|
155
|
+
errorText,
|
|
156
|
+
failureMessage,
|
|
157
|
+
gatewayId,
|
|
158
|
+
listenQuery,
|
|
159
|
+
messageBytes,
|
|
160
|
+
resultOf,
|
|
161
|
+
runUrl,
|
|
162
|
+
sameFormat,
|
|
163
|
+
session,
|
|
164
|
+
workersAiSocket
|
|
165
|
+
};
|
|
166
|
+
//# sourceMappingURL=wire.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/cloudflare/wire.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ProviderRequestOptions } from '@earendil-works/pi-ai';\nimport type { AudioFormat } from '../audio.ts';\nimport { isRecord, requestHeaders, stringField } from '../openai/wire.ts';\n\nconst placeholder = '{account_id}';\n\nconst speechEncodings: Record<string, string> = {\n pcm16: 'linear16',\n mp3: 'mp3',\n flac: 'flac',\n mulaw: 'mulaw',\n alaw: 'alaw',\n opus: 'opus',\n};\n\ninterface RouteModel {\n provider: string;\n baseUrl: string;\n headers?: Record<string, string>;\n}\n\nexport interface CloudflareRoute {\n headers: Record<string, string>;\n account: string;\n gateway: string;\n}\n\nfunction headerValue(headers: Record<string, string>, name: string): string | undefined {\n const expected = name.toLowerCase();\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() === expected && value.trim() !== '') return value;\n }\n return undefined;\n}\n\nfunction takeHeader(headers: Record<string, string>, name: string): string | undefined {\n const expected = name.toLowerCase();\n let found: string | undefined;\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() !== expected) continue;\n if (found === undefined && value.trim() !== '') found = value.trim();\n delete headers[key];\n }\n return found;\n}\n\nfunction accountFromUrl(baseUrl: string): string | undefined {\n const match = /\\/accounts\\/([^/]+)\\/ai\\/run/.exec(baseUrl);\n const found = match?.[1];\n if (found === undefined || found === placeholder) return undefined;\n return found;\n}\n\nexport function gatewayId(headers: Record<string, string>): string {\n const value = headerValue(headers, 'cf-aig-gateway-id');\n if (value === undefined) return 'default';\n return value.trim();\n}\n\nfunction stampGatewayAuth(headers: Record<string, string>): void {\n const gateway = headerValue(headers, 'cf-aig-authorization');\n const authorization = headerValue(headers, 'authorization');\n if (\n gateway === undefined &&\n authorization !== undefined &&\n authorization.toLowerCase().startsWith('bearer ')\n ) {\n headers['cf-aig-authorization'] = authorization.slice(authorization.indexOf(' ') + 1).trim();\n return;\n }\n if (gateway !== undefined && authorization === undefined)\n headers.Authorization = `Bearer ${gateway}`;\n}\n\nexport function session(\n model: RouteModel,\n options: ProviderRequestOptions | undefined\n): CloudflareRoute {\n const headers = requestHeaders(model, options);\n const fromHeader = takeHeader(headers, 'cf-account-id');\n const account = fromHeader ?? accountFromUrl(model.baseUrl);\n if (account === undefined) throw new Error('No Cloudflare account id');\n const gateway = gatewayId(headers);\n stampGatewayAuth(headers);\n return { headers, account, gateway };\n}\n\nexport function runUrl(baseUrl: string, modelId: string, account: string): string {\n const base = baseUrl.replaceAll(placeholder, account).replace(/\\/$/, '');\n return `${base}/${modelId}`;\n}\n\nexport function workersAiSocket(\n account: string,\n gateway: string,\n modelId: string,\n query: string\n): string {\n const extra = query === '' ? '' : `&${query}`;\n return `wss://gateway.ai.cloudflare.com/v1/${account}/${gateway}/workers-ai?model=${encodeURIComponent(modelId)}${extra}`;\n}\n\nexport function auraEncoding(format: AudioFormat): string {\n const name = speechEncodings[format.encoding];\n if (!name) throw new Error(`Unsupported speech format ${format.encoding}`);\n return name;\n}\n\nexport function listenQuery(modelId: string, format: AudioFormat): string {\n if (format.encoding !== 'pcm16' && format.encoding !== 'mulaw') {\n throw new Error(`Unsupported audio format ${format.encoding}`);\n }\n if (modelId.includes('/flux') && format.encoding !== 'pcm16') {\n throw new Error(`Unsupported audio format ${format.encoding}`);\n }\n const encoding = format.encoding === 'mulaw' ? 'mulaw' : 'linear16';\n const shared = `encoding=${encoding}&sample_rate=${format.sampleRate}`;\n if (modelId.includes('/flux')) return shared;\n return `${shared}&interim_results=true&vad_events=true`;\n}\n\nexport function sameFormat(left: AudioFormat, right: AudioFormat): boolean {\n return (\n left.encoding === right.encoding &&\n left.sampleRate === right.sampleRate &&\n left.channels === right.channels\n );\n}\n\nexport function connectSocket(url: string, headers: Record<string, string>): WebSocket {\n const Socket = WebSocket as unknown as new (\n address: string,\n init: { headers: Record<string, string> }\n ) => WebSocket;\n return new Socket(url, { headers });\n}\n\nexport function awaitOpen(\n socket: WebSocket,\n label: string,\n fail: (message: string) => void\n): Promise<void> {\n return new Promise((resolve) => {\n let opened = false;\n socket.addEventListener('open', () => {\n opened = true;\n resolve();\n });\n socket.addEventListener('error', () => {\n fail(`${label} socket failed`);\n if (!opened) resolve();\n });\n socket.addEventListener('close', () => {\n if (!opened) {\n fail(`${label} socket closed`);\n resolve();\n }\n });\n });\n}\n\nexport function audioFrame(data: Uint8Array): ArrayBuffer {\n const copy = new Uint8Array(data.byteLength);\n copy.set(data);\n return copy.buffer;\n}\n\nexport async function messageBytes(data: unknown): Promise<Uint8Array | undefined> {\n if (data instanceof ArrayBuffer) return new Uint8Array(data);\n if (data instanceof Uint8Array) return data;\n if (typeof Blob !== 'undefined' && data instanceof Blob)\n return new Uint8Array(await data.arrayBuffer());\n return undefined;\n}\n\nexport function failureMessage(body: Record<string, unknown>, fallback: string): string {\n const errors = body.errors;\n if (!Array.isArray(errors)) return fallback;\n const first = errors[0];\n if (!isRecord(first) || Array.isArray(first)) return fallback;\n const message = stringField(first, 'message');\n if (message === undefined || message === '') return fallback;\n return message;\n}\n\nexport function resultOf(body: unknown): unknown {\n if (!isRecord(body) || Array.isArray(body)) return body;\n if (body.success === false) throw new Error(failureMessage(body, 'Cloudflare request failed'));\n if (!Object.hasOwn(body, 'result')) return body;\n return body.result;\n}\n\nexport function errorText(text: string, status: number): string {\n if (text === '') return `HTTP ${status}`;\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return text;\n }\n if (!isRecord(parsed) || Array.isArray(parsed)) return text;\n if (Array.isArray(parsed.errors)) return failureMessage(parsed, text);\n const message = stringField(parsed, 'message');\n if (message !== undefined) return message;\n return text;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { blankUsage } from "../blank-usage.js";
|
|
2
|
+
function none() {
|
|
3
|
+
return [];
|
|
4
|
+
}
|
|
5
|
+
function isRecord(value) {
|
|
6
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
function text(value) {
|
|
9
|
+
return typeof value === "string" ? value : "";
|
|
10
|
+
}
|
|
11
|
+
function done(itemId, interrupted) {
|
|
12
|
+
return { type: "response_done", itemId, interrupted, usage: blankUsage() };
|
|
13
|
+
}
|
|
14
|
+
function ingestFishData(message) {
|
|
15
|
+
const type = message.type;
|
|
16
|
+
if (typeof type !== "string") return none();
|
|
17
|
+
if (type === "client_tool.call") return toolCall(message);
|
|
18
|
+
if (type === "error")
|
|
19
|
+
return [{ type: "error", error: text(message.code) || "error", fatal: false }];
|
|
20
|
+
if (type === "session.ended") {
|
|
21
|
+
const reason = text(message.reason);
|
|
22
|
+
return [{ type: "error", error: reason.length > 0 ? reason : "session ended", fatal: false }];
|
|
23
|
+
}
|
|
24
|
+
if (type === "tool.started" || type === "tool.completed" || type === "tool.failed") return none();
|
|
25
|
+
return none();
|
|
26
|
+
}
|
|
27
|
+
function toolCall(message) {
|
|
28
|
+
const name = text(message.toolName);
|
|
29
|
+
const id = text(message.callId);
|
|
30
|
+
if (name.length === 0 || id.length === 0) return none();
|
|
31
|
+
const params = isRecord(message.params) ? message.params : {};
|
|
32
|
+
const call = { type: "toolCall", id, name, arguments: params };
|
|
33
|
+
return [{ type: "tool_call", call, blocking: message.expectsResponse !== false }];
|
|
34
|
+
}
|
|
35
|
+
function ingestFishText(turn, packet, localIdentity) {
|
|
36
|
+
const itemId = text(packet.attributes["lk.segment_id"]);
|
|
37
|
+
const id = itemId.length > 0 ? itemId : "segment";
|
|
38
|
+
const final = packet.attributes["lk.transcription_final"] === "true";
|
|
39
|
+
if (packet.identity === localIdentity) {
|
|
40
|
+
return [{ type: "input_transcript", itemId: id, text: packet.text, final }];
|
|
41
|
+
}
|
|
42
|
+
if (turn.discardFinals > 0) {
|
|
43
|
+
if (final) turn.discardFinals -= 1;
|
|
44
|
+
return none();
|
|
45
|
+
}
|
|
46
|
+
turn.turnOpen = true;
|
|
47
|
+
return [{ type: "output_transcript", itemId: id, text: packet.text, final }];
|
|
48
|
+
}
|
|
49
|
+
function ingestFishAudio(turn, chunk) {
|
|
50
|
+
if (turn.dropAudio) return none();
|
|
51
|
+
turn.turnOpen = true;
|
|
52
|
+
return [{ type: "output_audio", itemId: "agent", chunk }];
|
|
53
|
+
}
|
|
54
|
+
function ingestFishState(turn, value) {
|
|
55
|
+
if (value === "speaking" || value === "thinking") {
|
|
56
|
+
turn.turnOpen = true;
|
|
57
|
+
return none();
|
|
58
|
+
}
|
|
59
|
+
if (value !== "listening") return none();
|
|
60
|
+
turn.dropAudio = false;
|
|
61
|
+
if (!turn.turnOpen) return none();
|
|
62
|
+
turn.turnOpen = false;
|
|
63
|
+
return [done("agent", false)];
|
|
64
|
+
}
|
|
65
|
+
function ingestSpeakers(turn, identities) {
|
|
66
|
+
const active = identities.length > 0;
|
|
67
|
+
if (!active) {
|
|
68
|
+
turn.speaking = false;
|
|
69
|
+
return none();
|
|
70
|
+
}
|
|
71
|
+
if (turn.speaking) return none();
|
|
72
|
+
turn.speaking = true;
|
|
73
|
+
return [{ type: "input_speech_start" }];
|
|
74
|
+
}
|
|
75
|
+
function userMessage(value, speak) {
|
|
76
|
+
const message = { type: "user.message", text: value };
|
|
77
|
+
if (!speak) message.audio = false;
|
|
78
|
+
return message;
|
|
79
|
+
}
|
|
80
|
+
function toolResult(callId, result, isError) {
|
|
81
|
+
const message = { type: "client_tool.result", callId, result };
|
|
82
|
+
if (isError) message.isError = true;
|
|
83
|
+
return message;
|
|
84
|
+
}
|
|
85
|
+
export {
|
|
86
|
+
ingestFishAudio,
|
|
87
|
+
ingestFishData,
|
|
88
|
+
ingestFishState,
|
|
89
|
+
ingestFishText,
|
|
90
|
+
ingestSpeakers,
|
|
91
|
+
toolResult,
|
|
92
|
+
userMessage
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/fish-agents/events.ts"],
|
|
4
|
+
"sourcesContent": ["import type { JsonObject, ToolCall } from '@earendil-works/pi-ai';\nimport type { AudioChunk } from '../audio.ts';\nimport { blankUsage } from '../blank-usage.ts';\nimport type { RealtimeEvent } from '../realtime.ts';\n\nexport interface FishTurn {\n dropAudio: boolean;\n discardFinals: number;\n speaking: boolean;\n turnOpen: boolean;\n}\n\nexport interface FishText {\n identity: string;\n text: string;\n attributes: Record<string, string>;\n}\n\nfunction none(): RealtimeEvent[] {\n return [];\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction text(value: unknown): string {\n return typeof value === 'string' ? value : '';\n}\n\nfunction done(itemId: string, interrupted: boolean): RealtimeEvent {\n return { type: 'response_done', itemId, interrupted, usage: blankUsage() };\n}\n\nexport function ingestFishData(message: Record<string, unknown>): RealtimeEvent[] {\n const type = message.type;\n if (typeof type !== 'string') return none();\n if (type === 'client_tool.call') return toolCall(message);\n if (type === 'error')\n return [{ type: 'error', error: text(message.code) || 'error', fatal: false }];\n if (type === 'session.ended') {\n const reason = text(message.reason);\n return [{ type: 'error', error: reason.length > 0 ? reason : 'session ended', fatal: false }];\n }\n if (type === 'tool.started' || type === 'tool.completed' || type === 'tool.failed') return none();\n return none();\n}\n\nfunction toolCall(message: Record<string, unknown>): RealtimeEvent[] {\n const name = text(message.toolName);\n const id = text(message.callId);\n if (name.length === 0 || id.length === 0) return none();\n const params = isRecord(message.params) ? message.params : {};\n const call: ToolCall = { type: 'toolCall', id, name, arguments: params as JsonObject };\n return [{ type: 'tool_call', call, blocking: message.expectsResponse !== false }];\n}\n\nexport function ingestFishText(\n turn: FishTurn,\n packet: FishText,\n localIdentity: string\n): RealtimeEvent[] {\n const itemId = text(packet.attributes['lk.segment_id']);\n const id = itemId.length > 0 ? itemId : 'segment';\n const final = packet.attributes['lk.transcription_final'] === 'true';\n if (packet.identity === localIdentity) {\n return [{ type: 'input_transcript', itemId: id, text: packet.text, final }];\n }\n if (turn.discardFinals > 0) {\n if (final) turn.discardFinals -= 1;\n return none();\n }\n turn.turnOpen = true;\n return [{ type: 'output_transcript', itemId: id, text: packet.text, final }];\n}\n\nexport function ingestFishAudio(turn: FishTurn, chunk: AudioChunk): RealtimeEvent[] {\n if (turn.dropAudio) return none();\n turn.turnOpen = true;\n return [{ type: 'output_audio', itemId: 'agent', chunk }];\n}\n\nexport function ingestFishState(turn: FishTurn, value: string): RealtimeEvent[] {\n if (value === 'speaking' || value === 'thinking') {\n turn.turnOpen = true;\n return none();\n }\n if (value !== 'listening') return none();\n turn.dropAudio = false;\n if (!turn.turnOpen) return none();\n turn.turnOpen = false;\n return [done('agent', false)];\n}\n\nexport function ingestSpeakers(turn: FishTurn, identities: string[]): RealtimeEvent[] {\n const active = identities.length > 0;\n if (!active) {\n turn.speaking = false;\n return none();\n }\n if (turn.speaking) return none();\n turn.speaking = true;\n return [{ type: 'input_speech_start' }];\n}\n\nexport function userMessage(value: string, speak: boolean): JsonObject {\n const message: JsonObject = { type: 'user.message', text: value };\n if (!speak) message.audio = false;\n return message;\n}\n\nexport function toolResult(callId: string, result: string, isError: boolean): JsonObject {\n const message: JsonObject = { type: 'client_tool.result', callId, result };\n if (isError) message.isError = true;\n return message;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { registerVoiceModel } from "../models.js";
|
|
2
|
+
import { registerRealtimeApiProvider } from "../registry.js";
|
|
3
|
+
import { connectFish } from "./session.js";
|
|
4
|
+
const voices = [{ id: "802e3bc2b27e49c2995d23ef70e6ac89", name: "Example" }];
|
|
5
|
+
function pcm16(sampleRate) {
|
|
6
|
+
return { encoding: "pcm16", sampleRate, channels: 1 };
|
|
7
|
+
}
|
|
8
|
+
const fishAgent = {
|
|
9
|
+
id: "fish-agent",
|
|
10
|
+
name: "Fish Agent",
|
|
11
|
+
api: "fish-agents",
|
|
12
|
+
provider: "fish",
|
|
13
|
+
baseUrl: "https://api.fish.audio",
|
|
14
|
+
type: "realtime",
|
|
15
|
+
transports: ["webrtc"],
|
|
16
|
+
toolCalls: "blocking",
|
|
17
|
+
inputFormat: pcm16(24e3),
|
|
18
|
+
outputFormat: pcm16(24e3),
|
|
19
|
+
maxSessionMs: 18e5,
|
|
20
|
+
resumable: false,
|
|
21
|
+
ephemeralAuth: true,
|
|
22
|
+
voices,
|
|
23
|
+
cascade: { stt: "deepgram:nova-3", llm: "fish", tts: "s2.1-pro" },
|
|
24
|
+
provisioning: "agent",
|
|
25
|
+
delivery: { interrupt: "native", when_idle: "approximate", silent: "approximate" },
|
|
26
|
+
truncation: "server",
|
|
27
|
+
liveToolUpdate: false,
|
|
28
|
+
toolTimeoutMs: { min: 1e3, max: 12e4 },
|
|
29
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
30
|
+
audioCost: { perInputMinute: 0 }
|
|
31
|
+
};
|
|
32
|
+
function registerFishAgents(options = {}) {
|
|
33
|
+
const unregisterModel = registerVoiceModel(fishAgent);
|
|
34
|
+
const unregisterApi = registerRealtimeApiProvider({
|
|
35
|
+
api: "fish-agents",
|
|
36
|
+
connect: (model, context, connectOptions) => connectFish(model, context, connectOptions, options)
|
|
37
|
+
});
|
|
38
|
+
return () => {
|
|
39
|
+
unregisterModel();
|
|
40
|
+
unregisterApi();
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
import { hashHostedSpec, memoryHostedCache } from "../hosted-agent.js";
|
|
44
|
+
import { agentConfig, httpsOrigin, provisionAgent } from "./provision.js";
|
|
45
|
+
import { importFishSdk, openFishRoom } from "./room.js";
|
|
46
|
+
export {
|
|
47
|
+
agentConfig,
|
|
48
|
+
fishAgent,
|
|
49
|
+
hashHostedSpec,
|
|
50
|
+
httpsOrigin,
|
|
51
|
+
importFishSdk,
|
|
52
|
+
memoryHostedCache,
|
|
53
|
+
openFishRoom,
|
|
54
|
+
provisionAgent,
|
|
55
|
+
registerFishAgents
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/fish-agents/index.ts"],
|
|
4
|
+
"sourcesContent": ["import type { RealtimeModel } from '../models.ts';\nimport { registerVoiceModel } from '../models.ts';\nimport { registerRealtimeApiProvider } from '../registry.ts';\nimport type { FishOptions } from './session.ts';\nimport { connectFish } from './session.ts';\n\nconst voices = [{ id: '802e3bc2b27e49c2995d23ef70e6ac89', name: 'Example' }];\n\nfunction pcm16(sampleRate: number) {\n return { encoding: 'pcm16' as const, sampleRate, channels: 1 as const };\n}\n\nexport const fishAgent: RealtimeModel<'fish-agents'> = {\n id: 'fish-agent',\n name: 'Fish Agent',\n api: 'fish-agents',\n provider: 'fish',\n baseUrl: 'https://api.fish.audio',\n type: 'realtime',\n transports: ['webrtc'],\n toolCalls: 'blocking',\n inputFormat: pcm16(24000),\n outputFormat: pcm16(24000),\n maxSessionMs: 1_800_000,\n resumable: false,\n ephemeralAuth: true,\n voices,\n cascade: { stt: 'deepgram:nova-3', llm: 'fish', tts: 's2.1-pro' },\n provisioning: 'agent',\n delivery: { interrupt: 'native', when_idle: 'approximate', silent: 'approximate' },\n truncation: 'server',\n liveToolUpdate: false,\n toolTimeoutMs: { min: 1000, max: 120_000 },\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n audioCost: { perInputMinute: 0 },\n};\n\nexport function registerFishAgents(options: FishOptions = {}): () => void {\n const unregisterModel = registerVoiceModel(fishAgent);\n const unregisterApi = registerRealtimeApiProvider({\n api: 'fish-agents',\n connect: (model, context, connectOptions) =>\n connectFish(model, context, connectOptions, options),\n });\n return () => {\n unregisterModel();\n unregisterApi();\n };\n}\n\nexport type { CustomLlm, HostedAgent, HostedAgentSpec, HostedCache } from '../hosted-agent.ts';\nexport { hashHostedSpec, memoryHostedCache } from '../hosted-agent.ts';\nexport type { ProvisionOptions } from './provision.ts';\nexport { agentConfig, httpsOrigin, provisionAgent } from './provision.ts';\nexport type { FishRoom, FishRoomFactory, FishSdk } from './room.ts';\nexport { importFishSdk, openFishRoom } from './room.ts';\nexport type { FishOptions } from './session.ts';\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,0BAA0B;AACnC,SAAS,mCAAmC;AAE5C,SAAS,mBAAmB;AAE5B,MAAM,SAAS,CAAC,EAAE,IAAI,oCAAoC,MAAM,UAAU,CAAC;AAE3E,SAAS,MAAM,YAAoB;AACjC,SAAO,EAAE,UAAU,SAAkB,YAAY,UAAU,EAAW;AACxE;AAEO,MAAM,YAA0C;AAAA,EACrD,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,KAAK;AAAA,EACL,UAAU;AAAA,EACV,SAAS;AAAA,EACT,MAAM;AAAA,EACN,YAAY,CAAC,QAAQ;AAAA,EACrB,WAAW;AAAA,EACX,aAAa,MAAM,IAAK;AAAA,EACxB,cAAc,MAAM,IAAK;AAAA,EACzB,cAAc;AAAA,EACd,WAAW;AAAA,EACX,eAAe;AAAA,EACf;AAAA,EACA,SAAS,EAAE,KAAK,mBAAmB,KAAK,QAAQ,KAAK,WAAW;AAAA,EAChE,cAAc;AAAA,EACd,UAAU,EAAE,WAAW,UAAU,WAAW,eAAe,QAAQ,cAAc;AAAA,EACjF,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,eAAe,EAAE,KAAK,KAAM,KAAK,KAAQ;AAAA,EACzC,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,YAAY,EAAE;AAAA,EACzD,WAAW,EAAE,gBAAgB,EAAE;AACjC;AAEO,SAAS,mBAAmB,UAAuB,CAAC,GAAe;AACxE,QAAM,kBAAkB,mBAAmB,SAAS;AACpD,QAAM,gBAAgB,4BAA4B;AAAA,IAChD,KAAK;AAAA,IACL,SAAS,CAAC,OAAO,SAAS,mBACxB,YAAY,OAAO,SAAS,gBAAgB,OAAO;AAAA,EACvD,CAAC;AACD,SAAO,MAAM;AACX,oBAAgB;AAChB,kBAAc;AAAA,EAChB;AACF;AAGA,SAAS,gBAAgB,yBAAyB;AAElD,SAAS,aAAa,aAAa,sBAAsB;AAEzD,SAAS,eAAe,oBAAoB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|