@effect-uai/google 0.3.0 → 0.4.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/dist/Gemini.mjs +1 -1
- package/dist/GeminiEmbedding.mjs +1 -1
- package/dist/GeminiSynthesizer.d.mts +48 -0
- package/dist/GeminiSynthesizer.d.mts.map +1 -0
- package/dist/GeminiSynthesizer.mjs +95 -0
- package/dist/GeminiSynthesizer.mjs.map +1 -0
- package/dist/GeminiSynthesizer.test.d.mts +1 -0
- package/dist/GeminiSynthesizer.test.mjs +84 -0
- package/dist/GeminiSynthesizer.test.mjs.map +1 -0
- package/dist/GeminiTranscriber.d.mts +42 -0
- package/dist/GeminiTranscriber.d.mts.map +1 -0
- package/dist/GeminiTranscriber.mjs +80 -0
- package/dist/GeminiTranscriber.mjs.map +1 -0
- package/dist/GeminiTranscriber.test.d.mts +1 -0
- package/dist/GeminiTranscriber.test.mjs +125 -0
- package/dist/GeminiTranscriber.test.mjs.map +1 -0
- package/dist/LyriaGenerator.d.mts +57 -0
- package/dist/LyriaGenerator.d.mts.map +1 -0
- package/dist/LyriaGenerator.mjs +202 -0
- package/dist/LyriaGenerator.mjs.map +1 -0
- package/dist/LyriaGenerator.test.d.mts +1 -0
- package/dist/LyriaGenerator.test.mjs +138 -0
- package/dist/LyriaGenerator.test.mjs.map +1 -0
- package/dist/{chunk-CfYAbeIz.mjs → chunk-uyGKjUfl.mjs} +2 -1
- package/dist/codec.mjs +1 -1
- package/dist/dist-DMsg8YLb.mjs +13782 -0
- package/dist/dist-DMsg8YLb.mjs.map +1 -0
- package/dist/geminiSpeechCodec.d.mts +37 -0
- package/dist/geminiSpeechCodec.d.mts.map +1 -0
- package/dist/geminiSpeechCodec.mjs +110 -0
- package/dist/geminiSpeechCodec.mjs.map +1 -0
- package/dist/geminiSpeechCodec.test.d.mts +1 -0
- package/dist/geminiSpeechCodec.test.mjs +78 -0
- package/dist/geminiSpeechCodec.test.mjs.map +1 -0
- package/dist/index.d.mts +5 -2
- package/dist/index.mjs +4 -1
- package/dist/magic-string.es-CaWgF-3p.mjs +1013 -0
- package/dist/magic-string.es-CaWgF-3p.mjs.map +1 -0
- package/dist/models.d.mts +35 -1
- package/dist/models.d.mts.map +1 -1
- package/package.json +14 -2
- package/src/GeminiSynthesizer.test.ts +93 -0
- package/src/GeminiSynthesizer.ts +214 -0
- package/src/GeminiTranscriber.test.ts +136 -0
- package/src/GeminiTranscriber.ts +204 -0
- package/src/LyriaGenerator.test.ts +162 -0
- package/src/LyriaGenerator.ts +394 -0
- package/src/geminiSpeechCodec.test.ts +56 -0
- package/src/geminiSpeechCodec.ts +137 -0
- package/src/index.ts +3 -0
- package/src/models.ts +84 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import * as AiError from "@effect-uai/core/AiError";
|
|
3
|
+
import { AudioSource } from "@effect-uai/core/Audio";
|
|
4
|
+
|
|
5
|
+
//#region src/geminiSpeechCodec.d.ts
|
|
6
|
+
/** Wire shape for an inline-audio part on `generateContent`. */
|
|
7
|
+
type InlineAudioData = {
|
|
8
|
+
readonly mimeType: string;
|
|
9
|
+
readonly data: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Encode any `AudioSource` into the `inlineData` shape expected by
|
|
13
|
+
* Gemini's `generateContent`. URL variants are rejected with
|
|
14
|
+
* `InvalidRequest` — Files API upload is a separate flow.
|
|
15
|
+
*
|
|
16
|
+
* Caller is responsible for keeping the total request body under
|
|
17
|
+
* 20 MB. Beyond that, Gemini's Files API is the only path.
|
|
18
|
+
*/
|
|
19
|
+
declare const audioSourceToInlineData: (audio: AudioSource) => Effect.Effect<InlineAudioData, AiError.AiError>;
|
|
20
|
+
/** Decode a base64-encoded audio payload into bytes. */
|
|
21
|
+
declare const decodeBase64Audio: (b64: string) => Effect.Effect<Uint8Array, AiError.AiError>;
|
|
22
|
+
/**
|
|
23
|
+
* Wrap a raw 16-bit signed little-endian PCM buffer in a standard 44-byte
|
|
24
|
+
* RIFF/WAVE header. Gemini TTS returns PCM-only — for playback or for
|
|
25
|
+
* round-tripping into Gemini transcription (which accepts wav, not raw
|
|
26
|
+
* PCM), the bytes need a container.
|
|
27
|
+
*/
|
|
28
|
+
declare const wrapPcmAsWav: (pcm: Uint8Array, sampleRate: number, channels: number, bitsPerSample?: number) => Uint8Array;
|
|
29
|
+
/**
|
|
30
|
+
* HTTP status → typed `AiError`. Shared between synthesizer and
|
|
31
|
+
* transcriber.
|
|
32
|
+
*/
|
|
33
|
+
declare const httpStatusError: (status: number, body: string) => AiError.AiError;
|
|
34
|
+
declare const transportFailure: (cause: unknown) => AiError.AiError;
|
|
35
|
+
//#endregion
|
|
36
|
+
export { InlineAudioData, audioSourceToInlineData, decodeBase64Audio, httpStatusError, transportFailure, wrapPcmAsWav };
|
|
37
|
+
//# sourceMappingURL=geminiSpeechCodec.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geminiSpeechCodec.d.mts","names":[],"sources":["../src/geminiSpeechCodec.ts"],"mappings":";;;;;;KAiBY,eAAA;EAAA,SACD,QAAA;EAAA,SACA,IAAA;AAAA;;;AAWX;;;;;;cAAa,uBAAA,GACX,KAAA,EAAO,WAAA,KACJ,MAAA,CAAO,MAAA,CAAO,eAAA,EAAiB,OAAA,CAAQ,OAAA;;cAe/B,iBAAA,GAAqB,GAAA,aAAc,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,OAAA,CAAQ,OAAA;;;;;;;cAarE,YAAA,GACX,GAAA,EAAK,UAAA,EACL,UAAA,UACA,QAAA,UACA,aAAA,cACC,UAAA;;;AAlBH;;cAiDa,eAAA,GAAkB,MAAA,UAAgB,IAAA,aAAiB,OAAA,CAAQ,OAAA;AAAA,cAuC3D,gBAAA,GAAoB,KAAA,cAAiB,OAAA,CAAQ,OAAA"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Effect, Encoding, Match, Result } from "effect";
|
|
2
|
+
import * as AiError from "@effect-uai/core/AiError";
|
|
3
|
+
//#region src/geminiSpeechCodec.ts
|
|
4
|
+
/**
|
|
5
|
+
* Shared codec helpers for Gemini speech endpoints. The TTS and STT
|
|
6
|
+
* variants both ride on `:generateContent`, so the audio-in / audio-out
|
|
7
|
+
* shapes are siblings.
|
|
8
|
+
*/
|
|
9
|
+
const urlNotSupported = new AiError.InvalidRequest({
|
|
10
|
+
provider: "gemini",
|
|
11
|
+
param: "audio",
|
|
12
|
+
raw: "Gemini inline audio does not accept URL sources. Either fetch the URL yourself and pass `{ _tag: \"bytes\", bytes, mimeType }`, or upload via the Files API and pass a `fileData` URI (not yet wired here)."
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* Encode any `AudioSource` into the `inlineData` shape expected by
|
|
16
|
+
* Gemini's `generateContent`. URL variants are rejected with
|
|
17
|
+
* `InvalidRequest` — Files API upload is a separate flow.
|
|
18
|
+
*
|
|
19
|
+
* Caller is responsible for keeping the total request body under
|
|
20
|
+
* 20 MB. Beyond that, Gemini's Files API is the only path.
|
|
21
|
+
*/
|
|
22
|
+
const audioSourceToInlineData = Match.type().pipe(Match.tag("bytes", (a) => Effect.succeed({
|
|
23
|
+
mimeType: a.mimeType,
|
|
24
|
+
data: Encoding.encodeBase64(a.bytes)
|
|
25
|
+
})), Match.tag("base64", (a) => Effect.succeed({
|
|
26
|
+
mimeType: a.mimeType,
|
|
27
|
+
data: a.base64
|
|
28
|
+
})), Match.tag("url", () => Effect.fail(urlNotSupported)), Match.exhaustive);
|
|
29
|
+
/** Decode a base64-encoded audio payload into bytes. */
|
|
30
|
+
const decodeBase64Audio = (b64) => Result.match(Encoding.decodeBase64(b64), {
|
|
31
|
+
onSuccess: Effect.succeed,
|
|
32
|
+
onFailure: (cause) => Effect.fail(new AiError.InvalidRequest({
|
|
33
|
+
provider: "gemini",
|
|
34
|
+
param: "audio",
|
|
35
|
+
raw: cause
|
|
36
|
+
}))
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* Wrap a raw 16-bit signed little-endian PCM buffer in a standard 44-byte
|
|
40
|
+
* RIFF/WAVE header. Gemini TTS returns PCM-only — for playback or for
|
|
41
|
+
* round-tripping into Gemini transcription (which accepts wav, not raw
|
|
42
|
+
* PCM), the bytes need a container.
|
|
43
|
+
*/
|
|
44
|
+
const wrapPcmAsWav = (pcm, sampleRate, channels, bitsPerSample = 16) => {
|
|
45
|
+
const dataSize = pcm.length;
|
|
46
|
+
const blockAlign = channels * bitsPerSample / 8;
|
|
47
|
+
const byteRate = sampleRate * blockAlign;
|
|
48
|
+
const totalSize = 44 + dataSize;
|
|
49
|
+
const buffer = new ArrayBuffer(totalSize);
|
|
50
|
+
const view = new DataView(buffer);
|
|
51
|
+
const writeAscii = (offset, s) => {
|
|
52
|
+
for (let i = 0; i < s.length; i++) view.setUint8(offset + i, s.charCodeAt(i));
|
|
53
|
+
};
|
|
54
|
+
writeAscii(0, "RIFF");
|
|
55
|
+
view.setUint32(4, totalSize - 8, true);
|
|
56
|
+
writeAscii(8, "WAVE");
|
|
57
|
+
writeAscii(12, "fmt ");
|
|
58
|
+
view.setUint32(16, 16, true);
|
|
59
|
+
view.setUint16(20, 1, true);
|
|
60
|
+
view.setUint16(22, channels, true);
|
|
61
|
+
view.setUint32(24, sampleRate, true);
|
|
62
|
+
view.setUint32(28, byteRate, true);
|
|
63
|
+
view.setUint16(32, blockAlign, true);
|
|
64
|
+
view.setUint16(34, bitsPerSample, true);
|
|
65
|
+
writeAscii(36, "data");
|
|
66
|
+
view.setUint32(40, dataSize, true);
|
|
67
|
+
new Uint8Array(buffer, 44, dataSize).set(pcm);
|
|
68
|
+
return new Uint8Array(buffer);
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* HTTP status → typed `AiError`. Shared between synthesizer and
|
|
72
|
+
* transcriber.
|
|
73
|
+
*/
|
|
74
|
+
const httpStatusError = (status, body) => Match.value(status).pipe(Match.when(429, () => new AiError.RateLimited({
|
|
75
|
+
provider: "gemini",
|
|
76
|
+
raw: body
|
|
77
|
+
})), Match.whenOr(408, 504, () => new AiError.Timeout({
|
|
78
|
+
provider: "gemini",
|
|
79
|
+
raw: body
|
|
80
|
+
})), Match.when(401, () => new AiError.AuthFailed({
|
|
81
|
+
provider: "gemini",
|
|
82
|
+
subtype: "auth",
|
|
83
|
+
raw: body
|
|
84
|
+
})), Match.when(403, () => new AiError.AuthFailed({
|
|
85
|
+
provider: "gemini",
|
|
86
|
+
subtype: "permission",
|
|
87
|
+
raw: body
|
|
88
|
+
})), Match.when(402, () => new AiError.AuthFailed({
|
|
89
|
+
provider: "gemini",
|
|
90
|
+
subtype: "billing",
|
|
91
|
+
raw: body
|
|
92
|
+
})), Match.when(413, () => new AiError.ContextLengthExceeded({
|
|
93
|
+
provider: "gemini",
|
|
94
|
+
raw: body
|
|
95
|
+
})), Match.when((n) => n >= 500, (n) => new AiError.Unavailable({
|
|
96
|
+
provider: "gemini",
|
|
97
|
+
status: n,
|
|
98
|
+
raw: body
|
|
99
|
+
})), Match.orElse(() => new AiError.InvalidRequest({
|
|
100
|
+
provider: "gemini",
|
|
101
|
+
raw: body
|
|
102
|
+
})));
|
|
103
|
+
const transportFailure = (cause) => new AiError.Unavailable({
|
|
104
|
+
provider: "gemini",
|
|
105
|
+
raw: cause
|
|
106
|
+
});
|
|
107
|
+
//#endregion
|
|
108
|
+
export { audioSourceToInlineData, decodeBase64Audio, httpStatusError, transportFailure, wrapPcmAsWav };
|
|
109
|
+
|
|
110
|
+
//# sourceMappingURL=geminiSpeechCodec.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geminiSpeechCodec.mjs","names":[],"sources":["../src/geminiSpeechCodec.ts"],"sourcesContent":["import { Effect, Encoding, Match, Result } from \"effect\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { AudioSource } from \"@effect-uai/core/Audio\"\n\n/**\n * Shared codec helpers for Gemini speech endpoints. The TTS and STT\n * variants both ride on `:generateContent`, so the audio-in / audio-out\n * shapes are siblings.\n */\n\nconst urlNotSupported: AiError.AiError = new AiError.InvalidRequest({\n provider: \"gemini\",\n param: \"audio\",\n raw: 'Gemini inline audio does not accept URL sources. Either fetch the URL yourself and pass `{ _tag: \"bytes\", bytes, mimeType }`, or upload via the Files API and pass a `fileData` URI (not yet wired here).',\n})\n\n/** Wire shape for an inline-audio part on `generateContent`. */\nexport type InlineAudioData = {\n readonly mimeType: string\n readonly data: string\n}\n\n/**\n * Encode any `AudioSource` into the `inlineData` shape expected by\n * Gemini's `generateContent`. URL variants are rejected with\n * `InvalidRequest` — Files API upload is a separate flow.\n *\n * Caller is responsible for keeping the total request body under\n * 20 MB. Beyond that, Gemini's Files API is the only path.\n */\nexport const audioSourceToInlineData: (\n audio: AudioSource,\n) => Effect.Effect<InlineAudioData, AiError.AiError> = Match.type<AudioSource>().pipe(\n Match.tag(\"bytes\", (a) =>\n Effect.succeed<InlineAudioData>({\n mimeType: a.mimeType,\n data: Encoding.encodeBase64(a.bytes),\n }),\n ),\n Match.tag(\"base64\", (a) =>\n Effect.succeed<InlineAudioData>({ mimeType: a.mimeType, data: a.base64 }),\n ),\n Match.tag(\"url\", () => Effect.fail(urlNotSupported)),\n Match.exhaustive,\n)\n\n/** Decode a base64-encoded audio payload into bytes. */\nexport const decodeBase64Audio = (b64: string): Effect.Effect<Uint8Array, AiError.AiError> =>\n Result.match(Encoding.decodeBase64(b64), {\n onSuccess: Effect.succeed,\n onFailure: (cause) =>\n Effect.fail(new AiError.InvalidRequest({ provider: \"gemini\", param: \"audio\", raw: cause })),\n })\n\n/**\n * Wrap a raw 16-bit signed little-endian PCM buffer in a standard 44-byte\n * RIFF/WAVE header. Gemini TTS returns PCM-only — for playback or for\n * round-tripping into Gemini transcription (which accepts wav, not raw\n * PCM), the bytes need a container.\n */\nexport const wrapPcmAsWav = (\n pcm: Uint8Array,\n sampleRate: number,\n channels: number,\n bitsPerSample = 16,\n): Uint8Array => {\n const dataSize = pcm.length\n const blockAlign = (channels * bitsPerSample) / 8\n const byteRate = sampleRate * blockAlign\n const totalSize = 44 + dataSize\n const buffer = new ArrayBuffer(totalSize)\n const view = new DataView(buffer)\n const writeAscii = (offset: number, s: string): void => {\n for (let i = 0; i < s.length; i++) view.setUint8(offset + i, s.charCodeAt(i))\n }\n writeAscii(0, \"RIFF\")\n view.setUint32(4, totalSize - 8, true)\n writeAscii(8, \"WAVE\")\n writeAscii(12, \"fmt \")\n view.setUint32(16, 16, true)\n view.setUint16(20, 1, true)\n view.setUint16(22, channels, true)\n view.setUint32(24, sampleRate, true)\n view.setUint32(28, byteRate, true)\n view.setUint16(32, blockAlign, true)\n view.setUint16(34, bitsPerSample, true)\n writeAscii(36, \"data\")\n view.setUint32(40, dataSize, true)\n new Uint8Array(buffer, 44, dataSize).set(pcm)\n return new Uint8Array(buffer)\n}\n\n/**\n * HTTP status → typed `AiError`. Shared between synthesizer and\n * transcriber.\n */\nexport const httpStatusError: (status: number, body: string) => AiError.AiError = (status, body) =>\n Match.value(status).pipe(\n Match.when(\n 429,\n (): AiError.AiError => new AiError.RateLimited({ provider: \"gemini\", raw: body }),\n ),\n Match.whenOr(\n 408,\n 504,\n (): AiError.AiError => new AiError.Timeout({ provider: \"gemini\", raw: body }),\n ),\n Match.when(\n 401,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"gemini\", subtype: \"auth\", raw: body }),\n ),\n Match.when(\n 403,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"gemini\", subtype: \"permission\", raw: body }),\n ),\n Match.when(\n 402,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"gemini\", subtype: \"billing\", raw: body }),\n ),\n Match.when(\n 413,\n (): AiError.AiError => new AiError.ContextLengthExceeded({ provider: \"gemini\", raw: body }),\n ),\n Match.when(\n (n) => n >= 500,\n (n): AiError.AiError => new AiError.Unavailable({ provider: \"gemini\", status: n, raw: body }),\n ),\n Match.orElse(\n (): AiError.AiError => new AiError.InvalidRequest({ provider: \"gemini\", raw: body }),\n ),\n )\n\nexport const transportFailure = (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider: \"gemini\", raw: cause })\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { i as it, n as globalExpect, r as describe } from "./dist-DMsg8YLb.mjs";
|
|
2
|
+
import { audioSourceToInlineData, wrapPcmAsWav } from "./geminiSpeechCodec.mjs";
|
|
3
|
+
import { Effect, Encoding } from "effect";
|
|
4
|
+
//#region src/geminiSpeechCodec.test.ts
|
|
5
|
+
describe("audioSourceToInlineData", () => {
|
|
6
|
+
it("encodes a `bytes` source to base64 with its MIME type", async () => {
|
|
7
|
+
const original = new Uint8Array([
|
|
8
|
+
222,
|
|
9
|
+
173,
|
|
10
|
+
190,
|
|
11
|
+
239
|
|
12
|
+
]);
|
|
13
|
+
const source = {
|
|
14
|
+
_tag: "bytes",
|
|
15
|
+
bytes: original,
|
|
16
|
+
mimeType: "audio/wav"
|
|
17
|
+
};
|
|
18
|
+
const inline = await Effect.runPromise(audioSourceToInlineData(source));
|
|
19
|
+
globalExpect(inline.mimeType).toBe("audio/wav");
|
|
20
|
+
globalExpect(inline.data).toBe(Encoding.encodeBase64(original));
|
|
21
|
+
});
|
|
22
|
+
it("passes a `base64` source through unchanged", async () => {
|
|
23
|
+
const b64 = Encoding.encodeBase64(new Uint8Array([
|
|
24
|
+
1,
|
|
25
|
+
2,
|
|
26
|
+
3
|
|
27
|
+
]));
|
|
28
|
+
const source = {
|
|
29
|
+
_tag: "base64",
|
|
30
|
+
base64: b64,
|
|
31
|
+
mimeType: "audio/mp3"
|
|
32
|
+
};
|
|
33
|
+
const inline = await Effect.runPromise(audioSourceToInlineData(source));
|
|
34
|
+
globalExpect(inline.mimeType).toBe("audio/mp3");
|
|
35
|
+
globalExpect(inline.data).toBe(b64);
|
|
36
|
+
});
|
|
37
|
+
it("fails InvalidRequest for a URL source", async () => {
|
|
38
|
+
const exit = await Effect.runPromiseExit(audioSourceToInlineData({
|
|
39
|
+
_tag: "url",
|
|
40
|
+
url: "https://x/y.mp3",
|
|
41
|
+
mimeType: "audio/mp3"
|
|
42
|
+
}));
|
|
43
|
+
globalExpect(exit._tag).toBe("Failure");
|
|
44
|
+
if (exit._tag === "Failure") {
|
|
45
|
+
globalExpect(JSON.stringify(exit.cause)).toContain("InvalidRequest");
|
|
46
|
+
globalExpect(JSON.stringify(exit.cause)).toContain("Files API");
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
describe("wrapPcmAsWav", () => {
|
|
51
|
+
it("produces a 44-byte RIFF/WAVE header for 16-bit mono PCM at 24 kHz", () => {
|
|
52
|
+
const pcm = new Uint8Array([
|
|
53
|
+
0,
|
|
54
|
+
1,
|
|
55
|
+
0,
|
|
56
|
+
2
|
|
57
|
+
]);
|
|
58
|
+
const wav = wrapPcmAsWav(pcm, 24e3, 1, 16);
|
|
59
|
+
globalExpect(wav.length).toBe(44 + pcm.length);
|
|
60
|
+
const ascii = (start, len) => String.fromCharCode(...Array.from(wav.slice(start, start + len)));
|
|
61
|
+
globalExpect(ascii(0, 4)).toBe("RIFF");
|
|
62
|
+
globalExpect(ascii(8, 4)).toBe("WAVE");
|
|
63
|
+
globalExpect(ascii(12, 4)).toBe("fmt ");
|
|
64
|
+
globalExpect(ascii(36, 4)).toBe("data");
|
|
65
|
+
const view = new DataView(wav.buffer);
|
|
66
|
+
globalExpect(view.getUint32(4, true)).toBe(wav.length - 8);
|
|
67
|
+
globalExpect(view.getUint16(20, true)).toBe(1);
|
|
68
|
+
globalExpect(view.getUint16(22, true)).toBe(1);
|
|
69
|
+
globalExpect(view.getUint32(24, true)).toBe(24e3);
|
|
70
|
+
globalExpect(view.getUint16(34, true)).toBe(16);
|
|
71
|
+
globalExpect(view.getUint32(40, true)).toBe(pcm.length);
|
|
72
|
+
globalExpect(Array.from(wav.slice(44))).toEqual(Array.from(pcm));
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
//#endregion
|
|
76
|
+
export {};
|
|
77
|
+
|
|
78
|
+
//# sourceMappingURL=geminiSpeechCodec.test.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geminiSpeechCodec.test.mjs","names":[],"sources":["../src/geminiSpeechCodec.test.ts"],"sourcesContent":["import { Effect, Encoding } from \"effect\"\nimport { describe, expect, it } from \"vitest\"\nimport type { AudioSource } from \"@effect-uai/core/Audio\"\nimport { audioSourceToInlineData, wrapPcmAsWav } from \"./geminiSpeechCodec.js\"\n\ndescribe(\"audioSourceToInlineData\", () => {\n it(\"encodes a `bytes` source to base64 with its MIME type\", async () => {\n const original = new Uint8Array([0xde, 0xad, 0xbe, 0xef])\n const source: AudioSource = { _tag: \"bytes\", bytes: original, mimeType: \"audio/wav\" }\n const inline = await Effect.runPromise(audioSourceToInlineData(source))\n expect(inline.mimeType).toBe(\"audio/wav\")\n expect(inline.data).toBe(Encoding.encodeBase64(original))\n })\n\n it(\"passes a `base64` source through unchanged\", async () => {\n const b64 = Encoding.encodeBase64(new Uint8Array([1, 2, 3]))\n const source: AudioSource = { _tag: \"base64\", base64: b64, mimeType: \"audio/mp3\" }\n const inline = await Effect.runPromise(audioSourceToInlineData(source))\n expect(inline.mimeType).toBe(\"audio/mp3\")\n expect(inline.data).toBe(b64)\n })\n\n it(\"fails InvalidRequest for a URL source\", async () => {\n const source: AudioSource = { _tag: \"url\", url: \"https://x/y.mp3\", mimeType: \"audio/mp3\" }\n const exit = await Effect.runPromiseExit(audioSourceToInlineData(source))\n expect(exit._tag).toBe(\"Failure\")\n if (exit._tag === \"Failure\") {\n expect(JSON.stringify(exit.cause)).toContain(\"InvalidRequest\")\n expect(JSON.stringify(exit.cause)).toContain(\"Files API\")\n }\n })\n})\n\ndescribe(\"wrapPcmAsWav\", () => {\n it(\"produces a 44-byte RIFF/WAVE header for 16-bit mono PCM at 24 kHz\", () => {\n const pcm = new Uint8Array([0x00, 0x01, 0x00, 0x02])\n const wav = wrapPcmAsWav(pcm, 24000, 1, 16)\n expect(wav.length).toBe(44 + pcm.length)\n const ascii = (start: number, len: number) =>\n String.fromCharCode(...Array.from(wav.slice(start, start + len)))\n expect(ascii(0, 4)).toBe(\"RIFF\")\n expect(ascii(8, 4)).toBe(\"WAVE\")\n expect(ascii(12, 4)).toBe(\"fmt \")\n expect(ascii(36, 4)).toBe(\"data\")\n // RIFF chunk size = total - 8\n const view = new DataView(wav.buffer)\n expect(view.getUint32(4, true)).toBe(wav.length - 8)\n expect(view.getUint16(20, true)).toBe(1) // PCM\n expect(view.getUint16(22, true)).toBe(1) // mono\n expect(view.getUint32(24, true)).toBe(24000) // sample rate\n expect(view.getUint16(34, true)).toBe(16) // bits per sample\n expect(view.getUint32(40, true)).toBe(pcm.length) // data size\n // Payload preserved\n expect(Array.from(wav.slice(44))).toEqual(Array.from(pcm))\n })\n})\n"],"mappings":";;;;AAKA,SAAS,iCAAiC;AACxC,IAAG,yDAAyD,YAAY;EACtE,MAAM,WAAW,IAAI,WAAW;GAAC;GAAM;GAAM;GAAM;GAAK,CAAC;EACzD,MAAM,SAAsB;GAAE,MAAM;GAAS,OAAO;GAAU,UAAU;GAAa;EACrF,MAAM,SAAS,MAAM,OAAO,WAAW,wBAAwB,OAAO,CAAC;AACvE,eAAO,OAAO,SAAS,CAAC,KAAK,YAAY;AACzC,eAAO,OAAO,KAAK,CAAC,KAAK,SAAS,aAAa,SAAS,CAAC;GACzD;AAEF,IAAG,8CAA8C,YAAY;EAC3D,MAAM,MAAM,SAAS,aAAa,IAAI,WAAW;GAAC;GAAG;GAAG;GAAE,CAAC,CAAC;EAC5D,MAAM,SAAsB;GAAE,MAAM;GAAU,QAAQ;GAAK,UAAU;GAAa;EAClF,MAAM,SAAS,MAAM,OAAO,WAAW,wBAAwB,OAAO,CAAC;AACvE,eAAO,OAAO,SAAS,CAAC,KAAK,YAAY;AACzC,eAAO,OAAO,KAAK,CAAC,KAAK,IAAI;GAC7B;AAEF,IAAG,yCAAyC,YAAY;EAEtD,MAAM,OAAO,MAAM,OAAO,eAAe,wBAAwB;GADnC,MAAM;GAAO,KAAK;GAAmB,UAAU;GACN,CAAC,CAAC;AACzE,eAAO,KAAK,KAAK,CAAC,KAAK,UAAU;AACjC,MAAI,KAAK,SAAS,WAAW;AAC3B,gBAAO,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC,UAAU,iBAAiB;AAC9D,gBAAO,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC,UAAU,YAAY;;GAE3D;EACF;AAEF,SAAS,sBAAsB;AAC7B,IAAG,2EAA2E;EAC5E,MAAM,MAAM,IAAI,WAAW;GAAC;GAAM;GAAM;GAAM;GAAK,CAAC;EACpD,MAAM,MAAM,aAAa,KAAK,MAAO,GAAG,GAAG;AAC3C,eAAO,IAAI,OAAO,CAAC,KAAK,KAAK,IAAI,OAAO;EACxC,MAAM,SAAS,OAAe,QAC5B,OAAO,aAAa,GAAG,MAAM,KAAK,IAAI,MAAM,OAAO,QAAQ,IAAI,CAAC,CAAC;AACnE,eAAO,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,OAAO;AAChC,eAAO,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,OAAO;AAChC,eAAO,MAAM,IAAI,EAAE,CAAC,CAAC,KAAK,OAAO;AACjC,eAAO,MAAM,IAAI,EAAE,CAAC,CAAC,KAAK,OAAO;EAEjC,MAAM,OAAO,IAAI,SAAS,IAAI,OAAO;AACrC,eAAO,KAAK,UAAU,GAAG,KAAK,CAAC,CAAC,KAAK,IAAI,SAAS,EAAE;AACpD,eAAO,KAAK,UAAU,IAAI,KAAK,CAAC,CAAC,KAAK,EAAE;AACxC,eAAO,KAAK,UAAU,IAAI,KAAK,CAAC,CAAC,KAAK,EAAE;AACxC,eAAO,KAAK,UAAU,IAAI,KAAK,CAAC,CAAC,KAAK,KAAM;AAC5C,eAAO,KAAK,UAAU,IAAI,KAAK,CAAC,CAAC,KAAK,GAAG;AACzC,eAAO,KAAK,UAAU,IAAI,KAAK,CAAC,CAAC,KAAK,IAAI,OAAO;AAEjD,eAAO,MAAM,KAAK,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC,QAAQ,MAAM,KAAK,IAAI,CAAC;GAC1D;EACF"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { t as codec_d_exports } from "./codec.mjs";
|
|
2
|
-
import { GoogleEmbeddingModel, GoogleModel } from "./models.mjs";
|
|
2
|
+
import { GeminiSttModel, GeminiTtsModel, GeminiVoiceName, GoogleEmbeddingModel, GoogleModel, LyriaModel } from "./models.mjs";
|
|
3
3
|
import { t as Gemini_d_exports } from "./Gemini.mjs";
|
|
4
4
|
import { t as GeminiEmbedding_d_exports } from "./GeminiEmbedding.mjs";
|
|
5
|
-
|
|
5
|
+
import { t as GeminiSynthesizer_d_exports } from "./GeminiSynthesizer.mjs";
|
|
6
|
+
import { t as GeminiTranscriber_d_exports } from "./GeminiTranscriber.mjs";
|
|
7
|
+
import { t as LyriaGenerator_d_exports } from "./LyriaGenerator.mjs";
|
|
8
|
+
export { Gemini_d_exports as Gemini, GeminiEmbedding_d_exports as GeminiEmbedding, GeminiSttModel, GeminiSynthesizer_d_exports as GeminiSynthesizer, GeminiTranscriber_d_exports as GeminiTranscriber, GeminiTtsModel, GeminiVoiceName, GoogleEmbeddingModel, GoogleModel, LyriaGenerator_d_exports as LyriaGenerator, LyriaModel, codec_d_exports as codec };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { t as codec_exports } from "./codec.mjs";
|
|
2
2
|
import { t as Gemini_exports } from "./Gemini.mjs";
|
|
3
3
|
import { t as GeminiEmbedding_exports } from "./GeminiEmbedding.mjs";
|
|
4
|
+
import { t as GeminiSynthesizer_exports } from "./GeminiSynthesizer.mjs";
|
|
5
|
+
import { t as GeminiTranscriber_exports } from "./GeminiTranscriber.mjs";
|
|
6
|
+
import { t as LyriaGenerator_exports } from "./LyriaGenerator.mjs";
|
|
4
7
|
import "./models.mjs";
|
|
5
|
-
export { Gemini_exports as Gemini, GeminiEmbedding_exports as GeminiEmbedding, codec_exports as codec };
|
|
8
|
+
export { Gemini_exports as Gemini, GeminiEmbedding_exports as GeminiEmbedding, GeminiSynthesizer_exports as GeminiSynthesizer, GeminiTranscriber_exports as GeminiTranscriber, LyriaGenerator_exports as LyriaGenerator, codec_exports as codec };
|