@effect-uai/google 0.16.0 → 0.18.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 +3 -3
- package/dist/Gemini.d.mts +1 -1
- package/dist/Gemini.mjs +1 -1
- package/dist/Gemini.mjs.map +1 -1
- package/dist/GeminiEmbedding.d.mts +1 -1
- package/dist/GeminiEmbedding.d.mts.map +1 -1
- package/dist/GeminiEmbedding.mjs +4 -3
- package/dist/GeminiEmbedding.mjs.map +1 -1
- package/dist/GeminiImageGenerator.d.mts +1 -1
- package/dist/GeminiImageGenerator.d.mts.map +1 -1
- package/dist/GeminiImageGenerator.mjs +4 -3
- package/dist/GeminiImageGenerator.mjs.map +1 -1
- package/dist/GeminiLiveSession.d.mts +1 -1
- package/dist/GeminiSynthesizer.d.mts +1 -1
- package/dist/GeminiSynthesizer.mjs +1 -1
- package/dist/GeminiSynthesizer.mjs.map +1 -1
- package/dist/GeminiSynthesizer.test.mjs +1 -1
- package/dist/GoogleDeepResearch.d.mts +1 -1
- package/dist/GoogleDeepResearch.mjs +1 -1
- package/dist/GoogleDeepResearch.mjs.map +1 -1
- package/dist/GoogleDeepResearch.test.mjs +1 -1
- package/dist/LyriaGenerator.d.mts +1 -1
- package/dist/LyriaGenerator.d.mts.map +1 -1
- package/dist/LyriaGenerator.mjs +4 -3
- package/dist/LyriaGenerator.mjs.map +1 -1
- package/dist/LyriaGenerator.test.mjs +4 -142
- package/dist/LyriaGenerator.test.mjs.map +1 -1
- package/dist/codec.d.mts.map +1 -1
- package/dist/codec.mjs +3 -2
- package/dist/codec.mjs.map +1 -1
- package/dist/dist-DHer1cql.mjs +1533 -0
- package/dist/dist-DHer1cql.mjs.map +1 -0
- package/dist/geminiSpeechCodec.d.mts.map +1 -1
- package/dist/geminiSpeechCodec.mjs +4 -3
- package/dist/geminiSpeechCodec.mjs.map +1 -1
- package/dist/geminiSpeechCodec.test.mjs +5 -4
- package/dist/geminiSpeechCodec.test.mjs.map +1 -1
- package/dist/geminiTools.test.mjs +1 -1
- package/dist/{test.DNmyFkvJ-BPz3G1HS.mjs → index.C4tKZ0yW-DjxCdpW_.mjs} +9115 -5993
- package/dist/index.C4tKZ0yW-DjxCdpW_.mjs.map +1 -0
- package/dist/{realtimeSession-7HSIjWh4.d.mts → realtimeSession-BlBgLDdp.d.mts} +2 -2
- package/dist/{realtimeSession-7HSIjWh4.d.mts.map → realtimeSession-BlBgLDdp.d.mts.map} +1 -1
- package/dist/realtimeSession.d.mts +1 -1
- package/dist/realtimeSession.mjs +6 -5
- package/dist/realtimeSession.mjs.map +1 -1
- package/dist/realtimeSession.test.mjs +69 -78
- package/dist/realtimeSession.test.mjs.map +1 -1
- package/package.json +4 -4
- package/src/Gemini.ts +1 -1
- package/src/GeminiEmbedding.ts +4 -3
- package/src/GeminiImageGenerator.ts +4 -3
- package/src/GeminiSynthesizer.ts +1 -1
- package/src/GoogleDeepResearch.ts +1 -1
- package/src/LyriaGenerator.test.ts +1 -1
- package/src/LyriaGenerator.ts +4 -14
- package/src/codec.ts +3 -2
- package/src/geminiSpeechCodec.test.ts +4 -3
- package/src/geminiSpeechCodec.ts +4 -3
- package/src/realtimeSession.test.ts +4 -3
- package/src/realtimeSession.ts +5 -5
- package/dist/magic-string.es-CUAnjKYC.mjs +0 -1014
- package/dist/magic-string.es-CUAnjKYC.mjs.map +0 -1
- package/dist/test.DNmyFkvJ-BPz3G1HS.mjs.map +0 -1
|
@@ -2,7 +2,7 @@ import { n as GeminiLiveModel } from "./models-BVSKJwCx.mjs";
|
|
|
2
2
|
import { Effect, Redacted, Scope } from "effect";
|
|
3
3
|
import * as AiError from "@effect-uai/core/AiError";
|
|
4
4
|
import { RealtimeSessionHandle } from "@effect-uai/core/RealtimeSession";
|
|
5
|
-
import * as Socket from "effect/
|
|
5
|
+
import * as Socket from "effect/socket/Socket";
|
|
6
6
|
import { CommonSessionRequest } from "@effect-uai/core/Realtime";
|
|
7
7
|
//#region src/realtimeSession.d.ts
|
|
8
8
|
type Config = {
|
|
@@ -59,4 +59,4 @@ declare const buildWsUrl: (cfg: Config) => string;
|
|
|
59
59
|
declare const openSession: (cfg: Config) => (request: GeminiLiveRequest) => Effect.Effect<RealtimeSessionHandle, AiError.AiError, Scope.Scope>;
|
|
60
60
|
//#endregion
|
|
61
61
|
export { openSession as i, GeminiLiveRequest as n, buildWsUrl as r, Config as t };
|
|
62
|
-
//# sourceMappingURL=realtimeSession-
|
|
62
|
+
//# sourceMappingURL=realtimeSession-BlBgLDdp.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtimeSession-
|
|
1
|
+
{"version":3,"file":"realtimeSession-BlBgLDdp.d.mts","names":[],"sources":["../src/realtimeSession.ts"],"mappings":";;;;;;;KA8CY;WACD,QAAQ,SAAS;WACjB;;WAEA,YAAY,OAAO;;;;;;;KAQlB,oBAAoB,KAAK;WAC1B,OAAO;;WAEP;aACE;aACA;aACA;aACA;;;WAGF;;;;;;;;;WASA;WACA;;WAEA;WACA;WACA;WACA;WACA;;WAEA;aACE;aACA;;;WAGF;;WAEA;;WAEA;;cAilBE,aAAU,KAAS;cAqCnB,cAAW,KAChB,YAAM,SAED,sBACR,OAAO,OAAO,uBAAuB,QAAQ,SAAS,MAAM"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as openSession, n as GeminiLiveRequest, r as buildWsUrl, t as Config } from "./realtimeSession-
|
|
1
|
+
import { i as openSession, n as GeminiLiveRequest, r as buildWsUrl, t as Config } from "./realtimeSession-BlBgLDdp.mjs";
|
|
2
2
|
export { Config, GeminiLiveRequest, buildWsUrl, openSession };
|
package/dist/realtimeSession.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { parsedResponse, toolDescriptorsToTools } from "./codec.mjs";
|
|
2
|
-
import { Cause, Duration, Effect,
|
|
2
|
+
import { Cause, Duration, Effect, Match, Pull, Queue, Redacted, Ref, Result, Schema, Stream } from "effect";
|
|
3
3
|
import * as AiError from "@effect-uai/core/AiError";
|
|
4
|
+
import { Base64 } from "effect/encoding";
|
|
4
5
|
import * as Capabilities from "@effect-uai/core/Capabilities";
|
|
5
|
-
import * as Socket from "effect/
|
|
6
|
+
import * as Socket from "effect/socket/Socket";
|
|
6
7
|
import { RealtimeEvent } from "@effect-uai/core/Realtime";
|
|
7
8
|
import * as WebSocketSession from "@effect-uai/core/WebSocketSession";
|
|
8
9
|
//#region src/realtimeSession.ts
|
|
@@ -178,7 +179,7 @@ const usageOf = (metadata) => ({
|
|
|
178
179
|
...metadata.responseTokenCount !== void 0 && { output_tokens: metadata.responseTokenCount },
|
|
179
180
|
...metadata.totalTokenCount !== void 0 && { total_tokens: metadata.totalTokenCount }
|
|
180
181
|
});
|
|
181
|
-
const decodeAudio = (b64) => Result.match(
|
|
182
|
+
const decodeAudio = (b64) => Result.match(Base64.decode(b64), {
|
|
182
183
|
onSuccess: Effect.succeed,
|
|
183
184
|
onFailure: (cause) => Effect.fail(new AiError.GenerationFailed({
|
|
184
185
|
provider: PROVIDER,
|
|
@@ -303,7 +304,7 @@ const videoPart = (frame) => Match.value(frame).pipe(Match.tag("base64", (s) =>
|
|
|
303
304
|
data: s.base64,
|
|
304
305
|
mimeType: s.mimeType
|
|
305
306
|
})), Match.tag("bytes", (s) => Effect.succeed({
|
|
306
|
-
data:
|
|
307
|
+
data: Base64.encode(s.bytes),
|
|
307
308
|
mimeType: s.mimeType
|
|
308
309
|
})), Match.tag("url", () => Effect.fail(new AiError.Unsupported({
|
|
309
310
|
provider: PROVIDER,
|
|
@@ -312,7 +313,7 @@ const videoPart = (frame) => Match.value(frame).pipe(Match.tag("base64", (s) =>
|
|
|
312
313
|
}))), Match.exhaustive);
|
|
313
314
|
const schedulingOf = (request) => Match.value(request.toolScheduling ?? "when-idle").pipe(Match.when("when-idle", () => "WHEN_IDLE"), Match.when("interrupt", () => "INTERRUPT"), Match.when("silent", () => "SILENT"), Match.exhaustive);
|
|
314
315
|
const handleInput = (send, state, options, input) => Match.value(input).pipe(Match.tag("Audio", (i) => send(JSON.stringify({ realtimeInput: { audio: {
|
|
315
|
-
data:
|
|
316
|
+
data: Base64.encode(i.bytes),
|
|
316
317
|
mimeType: `audio/pcm;rate=${options.rate}`
|
|
317
318
|
} } }))), Match.tag("Text", (i) => i.role === "system" ? Effect.fail(new AiError.Unsupported({
|
|
318
319
|
provider: PROVIDER,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtimeSession.mjs","names":[],"sources":["../src/realtimeSession.ts"],"sourcesContent":["/**\n * Gemini Live speech-to-speech over `BidiGenerateContent`.\n *\n * Auth is a query parameter, so this needs no `ws` package and no headers:\n * the platform `WebSocket` reaches it from Node, Bun, Deno and the browser.\n *\n * Two shapes differ from OpenAI Realtime and drive most of this file. There\n * are no response ids on the wire, so a turn id is minted here and every event\n * of that turn carries it; and there is no truncate op, so a\n * `PlaybackPosition` has nothing to send.\n */\nimport {\n Cause,\n Duration,\n Effect,\n Encoding,\n Match,\n Pull,\n Queue,\n Redacted,\n Ref,\n Result,\n Schema,\n type Scope,\n Stream,\n} from \"effect\"\nimport * as Socket from \"effect/unstable/socket/Socket\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { AudioFormat } from \"@effect-uai/core/Audio\"\nimport * as Capabilities from \"@effect-uai/core/Capabilities\"\nimport type { HistoryItem, Usage } from \"@effect-uai/core/Items\"\nimport {\n type CommonSessionRequest,\n RealtimeEvent,\n type RealtimeInput,\n} from \"@effect-uai/core/Realtime\"\nimport type { RealtimeSessionHandle } from \"@effect-uai/core/RealtimeSession\"\nimport * as WebSocketSession from \"@effect-uai/core/WebSocketSession\"\nimport { parsedResponse, toolDescriptorsToTools } from \"./codec.js\"\nimport type { GeminiLiveModel } from \"./models.js\"\n\nconst PROVIDER = \"gemini-live\"\n\nconst DEFAULT_BASE_URL = \"wss://generativelanguage.googleapis.com\"\nconst RPC = \"ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent\"\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n /** Build the socket yourself, for a proxy or an in-memory transport. */\n readonly webSocket?: Socket.WebSocketConstructor[\"Service\"]\n}\n\n/**\n * Gemini-typed session request. Everything past `CommonSessionRequest` is\n * provider-shaped. The configuration is immutable for the connection: changing\n * any of it means opening a new session.\n */\nexport type GeminiLiveRequest = Omit<CommonSessionRequest, \"model\"> & {\n readonly model: GeminiLiveModel\n /** Replaces the plain `turnDetection` switch when you need the knobs. */\n readonly vad?: {\n readonly startSensitivity?: \"high\" | \"low\"\n readonly endSensitivity?: \"high\" | \"low\"\n readonly prefixPaddingMs?: number\n readonly silenceDurationMs?: number\n }\n /** `\"none\"` turns barge-in off. Defaults to interrupting. */\n readonly activityHandling?: \"interrupt\" | \"none\"\n /**\n * When a tool result reaches the model, for the models that keep generating\n * while a tool runs. Defaults to `\"when-idle\"`: the server's own default is\n * to interrupt, which cuts off whatever the model was saying while it\n * waited, including the \"let me look that up\" it just spoke. `\"interrupt\"`\n * is right when the result cannot wait, `\"silent\"` when it is context the\n * model should have rather than something to announce.\n */\n readonly toolScheduling?: \"when-idle\" | \"interrupt\" | \"silent\"\n readonly turnCoverage?: \"activity\" | \"all\" | \"audioActivityAndAllVideo\"\n /** 3.1 only. Higher levels delay the first audio. */\n readonly thinkingLevel?: \"minimal\" | \"low\" | \"medium\" | \"high\"\n readonly mediaResolution?: \"low\" | \"medium\" | \"high\"\n readonly languageCode?: string\n readonly temperature?: number\n readonly maxOutputTokens?: number\n /** What turns an otherwise time-boxed session into an unbounded one. */\n readonly contextCompression?: {\n readonly triggerTokens?: number\n readonly targetTokens?: number\n }\n /** Grounding. Cannot be combined with `tools`; the pair is a 400. */\n readonly googleSearch?: boolean\n /** 2.5 only. */\n readonly proactiveAudio?: boolean\n /** 2.5 only. */\n readonly affectiveDialog?: boolean\n}\n\n// ---------------------------------------------------------------------------\n// AudioFormat -> wire\n// ---------------------------------------------------------------------------\n\n/** Output is 24 kHz whatever the input rate, and cannot be configured. */\nconst OUTPUT_RATE = 24000\n\nconst unsupportedFormat = (format: AudioFormat, field: string, expected: string) =>\n new AiError.Unsupported({\n provider: PROVIDER,\n capability: field,\n reason: `Gemini Live accepts ${expected}. Got ${JSON.stringify(format)}.`,\n })\n\n/** Any rate is accepted on input; 16 kHz is what the model works in. */\nconst inputRate = (format: AudioFormat): Effect.Effect<number, AiError.AiError> =>\n Match.value(format).pipe(\n Match.when({ container: \"raw\", encoding: \"pcm_s16le\", channels: 1 }, (f) =>\n Effect.succeed(f.sampleRate),\n ),\n Match.orElse((f) =>\n Effect.fail(unsupportedFormat(f, \"inputFormat\", \"mono pcm_s16le at any rate\")),\n ),\n )\n\nconst checkOutputFormat = (format: AudioFormat): Effect.Effect<void, AiError.AiError> =>\n Match.value(format).pipe(\n Match.when(\n { container: \"raw\", encoding: \"pcm_s16le\", sampleRate: OUTPUT_RATE, channels: 1 },\n () => Effect.void,\n ),\n Match.orElse((f) =>\n Effect.fail(unsupportedFormat(f, \"outputFormat\", `mono pcm_s16le at ${OUTPUT_RATE}`)),\n ),\n )\n\n// ---------------------------------------------------------------------------\n// Client frames\n// ---------------------------------------------------------------------------\n\nconst SENSITIVITY = { high: \"HIGH\", low: \"LOW\" } as const\n\nconst TURN_COVERAGE = {\n activity: \"TURN_INCLUDES_ONLY_ACTIVITY\",\n all: \"TURN_INCLUDES_ALL_INPUT\",\n audioActivityAndAllVideo: \"TURN_INCLUDES_AUDIO_ACTIVITY_AND_ALL_VIDEO\",\n} as const\n\nconst realtimeInputConfig = (request: GeminiLiveRequest) => ({\n automaticActivityDetection: {\n ...(request.turnDetection === \"manual\" && { disabled: true }),\n ...(request.vad?.startSensitivity !== undefined && {\n startOfSpeechSensitivity: `START_SENSITIVITY_${SENSITIVITY[request.vad.startSensitivity]}`,\n }),\n ...(request.vad?.endSensitivity !== undefined && {\n endOfSpeechSensitivity: `END_SENSITIVITY_${SENSITIVITY[request.vad.endSensitivity]}`,\n }),\n ...(request.vad?.prefixPaddingMs !== undefined && {\n prefixPaddingMs: request.vad.prefixPaddingMs,\n }),\n ...(request.vad?.silenceDurationMs !== undefined && {\n silenceDurationMs: request.vad.silenceDurationMs,\n }),\n },\n ...(request.activityHandling !== undefined && {\n activityHandling:\n request.activityHandling === \"none\" ? \"NO_INTERRUPTION\" : \"START_OF_ACTIVITY_INTERRUPTS\",\n }),\n ...(request.turnCoverage !== undefined && { turnCoverage: TURN_COVERAGE[request.turnCoverage] }),\n})\n\nconst generationConfig = (request: GeminiLiveRequest) => ({\n responseModalities: [\"AUDIO\"],\n ...(request.voiceId !== undefined && {\n speechConfig: {\n voiceConfig: { prebuiltVoiceConfig: { voiceName: request.voiceId } },\n ...(request.languageCode !== undefined && { languageCode: request.languageCode }),\n },\n }),\n ...(request.temperature !== undefined && { temperature: request.temperature }),\n ...(request.maxOutputTokens !== undefined && { maxOutputTokens: request.maxOutputTokens }),\n ...(request.mediaResolution !== undefined && {\n mediaResolution: `MEDIA_RESOLUTION_${request.mediaResolution.toUpperCase()}`,\n }),\n ...(request.thinkingLevel !== undefined && {\n thinkingConfig: { thinkingLevel: request.thinkingLevel.toUpperCase() },\n }),\n ...(request.affectiveDialog === true && { enableAffectiveDialog: true }),\n})\n\nconst toolsOf = (request: GeminiLiveRequest) =>\n request.googleSearch === true\n ? [{ googleSearch: {} }]\n : toolDescriptorsToTools(request.tools ?? [])\n\nconst setupFrame = (request: GeminiLiveRequest): string => {\n const tools = toolsOf(request)\n return JSON.stringify({\n setup: {\n model: `models/${request.model}`,\n generationConfig: generationConfig(request),\n ...(request.instructions !== undefined && {\n systemInstruction: { parts: [{ text: request.instructions }] },\n }),\n ...(tools.length > 0 && { tools }),\n realtimeInputConfig: realtimeInputConfig(request),\n ...(request.transcribeInput !== false && { inputAudioTranscription: {} }),\n // The only way to text on a native-audio model.\n outputAudioTranscription: {},\n // Always on, so a handle exists before the ten-minute socket ends.\n sessionResumption: request.resume === undefined ? {} : { handle: request.resume },\n ...(request.contextCompression !== undefined && {\n contextWindowCompression: {\n slidingWindow: {\n ...(request.contextCompression.targetTokens !== undefined && {\n targetTokens: request.contextCompression.targetTokens,\n }),\n },\n ...(request.contextCompression.triggerTokens !== undefined && {\n triggerTokens: request.contextCompression.triggerTokens,\n }),\n },\n }),\n ...(request.proactiveAudio === true && { proactivity: { proactiveAudio: true } }),\n // Seeded history arrives as `clientContent` right after the handshake.\n ...((request.history?.length ?? 0) > 0 && {\n historyConfig: { initialHistoryInClientContent: true },\n }),\n },\n })\n}\n\nconst messageText = (item: HistoryItem): string =>\n item.type !== \"message\"\n ? \"\"\n : item.content\n .map((block) =>\n block.type === \"input_text\" || block.type === \"output_text\" ? block.text : \"\",\n )\n .join(\"\")\n\n/**\n * Text-only seed, which is all `history` promises across providers. Anything\n * with no text of its own, a tool call or its output, has no place in the seed\n * and is reported once per kind rather than vanishing.\n */\nconst historyFrame = (\n history: ReadonlyArray<HistoryItem>,\n): Effect.Effect<string, AiError.AiError> =>\n Effect.gen(function* () {\n const kinds = new Set(history.filter((i) => messageText(i).length === 0).map((i) => i.type))\n yield* Effect.forEach(\n kinds,\n (kind) =>\n Capabilities.warnDropped({\n provider: PROVIDER,\n capability: \"history\",\n field: \"history\",\n value: kind,\n reason: `Gemini Live seeds a session with text turns only, so \\`${kind}\\` items are left out.`,\n }),\n { discard: true },\n )\n return JSON.stringify({\n clientContent: {\n turns: history\n .filter((item) => messageText(item).length > 0)\n .map((item) => ({\n role: item.type === \"message\" && item.role === \"assistant\" ? \"model\" : \"user\",\n parts: [{ text: messageText(item) }],\n })),\n turnComplete: true,\n },\n })\n })\n\n// ---------------------------------------------------------------------------\n// Server frames\n//\n// Gemini has no discriminator field: one message carries exactly one of these\n// keys, and `serverContent` can carry several of its own at once.\n// ---------------------------------------------------------------------------\n\nconst Part = Schema.Struct({\n text: Schema.optional(Schema.String),\n thought: Schema.optional(Schema.Boolean),\n inlineData: Schema.optional(\n Schema.Struct({\n mimeType: Schema.optional(Schema.String),\n data: Schema.optional(Schema.String),\n }),\n ),\n})\n\nconst Transcription = Schema.Struct({ text: Schema.optional(Schema.String) })\n\nconst ServerContent = Schema.Struct({\n modelTurn: Schema.optional(Schema.Struct({ parts: Schema.optional(Schema.Array(Part)) })),\n turnComplete: Schema.optional(Schema.Boolean),\n generationComplete: Schema.optional(Schema.Boolean),\n interrupted: Schema.optional(Schema.Boolean),\n inputTranscription: Schema.optional(Transcription),\n interimInputTranscription: Schema.optional(Transcription),\n outputTranscription: Schema.optional(Transcription),\n})\n\nconst UsageMetadata = Schema.Struct({\n promptTokenCount: Schema.optional(Schema.Number),\n responseTokenCount: Schema.optional(Schema.Number),\n totalTokenCount: Schema.optional(Schema.Number),\n})\n\nconst FunctionCall = Schema.Struct({\n id: Schema.optional(Schema.String),\n name: Schema.String,\n args: Schema.optional(Schema.Unknown),\n})\n\nconst ServerMessage = Schema.Struct({\n setupComplete: Schema.optional(Schema.Unknown),\n serverContent: Schema.optional(ServerContent),\n toolCall: Schema.optional(\n Schema.Struct({ functionCalls: Schema.optional(Schema.Array(FunctionCall)) }),\n ),\n toolCallCancellation: Schema.optional(\n Schema.Struct({ ids: Schema.optional(Schema.Array(Schema.String)) }),\n ),\n goAway: Schema.optional(Schema.Struct({ timeLeft: Schema.optional(Schema.String) })),\n sessionResumptionUpdate: Schema.optional(\n Schema.Struct({\n newHandle: Schema.optional(Schema.String),\n resumable: Schema.optional(Schema.Boolean),\n }),\n ),\n usageMetadata: Schema.optional(UsageMetadata),\n})\n\ntype ServerMessage = typeof ServerMessage.Type\n\n// ---------------------------------------------------------------------------\n// Session state\n// ---------------------------------------------------------------------------\n\ntype SessionState = {\n /** The turn now generating, if any. Minted here: the wire has no id. */\n readonly responseId: string | undefined\n readonly turns: number\n /** Call id to tool name, since `functionResponse` must repeat the name. */\n readonly callNames: Readonly<Record<string, string>>\n /** Latest `usageMetadata`, reported with the turn it lands in. */\n readonly usage: Usage | undefined\n /** The server has announced the close, so the next one is the lifetime cap. */\n readonly goingAway: boolean\n}\n\nconst initialState: SessionState = {\n responseId: undefined,\n turns: 0,\n callNames: {},\n usage: undefined,\n goingAway: false,\n}\n\n/**\n * The id every event of the current turn carries, minting one and announcing\n * the turn if this is its first event.\n */\nconst startTurn = (\n state: Ref.Ref<SessionState>,\n): Effect.Effect<readonly [string, ReadonlyArray<RealtimeEvent>]> =>\n Ref.modify(state, (s) => {\n const responseId = s.responseId ?? `turn_${s.turns + 1}`\n return s.responseId !== undefined\n ? [[responseId, []], s]\n : [\n [responseId, [RealtimeEvent.ResponseStarted({ responseId })]],\n { ...s, responseId, turns: s.turns + 1 },\n ]\n })\n\nconst usageOf = (metadata: typeof UsageMetadata.Type): Usage => ({\n ...(metadata.promptTokenCount !== undefined && { input_tokens: metadata.promptTokenCount }),\n ...(metadata.responseTokenCount !== undefined && { output_tokens: metadata.responseTokenCount }),\n ...(metadata.totalTokenCount !== undefined && { total_tokens: metadata.totalTokenCount }),\n})\n\nconst decodeAudio = (b64: string): Effect.Effect<Uint8Array, AiError.AiError> =>\n Result.match(Encoding.decodeBase64(b64), {\n onSuccess: Effect.succeed,\n onFailure: (cause) =>\n Effect.fail(\n new AiError.GenerationFailed({\n provider: PROVIDER,\n raw: { message: \"audio part base64 decode failed\", cause },\n }),\n ),\n })\n\n/** `timeLeft` is a proto duration, so `\"60s\"` rather than a number. */\nconst parseTimeLeft = (timeLeft: string | undefined): Duration.Duration | undefined => {\n if (timeLeft === undefined) return undefined\n const seconds = Number.parseFloat(timeLeft.replace(/s$/, \"\"))\n return Number.isFinite(seconds) ? Duration.seconds(seconds) : undefined\n}\n\n/** Thought parts are the model reasoning, not something to speak or show. */\nconst partToEvents = (\n responseId: string,\n part: typeof Part.Type,\n): Effect.Effect<ReadonlyArray<RealtimeEvent>, AiError.AiError> =>\n Match.value(part).pipe(\n Match.when({ thought: true }, () => Effect.succeed([])),\n Match.when({ inlineData: { data: Match.string } }, (p) =>\n Effect.map(decodeAudio(p.inlineData.data), (bytes) => [\n RealtimeEvent.AudioDelta({ responseId, bytes }),\n ]),\n ),\n Match.when({ text: Match.string }, (p) =>\n Effect.succeed([RealtimeEvent.OutputTranscriptDelta({ responseId, text: p.text })]),\n ),\n Match.orElse(() => Effect.succeed([])),\n )\n\nconst inputTranscripts = (content: typeof ServerContent.Type): ReadonlyArray<RealtimeEvent> => [\n ...(content.interimInputTranscription?.text === undefined\n ? []\n : [\n RealtimeEvent.InputTranscript({\n text: content.interimInputTranscription.text,\n final: false,\n }),\n ]),\n ...(content.inputTranscription?.text === undefined\n ? []\n : [RealtimeEvent.InputTranscript({ text: content.inputTranscription.text, final: true })]),\n]\n\n/**\n * One `serverContent` to its events. Several fields can arrive together, and\n * 3.1 packs several parts into one message, so this answers with a batch.\n */\nconst contentToEvents = (\n state: Ref.Ref<SessionState>,\n content: typeof ServerContent.Type,\n): Effect.Effect<ReadonlyArray<RealtimeEvent>, AiError.AiError> =>\n Effect.gen(function* () {\n // The user's own words belong to no turn, so they need none started.\n const input = inputTranscripts(content)\n const parts = content.modelTurn?.parts ?? []\n const spokenText = content.outputTranscription?.text\n const generating = parts.length > 0 || spokenText !== undefined\n const ending = content.interrupted === true || content.turnComplete === true\n if (!generating && !ending) return input\n\n const [responseId, started] = generating\n ? yield* startTurn(state)\n : [(yield* Ref.get(state)).responseId, [] as ReadonlyArray<RealtimeEvent>]\n // A terminal flag for a turn that already ended is nothing to report.\n if (responseId === undefined) return input\n\n const spoken = yield* Effect.forEach(parts, (part) => partToEvents(responseId, part))\n const transcript =\n spokenText === undefined\n ? []\n : [RealtimeEvent.OutputTranscriptDelta({ responseId, text: spokenText })]\n const body = [...input, ...started, ...spoken.flat(), ...transcript]\n if (!ending) return body\n\n const usage = (yield* Ref.get(state)).usage\n yield* Ref.update(state, (s) => ({ ...s, responseId: undefined, usage: undefined }))\n return [\n ...body,\n // Playback must stop before the turn is reported as over.\n ...(content.interrupted === true ? [RealtimeEvent.Interrupted({ responseId })] : []),\n RealtimeEvent.ResponseDone({\n responseId,\n reason: content.interrupted === true ? \"interrupted\" : \"complete\",\n ...(usage !== undefined && { usage }),\n }),\n ]\n })\n\nconst toolCallEvents = (\n state: Ref.Ref<SessionState>,\n calls: ReadonlyArray<typeof FunctionCall.Type>,\n): Effect.Effect<ReadonlyArray<RealtimeEvent>> =>\n Effect.gen(function* () {\n const [responseId, started] = yield* startTurn(state)\n yield* Ref.update(state, (s) => ({\n ...s,\n callNames: calls.reduce(\n (names, call) => ({ ...names, [call.id ?? call.name]: call.name }),\n s.callNames,\n ),\n }))\n return [\n ...started,\n ...calls.map((call) =>\n RealtimeEvent.ToolCall({\n responseId,\n call: {\n type: \"function_call\" as const,\n call_id: call.id ?? call.name,\n name: call.name,\n arguments: JSON.stringify(call.args ?? {}),\n providerData: undefined,\n },\n }),\n ),\n ]\n })\n\nconst toEvents = (\n state: Ref.Ref<SessionState>,\n frame: ServerMessage,\n): Effect.Effect<ReadonlyArray<RealtimeEvent>, AiError.AiError> =>\n Effect.gen(function* () {\n if (frame.usageMetadata !== undefined) {\n const usage = usageOf(frame.usageMetadata)\n yield* Ref.update(state, (s) => ({ ...s, usage }))\n }\n if (frame.serverContent !== undefined) return yield* contentToEvents(state, frame.serverContent)\n if (frame.toolCall !== undefined) {\n return yield* toolCallEvents(state, frame.toolCall.functionCalls ?? [])\n }\n const callIds = frame.toolCallCancellation?.ids ?? []\n if (callIds.length > 0) return [RealtimeEvent.ToolCallCancelled({ callIds })]\n if (frame.goAway !== undefined) {\n const timeLeft = parseTimeLeft(frame.goAway.timeLeft)\n yield* Ref.update(state, (s) => ({ ...s, goingAway: true }))\n return [RealtimeEvent.SessionEnding({ ...(timeLeft !== undefined && { timeLeft }) })]\n }\n // An unresumable update carries an empty handle; it must not replace one.\n const handle = frame.sessionResumptionUpdate?.newHandle\n return frame.sessionResumptionUpdate?.resumable === true &&\n handle !== undefined &&\n handle !== \"\"\n ? [RealtimeEvent.ResumptionHandle({ handle })]\n : []\n })\n\n// ---------------------------------------------------------------------------\n// Input handling\n// ---------------------------------------------------------------------------\n\nconst droppedActivity = (field: string) =>\n Capabilities.warnDropped({\n provider: PROVIDER,\n capability: \"manualTurns\",\n field,\n reason: \"The session uses automatic activity detection; boundaries are the server's.\",\n })\n\nconst videoPart = (\n frame: Extract<RealtimeInput, { readonly _tag: \"VideoFrame\" }>[\"frame\"],\n): Effect.Effect<{ readonly data: string; readonly mimeType: string }, AiError.AiError> =>\n Match.value(frame).pipe(\n Match.tag(\"base64\", (s) => Effect.succeed({ data: s.base64, mimeType: s.mimeType })),\n Match.tag(\"bytes\", (s) =>\n Effect.succeed({ data: Encoding.encodeBase64(s.bytes), mimeType: s.mimeType }),\n ),\n Match.tag(\"url\", () =>\n Effect.fail(\n new AiError.Unsupported({\n provider: PROVIDER,\n capability: \"videoInput\",\n reason: \"A live video frame must carry its bytes; a URL would need the Files API.\",\n }),\n ),\n ),\n Match.exhaustive,\n )\n\n/** Wire spelling of `toolScheduling`. */\ntype ToolScheduling = \"WHEN_IDLE\" | \"INTERRUPT\" | \"SILENT\"\n\nconst schedulingOf = (request: GeminiLiveRequest): ToolScheduling =>\n Match.value(request.toolScheduling ?? \"when-idle\").pipe(\n Match.when(\"when-idle\", () => \"WHEN_IDLE\" as const),\n Match.when(\"interrupt\", () => \"INTERRUPT\" as const),\n Match.when(\"silent\", () => \"SILENT\" as const),\n Match.exhaustive,\n )\n\nconst handleInput = (\n send: (frame: string) => Effect.Effect<void, AiError.AiError>,\n state: Ref.Ref<SessionState>,\n options: {\n readonly manual: boolean\n readonly rate: number\n readonly scheduling: ToolScheduling\n },\n input: RealtimeInput,\n): Effect.Effect<void, AiError.AiError> =>\n Match.value(input).pipe(\n Match.tag(\"Audio\", (i) =>\n send(\n JSON.stringify({\n realtimeInput: {\n audio: {\n data: Encoding.encodeBase64(i.bytes),\n mimeType: `audio/pcm;rate=${options.rate}`,\n },\n },\n }),\n ),\n ),\n Match.tag(\"Text\", (i) =>\n i.role === \"system\"\n ? Effect.fail(\n new AiError.Unsupported({\n provider: PROVIDER,\n capability: \"systemTurn\",\n reason: \"Gemini Live fixes the system instruction at setup for the connection.\",\n }),\n )\n : send(JSON.stringify({ realtimeInput: { text: i.text } })),\n ),\n Match.tag(\"ToolResult\", (i) =>\n Effect.gen(function* () {\n const name = (yield* Ref.get(state)).callNames[i.output.call_id]\n if (name === undefined) {\n return yield* Effect.fail(\n new AiError.InvalidRequest({\n provider: PROVIDER,\n raw: `no tool call ${i.output.call_id} in this session`,\n }),\n )\n }\n // Answering resumes generation on its own; there is no turn to ask\n // for. `scheduling` says when, and matters on a model that kept\n // generating while the tool ran.\n yield* send(\n JSON.stringify({\n toolResponse: {\n functionResponses: [\n {\n id: i.output.call_id,\n name,\n response: parsedResponse(i.output.output),\n scheduling: options.scheduling,\n },\n ],\n },\n }),\n )\n }),\n ),\n Match.tag(\"Interrupt\", () =>\n Effect.fail(\n new AiError.Unsupported({\n provider: PROVIDER,\n capability: \"interrupt\",\n reason:\n 'Gemini Live has no cancel op: the server interrupts when it hears speech. Pass activityHandling: \"none\" to turn that off.',\n }),\n ),\n ),\n Match.tag(\"VideoFrame\", (i) =>\n Effect.flatMap(videoPart(i.frame), (video) =>\n send(JSON.stringify({ realtimeInput: { video } })),\n ),\n ),\n Match.tag(\"ActivityStart\", () =>\n options.manual\n ? send(JSON.stringify({ realtimeInput: { activityStart: {} } }))\n : droppedActivity(\"ActivityStart\"),\n ),\n Match.tag(\"ActivityEnd\", () =>\n options.manual\n ? send(JSON.stringify({ realtimeInput: { activityEnd: {} } }))\n : droppedActivity(\"ActivityEnd\"),\n ),\n // Interruption already drops what was never generated, but audio the\n // server did send stays in history whether or not it was heard.\n Match.tag(\"PlaybackPosition\", () =>\n Capabilities.warnDropped({\n provider: PROVIDER,\n capability: \"truncate\",\n field: \"PlaybackPosition\",\n reason: \"Gemini Live has no truncate op, so the unheard tail stays in context.\",\n }),\n ),\n Match.exhaustive,\n )\n\n// ---------------------------------------------------------------------------\n// Connection\n// ---------------------------------------------------------------------------\n\nexport const buildWsUrl = (cfg: Config): string => {\n const url = new URL(cfg.baseUrl ?? DEFAULT_BASE_URL)\n url.protocol = url.protocol === \"http:\" ? \"ws:\" : \"wss:\"\n url.pathname = `${url.pathname.replace(/\\/$/, \"\")}/${RPC}`\n url.searchParams.set(\"key\", Redacted.value(cfg.apiKey))\n return url.toString()\n}\n\nconst withSocket =\n (cfg: Config) =>\n <A, E, R>(\n effect: Effect.Effect<A, E, R | Socket.WebSocketConstructor>,\n ): Effect.Effect<A, E, Exclude<R, Socket.WebSocketConstructor>> =>\n cfg.webSocket === undefined\n ? Effect.provide(effect, Socket.layerWebSocketConstructorGlobal)\n : Effect.provideService(effect, Socket.WebSocketConstructor, cfg.webSocket)\n\n/** Drop frames until `setupComplete`; a close before it is a failed handshake. */\nconst awaitSetupComplete = (\n session: WebSocketSession.WebSocketSession<ServerMessage>,\n): Effect.Effect<void, AiError.AiError> =>\n Queue.take(session.frames).pipe(\n Effect.catchCause((cause) =>\n Pull.isDoneCause(cause)\n ? Effect.fail(\n new AiError.Unavailable({\n provider: PROVIDER,\n raw: \"the socket closed during the handshake\",\n }),\n )\n : Effect.failCause(cause as Cause.Cause<AiError.AiError>),\n ),\n Effect.flatMap((frame) =>\n frame.setupComplete === undefined ? awaitSetupComplete(session) : Effect.void,\n ),\n )\n\nexport const openSession =\n (cfg: Config) =>\n (\n request: GeminiLiveRequest,\n ): Effect.Effect<RealtimeSessionHandle, AiError.AiError, Scope.Scope> =>\n Effect.gen(function* () {\n if (request.googleSearch === true && (request.tools?.length ?? 0) > 0) {\n return yield* Effect.fail(\n new AiError.InvalidRequest({\n provider: PROVIDER,\n raw: \"Gemini rejects googleSearch alongside function declarations; pick one per session.\",\n }),\n )\n }\n const rate = yield* inputRate(request.inputFormat)\n const scheduling = schedulingOf(request)\n yield* checkOutputFormat(request.outputFormat)\n\n const session = yield* WebSocketSession.open({\n url: buildWsUrl(cfg),\n provider: PROVIDER,\n schema: ServerMessage,\n openTimeout: Duration.seconds(30),\n }).pipe(withSocket(cfg))\n\n // The client speaks first here, and nothing else may be sent until the\n // server has accepted the configuration.\n yield* session.send(setupFrame(request))\n yield* awaitSetupComplete(session)\n\n const history = request.history ?? []\n if (history.length > 0) {\n const seed = yield* historyFrame(history)\n yield* session.send(seed)\n }\n\n const state = yield* Ref.make(initialState)\n const out = yield* Queue.bounded<RealtimeEvent, AiError.AiError | Cause.Done>(128)\n\n const pump = Stream.fromQueue(session.frames).pipe(\n Stream.mapEffect((frame) => toEvents(state, frame)),\n Stream.runForEach((events) => Queue.offerAll(out, events)),\n )\n\n // A close never synthesizes a `ResponseDone`; a turn still in flight\n // ends the stream as an incomplete turn instead.\n const finish = (cause: Cause.Cause<AiError.AiError> | undefined) =>\n Effect.gen(function* () {\n const { goingAway, responseId } = yield* Ref.get(state)\n if (responseId !== undefined) {\n return yield* Queue.fail(\n out,\n new AiError.IncompleteTurn({ raw: cause ?? \"the socket closed mid turn\" }),\n )\n }\n // `goAway` was the server announcing this close, so the session hit\n // its cap. Reconnecting works; retrying this session does not.\n if (goingAway) {\n return yield* Queue.fail(\n out,\n new AiError.SessionExpired({\n provider: PROVIDER,\n raw: cause ?? \"the socket closed after the server announced it was going away\",\n }),\n )\n }\n return yield* cause !== undefined && Cause.hasFails(cause)\n ? Queue.failCause(out, cause)\n : Queue.end(out)\n })\n\n yield* pump.pipe(\n Effect.matchCauseEffect({ onFailure: finish, onSuccess: () => finish(undefined) }),\n Effect.forkScoped,\n )\n\n const manual = request.turnDetection === \"manual\"\n return {\n send: (input) => handleInput(session.send, state, { manual, rate, scheduling }, input),\n events: Stream.fromQueue(out),\n }\n })\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"realtimeSession.mjs","names":[],"sources":["../src/realtimeSession.ts"],"sourcesContent":["/**\n * Gemini Live speech-to-speech over `BidiGenerateContent`.\n *\n * Auth is a query parameter, so this needs no `ws` package and no headers:\n * the platform `WebSocket` reaches it from Node, Bun, Deno and the browser.\n *\n * Two shapes differ from OpenAI Realtime and drive most of this file. There\n * are no response ids on the wire, so a turn id is minted here and every event\n * of that turn carries it; and there is no truncate op, so a\n * `PlaybackPosition` has nothing to send.\n */\nimport {\n Cause,\n Duration,\n Effect,\n Match,\n Pull,\n Queue,\n Redacted,\n Ref,\n Result,\n Schema,\n type Scope,\n Stream,\n} from \"effect\"\nimport { Base64 } from \"effect/encoding\"\nimport * as Socket from \"effect/socket/Socket\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { AudioFormat } from \"@effect-uai/core/Audio\"\nimport * as Capabilities from \"@effect-uai/core/Capabilities\"\nimport type { HistoryItem, Usage } from \"@effect-uai/core/Items\"\nimport {\n type CommonSessionRequest,\n RealtimeEvent,\n type RealtimeInput,\n} from \"@effect-uai/core/Realtime\"\nimport type { RealtimeSessionHandle } from \"@effect-uai/core/RealtimeSession\"\nimport * as WebSocketSession from \"@effect-uai/core/WebSocketSession\"\nimport { parsedResponse, toolDescriptorsToTools } from \"./codec.js\"\nimport type { GeminiLiveModel } from \"./models.js\"\n\nconst PROVIDER = \"gemini-live\"\n\nconst DEFAULT_BASE_URL = \"wss://generativelanguage.googleapis.com\"\nconst RPC = \"ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent\"\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n /** Build the socket yourself, for a proxy or an in-memory transport. */\n readonly webSocket?: Socket.WebSocketConstructor[\"Service\"]\n}\n\n/**\n * Gemini-typed session request. Everything past `CommonSessionRequest` is\n * provider-shaped. The configuration is immutable for the connection: changing\n * any of it means opening a new session.\n */\nexport type GeminiLiveRequest = Omit<CommonSessionRequest, \"model\"> & {\n readonly model: GeminiLiveModel\n /** Replaces the plain `turnDetection` switch when you need the knobs. */\n readonly vad?: {\n readonly startSensitivity?: \"high\" | \"low\"\n readonly endSensitivity?: \"high\" | \"low\"\n readonly prefixPaddingMs?: number\n readonly silenceDurationMs?: number\n }\n /** `\"none\"` turns barge-in off. Defaults to interrupting. */\n readonly activityHandling?: \"interrupt\" | \"none\"\n /**\n * When a tool result reaches the model, for the models that keep generating\n * while a tool runs. Defaults to `\"when-idle\"`: the server's own default is\n * to interrupt, which cuts off whatever the model was saying while it\n * waited, including the \"let me look that up\" it just spoke. `\"interrupt\"`\n * is right when the result cannot wait, `\"silent\"` when it is context the\n * model should have rather than something to announce.\n */\n readonly toolScheduling?: \"when-idle\" | \"interrupt\" | \"silent\"\n readonly turnCoverage?: \"activity\" | \"all\" | \"audioActivityAndAllVideo\"\n /** 3.1 only. Higher levels delay the first audio. */\n readonly thinkingLevel?: \"minimal\" | \"low\" | \"medium\" | \"high\"\n readonly mediaResolution?: \"low\" | \"medium\" | \"high\"\n readonly languageCode?: string\n readonly temperature?: number\n readonly maxOutputTokens?: number\n /** What turns an otherwise time-boxed session into an unbounded one. */\n readonly contextCompression?: {\n readonly triggerTokens?: number\n readonly targetTokens?: number\n }\n /** Grounding. Cannot be combined with `tools`; the pair is a 400. */\n readonly googleSearch?: boolean\n /** 2.5 only. */\n readonly proactiveAudio?: boolean\n /** 2.5 only. */\n readonly affectiveDialog?: boolean\n}\n\n// ---------------------------------------------------------------------------\n// AudioFormat -> wire\n// ---------------------------------------------------------------------------\n\n/** Output is 24 kHz whatever the input rate, and cannot be configured. */\nconst OUTPUT_RATE = 24000\n\nconst unsupportedFormat = (format: AudioFormat, field: string, expected: string) =>\n new AiError.Unsupported({\n provider: PROVIDER,\n capability: field,\n reason: `Gemini Live accepts ${expected}. Got ${JSON.stringify(format)}.`,\n })\n\n/** Any rate is accepted on input; 16 kHz is what the model works in. */\nconst inputRate = (format: AudioFormat): Effect.Effect<number, AiError.AiError> =>\n Match.value(format).pipe(\n Match.when({ container: \"raw\", encoding: \"pcm_s16le\", channels: 1 }, (f) =>\n Effect.succeed(f.sampleRate),\n ),\n Match.orElse((f) =>\n Effect.fail(unsupportedFormat(f, \"inputFormat\", \"mono pcm_s16le at any rate\")),\n ),\n )\n\nconst checkOutputFormat = (format: AudioFormat): Effect.Effect<void, AiError.AiError> =>\n Match.value(format).pipe(\n Match.when(\n { container: \"raw\", encoding: \"pcm_s16le\", sampleRate: OUTPUT_RATE, channels: 1 },\n () => Effect.void,\n ),\n Match.orElse((f) =>\n Effect.fail(unsupportedFormat(f, \"outputFormat\", `mono pcm_s16le at ${OUTPUT_RATE}`)),\n ),\n )\n\n// ---------------------------------------------------------------------------\n// Client frames\n// ---------------------------------------------------------------------------\n\nconst SENSITIVITY = { high: \"HIGH\", low: \"LOW\" } as const\n\nconst TURN_COVERAGE = {\n activity: \"TURN_INCLUDES_ONLY_ACTIVITY\",\n all: \"TURN_INCLUDES_ALL_INPUT\",\n audioActivityAndAllVideo: \"TURN_INCLUDES_AUDIO_ACTIVITY_AND_ALL_VIDEO\",\n} as const\n\nconst realtimeInputConfig = (request: GeminiLiveRequest) => ({\n automaticActivityDetection: {\n ...(request.turnDetection === \"manual\" && { disabled: true }),\n ...(request.vad?.startSensitivity !== undefined && {\n startOfSpeechSensitivity: `START_SENSITIVITY_${SENSITIVITY[request.vad.startSensitivity]}`,\n }),\n ...(request.vad?.endSensitivity !== undefined && {\n endOfSpeechSensitivity: `END_SENSITIVITY_${SENSITIVITY[request.vad.endSensitivity]}`,\n }),\n ...(request.vad?.prefixPaddingMs !== undefined && {\n prefixPaddingMs: request.vad.prefixPaddingMs,\n }),\n ...(request.vad?.silenceDurationMs !== undefined && {\n silenceDurationMs: request.vad.silenceDurationMs,\n }),\n },\n ...(request.activityHandling !== undefined && {\n activityHandling:\n request.activityHandling === \"none\" ? \"NO_INTERRUPTION\" : \"START_OF_ACTIVITY_INTERRUPTS\",\n }),\n ...(request.turnCoverage !== undefined && { turnCoverage: TURN_COVERAGE[request.turnCoverage] }),\n})\n\nconst generationConfig = (request: GeminiLiveRequest) => ({\n responseModalities: [\"AUDIO\"],\n ...(request.voiceId !== undefined && {\n speechConfig: {\n voiceConfig: { prebuiltVoiceConfig: { voiceName: request.voiceId } },\n ...(request.languageCode !== undefined && { languageCode: request.languageCode }),\n },\n }),\n ...(request.temperature !== undefined && { temperature: request.temperature }),\n ...(request.maxOutputTokens !== undefined && { maxOutputTokens: request.maxOutputTokens }),\n ...(request.mediaResolution !== undefined && {\n mediaResolution: `MEDIA_RESOLUTION_${request.mediaResolution.toUpperCase()}`,\n }),\n ...(request.thinkingLevel !== undefined && {\n thinkingConfig: { thinkingLevel: request.thinkingLevel.toUpperCase() },\n }),\n ...(request.affectiveDialog === true && { enableAffectiveDialog: true }),\n})\n\nconst toolsOf = (request: GeminiLiveRequest) =>\n request.googleSearch === true\n ? [{ googleSearch: {} }]\n : toolDescriptorsToTools(request.tools ?? [])\n\nconst setupFrame = (request: GeminiLiveRequest): string => {\n const tools = toolsOf(request)\n return JSON.stringify({\n setup: {\n model: `models/${request.model}`,\n generationConfig: generationConfig(request),\n ...(request.instructions !== undefined && {\n systemInstruction: { parts: [{ text: request.instructions }] },\n }),\n ...(tools.length > 0 && { tools }),\n realtimeInputConfig: realtimeInputConfig(request),\n ...(request.transcribeInput !== false && { inputAudioTranscription: {} }),\n // The only way to text on a native-audio model.\n outputAudioTranscription: {},\n // Always on, so a handle exists before the ten-minute socket ends.\n sessionResumption: request.resume === undefined ? {} : { handle: request.resume },\n ...(request.contextCompression !== undefined && {\n contextWindowCompression: {\n slidingWindow: {\n ...(request.contextCompression.targetTokens !== undefined && {\n targetTokens: request.contextCompression.targetTokens,\n }),\n },\n ...(request.contextCompression.triggerTokens !== undefined && {\n triggerTokens: request.contextCompression.triggerTokens,\n }),\n },\n }),\n ...(request.proactiveAudio === true && { proactivity: { proactiveAudio: true } }),\n // Seeded history arrives as `clientContent` right after the handshake.\n ...((request.history?.length ?? 0) > 0 && {\n historyConfig: { initialHistoryInClientContent: true },\n }),\n },\n })\n}\n\nconst messageText = (item: HistoryItem): string =>\n item.type !== \"message\"\n ? \"\"\n : item.content\n .map((block) =>\n block.type === \"input_text\" || block.type === \"output_text\" ? block.text : \"\",\n )\n .join(\"\")\n\n/**\n * Text-only seed, which is all `history` promises across providers. Anything\n * with no text of its own, a tool call or its output, has no place in the seed\n * and is reported once per kind rather than vanishing.\n */\nconst historyFrame = (\n history: ReadonlyArray<HistoryItem>,\n): Effect.Effect<string, AiError.AiError> =>\n Effect.gen(function* () {\n const kinds = new Set(history.filter((i) => messageText(i).length === 0).map((i) => i.type))\n yield* Effect.forEach(\n kinds,\n (kind) =>\n Capabilities.warnDropped({\n provider: PROVIDER,\n capability: \"history\",\n field: \"history\",\n value: kind,\n reason: `Gemini Live seeds a session with text turns only, so \\`${kind}\\` items are left out.`,\n }),\n { discard: true },\n )\n return JSON.stringify({\n clientContent: {\n turns: history\n .filter((item) => messageText(item).length > 0)\n .map((item) => ({\n role: item.type === \"message\" && item.role === \"assistant\" ? \"model\" : \"user\",\n parts: [{ text: messageText(item) }],\n })),\n turnComplete: true,\n },\n })\n })\n\n// ---------------------------------------------------------------------------\n// Server frames\n//\n// Gemini has no discriminator field: one message carries exactly one of these\n// keys, and `serverContent` can carry several of its own at once.\n// ---------------------------------------------------------------------------\n\nconst Part = Schema.Struct({\n text: Schema.optional(Schema.String),\n thought: Schema.optional(Schema.Boolean),\n inlineData: Schema.optional(\n Schema.Struct({\n mimeType: Schema.optional(Schema.String),\n data: Schema.optional(Schema.String),\n }),\n ),\n})\n\nconst Transcription = Schema.Struct({ text: Schema.optional(Schema.String) })\n\nconst ServerContent = Schema.Struct({\n modelTurn: Schema.optional(Schema.Struct({ parts: Schema.optional(Schema.Array(Part)) })),\n turnComplete: Schema.optional(Schema.Boolean),\n generationComplete: Schema.optional(Schema.Boolean),\n interrupted: Schema.optional(Schema.Boolean),\n inputTranscription: Schema.optional(Transcription),\n interimInputTranscription: Schema.optional(Transcription),\n outputTranscription: Schema.optional(Transcription),\n})\n\nconst UsageMetadata = Schema.Struct({\n promptTokenCount: Schema.optional(Schema.Number),\n responseTokenCount: Schema.optional(Schema.Number),\n totalTokenCount: Schema.optional(Schema.Number),\n})\n\nconst FunctionCall = Schema.Struct({\n id: Schema.optional(Schema.String),\n name: Schema.String,\n args: Schema.optional(Schema.Unknown),\n})\n\nconst ServerMessage = Schema.Struct({\n setupComplete: Schema.optional(Schema.Unknown),\n serverContent: Schema.optional(ServerContent),\n toolCall: Schema.optional(\n Schema.Struct({ functionCalls: Schema.optional(Schema.Array(FunctionCall)) }),\n ),\n toolCallCancellation: Schema.optional(\n Schema.Struct({ ids: Schema.optional(Schema.Array(Schema.String)) }),\n ),\n goAway: Schema.optional(Schema.Struct({ timeLeft: Schema.optional(Schema.String) })),\n sessionResumptionUpdate: Schema.optional(\n Schema.Struct({\n newHandle: Schema.optional(Schema.String),\n resumable: Schema.optional(Schema.Boolean),\n }),\n ),\n usageMetadata: Schema.optional(UsageMetadata),\n})\n\ntype ServerMessage = typeof ServerMessage.Type\n\n// ---------------------------------------------------------------------------\n// Session state\n// ---------------------------------------------------------------------------\n\ntype SessionState = {\n /** The turn now generating, if any. Minted here: the wire has no id. */\n readonly responseId: string | undefined\n readonly turns: number\n /** Call id to tool name, since `functionResponse` must repeat the name. */\n readonly callNames: Readonly<Record<string, string>>\n /** Latest `usageMetadata`, reported with the turn it lands in. */\n readonly usage: Usage | undefined\n /** The server has announced the close, so the next one is the lifetime cap. */\n readonly goingAway: boolean\n}\n\nconst initialState: SessionState = {\n responseId: undefined,\n turns: 0,\n callNames: {},\n usage: undefined,\n goingAway: false,\n}\n\n/**\n * The id every event of the current turn carries, minting one and announcing\n * the turn if this is its first event.\n */\nconst startTurn = (\n state: Ref.Ref<SessionState>,\n): Effect.Effect<readonly [string, ReadonlyArray<RealtimeEvent>]> =>\n Ref.modify(state, (s) => {\n const responseId = s.responseId ?? `turn_${s.turns + 1}`\n return s.responseId !== undefined\n ? [[responseId, []], s]\n : [\n [responseId, [RealtimeEvent.ResponseStarted({ responseId })]],\n { ...s, responseId, turns: s.turns + 1 },\n ]\n })\n\nconst usageOf = (metadata: typeof UsageMetadata.Type): Usage => ({\n ...(metadata.promptTokenCount !== undefined && { input_tokens: metadata.promptTokenCount }),\n ...(metadata.responseTokenCount !== undefined && { output_tokens: metadata.responseTokenCount }),\n ...(metadata.totalTokenCount !== undefined && { total_tokens: metadata.totalTokenCount }),\n})\n\nconst decodeAudio = (b64: string): Effect.Effect<Uint8Array, AiError.AiError> =>\n Result.match(Base64.decode(b64), {\n onSuccess: Effect.succeed,\n onFailure: (cause) =>\n Effect.fail(\n new AiError.GenerationFailed({\n provider: PROVIDER,\n raw: { message: \"audio part base64 decode failed\", cause },\n }),\n ),\n })\n\n/** `timeLeft` is a proto duration, so `\"60s\"` rather than a number. */\nconst parseTimeLeft = (timeLeft: string | undefined): Duration.Duration | undefined => {\n if (timeLeft === undefined) return undefined\n const seconds = Number.parseFloat(timeLeft.replace(/s$/, \"\"))\n return Number.isFinite(seconds) ? Duration.seconds(seconds) : undefined\n}\n\n/** Thought parts are the model reasoning, not something to speak or show. */\nconst partToEvents = (\n responseId: string,\n part: typeof Part.Type,\n): Effect.Effect<ReadonlyArray<RealtimeEvent>, AiError.AiError> =>\n Match.value(part).pipe(\n Match.when({ thought: true }, () => Effect.succeed([])),\n Match.when({ inlineData: { data: Match.string } }, (p) =>\n Effect.map(decodeAudio(p.inlineData.data), (bytes) => [\n RealtimeEvent.AudioDelta({ responseId, bytes }),\n ]),\n ),\n Match.when({ text: Match.string }, (p) =>\n Effect.succeed([RealtimeEvent.OutputTranscriptDelta({ responseId, text: p.text })]),\n ),\n Match.orElse(() => Effect.succeed([])),\n )\n\nconst inputTranscripts = (content: typeof ServerContent.Type): ReadonlyArray<RealtimeEvent> => [\n ...(content.interimInputTranscription?.text === undefined\n ? []\n : [\n RealtimeEvent.InputTranscript({\n text: content.interimInputTranscription.text,\n final: false,\n }),\n ]),\n ...(content.inputTranscription?.text === undefined\n ? []\n : [RealtimeEvent.InputTranscript({ text: content.inputTranscription.text, final: true })]),\n]\n\n/**\n * One `serverContent` to its events. Several fields can arrive together, and\n * 3.1 packs several parts into one message, so this answers with a batch.\n */\nconst contentToEvents = (\n state: Ref.Ref<SessionState>,\n content: typeof ServerContent.Type,\n): Effect.Effect<ReadonlyArray<RealtimeEvent>, AiError.AiError> =>\n Effect.gen(function* () {\n // The user's own words belong to no turn, so they need none started.\n const input = inputTranscripts(content)\n const parts = content.modelTurn?.parts ?? []\n const spokenText = content.outputTranscription?.text\n const generating = parts.length > 0 || spokenText !== undefined\n const ending = content.interrupted === true || content.turnComplete === true\n if (!generating && !ending) return input\n\n const [responseId, started] = generating\n ? yield* startTurn(state)\n : [(yield* Ref.get(state)).responseId, [] as ReadonlyArray<RealtimeEvent>]\n // A terminal flag for a turn that already ended is nothing to report.\n if (responseId === undefined) return input\n\n const spoken = yield* Effect.forEach(parts, (part) => partToEvents(responseId, part))\n const transcript =\n spokenText === undefined\n ? []\n : [RealtimeEvent.OutputTranscriptDelta({ responseId, text: spokenText })]\n const body = [...input, ...started, ...spoken.flat(), ...transcript]\n if (!ending) return body\n\n const usage = (yield* Ref.get(state)).usage\n yield* Ref.update(state, (s) => ({ ...s, responseId: undefined, usage: undefined }))\n return [\n ...body,\n // Playback must stop before the turn is reported as over.\n ...(content.interrupted === true ? [RealtimeEvent.Interrupted({ responseId })] : []),\n RealtimeEvent.ResponseDone({\n responseId,\n reason: content.interrupted === true ? \"interrupted\" : \"complete\",\n ...(usage !== undefined && { usage }),\n }),\n ]\n })\n\nconst toolCallEvents = (\n state: Ref.Ref<SessionState>,\n calls: ReadonlyArray<typeof FunctionCall.Type>,\n): Effect.Effect<ReadonlyArray<RealtimeEvent>> =>\n Effect.gen(function* () {\n const [responseId, started] = yield* startTurn(state)\n yield* Ref.update(state, (s) => ({\n ...s,\n callNames: calls.reduce(\n (names, call) => ({ ...names, [call.id ?? call.name]: call.name }),\n s.callNames,\n ),\n }))\n return [\n ...started,\n ...calls.map((call) =>\n RealtimeEvent.ToolCall({\n responseId,\n call: {\n type: \"function_call\" as const,\n call_id: call.id ?? call.name,\n name: call.name,\n arguments: JSON.stringify(call.args ?? {}),\n providerData: undefined,\n },\n }),\n ),\n ]\n })\n\nconst toEvents = (\n state: Ref.Ref<SessionState>,\n frame: ServerMessage,\n): Effect.Effect<ReadonlyArray<RealtimeEvent>, AiError.AiError> =>\n Effect.gen(function* () {\n if (frame.usageMetadata !== undefined) {\n const usage = usageOf(frame.usageMetadata)\n yield* Ref.update(state, (s) => ({ ...s, usage }))\n }\n if (frame.serverContent !== undefined) return yield* contentToEvents(state, frame.serverContent)\n if (frame.toolCall !== undefined) {\n return yield* toolCallEvents(state, frame.toolCall.functionCalls ?? [])\n }\n const callIds = frame.toolCallCancellation?.ids ?? []\n if (callIds.length > 0) return [RealtimeEvent.ToolCallCancelled({ callIds })]\n if (frame.goAway !== undefined) {\n const timeLeft = parseTimeLeft(frame.goAway.timeLeft)\n yield* Ref.update(state, (s) => ({ ...s, goingAway: true }))\n return [RealtimeEvent.SessionEnding({ ...(timeLeft !== undefined && { timeLeft }) })]\n }\n // An unresumable update carries an empty handle; it must not replace one.\n const handle = frame.sessionResumptionUpdate?.newHandle\n return frame.sessionResumptionUpdate?.resumable === true &&\n handle !== undefined &&\n handle !== \"\"\n ? [RealtimeEvent.ResumptionHandle({ handle })]\n : []\n })\n\n// ---------------------------------------------------------------------------\n// Input handling\n// ---------------------------------------------------------------------------\n\nconst droppedActivity = (field: string) =>\n Capabilities.warnDropped({\n provider: PROVIDER,\n capability: \"manualTurns\",\n field,\n reason: \"The session uses automatic activity detection; boundaries are the server's.\",\n })\n\nconst videoPart = (\n frame: Extract<RealtimeInput, { readonly _tag: \"VideoFrame\" }>[\"frame\"],\n): Effect.Effect<{ readonly data: string; readonly mimeType: string }, AiError.AiError> =>\n Match.value(frame).pipe(\n Match.tag(\"base64\", (s) => Effect.succeed({ data: s.base64, mimeType: s.mimeType })),\n Match.tag(\"bytes\", (s) =>\n Effect.succeed({ data: Base64.encode(s.bytes), mimeType: s.mimeType }),\n ),\n Match.tag(\"url\", () =>\n Effect.fail(\n new AiError.Unsupported({\n provider: PROVIDER,\n capability: \"videoInput\",\n reason: \"A live video frame must carry its bytes; a URL would need the Files API.\",\n }),\n ),\n ),\n Match.exhaustive,\n )\n\n/** Wire spelling of `toolScheduling`. */\ntype ToolScheduling = \"WHEN_IDLE\" | \"INTERRUPT\" | \"SILENT\"\n\nconst schedulingOf = (request: GeminiLiveRequest): ToolScheduling =>\n Match.value(request.toolScheduling ?? \"when-idle\").pipe(\n Match.when(\"when-idle\", () => \"WHEN_IDLE\" as const),\n Match.when(\"interrupt\", () => \"INTERRUPT\" as const),\n Match.when(\"silent\", () => \"SILENT\" as const),\n Match.exhaustive,\n )\n\nconst handleInput = (\n send: (frame: string) => Effect.Effect<void, AiError.AiError>,\n state: Ref.Ref<SessionState>,\n options: {\n readonly manual: boolean\n readonly rate: number\n readonly scheduling: ToolScheduling\n },\n input: RealtimeInput,\n): Effect.Effect<void, AiError.AiError> =>\n Match.value(input).pipe(\n Match.tag(\"Audio\", (i) =>\n send(\n JSON.stringify({\n realtimeInput: {\n audio: {\n data: Base64.encode(i.bytes),\n mimeType: `audio/pcm;rate=${options.rate}`,\n },\n },\n }),\n ),\n ),\n Match.tag(\"Text\", (i) =>\n i.role === \"system\"\n ? Effect.fail(\n new AiError.Unsupported({\n provider: PROVIDER,\n capability: \"systemTurn\",\n reason: \"Gemini Live fixes the system instruction at setup for the connection.\",\n }),\n )\n : send(JSON.stringify({ realtimeInput: { text: i.text } })),\n ),\n Match.tag(\"ToolResult\", (i) =>\n Effect.gen(function* () {\n const name = (yield* Ref.get(state)).callNames[i.output.call_id]\n if (name === undefined) {\n return yield* Effect.fail(\n new AiError.InvalidRequest({\n provider: PROVIDER,\n raw: `no tool call ${i.output.call_id} in this session`,\n }),\n )\n }\n // Answering resumes generation on its own; there is no turn to ask\n // for. `scheduling` says when, and matters on a model that kept\n // generating while the tool ran.\n yield* send(\n JSON.stringify({\n toolResponse: {\n functionResponses: [\n {\n id: i.output.call_id,\n name,\n response: parsedResponse(i.output.output),\n scheduling: options.scheduling,\n },\n ],\n },\n }),\n )\n }),\n ),\n Match.tag(\"Interrupt\", () =>\n Effect.fail(\n new AiError.Unsupported({\n provider: PROVIDER,\n capability: \"interrupt\",\n reason:\n 'Gemini Live has no cancel op: the server interrupts when it hears speech. Pass activityHandling: \"none\" to turn that off.',\n }),\n ),\n ),\n Match.tag(\"VideoFrame\", (i) =>\n Effect.flatMap(videoPart(i.frame), (video) =>\n send(JSON.stringify({ realtimeInput: { video } })),\n ),\n ),\n Match.tag(\"ActivityStart\", () =>\n options.manual\n ? send(JSON.stringify({ realtimeInput: { activityStart: {} } }))\n : droppedActivity(\"ActivityStart\"),\n ),\n Match.tag(\"ActivityEnd\", () =>\n options.manual\n ? send(JSON.stringify({ realtimeInput: { activityEnd: {} } }))\n : droppedActivity(\"ActivityEnd\"),\n ),\n // Interruption already drops what was never generated, but audio the\n // server did send stays in history whether or not it was heard.\n Match.tag(\"PlaybackPosition\", () =>\n Capabilities.warnDropped({\n provider: PROVIDER,\n capability: \"truncate\",\n field: \"PlaybackPosition\",\n reason: \"Gemini Live has no truncate op, so the unheard tail stays in context.\",\n }),\n ),\n Match.exhaustive,\n )\n\n// ---------------------------------------------------------------------------\n// Connection\n// ---------------------------------------------------------------------------\n\nexport const buildWsUrl = (cfg: Config): string => {\n const url = new URL(cfg.baseUrl ?? DEFAULT_BASE_URL)\n url.protocol = url.protocol === \"http:\" ? \"ws:\" : \"wss:\"\n url.pathname = `${url.pathname.replace(/\\/$/, \"\")}/${RPC}`\n url.searchParams.set(\"key\", Redacted.value(cfg.apiKey))\n return url.toString()\n}\n\nconst withSocket =\n (cfg: Config) =>\n <A, E, R>(\n effect: Effect.Effect<A, E, R | Socket.WebSocketConstructor>,\n ): Effect.Effect<A, E, Exclude<R, Socket.WebSocketConstructor>> =>\n cfg.webSocket === undefined\n ? Effect.provide(effect, Socket.layerWebSocketConstructorGlobal)\n : Effect.provideService(effect, Socket.WebSocketConstructor, cfg.webSocket)\n\n/** Drop frames until `setupComplete`; a close before it is a failed handshake. */\nconst awaitSetupComplete = (\n session: WebSocketSession.WebSocketSession<ServerMessage>,\n): Effect.Effect<void, AiError.AiError> =>\n Queue.take(session.frames).pipe(\n Effect.catchCause((cause) =>\n Pull.isDoneCause(cause)\n ? Effect.fail(\n new AiError.Unavailable({\n provider: PROVIDER,\n raw: \"the socket closed during the handshake\",\n }),\n )\n : Effect.failCause(cause as Cause.Cause<AiError.AiError>),\n ),\n Effect.flatMap((frame) =>\n frame.setupComplete === undefined ? awaitSetupComplete(session) : Effect.void,\n ),\n )\n\nexport const openSession =\n (cfg: Config) =>\n (\n request: GeminiLiveRequest,\n ): Effect.Effect<RealtimeSessionHandle, AiError.AiError, Scope.Scope> =>\n Effect.gen(function* () {\n if (request.googleSearch === true && (request.tools?.length ?? 0) > 0) {\n return yield* Effect.fail(\n new AiError.InvalidRequest({\n provider: PROVIDER,\n raw: \"Gemini rejects googleSearch alongside function declarations; pick one per session.\",\n }),\n )\n }\n const rate = yield* inputRate(request.inputFormat)\n const scheduling = schedulingOf(request)\n yield* checkOutputFormat(request.outputFormat)\n\n const session = yield* WebSocketSession.open({\n url: buildWsUrl(cfg),\n provider: PROVIDER,\n schema: ServerMessage,\n openTimeout: Duration.seconds(30),\n }).pipe(withSocket(cfg))\n\n // The client speaks first here, and nothing else may be sent until the\n // server has accepted the configuration.\n yield* session.send(setupFrame(request))\n yield* awaitSetupComplete(session)\n\n const history = request.history ?? []\n if (history.length > 0) {\n const seed = yield* historyFrame(history)\n yield* session.send(seed)\n }\n\n const state = yield* Ref.make(initialState)\n const out = yield* Queue.bounded<RealtimeEvent, AiError.AiError | Cause.Done>(128)\n\n const pump = Stream.fromQueue(session.frames).pipe(\n Stream.mapEffect((frame) => toEvents(state, frame)),\n Stream.runForEach((events) => Queue.offerAll(out, events)),\n )\n\n // A close never synthesizes a `ResponseDone`; a turn still in flight\n // ends the stream as an incomplete turn instead.\n const finish = (cause: Cause.Cause<AiError.AiError> | undefined) =>\n Effect.gen(function* () {\n const { goingAway, responseId } = yield* Ref.get(state)\n if (responseId !== undefined) {\n return yield* Queue.fail(\n out,\n new AiError.IncompleteTurn({ raw: cause ?? \"the socket closed mid turn\" }),\n )\n }\n // `goAway` was the server announcing this close, so the session hit\n // its cap. Reconnecting works; retrying this session does not.\n if (goingAway) {\n return yield* Queue.fail(\n out,\n new AiError.SessionExpired({\n provider: PROVIDER,\n raw: cause ?? \"the socket closed after the server announced it was going away\",\n }),\n )\n }\n return yield* cause !== undefined && Cause.hasFails(cause)\n ? Queue.failCause(out, cause)\n : Queue.end(out)\n })\n\n yield* pump.pipe(\n Effect.matchCauseEffect({ onFailure: finish, onSuccess: () => finish(undefined) }),\n Effect.forkScoped,\n )\n\n const manual = request.turnDetection === \"manual\"\n return {\n send: (input) => handleInput(session.send, state, { manual, rate, scheduling }, input),\n events: Stream.fromQueue(out),\n }\n })\n"],"mappings":"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"}
|
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
import { openSession } from "./realtimeSession.mjs";
|
|
2
|
-
import { a as
|
|
3
|
-
import { Duration, Effect,
|
|
2
|
+
import { a as beforeAll, c as it$2, i as afterAll, n as globalExpect, o as beforeEach, s as describe, t as TestRunner } from "./index.C4tKZ0yW-DjxCdpW_.mjs";
|
|
3
|
+
import { Duration, Effect, Fiber, Logger, Redacted, Ref, Stream } from "effect";
|
|
4
|
+
import { Base64 } from "effect/encoding";
|
|
4
5
|
import { RealtimeInput } from "@effect-uai/core/Realtime";
|
|
6
|
+
import * as Arbitrary from "effect/Arbitrary";
|
|
5
7
|
import * as Cause$1 from "effect/Cause";
|
|
6
8
|
import * as Duration$1 from "effect/Duration";
|
|
7
9
|
import * as Effect$1 from "effect/Effect";
|
|
8
10
|
import * as Exit from "effect/Exit";
|
|
9
|
-
import { flow, pipe as pipe$1 } from "effect/Function";
|
|
11
|
+
import { constVoid, flow, pipe as pipe$1 } from "effect/Function";
|
|
10
12
|
import * as Layer$1 from "effect/Layer";
|
|
11
|
-
import { isObject } from "effect/Predicate";
|
|
12
|
-
import * as Rec from "effect/Record";
|
|
13
13
|
import * as Schedule from "effect/Schedule";
|
|
14
|
-
import * as Schema$1 from "effect/Schema";
|
|
15
14
|
import * as Scope$1 from "effect/Scope";
|
|
16
|
-
import * as fc from "effect/testing/FastCheck";
|
|
17
15
|
import * as TestClock from "effect/testing/TestClock";
|
|
18
16
|
import * as TestConsole from "effect/testing/TestConsole";
|
|
19
17
|
import * as FakeWebSocket from "@effect-uai/core/testing/FakeWebSocket";
|
|
20
|
-
//#region ../../../node_modules/.pnpm/@effect+vitest@4.0.
|
|
18
|
+
//#region ../../../node_modules/.pnpm/@effect+vitest@4.0.1_effect@4.0.1_vitest@5.0.3_@types+node@26.2.0_vite@7.3.2_@types+nod_65816e3353e20bfeb9b2e1494bdddeef/node_modules/@effect/vitest/dist/internal/internal.js
|
|
21
19
|
/**
|
|
22
20
|
* @since 4.0.0
|
|
23
21
|
*/
|
|
@@ -31,11 +29,34 @@ const runPromise = /*#__PURE__*/ Effect$1.fnUntraced(function* (effect, _ctx) {
|
|
|
31
29
|
return yield* exit;
|
|
32
30
|
}, (effect, _, ctx) => Effect$1.runPromise(effect, { signal: ctx?.signal }));
|
|
33
31
|
/** @internal */
|
|
34
|
-
const runTest = (ctx) => (effect) =>
|
|
32
|
+
const runTest = (ctx) => (effect) => {
|
|
33
|
+
const promise = runPromise(effect, ctx);
|
|
34
|
+
if (ctx) {
|
|
35
|
+
const onAbort = () => ctx.onTestFinished(() => promise.then(constVoid, constVoid));
|
|
36
|
+
ctx.signal.addEventListener("abort", onAbort, { once: true });
|
|
37
|
+
const cleanup = () => ctx.signal.removeEventListener("abort", onAbort);
|
|
38
|
+
promise.then(cleanup, cleanup);
|
|
39
|
+
if (ctx.signal.aborted) onAbort();
|
|
40
|
+
}
|
|
41
|
+
return promise;
|
|
42
|
+
};
|
|
35
43
|
const TestEnv = /*#__PURE__*/ Layer$1.mergeAll(TestConsole.layer, /*#__PURE__*/ TestClock.layer());
|
|
36
44
|
/** @internal */
|
|
37
45
|
const testOptions = (timeout) => typeof timeout === "number" ? { timeout } : timeout ?? {};
|
|
38
46
|
const hookTimeout = (timeout) => timeout === void 0 ? void 0 : Duration$1.toMillis(Duration$1.fromInputUnsafe(timeout));
|
|
47
|
+
const propertyTestOptions = (timeout) => typeof timeout === "number" ? void 0 : timeout;
|
|
48
|
+
const checkOptions = (timeout) => propertyTestOptions(timeout)?.arbitrary;
|
|
49
|
+
const compileArbitraryInput = (input) => Arbitrary.isArbitrary(input) ? input : Arbitrary.schema(input);
|
|
50
|
+
const makeArbitrary = (arbitraries) => Arbitrary.all(Array.isArray(arbitraries) ? arbitraries.map(compileArbitraryInput) : Object.fromEntries(Object.entries(arbitraries).map(([key, input]) => [key, compileArbitraryInput(input)])));
|
|
51
|
+
const normalizeProperty = (property, value) => Effect$1.catchCause(Effect$1.suspend(() => {
|
|
52
|
+
const output = property(value);
|
|
53
|
+
return Effect$1.isEffect(output) ? output : Effect$1.succeed(output);
|
|
54
|
+
}), (cause) => Cause$1.hasInterrupts(cause) ? Effect$1.failCause(cause) : Effect$1.fail(cause));
|
|
55
|
+
const runCheck = (ctx, arbitrary, property, options) => runTest(ctx)(Effect$1.flatMapEager(Arbitrary.checkEffect(arbitrary, (value) => normalizeProperty(property, value), options), (result) => {
|
|
56
|
+
const failure = Arbitrary.formatCheckFailure(result);
|
|
57
|
+
return failure === void 0 ? Effect$1.void : Effect$1.die(new Error(failure));
|
|
58
|
+
}));
|
|
59
|
+
const withFixturesOf = (source, f) => Object.defineProperty(f, "toString", { value: () => source.toString() });
|
|
39
60
|
const makeItProxy = (it, overrides) => new Proxy(it, {
|
|
40
61
|
apply(target, thisArg, argArray) {
|
|
41
62
|
return Reflect.apply(target, thisArg, argArray);
|
|
@@ -53,77 +74,44 @@ const collectTasks = (tasks, acc = []) => {
|
|
|
53
74
|
}
|
|
54
75
|
return acc;
|
|
55
76
|
};
|
|
56
|
-
|
|
57
|
-
const
|
|
77
|
+
const registerProp = (it, name, arbitraries, property, timeout) => {
|
|
78
|
+
const arbitrary = makeArbitrary(arbitraries);
|
|
79
|
+
it(name, testOptions(timeout), withFixturesOf(() => {}, (ctx) => runCheck(ctx, arbitrary, (values) => property(values, ctx), checkOptions(timeout))));
|
|
80
|
+
};
|
|
81
|
+
const makeTester = (mapEffect, it) => {
|
|
58
82
|
const run = (ctx, args, self) => pipe$1(Effect$1.suspend(() => self(...args)), mapEffect, runTest(ctx));
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (Array.isArray(arbitraries)) {
|
|
68
|
-
const arbs = arbitraries.map((arbitrary) => {
|
|
69
|
-
if (Schema$1.isSchema(arbitrary)) return Schema$1.toArbitrary(arbitrary)(fc);
|
|
70
|
-
return arbitrary;
|
|
71
|
-
});
|
|
72
|
-
return it(name, testOptions(timeout), (ctx) => fc.assert(fc.asyncProperty(...arbs, (...as) => run(ctx, [as, ctx], self)), isObject(timeout) ? timeout?.fastCheck : {}));
|
|
73
|
-
}
|
|
74
|
-
const arbs = fc.record(Object.keys(arbitraries).reduce(function(result, key) {
|
|
75
|
-
const arb = arbitraries[key];
|
|
76
|
-
Rec.assignProperty(result, key, Schema$1.isSchema(arb) ? Schema$1.toArbitrary(arb)(fc) : arb);
|
|
77
|
-
return result;
|
|
78
|
-
}, {}));
|
|
79
|
-
return it(name, testOptions(timeout), (ctx) => fc.assert(fc.asyncProperty(arbs, (...as) => run(ctx, [as[0], ctx], self)), isObject(timeout) ? timeout?.fastCheck : {}));
|
|
80
|
-
};
|
|
81
|
-
return Object.assign(f, {
|
|
82
|
-
skip,
|
|
83
|
-
skipIf,
|
|
84
|
-
runIf,
|
|
85
|
-
only,
|
|
83
|
+
const register = (test) => (name, self, timeout) => test(name, testOptions(timeout), withFixturesOf(self, (ctx) => run(ctx, [ctx], self)));
|
|
84
|
+
const each = (cases) => (name, self, timeout) => it.for(cases)(name, testOptions(timeout), withFixturesOf(self, (args, ctx) => run(ctx, [args, ctx], self).then(constVoid)));
|
|
85
|
+
const prop = (name, arbitraries, self, timeout) => registerProp(it, name, arbitraries, (values, ctx) => Effect$1.mapEager(mapEffect(Effect$1.suspend(() => self(values, ctx))), (value) => value !== false), timeout);
|
|
86
|
+
return Object.assign(register(it), {
|
|
87
|
+
skip: register(it.skip),
|
|
88
|
+
skipIf: (condition) => register(it.skipIf(condition)),
|
|
89
|
+
runIf: (condition) => register(it.runIf(condition)),
|
|
90
|
+
only: register(it.only),
|
|
86
91
|
each,
|
|
87
|
-
fails,
|
|
92
|
+
fails: register(it.fails),
|
|
88
93
|
prop
|
|
89
94
|
});
|
|
90
95
|
};
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
if (Array.isArray(arbitraries)) {
|
|
94
|
-
const arbs = arbitraries.map((arbitrary) => {
|
|
95
|
-
if (Schema$1.isSchema(arbitrary)) throw new Error("Schemas are not supported yet");
|
|
96
|
-
return arbitrary;
|
|
97
|
-
});
|
|
98
|
-
return it$1(name, testOptions(timeout), (ctx) => fc.assert(fc.property(...arbs, (...as) => self(as, ctx)), isObject(timeout) ? timeout?.fastCheck : {}));
|
|
99
|
-
}
|
|
100
|
-
const arbs = fc.record(Object.keys(arbitraries).reduce(function(result, key) {
|
|
101
|
-
const arb = arbitraries[key];
|
|
102
|
-
if (Schema$1.isSchema(arb)) throw new Error("Schemas are not supported yet");
|
|
103
|
-
Rec.assignProperty(result, key, arb);
|
|
104
|
-
return result;
|
|
105
|
-
}, {}));
|
|
106
|
-
return it$1(name, testOptions(timeout), (ctx) => fc.assert(fc.property(arbs, (as) => self(as, ctx)), isObject(timeout) ? timeout?.fastCheck : {}));
|
|
107
|
-
};
|
|
108
|
-
/** @internal */
|
|
109
|
-
const layer = (layer_, options) => (...args) => {
|
|
96
|
+
const makeProp = (it) => (name, arbitraries, self, timeout) => registerProp(it, name, arbitraries, (values, ctx) => self(values, ctx) !== false, timeout);
|
|
97
|
+
const makeLayer = (it) => (layer_, options) => (...args) => {
|
|
110
98
|
const excludeTestServices = options?.excludeTestServices ?? false;
|
|
111
99
|
const withTestEnv = excludeTestServices ? layer_ : Layer$1.provideMerge(layer_, TestEnv);
|
|
112
100
|
const memoMap = options?.memoMap ?? Effect$1.runSync(Layer$1.makeMemoMap);
|
|
113
101
|
const scope = Effect$1.runSync(Scope$1.make());
|
|
114
102
|
const contextEffect = Layer$1.buildWithMemoMap(withTestEnv, memoMap, scope).pipe(Effect$1.orDie, Effect$1.cached, Effect$1.runSync);
|
|
115
103
|
let closed = false;
|
|
116
|
-
const closeScope = (
|
|
104
|
+
const closeScope = () => {
|
|
117
105
|
if (closed) return Promise.resolve();
|
|
118
106
|
closed = true;
|
|
119
|
-
return runPromise(Scope$1.close(scope, Exit.void)
|
|
107
|
+
return runPromise(Scope$1.close(scope, Exit.void));
|
|
120
108
|
};
|
|
121
|
-
const
|
|
109
|
+
const methods = makeItProxy(it, {
|
|
122
110
|
effect: makeTester((effect) => Effect$1.flatMap(contextEffect, (context) => effect.pipe(Effect$1.scoped, Effect$1.provide(context))), it),
|
|
123
|
-
prop,
|
|
111
|
+
prop: makeProp(it),
|
|
124
112
|
flakyTest,
|
|
125
113
|
layer(nestedLayer, options) {
|
|
126
|
-
return
|
|
114
|
+
return makeLayer(it)(Layer$1.provideMerge(nestedLayer, withTestEnv), {
|
|
127
115
|
...options,
|
|
128
116
|
memoMap: Layer$1.forkMemoMapUnsafe(memoMap),
|
|
129
117
|
excludeTestServices
|
|
@@ -133,7 +121,7 @@ const layer = (layer_, options) => (...args) => {
|
|
|
133
121
|
if (args.length === 1) {
|
|
134
122
|
const currentSuite = getCurrentSuite();
|
|
135
123
|
const previousTasks = new Set(currentSuite.tasks);
|
|
136
|
-
args[0](
|
|
124
|
+
args[0](methods);
|
|
137
125
|
const blockTasks = collectTasks(currentSuite.tasks.filter((task) => !previousTasks.has(task)));
|
|
138
126
|
if (blockTasks.length === 0) {
|
|
139
127
|
afterAll(() => closeScope(), hookTimeout(options?.timeout));
|
|
@@ -141,21 +129,22 @@ const layer = (layer_, options) => (...args) => {
|
|
|
141
129
|
}
|
|
142
130
|
const blockTaskSet = new Set(blockTasks);
|
|
143
131
|
let remaining = blockTasks.length;
|
|
144
|
-
beforeEach((
|
|
145
|
-
if (!blockTaskSet.has(
|
|
146
|
-
|
|
132
|
+
beforeEach(({ onTestFinished, signal, task }) => {
|
|
133
|
+
if (!blockTaskSet.has(task)) return;
|
|
134
|
+
onTestFinished(() => {
|
|
147
135
|
remaining--;
|
|
148
|
-
if (remaining === 0) return closeScope(
|
|
136
|
+
if (remaining === 0) return closeScope();
|
|
149
137
|
});
|
|
150
|
-
return runPromise(Effect$1.asVoid(contextEffect),
|
|
138
|
+
return runPromise(Effect$1.asVoid(contextEffect), { signal });
|
|
151
139
|
}, hookTimeout(options?.timeout));
|
|
152
140
|
afterAll(() => closeScope(), hookTimeout(options?.timeout));
|
|
153
141
|
return;
|
|
154
142
|
}
|
|
155
|
-
|
|
143
|
+
const suiteOptions = options?.concurrent === void 0 ? {} : { concurrent: options.concurrent };
|
|
144
|
+
return describe(args[0], suiteOptions, () => {
|
|
156
145
|
beforeAll(() => runPromise(Effect$1.asVoid(contextEffect)), hookTimeout(options?.timeout));
|
|
157
146
|
afterAll(() => closeScope(), hookTimeout(options?.timeout));
|
|
158
|
-
return args[1](
|
|
147
|
+
return args[1](methods);
|
|
159
148
|
});
|
|
160
149
|
};
|
|
161
150
|
/** @internal */
|
|
@@ -165,20 +154,22 @@ const makeMethods = (it) => makeItProxy(it, {
|
|
|
165
154
|
effect: makeTester(flow(Effect$1.scoped, Effect$1.provide(TestEnv)), it),
|
|
166
155
|
live: makeTester(Effect$1.scoped, it),
|
|
167
156
|
flakyTest,
|
|
168
|
-
layer,
|
|
169
|
-
prop
|
|
157
|
+
layer: makeLayer(it),
|
|
158
|
+
prop: makeProp(it)
|
|
170
159
|
});
|
|
171
160
|
/** @internal */
|
|
172
|
-
const
|
|
161
|
+
const it$1 = /*#__PURE__*/ makeMethods(it$2);
|
|
162
|
+
/** @internal */
|
|
163
|
+
const { effect, layer, live, prop } = it$1;
|
|
173
164
|
//#endregion
|
|
174
|
-
//#region ../../../node_modules/.pnpm/@effect+vitest@4.0.
|
|
165
|
+
//#region ../../../node_modules/.pnpm/@effect+vitest@4.0.1_effect@4.0.1_vitest@5.0.3_@types+node@26.2.0_vite@7.3.2_@types+nod_65816e3353e20bfeb9b2e1494bdddeef/node_modules/@effect/vitest/dist/index.js
|
|
175
166
|
/**
|
|
176
167
|
* @since 4.0.0
|
|
177
168
|
*/
|
|
178
169
|
/**
|
|
179
170
|
* @since 4.0.0
|
|
180
171
|
*/
|
|
181
|
-
const it =
|
|
172
|
+
const it = it$1;
|
|
182
173
|
//#endregion
|
|
183
174
|
//#region src/realtimeSession.test.ts
|
|
184
175
|
const request = {
|
|
@@ -225,7 +216,7 @@ const transcript = (server, script, options) => Effect.gen(function* () {
|
|
|
225
216
|
const tags = (events) => events.map((e) => e._tag);
|
|
226
217
|
const audioPart = (bytes) => ({ inlineData: {
|
|
227
218
|
mimeType: "audio/pcm;rate=24000",
|
|
228
|
-
data:
|
|
219
|
+
data: Base64.encode(new Uint8Array(bytes))
|
|
229
220
|
} });
|
|
230
221
|
describe("Gemini live session turns", () => {
|
|
231
222
|
it.live("mints one turn id for a whole answer, and reports its usage at the end", () => Effect.gen(function* () {
|
|
@@ -494,7 +485,7 @@ describe("Gemini live session video", () => {
|
|
|
494
485
|
}));
|
|
495
486
|
const sent = yield* server.sent;
|
|
496
487
|
globalExpect(sent.filter((f) => f.realtimeInput?.video !== void 0)).toEqual([{ realtimeInput: { video: {
|
|
497
|
-
data:
|
|
488
|
+
data: Base64.encode(new Uint8Array([1, 2])),
|
|
498
489
|
mimeType: "image/jpeg"
|
|
499
490
|
} } }]);
|
|
500
491
|
}).pipe(Effect.scoped));
|