🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@ai-sdk/google

Package Overview
Dependencies
Maintainers
3
Versions
591
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ai-sdk/google - npm Package Compare versions

Comparing version
4.0.25
to
4.0.26
+31
src/get-realtime-base-url.ts
/**
* Returns the base URL for Live API (WebSocket) endpoints, stripping a
* trailing `/v1beta` or `/v1alpha` API version segment: Live API paths carry
* their own version and are not nested under it.
*/
export function getRealtimeBaseURL(baseURL: string): URL {
const url = new URL(baseURL);
const pathSegments = url.pathname.split('/');
const version = pathSegments.at(-1);
if (version === 'v1beta' || version === 'v1alpha') {
pathSegments.pop();
url.pathname = pathSegments.join('/') || '/';
}
return url;
}
/**
* Builds a `wss://` Live API WebSocket URL for the given bidi service path
* (e.g. `google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent`).
*/
export function getRealtimeWebSocketURL(
baseURL: string,
webSocketPath: string,
): URL {
const url = getRealtimeBaseURL(baseURL);
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
url.pathname = `${url.pathname.replace(/\/$/, '')}/ws/${webSocketPath}`;
return url;
}
import {
lazySchema,
zodSchema,
type InferSchema,
} from '@ai-sdk/provider-utils';
import { z } from 'zod/v4';
export type GoogleTranslationModelId =
| 'gemini-3.5-live-translate-preview'
| (string & {});
export const googleTranslationModelOptions = lazySchema(() =>
zodSchema(
z.object({
/**
* Whether input audio already in the target language should be echoed
* instead of producing silence.
*/
echoTargetLanguage: z.boolean().optional(),
}),
),
);
export type GoogleTranslationModelOptions = InferSchema<
typeof googleTranslationModelOptions
>;
import {
InvalidArgumentError,
type Experimental_SpeechTranslationModelV4 as TranslationModelV4,
type Experimental_SpeechTranslationModelV4StreamOptions as SpeechTranslationModelV4StreamOptions,
type Experimental_SpeechTranslationModelV4StreamPart as SpeechTranslationModelV4StreamPart,
type Experimental_SpeechTranslationModelV4Usage as SpeechTranslationModelV4Usage,
type SharedV4Warning,
} from '@ai-sdk/provider';
import {
connectToWebSocket,
combineHeaders,
convertBase64ToUint8Array,
convertToBase64,
parseProviderOptions,
safeParseJSON,
serializeModelOptions,
WORKFLOW_DESERIALIZE,
WORKFLOW_SERIALIZE,
waitForWebSocketBufferDrain,
type WebSocketConnection,
type WebSocketConstructor,
type WebSocketLike,
} from '@ai-sdk/provider-utils';
import { getModelPath } from '../get-model-path';
import { getRealtimeWebSocketURL } from '../get-realtime-base-url';
import {
googleTranslationModelOptions,
type GoogleTranslationModelId,
type GoogleTranslationModelOptions,
} from './google-translation-model-options';
const liveWebSocketPath =
'google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent';
/**
* After the input audio has ended, finish after this much trailing output
* silence. Live Translation is continuous and does not emit turnComplete.
*/
const defaultFinishGraceMs = 1000;
const googleLiveOutputAudioRate = 24000;
const pcm16SilenceAmplitudeThreshold = 128;
function getLiveWebSocketURL(baseURL: string, apiKey: string): URL {
const url = getRealtimeWebSocketURL(baseURL, liveWebSocketPath);
url.searchParams.set('key', apiKey);
return url;
}
type GoogleLiveTokensDetail = {
modality?: string;
tokenCount?: number;
};
type GoogleLiveServerMessage = {
setupComplete?: unknown;
serverContent?: {
modelTurn?: {
parts?: Array<{
inlineData?: { data?: string };
}>;
};
outputTranscription?: { text?: string };
inputTranscription?: { text?: string };
turnComplete?: boolean;
};
inputTranscription?: { text?: string };
usageMetadata?: {
promptTokensDetails?: GoogleLiveTokensDetail[];
responseTokensDetails?: GoogleLiveTokensDetail[];
};
error?: { message?: string };
};
export type GoogleTranslationModelConfig = {
provider: string;
baseURL: string;
headers: () => Record<string, string | undefined>;
webSocket?: WebSocketConstructor;
_internal?: {
currentDate?: () => Date;
finishGraceMs?: number;
};
};
export class GoogleTranslationModel implements TranslationModelV4 {
readonly specificationVersion = 'v4';
readonly modelId: GoogleTranslationModelId;
private readonly config: GoogleTranslationModelConfig;
static [WORKFLOW_SERIALIZE](model: GoogleTranslationModel) {
return serializeModelOptions({
modelId: model.modelId,
config: model.config,
});
}
static [WORKFLOW_DESERIALIZE](options: {
modelId: GoogleTranslationModelId;
config: GoogleTranslationModelConfig;
}) {
return new GoogleTranslationModel(options.modelId, options.config);
}
get provider(): string {
return this.config.provider;
}
constructor(
modelId: GoogleTranslationModelId,
config: GoogleTranslationModelConfig,
) {
this.modelId = modelId;
this.config = config;
}
async doStream(
options: SpeechTranslationModelV4StreamOptions,
): Promise<Awaited<ReturnType<TranslationModelV4['doStream']>>> {
if (options.targetLanguage == null) {
throw new InvalidArgumentError({
argument: 'targetLanguage',
message: `targetLanguage is required for translation model '${this.modelId}'.`,
});
}
const currentDate = this.config._internal?.currentDate?.() ?? new Date();
const googleOptions = await parseProviderOptions({
provider: 'google',
providerOptions: options.providerOptions,
schema: googleTranslationModelOptions,
});
const warnings: SharedV4Warning[] = [];
validateGoogleTranslationInputAudioFormat(options.inputAudioFormat);
if (options.sourceLanguage != null) {
warnings.push({
type: 'unsupported',
feature: 'sourceLanguage',
details:
'The Gemini Live translation API auto-detects the source language and does not accept a source language.',
});
}
if (options.outputAudioFormat != null) {
warnings.push({
type: 'unsupported',
feature: 'outputAudioFormat',
details:
'The Gemini Live API always outputs 24kHz 16-bit PCM audio and does not accept an output audio format.',
});
}
const headers = combineHeaders(this.config.headers(), options.headers);
// last case-variant wins: combineHeaders keeps case-distinct keys and
// spreads per-call headers after configuration headers
let apiKey: string | undefined;
for (const [key, value] of Object.entries(headers)) {
if (key.toLowerCase() === 'x-goog-api-key' && value != null) {
apiKey = value;
}
}
if (apiKey == null) {
throw new Error(
'Google Generative AI API key is required for streaming translation.',
);
}
const webSocketHeaders = Object.fromEntries(
Object.entries(headers).filter(
([key]) => key.toLowerCase() !== 'x-goog-api-key',
),
);
const setup = buildGoogleLiveTranslationSetup({
modelId: this.modelId,
targetLanguage: options.targetLanguage,
providerOptions: googleOptions,
});
return {
request: { body: setup },
response: {
timestamp: currentDate,
modelId: this.modelId,
},
stream: createGoogleLiveTranslationStream({
webSocket: this.config.webSocket,
url: getLiveWebSocketURL(this.config.baseURL, apiKey),
headers: webSocketHeaders,
setup,
inputAudioRate: options.inputAudioFormat.rate ?? 16000,
finishGraceMs:
this.config._internal?.finishGraceMs ?? defaultFinishGraceMs,
warnings,
audio: options.audio,
abortSignal: options.abortSignal,
includeRawChunks: options.includeRawChunks,
}),
};
}
}
function createGoogleLiveTranslationStream({
webSocket,
url,
headers,
setup,
inputAudioRate,
finishGraceMs,
warnings,
audio,
abortSignal,
includeRawChunks,
}: {
webSocket: WebSocketConstructor | undefined;
url: URL;
headers: Record<string, string | undefined>;
setup: unknown;
inputAudioRate: number;
finishGraceMs: number;
warnings: SharedV4Warning[];
audio: ReadableStream<Uint8Array | string>;
abortSignal: AbortSignal | undefined;
includeRawChunks: boolean | undefined;
}) {
let finished = false;
let cleanup: (closeCode?: number) => void = () => {};
return new ReadableStream<SpeechTranslationModelV4StreamPart>({
start: controller => {
let audioReader:
| ReadableStreamDefaultReader<Uint8Array | string>
| undefined;
let connection: WebSocketConnection | undefined;
// The Live API contract requires waiting for the `setupComplete`
// server message before sending realtime input: the audio send loop
// is gated on this promise.
let resolveSetupComplete!: () => void;
const setupComplete = new Promise<void>(resolve => {
resolveSetupComplete = resolve;
});
// Google Live messages carry no response/item IDs; a turn counter
// generates consistent synthetic IDs (like the realtime event mapper).
let turnCounter = 0;
// Transcription fragments arrive incrementally and are accumulated per
// turn; `turnComplete` finalizes the current turn.
let sourceText = '';
let sourceTurnBuffer = '';
let translationText = '';
let translationTurnBuffer = '';
let audioEnded = false;
let usage: SpeechTranslationModelV4Usage | undefined;
// Live Translation is a continuous pipeline rather than a turn-based
// model. After audioStreamEnd it keeps sending PCM silence indefinitely
// and does not emit turnComplete. Drain translated speech, then finish
// after enough trailing silence. Keep turnComplete handling as a
// fallback for compatible server implementations and test doubles.
let openTurn = false;
let sawTurnComplete = false;
let trailingSilenceMs = 0;
let finishTimer: ReturnType<typeof setTimeout> | undefined;
const itemId = () => `google-item-${turnCounter}`;
const cancelPendingFinish = () => {
if (finishTimer != null) {
clearTimeout(finishTimer);
finishTimer = undefined;
}
};
const schedulePendingFinish = () => {
if (finished || finishTimer != null) return;
finishTimer = setTimeout(() => {
finishTimer = undefined;
finish();
}, finishGraceMs);
};
const onTurnActivity = () => {
openTurn = true;
trailingSilenceMs = 0;
cancelPendingFinish();
};
cleanup = (closeCode?: number) => {
cancelPendingFinish();
if (audioReader != null) {
void audioReader.cancel().catch(() => {});
} else {
// pre-open failure or abort: cancel the caller's audio stream so an
// upstream producer piping into it does not hang:
void audio.cancel().catch(() => {});
}
connection?.close(closeCode);
};
const finishWithError = (error: unknown) => {
if (finished) return;
finished = true;
cleanup();
controller.error(error);
};
const finish = () => {
if (finished) return;
if (sourceTurnBuffer !== '' || translationTurnBuffer !== '') {
completeTurn();
}
finished = true;
controller.enqueue({
type: 'finish',
sourceText,
outputText: translationText,
usage,
});
controller.close();
cleanup(1000);
};
const completeTurn = () => {
if (sourceTurnBuffer !== '') {
controller.enqueue({
type: 'source-transcript-final',
id: itemId(),
text: sourceTurnBuffer,
});
sourceText += sourceTurnBuffer;
sourceTurnBuffer = '';
}
if (translationTurnBuffer !== '') {
controller.enqueue({
type: 'output-text-final',
id: itemId(),
text: translationTurnBuffer,
});
translationText += translationTurnBuffer;
translationTurnBuffer = '';
}
turnCounter++;
};
const sendAudio = async (socket: WebSocketLike) => {
audioReader = audio.getReader();
try {
while (true) {
const { done, value } = await audioReader.read();
if (done || finished) break;
socket.send(
JSON.stringify({
realtimeInput: {
audio: {
data: convertToBase64(value),
mimeType: `audio/pcm;rate=${inputAudioRate}`,
},
},
}),
);
// backpressure: pause reads while the socket buffer is full
await waitForWebSocketBufferDrain(socket);
}
} finally {
audioReader.releaseLock();
// unlocked again: cleanup must cancel `audio`, not the reader
audioReader = undefined;
}
if (!finished) {
socket.send(
JSON.stringify({ realtimeInput: { audioStreamEnd: true } }),
);
audioEnded = true;
// a turnComplete already received after the final audio chunk
// satisfies the finish condition:
if (sawTurnComplete && !openTurn) {
schedulePendingFinish();
}
}
};
connection = connectToWebSocket({
url,
headers,
webSocket,
abortSignal,
onAbort: finishWithError,
onProcessingError: finishWithError,
onOpen: socket => {
controller.enqueue({ type: 'stream-start', warnings });
socket.send(JSON.stringify({ setup }));
// audio may only be sent after the server acknowledged the setup:
void setupComplete
.then(() => (finished ? undefined : sendAudio(socket)))
.catch(finishWithError);
},
onMessageText: async text => {
if (finished) return;
const parsed = await safeParseJSON({ text });
if (!parsed.success) return;
const message = parsed.value as GoogleLiveServerMessage;
if (includeRawChunks) {
controller.enqueue({ type: 'raw', rawValue: message });
}
if (message.setupComplete != null) {
resolveSetupComplete();
}
if (message.usageMetadata != null) {
usage = accumulateGoogleLiveUsage(usage, message.usageMetadata);
}
if (message.error != null) {
finishWithError(
new Error(message.error.message ?? 'Google Live API error'),
);
return;
}
const inputTranscriptionText =
message.serverContent?.inputTranscription?.text ??
message.inputTranscription?.text;
if (inputTranscriptionText) {
onTurnActivity();
sourceTurnBuffer += inputTranscriptionText;
controller.enqueue({
type: 'source-transcript-delta',
id: itemId(),
delta: inputTranscriptionText,
});
}
const serverContent = message.serverContent;
if (serverContent == null) {
return;
}
for (const part of serverContent.modelTurn?.parts ?? []) {
if (part.inlineData?.data) {
controller.enqueue({
type: 'audio',
id: itemId(),
audio: part.inlineData.data,
});
const silenceDurationMs = getPcm16SilenceDurationMs(
part.inlineData.data,
);
if (audioEnded && silenceDurationMs != null) {
trailingSilenceMs += silenceDurationMs;
if (trailingSilenceMs >= finishGraceMs) {
finish();
return;
}
} else {
onTurnActivity();
}
}
}
if (serverContent.outputTranscription?.text) {
onTurnActivity();
translationTurnBuffer += serverContent.outputTranscription.text;
controller.enqueue({
type: 'output-text-delta',
id: itemId(),
delta: serverContent.outputTranscription.text,
});
}
if (serverContent.turnComplete) {
completeTurn();
openTurn = false;
sawTurnComplete = true;
if (audioEnded) {
schedulePendingFinish();
}
}
},
onSocketError: () => {
finishWithError(new Error('Google Live translation error'));
},
onClose: ({ code, reason }) => {
if (finished) return;
// a close while a finish is pending confirms that no further turn
// activity follows:
if (finishTimer != null) {
finish();
return;
}
// a close before the finish condition was reached is an abnormal
// termination: surface the close diagnostics
finishWithError(
new Error(
`Google Live translation WebSocket closed unexpectedly before finishing` +
` (code ${code ?? 'unknown'}${reason ? `, reason: ${reason}` : ''}).`,
),
);
},
});
},
cancel: () => {
if (finished) return;
finished = true;
cleanup();
},
});
}
function accumulateGoogleLiveUsage(
usage: SpeechTranslationModelV4Usage | undefined,
usageMetadata: {
promptTokensDetails?: GoogleLiveTokensDetail[];
responseTokensDetails?: GoogleLiveTokensDetail[];
},
): SpeechTranslationModelV4Usage | undefined {
let inputAudioTokens = usage?.inputAudioTokens;
let outputAudioTokens = usage?.outputAudioTokens;
// Live Translation emits periodic usage deltas. Its TEXT prompt detail is
// internal translation context (the public input is audio-only), so only
// aggregate the billable input/output audio modalities.
for (const detail of usageMetadata.promptTokensDetails ?? []) {
if (detail.modality === 'AUDIO' && detail.tokenCount != null) {
inputAudioTokens = (inputAudioTokens ?? 0) + detail.tokenCount;
}
}
for (const detail of usageMetadata.responseTokensDetails ?? []) {
if (detail.modality === 'AUDIO' && detail.tokenCount != null) {
outputAudioTokens = (outputAudioTokens ?? 0) + detail.tokenCount;
}
}
if (inputAudioTokens == null && outputAudioTokens == null) {
return usage;
}
return {
...usage,
...(inputAudioTokens != null ? { inputAudioTokens } : {}),
...(outputAudioTokens != null ? { outputAudioTokens } : {}),
};
}
function getPcm16SilenceDurationMs(audio: string): number | undefined {
let bytes: Uint8Array;
try {
bytes = convertBase64ToUint8Array(audio);
} catch {
return undefined;
}
if (bytes.byteLength < 2) {
return undefined;
}
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
const sampleCount = Math.floor(bytes.byteLength / 2);
for (let i = 0; i < sampleCount; i++) {
if (Math.abs(view.getInt16(i * 2, true)) > pcm16SilenceAmplitudeThreshold) {
return undefined;
}
}
return (sampleCount / googleLiveOutputAudioRate) * 1000;
}
function buildGoogleLiveTranslationSetup({
modelId,
targetLanguage,
providerOptions,
}: {
modelId: string;
targetLanguage: string;
providerOptions: GoogleTranslationModelOptions | undefined;
}) {
return {
model: getModelPath(modelId),
generationConfig: {
responseModalities: ['AUDIO'],
translationConfig: {
targetLanguageCode: targetLanguage,
...(providerOptions?.echoTargetLanguage != null
? { echoTargetLanguage: providerOptions.echoTargetLanguage }
: {}),
},
},
inputAudioTranscription: {},
outputAudioTranscription: {},
};
}
function validateGoogleTranslationInputAudioFormat(
inputAudioFormat: SpeechTranslationModelV4StreamOptions['inputAudioFormat'],
) {
if (
inputAudioFormat.type !== 'audio/pcm' ||
(inputAudioFormat.rate != null && inputAudioFormat.rate !== 16000)
) {
throw new InvalidArgumentError({
argument: 'inputAudioFormat',
message:
'The Gemini Live translation API only supports 16kHz 16-bit PCM input audio.',
});
}
}
+50
-3
import * as _ai_sdk_provider_utils from '@ai-sdk/provider-utils';
import { InferSchema, FetchFunction } from '@ai-sdk/provider-utils';
import { ProviderV4, LanguageModelV4, ImageModelV4, EmbeddingModelV4, Experimental_VideoModelV4, SpeechModelV4, FilesV4, Experimental_RealtimeFactoryV4, Experimental_RealtimeModelV4, Experimental_RealtimeModelV4ClientSecretOptions, Experimental_RealtimeModelV4ClientSecretResult, Experimental_RealtimeModelV4ServerEvent, Experimental_RealtimeModelV4ClientEvent, Experimental_RealtimeModelV4SessionConfig } from '@ai-sdk/provider';
import { InferSchema, FetchFunction, WebSocketConstructor, WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from '@ai-sdk/provider-utils';
import * as _ai_sdk_provider from '@ai-sdk/provider';
import { ProviderV4, LanguageModelV4, ImageModelV4, EmbeddingModelV4, Experimental_VideoModelV4, Experimental_SpeechTranslationModelV4, SpeechModelV4, FilesV4, Experimental_RealtimeFactoryV4, Experimental_RealtimeModelV4, Experimental_RealtimeModelV4ClientSecretOptions, Experimental_RealtimeModelV4ClientSecretResult, Experimental_RealtimeModelV4ServerEvent, Experimental_RealtimeModelV4ClientEvent, Experimental_RealtimeModelV4SessionConfig, Experimental_SpeechTranslationModelV4StreamOptions } from '@ai-sdk/provider';

@@ -513,2 +514,8 @@ declare const googleErrorDataSchema: _ai_sdk_provider_utils.LazySchema<{

type GoogleTranslationModelId = 'gemini-3.5-live-translate-preview' | (string & {});
declare const googleTranslationModelOptions: _ai_sdk_provider_utils.LazySchema<{
echoTargetLanguage?: boolean | undefined;
}>;
type GoogleTranslationModelOptions = InferSchema<typeof googleTranslationModelOptions>;
interface GoogleProvider extends ProviderV4 {

@@ -551,2 +558,10 @@ (modelId: GoogleModelId): LanguageModelV4;

/**
* Creates an experimental model for streaming speech translation.
*/
translation(modelId: GoogleTranslationModelId): Experimental_SpeechTranslationModelV4;
/**
* Creates an experimental model for streaming speech translation.
*/
speechTranslationModel(modelId: GoogleTranslationModelId): Experimental_SpeechTranslationModelV4;
/**
* Creates a model for speech generation (text-to-speech).

@@ -601,2 +616,7 @@ */

/**
* Custom WebSocket implementation. This is useful for testing or for
* runtimes that need a WebSocket constructor with header support.
*/
webSocket?: WebSocketConstructor;
/**
* Custom provider name

@@ -664,4 +684,31 @@ * Defaults to 'google.generative-ai'.

type GoogleTranslationModelConfig = {
provider: string;
baseURL: string;
headers: () => Record<string, string | undefined>;
webSocket?: WebSocketConstructor;
_internal?: {
currentDate?: () => Date;
finishGraceMs?: number;
};
};
declare class GoogleTranslationModel implements Experimental_SpeechTranslationModelV4 {
readonly specificationVersion = "v4";
readonly modelId: GoogleTranslationModelId;
private readonly config;
static [WORKFLOW_SERIALIZE](model: GoogleTranslationModel): {
modelId: string;
config: _ai_sdk_provider.JSONObject;
};
static [WORKFLOW_DESERIALIZE](options: {
modelId: GoogleTranslationModelId;
config: GoogleTranslationModelConfig;
}): GoogleTranslationModel;
get provider(): string;
constructor(modelId: GoogleTranslationModelId, config: GoogleTranslationModelConfig);
doStream(options: Experimental_SpeechTranslationModelV4StreamOptions): Promise<Awaited<ReturnType<Experimental_SpeechTranslationModelV4['doStream']>>>;
}
declare const VERSION: string;
export { GoogleRealtimeModel as Experimental_GoogleRealtimeModel, type GoogleRealtimeModelConfig as Experimental_GoogleRealtimeModelConfig, type GoogleRealtimeModelId as Experimental_GoogleRealtimeModelId, type GoogleRealtimeModelOptions as Experimental_GoogleRealtimeModelOptions, type GoogleEmbeddingModelOptions, type GoogleErrorData, type GoogleFilesUploadOptions, type GoogleEmbeddingModelOptions as GoogleGenerativeAIEmbeddingProviderOptions, type GoogleImageModelOptions as GoogleGenerativeAIImageProviderOptions, type GoogleProvider as GoogleGenerativeAIProvider, type GoogleProviderMetadata as GoogleGenerativeAIProviderMetadata, type GoogleLanguageModelOptions as GoogleGenerativeAIProviderOptions, type GoogleProviderSettings as GoogleGenerativeAIProviderSettings, type GoogleVideoModelId as GoogleGenerativeAIVideoModelId, type GoogleVideoModelOptions as GoogleGenerativeAIVideoProviderOptions, type GoogleImageModelOptions, type GoogleInteractionsAgentName, type GoogleInteractionsModelId, type GoogleInteractionsProviderMetadata, type GoogleLanguageModelInteractionsOptions, type GoogleLanguageModelOptions, type GoogleProvider, type GoogleProviderMetadata, type GoogleProviderSettings, type GoogleSpeechModelId, type GoogleSpeechModelOptions, type GoogleVideoModelId, type GoogleVideoModelOptions, VERSION, createGoogle, createGoogle as createGoogleGenerativeAI, google };
export { GoogleRealtimeModel as Experimental_GoogleRealtimeModel, type GoogleRealtimeModelConfig as Experimental_GoogleRealtimeModelConfig, type GoogleRealtimeModelId as Experimental_GoogleRealtimeModelId, type GoogleRealtimeModelOptions as Experimental_GoogleRealtimeModelOptions, GoogleTranslationModel as Experimental_GoogleTranslationModel, type GoogleTranslationModelConfig as Experimental_GoogleTranslationModelConfig, type GoogleTranslationModelId as Experimental_GoogleTranslationModelId, type GoogleTranslationModelOptions as Experimental_GoogleTranslationModelOptions, type GoogleEmbeddingModelOptions, type GoogleErrorData, type GoogleFilesUploadOptions, type GoogleEmbeddingModelOptions as GoogleGenerativeAIEmbeddingProviderOptions, type GoogleImageModelOptions as GoogleGenerativeAIImageProviderOptions, type GoogleProvider as GoogleGenerativeAIProvider, type GoogleProviderMetadata as GoogleGenerativeAIProviderMetadata, type GoogleLanguageModelOptions as GoogleGenerativeAIProviderOptions, type GoogleProviderSettings as GoogleGenerativeAIProviderSettings, type GoogleVideoModelId as GoogleGenerativeAIVideoModelId, type GoogleVideoModelOptions as GoogleGenerativeAIVideoProviderOptions, type GoogleImageModelOptions, type GoogleInteractionsAgentName, type GoogleInteractionsModelId, type GoogleInteractionsProviderMetadata, type GoogleLanguageModelInteractionsOptions, type GoogleLanguageModelOptions, type GoogleProvider, type GoogleProviderMetadata, type GoogleProviderSettings, type GoogleSpeechModelId, type GoogleSpeechModelOptions, type GoogleVideoModelId, type GoogleVideoModelOptions, VERSION, createGoogle, createGoogle as createGoogleGenerativeAI, google };
+2
-2
{
"name": "@ai-sdk/google",
"version": "4.0.25",
"version": "4.0.26",
"type": "module",

@@ -39,3 +39,3 @@ "license": "Apache-2.0",

"@ai-sdk/provider": "4.0.4",
"@ai-sdk/provider-utils": "5.0.13"
"@ai-sdk/provider-utils": "5.0.14"
},

@@ -42,0 +42,0 @@ "devDependencies": {

@@ -11,2 +11,3 @@ import type {

SpeechModelV4,
Experimental_SpeechTranslationModelV4 as SpeechTranslationModelV4,
} from '@ai-sdk/provider';

@@ -19,2 +20,3 @@ import {

type FetchFunction,
type WebSocketConstructor,
} from '@ai-sdk/provider-utils';

@@ -45,2 +47,4 @@ import { VERSION } from './version';

import { GoogleRealtimeModel } from './realtime/google-realtime-model';
import { GoogleTranslationModel } from './translation/google-translation-model';
import type { GoogleTranslationModelId } from './translation/google-translation-model-options';

@@ -98,2 +102,14 @@ export interface GoogleProvider extends ProviderV4 {

/**
* Creates an experimental model for streaming speech translation.
*/
translation(modelId: GoogleTranslationModelId): SpeechTranslationModelV4;
/**
* Creates an experimental model for streaming speech translation.
*/
speechTranslationModel(
modelId: GoogleTranslationModelId,
): SpeechTranslationModelV4;
/**
* Creates a model for speech generation (text-to-speech).

@@ -160,2 +176,8 @@ */

/**
* Custom WebSocket implementation. This is useful for testing or for
* runtimes that need a WebSocket constructor with header support.
*/
webSocket?: WebSocketConstructor;
/**
* Custom provider name

@@ -296,2 +318,10 @@ * Defaults to 'google.generative-ai'.

const createTranslationModel = (modelId: GoogleTranslationModelId) =>
new GoogleTranslationModel(modelId, {
provider: `${providerName}.translation`,
baseURL,
headers: getHeaders,
webSocket: options.webSocket,
});
const createSpeechModel = (modelId: GoogleSpeechModelId) =>

@@ -367,2 +397,4 @@ new GoogleSpeechModel(modelId, {

provider.speechModel = createSpeechModel;
provider.translation = createTranslationModel;
provider.speechTranslationModel = createTranslationModel;
provider.interactions = createInteractionsModel;

@@ -369,0 +401,0 @@ provider.tools = googleTools;

@@ -63,3 +63,9 @@ export type { GoogleErrorData } from './google-error';

} from './realtime/google-realtime-model-options';
export { GoogleTranslationModel as Experimental_GoogleTranslationModel } from './translation/google-translation-model';
export type { GoogleTranslationModelConfig as Experimental_GoogleTranslationModelConfig } from './translation/google-translation-model';
export type {
GoogleTranslationModelId as Experimental_GoogleTranslationModelId,
GoogleTranslationModelOptions as Experimental_GoogleTranslationModelOptions,
} from './translation/google-translation-model-options';
export { VERSION } from './version';

@@ -11,2 +11,6 @@ import type {

import {
getRealtimeBaseURL,
getRealtimeWebSocketURL,
} from '../get-realtime-base-url';
import {
GoogleRealtimeEventMapper,

@@ -19,15 +23,2 @@ buildGoogleSessionConfig,

function getRealtimeBaseURL(baseURL: string): URL {
const url = new URL(baseURL);
const pathSegments = url.pathname.split('/');
const version = pathSegments.at(-1);
if (version === 'v1beta' || version === 'v1alpha') {
pathSegments.pop();
url.pathname = pathSegments.join('/') || '/';
}
return url;
}
function getAuthTokensURL(baseURL: string): string {

@@ -40,6 +31,3 @@ const url = getRealtimeBaseURL(baseURL);

function getWebSocketURL(baseURL: string): string {
const url = getRealtimeBaseURL(baseURL);
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
url.pathname = `${url.pathname.replace(/\/$/, '')}/ws/${realtimeWebSocketPath}`;
return url.toString();
return getRealtimeWebSocketURL(baseURL, realtimeWebSocketPath).toString();
}

@@ -46,0 +34,0 @@

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet