@zhivex-ai/vertex 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +995 -10
- package/dist/anthropic.d.ts +13 -4
- package/dist/anthropic.d.ts.map +1 -1
- package/dist/anthropic.js +86 -16
- package/dist/anthropic.js.map +1 -1
- package/dist/capabilities.d.ts +2 -1
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +12 -3
- package/dist/capabilities.js.map +1 -1
- package/dist/chat-profiles.d.ts +8 -0
- package/dist/chat-profiles.d.ts.map +1 -0
- package/dist/chat-profiles.js +61 -0
- package/dist/chat-profiles.js.map +1 -0
- package/dist/chat.d.ts +10 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +120 -0
- package/dist/chat.js.map +1 -0
- package/dist/embeddings.d.ts +71 -0
- package/dist/embeddings.d.ts.map +1 -0
- package/dist/embeddings.js +166 -0
- package/dist/embeddings.js.map +1 -0
- package/dist/endpoints.d.ts +76 -0
- package/dist/endpoints.d.ts.map +1 -0
- package/dist/endpoints.js +152 -0
- package/dist/endpoints.js.map +1 -0
- package/dist/grpc.d.ts +33 -0
- package/dist/grpc.d.ts.map +1 -0
- package/dist/grpc.js +161 -0
- package/dist/grpc.js.map +1 -0
- package/dist/index.d.ts +41 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +654 -293
- package/dist/index.js.map +1 -1
- package/dist/inline-thinking.d.ts +3 -0
- package/dist/inline-thinking.d.ts.map +1 -0
- package/dist/inline-thinking.js +106 -0
- package/dist/inline-thinking.js.map +1 -0
- package/dist/interactions.d.ts +21 -0
- package/dist/interactions.d.ts.map +1 -0
- package/dist/interactions.js +240 -0
- package/dist/interactions.js.map +1 -0
- package/dist/multimodal-embeddings.d.ts +53 -0
- package/dist/multimodal-embeddings.d.ts.map +1 -0
- package/dist/multimodal-embeddings.js +135 -0
- package/dist/multimodal-embeddings.js.map +1 -0
- package/dist/responses.d.ts +3 -0
- package/dist/responses.d.ts.map +1 -0
- package/dist/responses.js +120 -0
- package/dist/responses.js.map +1 -0
- package/dist/specialized.d.ts +14 -0
- package/dist/specialized.d.ts.map +1 -0
- package/dist/specialized.js +117 -0
- package/dist/specialized.js.map +1 -0
- package/dist/tensors.d.ts +22 -0
- package/dist/tensors.d.ts.map +1 -0
- package/dist/tensors.js +49 -0
- package/dist/tensors.js.map +1 -0
- package/dist/token-counting.d.ts +37 -0
- package/dist/token-counting.d.ts.map +1 -0
- package/dist/token-counting.js +33 -0
- package/dist/token-counting.js.map +1 -0
- package/dist/transcription.d.ts +60 -0
- package/dist/transcription.d.ts.map +1 -0
- package/dist/transcription.js +88 -0
- package/dist/transcription.js.map +1 -0
- package/dist/virtual-try-on.d.ts +23 -0
- package/dist/virtual-try-on.d.ts.map +1 -0
- package/dist/virtual-try-on.js +80 -0
- package/dist/virtual-try-on.js.map +1 -0
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -1,8 +1,31 @@
|
|
|
1
|
+
import { normalizeVertexTranscriptionConfig, transcriptionRequest, transcriptionResponse } from "./transcription.js";
|
|
2
|
+
import { createVertexVirtualTryOnClient } from "./virtual-try-on.js";
|
|
3
|
+
import { createVertexEndpointsClient } from "./endpoints.js";
|
|
4
|
+
import { createVertexGrpcClient } from "./grpc.js";
|
|
5
|
+
import { createVertexResponsesModel } from "./responses.js";
|
|
6
|
+
import { VertexLegacyMultimodalEmbeddingModel } from "./multimodal-embeddings.js";
|
|
7
|
+
import { createVertexInteractionsClient } from "./interactions.js";
|
|
8
|
+
import { createVertexSpecializedClients } from "./specialized.js";
|
|
9
|
+
import { VertexEmbeddingModel, VertexOpenEmbeddingModel } from "./embeddings.js";
|
|
10
|
+
import { createVertexChatModel, isVertexChatModel } from "./chat.js";
|
|
1
11
|
import { createVertexClaudeModel } from "./anthropic.js";
|
|
12
|
+
import { createVertexClaudeClient } from "./token-counting.js";
|
|
2
13
|
import { GoogleAuth } from "google-auth-library";
|
|
3
14
|
import { toJSONSchema } from "zod";
|
|
4
|
-
import { capabilities, groundedCapabilities, imageGenerationCapabilities, isGeminiLiveTranslateModel, musicGenerationCapabilities, realtimeCapabilities, speechCapabilities, transcriptionCapabilities, videoGenerationCapabilities, } from "./capabilities.js";
|
|
5
|
-
import { CallbackRealtimeSession, ConfigurationError, ProviderHTTPError, UnsupportedFeatureError, assertTrustedEndpoint, createMcpToolSet, createProviderAdapter, decodeBase64WithLimit, encodeAudioFrame, encodeMediaFrame, isCallableToolDefinition, isHostedToolDefinition, normalizeFinishReason, openWebSocketConnection, readErrorBodyWithLimit, readJsonWithLimit, streamSSE, toToolSet, toolResultPayload, unsupportedBrowserToken, withRetry, withTimeoutSignal } from "@zhivex-ai/core";
|
|
15
|
+
import { capabilities, groundedCapabilities, imageGenerationCapabilities, isGeminiLiveTranslateModel, isVertexLiveTranscribeModel, musicGenerationCapabilities, realtimeCapabilities, speechCapabilities, transcriptionCapabilities, videoGenerationCapabilities, } from "./capabilities.js";
|
|
16
|
+
import { CallbackRealtimeSession, ConfigurationError, ProviderHTTPError, UnsupportedFeatureError, assertTrustedEndpoint, createMcpToolSet, createProviderAdapter, decodeBase64WithLimit, encodeAudioFrame, encodeMediaFrame, isCallableToolDefinition, isHostedToolDefinition, normalizeFinishReason, openWebSocketConnection, readErrorBodyWithLimit, readJsonWithLimit, streamSSE, toToolSet, toolResultPayload, unsupportedBrowserToken, withRetry, withTimeoutSignal } from "@zhivex-ai/core/provider";
|
|
17
|
+
const vertexApiHost = (location) => location === "global"
|
|
18
|
+
? "aiplatform.googleapis.com"
|
|
19
|
+
: location === "us" || location === "eu"
|
|
20
|
+
? `aiplatform.${location}.rep.googleapis.com`
|
|
21
|
+
: `${encodeVertexPathSegment(location, "Vertex location")}-aiplatform.googleapis.com`;
|
|
22
|
+
// Resource names returned by Google are already project-qualified.
|
|
23
|
+
const vertexResourceURL = (baseURL, name) => {
|
|
24
|
+
const encoded = encodeVertexResourceName(name, "Vertex resource name");
|
|
25
|
+
return name.startsWith("projects/")
|
|
26
|
+
? `${baseURL.replace(/\/projects\/.*$/, "")}/${encoded}`
|
|
27
|
+
: `${baseURL}/${encoded}`;
|
|
28
|
+
};
|
|
6
29
|
const encodeVertexPathSegment = (value, label) => {
|
|
7
30
|
if (!value || value === "." || value === ".." || /[\\/?#\s]/.test(value)) {
|
|
8
31
|
throw new ConfigurationError(`${label} must be a non-empty opaque identifier without path separators.`);
|
|
@@ -193,7 +216,7 @@ const normalizeBatchJob = (json) => ({
|
|
|
193
216
|
name: json.name ?? "",
|
|
194
217
|
model: json.model,
|
|
195
218
|
state: json.state ?? json.metadata?.state,
|
|
196
|
-
done: json.done,
|
|
219
|
+
done: json.done ?? (["JOB_STATE_SUCCEEDED", "JOB_STATE_FAILED", "JOB_STATE_CANCELLED", "JOB_STATE_EXPIRED", "JOB_STATE_PARTIALLY_SUCCEEDED"].includes(json.state ?? json.metadata?.state)),
|
|
197
220
|
createTime: json.createTime ?? json.create_time ?? json.metadata?.createTime,
|
|
198
221
|
updateTime: json.updateTime ?? json.update_time ?? json.metadata?.updateTime,
|
|
199
222
|
rawResponse: json,
|
|
@@ -275,18 +298,33 @@ const appendVertexApiKey = (auth, input) => {
|
|
|
275
298
|
}
|
|
276
299
|
return new Request(appendQuery(input.url, { key: auth.apiKey }), input);
|
|
277
300
|
};
|
|
301
|
+
const awaitVertexToken = async (getAccessToken, signal) => {
|
|
302
|
+
signal?.throwIfAborted();
|
|
303
|
+
const token = Promise.resolve().then(getAccessToken);
|
|
304
|
+
const accessToken = signal ? await new Promise((resolve, reject) => {
|
|
305
|
+
const aborted = () => reject(signal.reason);
|
|
306
|
+
signal.addEventListener("abort", aborted, { once: true });
|
|
307
|
+
token.then(resolve, reject).finally(() => signal.removeEventListener("abort", aborted));
|
|
308
|
+
if (signal.aborted)
|
|
309
|
+
aborted();
|
|
310
|
+
}) : await token;
|
|
311
|
+
signal?.throwIfAborted();
|
|
312
|
+
if (!accessToken)
|
|
313
|
+
throw new ConfigurationError("Missing Vertex access token.");
|
|
314
|
+
return accessToken;
|
|
315
|
+
};
|
|
278
316
|
const createVertexAuthenticatedFetch = (fetcher, auth) => (async (input, init) => {
|
|
317
|
+
const signal = init?.signal ?? (input instanceof Request ? input.signal : undefined);
|
|
318
|
+
signal?.throwIfAborted();
|
|
279
319
|
const headers = new Headers(input instanceof Request ? input.headers : undefined);
|
|
280
320
|
if (init?.headers) {
|
|
281
321
|
new Headers(init.headers).forEach((value, key) => headers.set(key, value));
|
|
282
322
|
}
|
|
283
323
|
if (auth.type === "bearer") {
|
|
284
|
-
const accessToken = await auth.getAccessToken
|
|
285
|
-
if (!accessToken) {
|
|
286
|
-
throw new ConfigurationError("Missing Vertex access token.");
|
|
287
|
-
}
|
|
324
|
+
const accessToken = await awaitVertexToken(auth.getAccessToken, signal);
|
|
288
325
|
headers.set("authorization", `Bearer ${accessToken}`);
|
|
289
326
|
}
|
|
327
|
+
signal?.throwIfAborted();
|
|
290
328
|
return fetcher(appendVertexApiKey(auth, input), {
|
|
291
329
|
...init,
|
|
292
330
|
headers
|
|
@@ -382,7 +420,7 @@ const mapTools = (tools) => tools
|
|
|
382
420
|
.map((tool) => ({
|
|
383
421
|
name: tool.name,
|
|
384
422
|
description: tool.description,
|
|
385
|
-
parameters: toJSONSchema(tool.schema)
|
|
423
|
+
parameters: toVertexSchema(toJSONSchema(tool.schema))
|
|
386
424
|
}));
|
|
387
425
|
if (functionDeclarations.length) {
|
|
388
426
|
mappedTools.push({ functionDeclarations });
|
|
@@ -395,6 +433,9 @@ const mapTools = (tools) => tools
|
|
|
395
433
|
const config = tool.config && typeof tool.config === "object" && !Array.isArray(tool.config)
|
|
396
434
|
? { ...tool.config }
|
|
397
435
|
: {};
|
|
436
|
+
if (config.enableWidget !== undefined && typeof config.enableWidget !== "boolean") {
|
|
437
|
+
throw new ConfigurationError("Vertex Google Maps enableWidget must be boolean.");
|
|
438
|
+
}
|
|
398
439
|
delete config.latitude;
|
|
399
440
|
delete config.longitude;
|
|
400
441
|
mappedTools.push({ googleMaps: config });
|
|
@@ -420,8 +461,8 @@ const mapGoogleMapsRetrievalConfig = (tools) => {
|
|
|
420
461
|
if (latitude === undefined && longitude === undefined) {
|
|
421
462
|
return undefined;
|
|
422
463
|
}
|
|
423
|
-
if (typeof latitude !== "number" || typeof longitude !== "number") {
|
|
424
|
-
throw new ConfigurationError('Provider "vertex" Google Maps grounding requires
|
|
464
|
+
if (typeof latitude !== "number" || typeof longitude !== "number" || !Number.isFinite(latitude) || !Number.isFinite(longitude) || Math.abs(latitude) > 90 || Math.abs(longitude) > 180) {
|
|
465
|
+
throw new ConfigurationError('Provider "vertex" Google Maps grounding requires finite latitude in [-90, 90] and longitude in [-180, 180].');
|
|
425
466
|
}
|
|
426
467
|
return {
|
|
427
468
|
latLng: {
|
|
@@ -430,8 +471,11 @@ const mapGoogleMapsRetrievalConfig = (tools) => {
|
|
|
430
471
|
}
|
|
431
472
|
};
|
|
432
473
|
};
|
|
433
|
-
const mapToolConfig = (toolChoice, tools) => {
|
|
474
|
+
const mapToolConfig = (toolChoice, tools, messages) => {
|
|
434
475
|
const retrievalConfig = mapGoogleMapsRetrievalConfig(tools);
|
|
476
|
+
// A forced initial call must not prevent answering after its result arrives.
|
|
477
|
+
if (messages.at(-1)?.role === "tool" && toolChoice !== "none")
|
|
478
|
+
return retrievalConfig ? { retrievalConfig } : undefined;
|
|
435
479
|
if (!toolChoice || toolChoice === "auto") {
|
|
436
480
|
return retrievalConfig ? { retrievalConfig } : undefined;
|
|
437
481
|
}
|
|
@@ -468,7 +512,7 @@ const mapRealtimeProviderOptions = (providerOptions) => providerOptions
|
|
|
468
512
|
: {};
|
|
469
513
|
const vertexRealtimeURL = (location, apiVersion, providerOptions, override) => {
|
|
470
514
|
const candidate = override ?? (typeof providerOptions?.realtime_url === "string" ? providerOptions.realtime_url : undefined);
|
|
471
|
-
const host = location
|
|
515
|
+
const host = vertexApiHost(location);
|
|
472
516
|
return candidate || `wss://${host}/ws/google.cloud.aiplatform.${apiVersion}.LlmBidiService/BidiGenerateContent`;
|
|
473
517
|
};
|
|
474
518
|
const vertexRealtimeHeaders = (accessToken, providerOptions) => ({
|
|
@@ -505,8 +549,7 @@ const mapRealtimeTranslationConfig = (config) => {
|
|
|
505
549
|
: {};
|
|
506
550
|
const translationConfig = {
|
|
507
551
|
...providerTranslationConfig,
|
|
508
|
-
...(config.translation?.targetLanguage ? { targetLanguageCode: config.translation.targetLanguage } : {})
|
|
509
|
-
...(config.translation?.sourceLanguage ? { sourceLanguageCode: config.translation.sourceLanguage } : {})
|
|
552
|
+
...(config.translation?.targetLanguage ? { targetLanguageCode: config.translation.targetLanguage } : {})
|
|
510
553
|
};
|
|
511
554
|
return Object.keys(translationConfig).length ? translationConfig : undefined;
|
|
512
555
|
};
|
|
@@ -520,6 +563,9 @@ const assertVertexRealtimeTranslateConfig = (config, modelId) => {
|
|
|
520
563
|
if (!config.translation?.targetLanguage) {
|
|
521
564
|
throw new UnsupportedFeatureError('Model "vertex/gemini-3.5-live-translate-preview" requires "translation.targetLanguage".');
|
|
522
565
|
}
|
|
566
|
+
if (config.translation.sourceLanguage !== undefined) {
|
|
567
|
+
throw new UnsupportedFeatureError("Vertex Live Translate detects the source language automatically; translation.sourceLanguage is not supported.");
|
|
568
|
+
}
|
|
523
569
|
const tools = toToolSet(config.tools);
|
|
524
570
|
if (tools && Object.keys(tools).length > 0) {
|
|
525
571
|
throw new UnsupportedFeatureError('Model "vertex/gemini-3.5-live-translate-preview" does not support realtime tools.');
|
|
@@ -531,15 +577,51 @@ const assertVertexRealtimeTranslateConfig = (config, modelId) => {
|
|
|
531
577
|
throw new UnsupportedFeatureError('Model "vertex/gemini-3.5-live-translate-preview" does not support realtime system instructions.');
|
|
532
578
|
}
|
|
533
579
|
};
|
|
580
|
+
const liveTranscriptionConfig = (config) => {
|
|
581
|
+
if (config.inputAudioTranscription === false)
|
|
582
|
+
throw new UnsupportedFeatureError("Live Transcribe cannot disable input transcription.");
|
|
583
|
+
const value = normalizeVertexTranscriptionConfig(config.inputAudioTranscription === true ? {} : config.inputAudioTranscription, config.inputTranscription?.language);
|
|
584
|
+
if (value.wordTimestamp || value.diarization)
|
|
585
|
+
throw new UnsupportedFeatureError("Live Transcribe does not support word timestamps or diarization.");
|
|
586
|
+
return value;
|
|
587
|
+
};
|
|
534
588
|
const assertVertexRealtimeConfig = (config, modelId) => {
|
|
535
589
|
assertVertexRealtimeTranslateConfig(config, modelId);
|
|
590
|
+
if (isVertexLiveTranscribeModel(modelId)) {
|
|
591
|
+
if (config.mode !== undefined && config.mode !== "transcription")
|
|
592
|
+
throw new UnsupportedFeatureError("Live Transcribe requires transcription mode.");
|
|
593
|
+
for (const key of ["instructions", "voice", "reasoning", "translation", "mediaResolution", "affectiveDialog", "proactiveAudio", "outputAudioMediaType", "outputSampleRateHz", "turnDetection", "noiseReduction", "autoResponse"]) {
|
|
594
|
+
if (config[key] !== undefined)
|
|
595
|
+
throw new UnsupportedFeatureError(`Live Transcribe does not support ${key}.`);
|
|
596
|
+
}
|
|
597
|
+
if (config.outputAudioTranscription)
|
|
598
|
+
throw new UnsupportedFeatureError("Live Transcribe does not produce output audio transcription.");
|
|
599
|
+
if (Object.keys(toToolSet(config.tools) ?? {}).length)
|
|
600
|
+
throw new UnsupportedFeatureError("Live Transcribe does not support tools.");
|
|
601
|
+
if (config.toolChoice !== undefined && config.toolChoice !== "none")
|
|
602
|
+
throw new UnsupportedFeatureError("Live Transcribe does not support tool selection.");
|
|
603
|
+
if (config.inputTranscription && Object.keys(config.inputTranscription).some(key => key !== "language"))
|
|
604
|
+
throw new UnsupportedFeatureError("Live Transcribe inputTranscription accepts only language; use inputAudioTranscription for native options.");
|
|
605
|
+
for (const key of ["model", "generationConfig", "generation_config", "inputAudioTranscription", "input_audio_transcription", "systemInstruction", "system_instruction", "tools", "translationConfig", "outputAudioTranscription"]) {
|
|
606
|
+
if (config.providerOptions?.[key] !== undefined)
|
|
607
|
+
throw new ConfigurationError(`Live Transcribe does not accept providerOptions.${key}.`);
|
|
608
|
+
}
|
|
609
|
+
liveTranscriptionConfig(config);
|
|
610
|
+
}
|
|
536
611
|
if (config.toolChoice !== undefined && !["auto", "none"].includes(String(config.toolChoice))) {
|
|
537
612
|
throw new UnsupportedFeatureError("Vertex Live supports automatic tool selection or tool disabling, but not required or named tool choice.");
|
|
538
613
|
}
|
|
539
614
|
};
|
|
540
|
-
const vertexRealtimeSetup = (config,
|
|
615
|
+
const vertexRealtimeSetup = (config, modelResource) => isVertexLiveTranscribeModel(modelResource.split("/").at(-1)) ? {
|
|
616
|
+
setup: {
|
|
617
|
+
...mapRealtimeProviderOptions(config.providerOptions),
|
|
618
|
+
model: modelResource,
|
|
619
|
+
generationConfig: { responseModalities: ["TEXT"] },
|
|
620
|
+
inputAudioTranscription: liveTranscriptionConfig(config)
|
|
621
|
+
}
|
|
622
|
+
} : ({
|
|
541
623
|
setup: {
|
|
542
|
-
model:
|
|
624
|
+
model: modelResource,
|
|
543
625
|
generationConfig: {
|
|
544
626
|
...(config.voice
|
|
545
627
|
? {
|
|
@@ -552,10 +634,11 @@ const vertexRealtimeSetup = (config, modelId) => ({
|
|
|
552
634
|
}
|
|
553
635
|
}
|
|
554
636
|
: {}),
|
|
555
|
-
responseModalities:
|
|
637
|
+
responseModalities: isGeminiLiveTranslateModel(modelResource.split("/").at(-1)) && config.outputAudioTranscription
|
|
638
|
+
? ["AUDIO", "TEXT"] : ["AUDIO"],
|
|
639
|
+
...(mapRealtimeTranslationConfig(config) ? { translationConfig: mapRealtimeTranslationConfig(config) } : {}),
|
|
556
640
|
...(mapRealtimeThinkingConfig(config) ? { thinkingConfig: mapRealtimeThinkingConfig(config) } : {})
|
|
557
641
|
},
|
|
558
|
-
...(mapRealtimeTranslationConfig(config) ? { translationConfig: mapRealtimeTranslationConfig(config) } : {}),
|
|
559
642
|
...(mapRealtimeTranscriptionConfig(config.inputAudioTranscription ?? (config.inputTranscription ? true : undefined))
|
|
560
643
|
? {
|
|
561
644
|
inputAudioTranscription: mapRealtimeTranscriptionConfig(config.inputAudioTranscription ?? (config.inputTranscription ? true : undefined))
|
|
@@ -580,11 +663,44 @@ const vertexRealtimeSetup = (config, modelId) => ({
|
|
|
580
663
|
...mapRealtimeProviderOptions(config.providerOptions)
|
|
581
664
|
}
|
|
582
665
|
});
|
|
583
|
-
const
|
|
666
|
+
const vertexRealtimeTimeLeftMs = (goAway) => {
|
|
667
|
+
const milliseconds = goAway.timeLeftMs ?? goAway.time_left_ms;
|
|
668
|
+
if (typeof milliseconds === "number" && Number.isFinite(milliseconds) && milliseconds >= 0)
|
|
669
|
+
return milliseconds;
|
|
670
|
+
const duration = goAway.timeLeft ?? goAway.time_left;
|
|
671
|
+
if (typeof duration !== "string" || !/^\d+(?:\.\d{1,9})?s$/.test(duration))
|
|
672
|
+
return undefined;
|
|
673
|
+
const value = Number(duration.slice(0, -1)) * 1000;
|
|
674
|
+
return Number.isFinite(value) && value <= Number.MAX_SAFE_INTEGER ? value : undefined;
|
|
675
|
+
};
|
|
676
|
+
const parseVertexRealtimeEvent = (payload, transcriptionOnly = false) => {
|
|
584
677
|
if ("setupComplete" in payload) {
|
|
585
678
|
return [];
|
|
586
679
|
}
|
|
587
680
|
const providerMetadata = sanitizeMediaResponse(payload);
|
|
681
|
+
const cancellation = payload.toolCallCancellation ?? payload.tool_call_cancellation;
|
|
682
|
+
if (cancellation !== undefined) {
|
|
683
|
+
const ids = cancellation && typeof cancellation === "object" ? cancellation.ids : undefined;
|
|
684
|
+
if (!Array.isArray(ids) || ids.some(id => typeof id !== "string" || !id)) {
|
|
685
|
+
throw new ConfigurationError("Vertex Live returned invalid tool cancellation IDs.");
|
|
686
|
+
}
|
|
687
|
+
return [{ type: "realtime-tool-call-cancellation", toolCallIds: [...new Set(ids)] }];
|
|
688
|
+
}
|
|
689
|
+
const liveToolCall = payload.toolCall ?? payload.tool_call;
|
|
690
|
+
if (liveToolCall && typeof liveToolCall === "object") {
|
|
691
|
+
const calls = liveToolCall.functionCalls ?? liveToolCall.function_calls;
|
|
692
|
+
if (!Array.isArray(calls))
|
|
693
|
+
throw new ConfigurationError("Vertex Live returned an invalid function calls array.");
|
|
694
|
+
const ids = new Set();
|
|
695
|
+
return calls.map((call) => {
|
|
696
|
+
if (!call || typeof call.id !== "string" || !call.id || typeof call.name !== "string" || !call.name
|
|
697
|
+
|| ids.has(call.id) || (call.args !== undefined && (!call.args || typeof call.args !== "object" || Array.isArray(call.args)))) {
|
|
698
|
+
throw new ConfigurationError("Vertex Live returned an invalid or duplicate function call.");
|
|
699
|
+
}
|
|
700
|
+
ids.add(call.id);
|
|
701
|
+
return { type: "realtime-tool-call", toolCall: { id: call.id, name: call.name, input: (call.args ?? {}) } };
|
|
702
|
+
});
|
|
703
|
+
}
|
|
588
704
|
const serverContent = typeof payload.serverContent === "object" && payload.serverContent
|
|
589
705
|
? payload.serverContent
|
|
590
706
|
: typeof payload.server_content === "object" && payload.server_content
|
|
@@ -635,6 +751,11 @@ const parseVertexRealtimeEvent = (payload) => {
|
|
|
635
751
|
});
|
|
636
752
|
}
|
|
637
753
|
}
|
|
754
|
+
const interim = serverContent.interimInputTranscription ?? serverContent.interim_input_transcription;
|
|
755
|
+
if (transcriptionOnly && interim && typeof interim === "object") {
|
|
756
|
+
// Interim hypotheses can replace earlier words; they are not text deltas.
|
|
757
|
+
events.push({ type: "realtime-provider-data", provider: "vertex", data: { type: "vertex_transcription_interim", transcription: sanitizeMediaResponse(interim) } });
|
|
758
|
+
}
|
|
638
759
|
const inputTranscription = typeof serverContent.inputTranscription === "object" && serverContent.inputTranscription
|
|
639
760
|
? serverContent.inputTranscription
|
|
640
761
|
: typeof serverContent.input_transcription === "object" && serverContent.input_transcription
|
|
@@ -645,7 +766,7 @@ const parseVertexRealtimeEvent = (payload) => {
|
|
|
645
766
|
type: "realtime-transcript",
|
|
646
767
|
text: inputTranscription.text,
|
|
647
768
|
role: "user",
|
|
648
|
-
isFinal: Boolean(inputTranscription.finished ?? serverContent.turnComplete ?? serverContent.turn_complete),
|
|
769
|
+
isFinal: transcriptionOnly || Boolean(inputTranscription.finished ?? serverContent.turnComplete ?? serverContent.turn_complete),
|
|
649
770
|
providerMetadata
|
|
650
771
|
});
|
|
651
772
|
}
|
|
@@ -663,6 +784,13 @@ const parseVertexRealtimeEvent = (payload) => {
|
|
|
663
784
|
providerMetadata
|
|
664
785
|
});
|
|
665
786
|
}
|
|
787
|
+
if (serverContent.interrupted === true) {
|
|
788
|
+
events.push({
|
|
789
|
+
type: "realtime-response-complete",
|
|
790
|
+
reason: "interrupted",
|
|
791
|
+
providerMetadata
|
|
792
|
+
});
|
|
793
|
+
}
|
|
666
794
|
if (serverContent.generationComplete || serverContent.generation_complete) {
|
|
667
795
|
events.push({
|
|
668
796
|
type: "realtime-response-complete",
|
|
@@ -707,11 +835,7 @@ const parseVertexRealtimeEvent = (payload) => {
|
|
|
707
835
|
return [
|
|
708
836
|
{
|
|
709
837
|
type: "realtime-go-away",
|
|
710
|
-
timeLeftMs:
|
|
711
|
-
? goAway.timeLeftMs
|
|
712
|
-
: typeof goAway.time_left_ms === "number"
|
|
713
|
-
? goAway.time_left_ms
|
|
714
|
-
: undefined,
|
|
838
|
+
timeLeftMs: vertexRealtimeTimeLeftMs(goAway),
|
|
715
839
|
providerMetadata
|
|
716
840
|
}
|
|
717
841
|
];
|
|
@@ -727,11 +851,14 @@ const parseVertexRealtimeEvent = (payload) => {
|
|
|
727
851
|
}
|
|
728
852
|
return [];
|
|
729
853
|
};
|
|
730
|
-
const createVertexRealtimeEventParser = () => {
|
|
854
|
+
const createVertexRealtimeEventParser = (transcriptionOnly = false) => {
|
|
731
855
|
let outputTranscript = "";
|
|
732
856
|
return (payload) => {
|
|
733
857
|
const events = [];
|
|
734
|
-
for (const event of parseVertexRealtimeEvent(payload)) {
|
|
858
|
+
for (const event of parseVertexRealtimeEvent(payload, transcriptionOnly)) {
|
|
859
|
+
if (event.type === "realtime-response-complete" && event.reason === "interrupted") {
|
|
860
|
+
outputTranscript = "";
|
|
861
|
+
}
|
|
735
862
|
if (event.type === "realtime-transcript" && event.role === "assistant") {
|
|
736
863
|
if (event.isFinal) {
|
|
737
864
|
const completeText = event.text.startsWith(outputTranscript)
|
|
@@ -956,27 +1083,47 @@ class VertexContextCachesClient {
|
|
|
956
1083
|
return index >= 0 ? this.baseURL.slice(index + 1) : this.baseURL;
|
|
957
1084
|
}
|
|
958
1085
|
async create(input) {
|
|
959
|
-
|
|
1086
|
+
const modelId = input.modelId.replace(/^publishers\/google\/models\//, "");
|
|
1087
|
+
this.assertModelLocation(modelId);
|
|
1088
|
+
if (input.ttl !== undefined && input.expireTime !== undefined) {
|
|
1089
|
+
throw new ConfigurationError("Vertex context caches accept either ttl or expireTime, not both.");
|
|
1090
|
+
}
|
|
1091
|
+
for (const key of ["model", "contents", "systemInstruction", "system_instruction", "tools", "displayName", "display_name", "ttl", "expireTime", "expire_time"]) {
|
|
1092
|
+
if (input.providerOptions?.[key] !== undefined) {
|
|
1093
|
+
throw new ConfigurationError(`Vertex context cache providerOptions.${key} conflicts with a dedicated input field.`);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
const nativeOptions = { ...input.providerOptions };
|
|
1097
|
+
if (nativeOptions.kmsKeyName !== undefined) {
|
|
1098
|
+
if (nativeOptions.encryptionSpec !== undefined || nativeOptions.encryption_spec !== undefined) {
|
|
1099
|
+
throw new ConfigurationError("Vertex cache kmsKeyName conflicts with encryptionSpec.");
|
|
1100
|
+
}
|
|
1101
|
+
if (typeof nativeOptions.kmsKeyName !== "string" || !/^projects\/[^/]+\/locations\/[^/]+\/keyRings\/[^/]+\/cryptoKeys\/[^/]+$/.test(nativeOptions.kmsKeyName)) {
|
|
1102
|
+
throw new ConfigurationError("Vertex cache kmsKeyName must identify a Cloud KMS crypto key.");
|
|
1103
|
+
}
|
|
1104
|
+
nativeOptions.encryptionSpec = { kmsKeyName: nativeOptions.kmsKeyName };
|
|
1105
|
+
delete nativeOptions.kmsKeyName;
|
|
1106
|
+
}
|
|
960
1107
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
961
1108
|
try {
|
|
962
|
-
const
|
|
1109
|
+
const json = await withRetry(() => this.fetcher(`${this.baseURL}/cachedContents`, {
|
|
963
1110
|
method: "POST",
|
|
964
1111
|
headers: this.headers(),
|
|
965
1112
|
signal,
|
|
966
1113
|
body: JSON.stringify({
|
|
967
|
-
model:
|
|
968
|
-
?
|
|
969
|
-
: `${this.resourceBase()}/publishers/google/models/${
|
|
1114
|
+
model: modelId.startsWith("projects/")
|
|
1115
|
+
? modelId
|
|
1116
|
+
: `${this.resourceBase()}/publishers/google/models/${modelId}`,
|
|
970
1117
|
contents: mapMessages(input.contents),
|
|
971
1118
|
...(input.system ? { systemInstruction: { parts: [{ text: input.system }] } } : { systemInstruction: systemInstruction(input.contents) }),
|
|
972
1119
|
...(input.tools ? { tools: mapTools(toToolSet(input.tools)) } : {}),
|
|
973
1120
|
...(input.displayName ? { displayName: input.displayName } : {}),
|
|
974
1121
|
...(input.ttl ? { ttl: input.ttl } : {}),
|
|
975
1122
|
...(input.expireTime ? { expireTime: input.expireTime } : {}),
|
|
976
|
-
...
|
|
1123
|
+
...nativeOptions
|
|
977
1124
|
})
|
|
978
|
-
}), input);
|
|
979
|
-
return normalizeCachedContent(
|
|
1125
|
+
}).then(parseJson), { ...input, abortSignal: signal });
|
|
1126
|
+
return normalizeCachedContent(json);
|
|
980
1127
|
}
|
|
981
1128
|
finally {
|
|
982
1129
|
cleanup();
|
|
@@ -985,8 +1132,28 @@ class VertexContextCachesClient {
|
|
|
985
1132
|
async get(input) {
|
|
986
1133
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
987
1134
|
try {
|
|
988
|
-
const
|
|
989
|
-
return normalizeCachedContent(
|
|
1135
|
+
const json = await withRetry(() => this.fetcher(vertexResourceURL(this.baseURL, input.name), { method: "GET", headers: this.headers(), signal }).then(parseJson), { ...input, abortSignal: signal });
|
|
1136
|
+
return normalizeCachedContent(json);
|
|
1137
|
+
}
|
|
1138
|
+
finally {
|
|
1139
|
+
cleanup();
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
async update(input) {
|
|
1143
|
+
if ((input.ttl !== undefined) === (input.expireTime !== undefined)) {
|
|
1144
|
+
throw new ConfigurationError("Vertex cache update requires exactly one of ttl or expireTime.");
|
|
1145
|
+
}
|
|
1146
|
+
if (input.ttl !== undefined && (!/^\d+(?:\.\d{1,9})?s$/.test(input.ttl) || Number(input.ttl.slice(0, -1)) <= 0)) {
|
|
1147
|
+
throw new ConfigurationError("Vertex cache ttl must be a positive duration in seconds, such as 3600s.");
|
|
1148
|
+
}
|
|
1149
|
+
if (input.expireTime !== undefined && (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/.test(input.expireTime) || !Number.isFinite(Date.parse(input.expireTime)))) {
|
|
1150
|
+
throw new ConfigurationError("Vertex cache expireTime must be an RFC 3339 timestamp.");
|
|
1151
|
+
}
|
|
1152
|
+
const field = input.ttl !== undefined ? "ttl" : "expireTime";
|
|
1153
|
+
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1154
|
+
try {
|
|
1155
|
+
const json = await withRetry(() => this.fetcher(appendQuery(vertexResourceURL(this.baseURL, input.name), { updateMask: field }), { method: "PATCH", headers: this.headers(), redirect: "error", signal, body: JSON.stringify({ [field]: input[field] }) }).then(parseJson), { ...input, abortSignal: signal });
|
|
1156
|
+
return normalizeCachedContent(json);
|
|
990
1157
|
}
|
|
991
1158
|
finally {
|
|
992
1159
|
cleanup();
|
|
@@ -995,12 +1162,11 @@ class VertexContextCachesClient {
|
|
|
995
1162
|
async list(input = {}) {
|
|
996
1163
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
997
1164
|
try {
|
|
998
|
-
const
|
|
1165
|
+
const json = await withRetry(() => this.fetcher(appendQuery(`${this.baseURL}/cachedContents`, { pageSize: input.pageSize, pageToken: input.pageToken }), {
|
|
999
1166
|
method: "GET",
|
|
1000
1167
|
headers: this.headers(),
|
|
1001
1168
|
signal
|
|
1002
|
-
}), input);
|
|
1003
|
-
const json = await parseJson(response);
|
|
1169
|
+
}).then(parseJson), { ...input, abortSignal: signal });
|
|
1004
1170
|
return {
|
|
1005
1171
|
caches: (json.cachedContents ?? json.cached_contents ?? []).map(normalizeCachedContent),
|
|
1006
1172
|
nextPageToken: json.nextPageToken,
|
|
@@ -1014,8 +1180,10 @@ class VertexContextCachesClient {
|
|
|
1014
1180
|
async delete(input) {
|
|
1015
1181
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1016
1182
|
try {
|
|
1017
|
-
const
|
|
1018
|
-
|
|
1183
|
+
const json = await withRetry(async () => {
|
|
1184
|
+
const response = await this.fetcher(vertexResourceURL(this.baseURL, input.name), { method: "DELETE", headers: this.headers(), signal });
|
|
1185
|
+
return response.status === 204 ? {} : await parseJson(response);
|
|
1186
|
+
}, { ...input, abortSignal: signal });
|
|
1019
1187
|
return { name: input.name, rawResponse: json };
|
|
1020
1188
|
}
|
|
1021
1189
|
finally {
|
|
@@ -1025,105 +1193,94 @@ class VertexContextCachesClient {
|
|
|
1025
1193
|
}
|
|
1026
1194
|
class VertexBatchesClient {
|
|
1027
1195
|
baseURL;
|
|
1028
|
-
accessToken;
|
|
1029
1196
|
fetcher;
|
|
1030
|
-
|
|
1031
|
-
constructor(baseURL,
|
|
1197
|
+
assertAccess;
|
|
1198
|
+
constructor(baseURL, fetcher, assertAccess) {
|
|
1032
1199
|
this.baseURL = baseURL;
|
|
1033
|
-
this.accessToken = accessToken;
|
|
1034
1200
|
this.fetcher = fetcher;
|
|
1035
|
-
this.
|
|
1201
|
+
this.assertAccess = assertAccess;
|
|
1036
1202
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
"content-type": "application/json"
|
|
1040
|
-
};
|
|
1041
|
-
}
|
|
1042
|
-
async create(input) {
|
|
1043
|
-
this.assertModelLocation(input.modelId);
|
|
1203
|
+
async request(path, method, input, body) {
|
|
1204
|
+
this.assertAccess();
|
|
1044
1205
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1045
1206
|
try {
|
|
1046
|
-
const response = await withRetry(() =>
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
},
|
|
1060
|
-
...(input.providerOptions ?? {})
|
|
1061
|
-
}
|
|
1062
|
-
})
|
|
1063
|
-
}), input);
|
|
1064
|
-
return normalizeBatchJob(await parseJson(response));
|
|
1207
|
+
const response = await withRetry(async () => {
|
|
1208
|
+
const result = await this.fetcher(path, {
|
|
1209
|
+
method, headers: { "content-type": "application/json" }, redirect: "error", signal,
|
|
1210
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) })
|
|
1211
|
+
});
|
|
1212
|
+
if (!result.ok)
|
|
1213
|
+
await parseJson(result);
|
|
1214
|
+
return result;
|
|
1215
|
+
}, { ...input, abortSignal: signal });
|
|
1216
|
+
// Cancel returns Empty; delete returns a long-running operation.
|
|
1217
|
+
if (response.status === 204)
|
|
1218
|
+
return {};
|
|
1219
|
+
return await parseJson(response);
|
|
1065
1220
|
}
|
|
1066
1221
|
finally {
|
|
1067
1222
|
cleanup();
|
|
1068
1223
|
}
|
|
1069
1224
|
}
|
|
1225
|
+
async create(input) {
|
|
1226
|
+
this.assertAccess();
|
|
1227
|
+
if (input.requests)
|
|
1228
|
+
throw new UnsupportedFeatureError("Vertex batch jobs require Cloud Storage or BigQuery input; upload inline requests first.");
|
|
1229
|
+
const options = input.providerOptions ?? {};
|
|
1230
|
+
if (input.fileName !== undefined && options.inputConfig !== undefined) {
|
|
1231
|
+
throw new ConfigurationError("Vertex batch input requires either fileName or providerOptions.inputConfig, not both.");
|
|
1232
|
+
}
|
|
1233
|
+
if (input.fileName?.startsWith("bq://") && !/^bq:\/\/[^.\s/]+\.[^.\s/]+\.[^.\s/]+$/.test(input.fileName)) {
|
|
1234
|
+
throw new ConfigurationError("Vertex batch BigQuery input must use bq://project.dataset.table.");
|
|
1235
|
+
}
|
|
1236
|
+
if (input.fileName && !input.fileName.startsWith("gs://") && !input.fileName.startsWith("bq://")) {
|
|
1237
|
+
throw new ConfigurationError("Vertex batch fileName must be a gs:// or bq:// URI, not a Gemini Files API ID.");
|
|
1238
|
+
}
|
|
1239
|
+
const inputConfig = options.inputConfig ?? (input.fileName?.startsWith("gs://")
|
|
1240
|
+
? { instancesFormat: "jsonl", gcsSource: { uris: [input.fileName] } }
|
|
1241
|
+
: input.fileName ? { instancesFormat: "bigquery", bigquerySource: { inputUri: input.fileName } } : undefined);
|
|
1242
|
+
if (!inputConfig || !options.outputConfig)
|
|
1243
|
+
throw new ConfigurationError("Vertex batch jobs require inputConfig (or fileName) and providerOptions.outputConfig.");
|
|
1244
|
+
const model = input.modelId.startsWith("projects/")
|
|
1245
|
+
? (encodeVertexResourceName(input.modelId, "Vertex batch model"), input.modelId)
|
|
1246
|
+
: input.modelId.startsWith("claude-") ? `publishers/anthropic/models/${encodeVertexPathSegment(input.modelId, "Vertex Claude model")}`
|
|
1247
|
+
: vertexPublisherResource(input.modelId);
|
|
1248
|
+
const publisher = model.match(/(?:^|\/)publishers\/([^/]+)\/models\//)?.[1];
|
|
1249
|
+
if (publisher && publisher !== "google" && /\/locations\/global\/?$/.test(new URL(this.baseURL).pathname)) {
|
|
1250
|
+
throw new ConfigurationError("Vertex partner batch jobs require a supported regional endpoint; global is not supported. Configure location for the selected model.");
|
|
1251
|
+
}
|
|
1252
|
+
const json = await this.request(`${this.baseURL}/batchPredictionJobs`, "POST", input, {
|
|
1253
|
+
...options, displayName: input.displayName ?? "zhivex-batch", model, inputConfig
|
|
1254
|
+
});
|
|
1255
|
+
return normalizeBatchJob(json);
|
|
1256
|
+
}
|
|
1070
1257
|
async get(input) {
|
|
1071
|
-
|
|
1072
|
-
try {
|
|
1073
|
-
const response = await withRetry(() => this.fetcher(`${this.baseURL}/${encodeVertexResourceName(input.name, "Vertex batch name")}`, { method: "GET", headers: this.headers(), signal }), input);
|
|
1074
|
-
return normalizeBatchJob(await parseJson(response));
|
|
1075
|
-
}
|
|
1076
|
-
finally {
|
|
1077
|
-
cleanup();
|
|
1078
|
-
}
|
|
1258
|
+
return normalizeBatchJob(await this.request(vertexResourceURL(this.baseURL, input.name), "GET", input));
|
|
1079
1259
|
}
|
|
1080
1260
|
async list(input = {}) {
|
|
1081
|
-
const
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
signal
|
|
1087
|
-
}), input);
|
|
1088
|
-
const json = await parseJson(response);
|
|
1089
|
-
return {
|
|
1090
|
-
batches: (json.batches ?? []).map(normalizeBatchJob),
|
|
1091
|
-
nextPageToken: json.nextPageToken,
|
|
1092
|
-
rawResponse: json
|
|
1093
|
-
};
|
|
1094
|
-
}
|
|
1095
|
-
finally {
|
|
1096
|
-
cleanup();
|
|
1097
|
-
}
|
|
1261
|
+
const json = await this.request(appendQuery(`${this.baseURL}/batchPredictionJobs`, {
|
|
1262
|
+
pageSize: input.pageSize, pageToken: input.pageToken,
|
|
1263
|
+
filter: typeof input.providerOptions?.filter === "string" ? input.providerOptions.filter : undefined
|
|
1264
|
+
}), "GET", input);
|
|
1265
|
+
return { batches: (json.batchPredictionJobs ?? []).map(normalizeBatchJob), nextPageToken: json.nextPageToken, rawResponse: json };
|
|
1098
1266
|
}
|
|
1099
1267
|
async cancel(input) {
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
method: "POST",
|
|
1104
|
-
headers: this.headers(),
|
|
1105
|
-
signal,
|
|
1106
|
-
body: JSON.stringify(input.providerOptions ?? {})
|
|
1107
|
-
}), input);
|
|
1108
|
-
return normalizeBatchJob(await parseJson(response));
|
|
1109
|
-
}
|
|
1110
|
-
finally {
|
|
1111
|
-
cleanup();
|
|
1112
|
-
}
|
|
1268
|
+
await this.request(`${vertexResourceURL(this.baseURL, input.name)}:cancel`, "POST", input, {});
|
|
1269
|
+
// Cancellation is asynchronous; report the actual job state.
|
|
1270
|
+
return this.get(input);
|
|
1113
1271
|
}
|
|
1114
1272
|
async delete(input) {
|
|
1115
|
-
const
|
|
1116
|
-
|
|
1117
|
-
const response = await withRetry(() => this.fetcher(`${this.baseURL}/${encodeVertexResourceName(input.name, "Vertex batch name")}:delete`, { method: "POST", headers: this.headers(), signal }), input);
|
|
1118
|
-
const json = await parseJson(response);
|
|
1119
|
-
return { name: input.name, rawResponse: json };
|
|
1120
|
-
}
|
|
1121
|
-
finally {
|
|
1122
|
-
cleanup();
|
|
1123
|
-
}
|
|
1273
|
+
const json = await this.request(vertexResourceURL(this.baseURL, input.name), "DELETE", input);
|
|
1274
|
+
return { name: input.name, rawResponse: json };
|
|
1124
1275
|
}
|
|
1125
1276
|
}
|
|
1126
1277
|
const vertexPublisherResource = (modelId) => {
|
|
1278
|
+
if (/^(?:projects\/[^/]+\/locations\/[^/]+\/)?endpoints\/[^/]+$/.test(modelId)) {
|
|
1279
|
+
return encodeVertexResourceName(modelId, "Vertex endpoint");
|
|
1280
|
+
}
|
|
1281
|
+
if (modelId.startsWith("endpoints/") || modelId.startsWith("projects/")) {
|
|
1282
|
+
throw new ConfigurationError("Vertex prediction endpoint must use endpoints/<id> or projects/<project>/locations/<location>/endpoints/<id>.");
|
|
1283
|
+
}
|
|
1127
1284
|
if (modelId.startsWith("publishers/")) {
|
|
1128
1285
|
const segments = modelId.split("/");
|
|
1129
1286
|
if (segments.length !== 4 || segments[2] !== "models") {
|
|
@@ -1131,6 +1288,10 @@ const vertexPublisherResource = (modelId) => {
|
|
|
1131
1288
|
}
|
|
1132
1289
|
return `publishers/${encodeVertexPathSegment(segments[1], "Vertex publisher")}/models/${encodeVertexPathSegment(segments[3], "Vertex model ID")}`;
|
|
1133
1290
|
}
|
|
1291
|
+
if (/^[^/]+\/[^/]+$/.test(modelId)) {
|
|
1292
|
+
const [publisher, model] = modelId.split("/");
|
|
1293
|
+
return `publishers/${encodeVertexPathSegment(publisher, "Vertex publisher")}/models/${encodeVertexPathSegment(model, "Vertex model ID")}`;
|
|
1294
|
+
}
|
|
1134
1295
|
return `publishers/google/models/${encodeVertexPathSegment(modelId, "Vertex model ID")}`;
|
|
1135
1296
|
};
|
|
1136
1297
|
class VertexPredictionModel {
|
|
@@ -1157,27 +1318,38 @@ class VertexPredictionModel {
|
|
|
1157
1318
|
};
|
|
1158
1319
|
}
|
|
1159
1320
|
url(action) {
|
|
1160
|
-
|
|
1321
|
+
const resource = vertexPublisherResource(this.modelId);
|
|
1322
|
+
const base = this.modelId.startsWith("projects/") ? this.baseURL.replace(/\/projects\/.*$/, "") : this.baseURL;
|
|
1323
|
+
return `${base}/${resource}:${encodeVertexPathSegment(action, "Vertex prediction action")}`;
|
|
1161
1324
|
}
|
|
1162
1325
|
body(input) {
|
|
1163
|
-
|
|
1326
|
+
if (input.body !== undefined)
|
|
1327
|
+
return input.body;
|
|
1328
|
+
const nativeOptions = { ...input.providerOptions };
|
|
1329
|
+
delete nativeOptions.action;
|
|
1330
|
+
for (const key of ["instances", "parameters"]) {
|
|
1331
|
+
if (input[key] !== undefined && nativeOptions[key] !== undefined) {
|
|
1332
|
+
throw new ConfigurationError(`Vertex prediction providerOptions.${key} conflicts with a dedicated input field.`);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
return {
|
|
1164
1336
|
...(input.instances ? { instances: input.instances } : {}),
|
|
1165
1337
|
...(input.parameters ? { parameters: input.parameters } : {}),
|
|
1166
|
-
...
|
|
1338
|
+
...nativeOptions
|
|
1167
1339
|
};
|
|
1168
1340
|
}
|
|
1169
1341
|
async predictRaw(input) {
|
|
1170
1342
|
const action = typeof input.providerOptions?.action === "string" ? input.providerOptions.action : "predict";
|
|
1171
1343
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1172
1344
|
try {
|
|
1173
|
-
const
|
|
1345
|
+
const json = await withRetry(() => this.fetcher(this.url(action), {
|
|
1174
1346
|
method: "POST",
|
|
1175
1347
|
redirect: "error",
|
|
1176
1348
|
headers: this.headers(),
|
|
1177
1349
|
signal,
|
|
1178
1350
|
body: JSON.stringify(this.body(input))
|
|
1179
|
-
}), input);
|
|
1180
|
-
return normalizePredictionResult(
|
|
1351
|
+
}).then(parseJson), { ...input, abortSignal: signal });
|
|
1352
|
+
return normalizePredictionResult(json);
|
|
1181
1353
|
}
|
|
1182
1354
|
finally {
|
|
1183
1355
|
cleanup();
|
|
@@ -1186,14 +1358,14 @@ class VertexPredictionModel {
|
|
|
1186
1358
|
async rawPredict(input) {
|
|
1187
1359
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1188
1360
|
try {
|
|
1189
|
-
const
|
|
1361
|
+
const json = await withRetry(() => this.fetcher(this.url("rawPredict"), {
|
|
1190
1362
|
method: "POST",
|
|
1191
1363
|
redirect: "error",
|
|
1192
1364
|
headers: this.headers(),
|
|
1193
1365
|
signal,
|
|
1194
1366
|
body: JSON.stringify(this.body(input))
|
|
1195
|
-
}), input);
|
|
1196
|
-
return normalizePredictionResult(
|
|
1367
|
+
}).then(parseJson), { ...input, abortSignal: signal });
|
|
1368
|
+
return normalizePredictionResult(json);
|
|
1197
1369
|
}
|
|
1198
1370
|
finally {
|
|
1199
1371
|
cleanup();
|
|
@@ -1202,14 +1374,14 @@ class VertexPredictionModel {
|
|
|
1202
1374
|
async invoke(input) {
|
|
1203
1375
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1204
1376
|
try {
|
|
1205
|
-
const
|
|
1377
|
+
const json = await withRetry(() => this.fetcher(this.url("invoke"), {
|
|
1206
1378
|
method: "POST",
|
|
1207
1379
|
redirect: "error",
|
|
1208
1380
|
headers: this.headers(),
|
|
1209
1381
|
signal,
|
|
1210
1382
|
body: JSON.stringify(this.body(input))
|
|
1211
|
-
}), input);
|
|
1212
|
-
return normalizePredictionResult(
|
|
1383
|
+
}).then(parseJson), { ...input, abortSignal: signal });
|
|
1384
|
+
return normalizePredictionResult(json);
|
|
1213
1385
|
}
|
|
1214
1386
|
finally {
|
|
1215
1387
|
cleanup();
|
|
@@ -1218,23 +1390,26 @@ class VertexPredictionModel {
|
|
|
1218
1390
|
async predictLongRunning(input) {
|
|
1219
1391
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1220
1392
|
try {
|
|
1221
|
-
const
|
|
1393
|
+
const json = await withRetry(() => this.fetcher(this.url("predictLongRunning"), {
|
|
1222
1394
|
method: "POST",
|
|
1223
1395
|
redirect: "error",
|
|
1224
1396
|
headers: this.headers(),
|
|
1225
1397
|
signal,
|
|
1226
1398
|
body: JSON.stringify(this.body(input))
|
|
1227
|
-
}), input);
|
|
1228
|
-
return normalizeOperation(
|
|
1399
|
+
}).then(parseJson), { ...input, abortSignal: signal });
|
|
1400
|
+
return normalizeOperation(json);
|
|
1229
1401
|
}
|
|
1230
1402
|
finally {
|
|
1231
1403
|
cleanup();
|
|
1232
1404
|
}
|
|
1233
1405
|
}
|
|
1234
1406
|
async fetchPredictionOperation(input) {
|
|
1407
|
+
if (input.providerOptions?.operationName !== undefined || input.providerOptions?.operation_name !== undefined) {
|
|
1408
|
+
throw new ConfigurationError("Vertex prediction operationName must be supplied through name.");
|
|
1409
|
+
}
|
|
1235
1410
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1236
1411
|
try {
|
|
1237
|
-
const
|
|
1412
|
+
const json = await withRetry(() => this.fetcher(this.url("fetchPredictOperation"), {
|
|
1238
1413
|
method: "POST",
|
|
1239
1414
|
redirect: "error",
|
|
1240
1415
|
headers: this.headers(),
|
|
@@ -1243,8 +1418,8 @@ class VertexPredictionModel {
|
|
|
1243
1418
|
operationName: input.name,
|
|
1244
1419
|
...(input.providerOptions ?? {})
|
|
1245
1420
|
})
|
|
1246
|
-
}), input);
|
|
1247
|
-
return normalizeOperation(
|
|
1421
|
+
}).then(parseJson), { ...input, abortSignal: signal });
|
|
1422
|
+
return normalizeOperation(json);
|
|
1248
1423
|
}
|
|
1249
1424
|
finally {
|
|
1250
1425
|
cleanup();
|
|
@@ -1277,7 +1452,7 @@ class VertexLanguageModel {
|
|
|
1277
1452
|
assertCurrentGeminiGenerateInput("vertex", this.modelId, input);
|
|
1278
1453
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1279
1454
|
try {
|
|
1280
|
-
const
|
|
1455
|
+
const json = await withRetry(() => this.fetcher(this.url("generateContent"), {
|
|
1281
1456
|
method: "POST",
|
|
1282
1457
|
headers: this.headers(),
|
|
1283
1458
|
signal,
|
|
@@ -1286,11 +1461,10 @@ class VertexLanguageModel {
|
|
|
1286
1461
|
contents: mapMessages(input.messages),
|
|
1287
1462
|
systemInstruction: systemInstruction(input.messages),
|
|
1288
1463
|
tools: mapTools(input.tools),
|
|
1289
|
-
toolConfig: mapToolConfig(input.toolChoice, input.tools),
|
|
1464
|
+
toolConfig: mapToolConfig(input.toolChoice, input.tools, input.messages),
|
|
1290
1465
|
generationConfig: generationConfig(this.modelId, input)
|
|
1291
1466
|
})
|
|
1292
|
-
}), input);
|
|
1293
|
-
const json = await parseJson(response);
|
|
1467
|
+
}).then(parseJson), { ...input, abortSignal: signal });
|
|
1294
1468
|
const candidate = json.candidates?.[0];
|
|
1295
1469
|
const assistantMessage = parseAssistantMessage(candidate);
|
|
1296
1470
|
return {
|
|
@@ -1321,10 +1495,17 @@ class VertexLanguageModel {
|
|
|
1321
1495
|
contents: mapMessages(input.messages),
|
|
1322
1496
|
systemInstruction: systemInstruction(input.messages),
|
|
1323
1497
|
tools: mapTools(input.tools),
|
|
1324
|
-
toolConfig: mapToolConfig(input.toolChoice, input.tools),
|
|
1498
|
+
toolConfig: mapToolConfig(input.toolChoice, input.tools, input.messages),
|
|
1325
1499
|
generationConfig: generationConfig(this.modelId, input)
|
|
1326
1500
|
})
|
|
1327
|
-
})
|
|
1501
|
+
}).then(async (response) => {
|
|
1502
|
+
if (!response.ok)
|
|
1503
|
+
await parseJson(response);
|
|
1504
|
+
return response;
|
|
1505
|
+
}), { ...input, abortSignal: signal }).catch((error) => {
|
|
1506
|
+
cleanup();
|
|
1507
|
+
throw error;
|
|
1508
|
+
});
|
|
1328
1509
|
return (async function* () {
|
|
1329
1510
|
try {
|
|
1330
1511
|
for await (const event of streamSSE(response)) {
|
|
@@ -1365,57 +1546,6 @@ class VertexLanguageModel {
|
|
|
1365
1546
|
})();
|
|
1366
1547
|
}
|
|
1367
1548
|
}
|
|
1368
|
-
class VertexEmbeddingModel {
|
|
1369
|
-
modelId;
|
|
1370
|
-
baseURL;
|
|
1371
|
-
accessToken;
|
|
1372
|
-
fetcher;
|
|
1373
|
-
provider = "vertex";
|
|
1374
|
-
capabilities = capabilities;
|
|
1375
|
-
constructor(modelId, baseURL, accessToken, fetcher) {
|
|
1376
|
-
this.modelId = modelId;
|
|
1377
|
-
this.baseURL = baseURL;
|
|
1378
|
-
this.accessToken = accessToken;
|
|
1379
|
-
this.fetcher = fetcher;
|
|
1380
|
-
}
|
|
1381
|
-
url() {
|
|
1382
|
-
return `${this.baseURL}/publishers/google/models/${encodeVertexPathSegment(this.modelId, "Vertex model ID")}:predict`;
|
|
1383
|
-
}
|
|
1384
|
-
headers() {
|
|
1385
|
-
return {
|
|
1386
|
-
"content-type": "application/json"
|
|
1387
|
-
};
|
|
1388
|
-
}
|
|
1389
|
-
async embed(input) {
|
|
1390
|
-
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1391
|
-
const values = input.values.map((value) => {
|
|
1392
|
-
if (typeof value !== "string") {
|
|
1393
|
-
throw new UnsupportedFeatureError('Provider "vertex" does not support multimodal embedding values.');
|
|
1394
|
-
}
|
|
1395
|
-
return value;
|
|
1396
|
-
});
|
|
1397
|
-
try {
|
|
1398
|
-
const response = await withRetry(() => this.fetcher(this.url(), {
|
|
1399
|
-
method: "POST",
|
|
1400
|
-
headers: this.headers(),
|
|
1401
|
-
signal,
|
|
1402
|
-
body: JSON.stringify({
|
|
1403
|
-
instances: values.map((value) => ({
|
|
1404
|
-
content: value
|
|
1405
|
-
}))
|
|
1406
|
-
})
|
|
1407
|
-
}), input);
|
|
1408
|
-
const json = await parseJson(response);
|
|
1409
|
-
return {
|
|
1410
|
-
embeddings: (json.predictions ?? []).map((prediction) => prediction.embeddings?.values ?? []),
|
|
1411
|
-
rawResponse: json
|
|
1412
|
-
};
|
|
1413
|
-
}
|
|
1414
|
-
finally {
|
|
1415
|
-
cleanup();
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
1549
|
class VertexTranscriptionModel {
|
|
1420
1550
|
modelId;
|
|
1421
1551
|
baseURL;
|
|
@@ -1438,39 +1568,16 @@ class VertexTranscriptionModel {
|
|
|
1438
1568
|
};
|
|
1439
1569
|
}
|
|
1440
1570
|
async transcribe(input) {
|
|
1571
|
+
const body = transcriptionRequest(this.modelId, input, toBase64(input.audio.data));
|
|
1441
1572
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1442
1573
|
try {
|
|
1443
|
-
const
|
|
1574
|
+
const json = await withRetry(() => this.fetcher(this.url(), {
|
|
1444
1575
|
method: "POST",
|
|
1445
1576
|
headers: this.headers(),
|
|
1446
1577
|
signal,
|
|
1447
|
-
body: JSON.stringify(
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
role: "user",
|
|
1451
|
-
parts: [
|
|
1452
|
-
{
|
|
1453
|
-
inlineData: {
|
|
1454
|
-
mimeType: input.audio.mediaType,
|
|
1455
|
-
data: toBase64(input.audio.data)
|
|
1456
|
-
}
|
|
1457
|
-
},
|
|
1458
|
-
{
|
|
1459
|
-
text: input.prompt ??
|
|
1460
|
-
`Transcribe this audio${input.language ? ` in ${input.language}` : ""}. Return only the transcript.`
|
|
1461
|
-
}
|
|
1462
|
-
]
|
|
1463
|
-
}
|
|
1464
|
-
],
|
|
1465
|
-
...input.providerOptions
|
|
1466
|
-
})
|
|
1467
|
-
}), input);
|
|
1468
|
-
const json = await parseJson(response);
|
|
1469
|
-
const text = json.candidates?.[0]?.content?.parts?.find((part) => typeof part.text === "string")?.text ?? "";
|
|
1470
|
-
return {
|
|
1471
|
-
text,
|
|
1472
|
-
rawResponse: json
|
|
1473
|
-
};
|
|
1578
|
+
body: JSON.stringify(body)
|
|
1579
|
+
}).then(parseJson), { ...input, abortSignal: signal });
|
|
1580
|
+
return transcriptionResponse(json);
|
|
1474
1581
|
}
|
|
1475
1582
|
finally {
|
|
1476
1583
|
cleanup();
|
|
@@ -1501,7 +1608,7 @@ class VertexSpeechModel {
|
|
|
1501
1608
|
async generateSpeech(input) {
|
|
1502
1609
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1503
1610
|
try {
|
|
1504
|
-
const
|
|
1611
|
+
const json = await withRetry(() => this.fetcher(this.url(), {
|
|
1505
1612
|
method: "POST",
|
|
1506
1613
|
headers: this.headers(),
|
|
1507
1614
|
signal,
|
|
@@ -1519,8 +1626,7 @@ class VertexSpeechModel {
|
|
|
1519
1626
|
},
|
|
1520
1627
|
...input.providerOptions
|
|
1521
1628
|
})
|
|
1522
|
-
}), input);
|
|
1523
|
-
const json = await parseJson(response);
|
|
1629
|
+
}).then(parseJson), { ...input, abortSignal: signal });
|
|
1524
1630
|
const audioPart = json.candidates?.[0]?.content?.parts?.find((part) => part.inlineData?.data);
|
|
1525
1631
|
return {
|
|
1526
1632
|
audio: decodeMedia(audioPart?.inlineData?.data ?? "", "models.generateContent"),
|
|
@@ -1552,7 +1658,11 @@ class VertexSpeechModel {
|
|
|
1552
1658
|
},
|
|
1553
1659
|
...input.providerOptions
|
|
1554
1660
|
})
|
|
1555
|
-
})
|
|
1661
|
+
}).then(async (response) => {
|
|
1662
|
+
if (!response.ok)
|
|
1663
|
+
await parseJson(response);
|
|
1664
|
+
return response;
|
|
1665
|
+
}), { ...input, abortSignal: signal }).catch(error => { cleanup(); throw error; });
|
|
1556
1666
|
return (async function* () {
|
|
1557
1667
|
try {
|
|
1558
1668
|
for await (const event of streamSSE(response)) {
|
|
@@ -1601,7 +1711,18 @@ class VertexImageGenerationModel {
|
|
|
1601
1711
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1602
1712
|
try {
|
|
1603
1713
|
if (isImagenModel(this.modelId)) {
|
|
1604
|
-
|
|
1714
|
+
if (input.images?.length)
|
|
1715
|
+
throw new UnsupportedFeatureError("Imagen text-to-image generation does not accept images; use the native editing referenceImages contract through predictionModel().");
|
|
1716
|
+
const outputOptions = input.providerOptions?.outputOptions;
|
|
1717
|
+
if (input.outputMimeType !== undefined && outputOptions !== undefined &&
|
|
1718
|
+
(!outputOptions || typeof outputOptions !== "object" || Array.isArray(outputOptions))) {
|
|
1719
|
+
throw new ConfigurationError("Imagen providerOptions.outputOptions must be an object.");
|
|
1720
|
+
}
|
|
1721
|
+
const nativeOutputOptions = outputOptions;
|
|
1722
|
+
if (input.outputMimeType !== undefined && nativeOutputOptions?.mimeType !== undefined &&
|
|
1723
|
+
nativeOutputOptions.mimeType !== input.outputMimeType)
|
|
1724
|
+
throw new ConfigurationError("Conflicting Imagen output MIME types.");
|
|
1725
|
+
const json = await withRetry(() => this.fetcher(`${this.baseURL}/publishers/google/models/${encodeVertexPathSegment(this.modelId, "Vertex model ID")}:predict`, {
|
|
1605
1726
|
method: "POST",
|
|
1606
1727
|
headers: this.headers(),
|
|
1607
1728
|
signal,
|
|
@@ -1612,16 +1733,15 @@ class VertexImageGenerationModel {
|
|
|
1612
1733
|
}
|
|
1613
1734
|
],
|
|
1614
1735
|
parameters: {
|
|
1615
|
-
...
|
|
1616
|
-
...(input.
|
|
1617
|
-
...(input.
|
|
1618
|
-
...(input.
|
|
1619
|
-
...(input.
|
|
1620
|
-
...input.
|
|
1736
|
+
...input.providerOptions,
|
|
1737
|
+
...(input.negativePrompt ? { negativePrompt: input.negativePrompt } : {}),
|
|
1738
|
+
...(input.count ? { sampleCount: input.count } : {}),
|
|
1739
|
+
...(input.aspectRatio ? { aspectRatio: input.aspectRatio } : {}),
|
|
1740
|
+
...(input.size ? { sampleImageSize: input.size } : {}),
|
|
1741
|
+
...(input.outputMimeType ? { outputOptions: { ...nativeOutputOptions, mimeType: input.outputMimeType } } : {})
|
|
1621
1742
|
}
|
|
1622
1743
|
})
|
|
1623
|
-
}), input);
|
|
1624
|
-
const json = await parseJson(response);
|
|
1744
|
+
}).then(parseJson), { ...input, abortSignal: signal });
|
|
1625
1745
|
const images = (Array.isArray(json.predictions) ? json.predictions : [])
|
|
1626
1746
|
.map((prediction) => ({
|
|
1627
1747
|
data: prediction.bytesBase64Encoded || prediction.imageBytes
|
|
@@ -1639,7 +1759,7 @@ class VertexImageGenerationModel {
|
|
|
1639
1759
|
};
|
|
1640
1760
|
}
|
|
1641
1761
|
const { generationConfig, providerOptions } = splitGenerationConfig(input.providerOptions);
|
|
1642
|
-
const
|
|
1762
|
+
const json = await withRetry(() => this.fetcher(`${this.baseURL}/publishers/google/models/${encodeVertexPathSegment(this.modelId, "Vertex model ID")}:generateContent`, {
|
|
1643
1763
|
method: "POST",
|
|
1644
1764
|
headers: this.headers(),
|
|
1645
1765
|
signal,
|
|
@@ -1669,8 +1789,7 @@ class VertexImageGenerationModel {
|
|
|
1669
1789
|
...generationConfig
|
|
1670
1790
|
}
|
|
1671
1791
|
})
|
|
1672
|
-
}), input);
|
|
1673
|
-
const json = await parseJson(response);
|
|
1792
|
+
}).then(parseJson), { ...input, abortSignal: signal });
|
|
1674
1793
|
const { media, text } = collectInlineMedia(json, input.outputMimeType ?? "image/png", "models.generateContent");
|
|
1675
1794
|
return {
|
|
1676
1795
|
images: media,
|
|
@@ -1688,13 +1807,15 @@ class VertexMusicGenerationModel {
|
|
|
1688
1807
|
baseURL;
|
|
1689
1808
|
accessToken;
|
|
1690
1809
|
fetcher;
|
|
1810
|
+
interactions;
|
|
1691
1811
|
provider = "vertex";
|
|
1692
1812
|
capabilities = musicGenerationCapabilities;
|
|
1693
|
-
constructor(modelId, baseURL, accessToken, fetcher) {
|
|
1813
|
+
constructor(modelId, baseURL, accessToken, fetcher, interactions) {
|
|
1694
1814
|
this.modelId = modelId;
|
|
1695
1815
|
this.baseURL = baseURL;
|
|
1696
1816
|
this.accessToken = accessToken;
|
|
1697
1817
|
this.fetcher = fetcher;
|
|
1818
|
+
this.interactions = interactions;
|
|
1698
1819
|
}
|
|
1699
1820
|
headers() {
|
|
1700
1821
|
return {
|
|
@@ -1702,10 +1823,33 @@ class VertexMusicGenerationModel {
|
|
|
1702
1823
|
};
|
|
1703
1824
|
}
|
|
1704
1825
|
async generateMusic(input) {
|
|
1826
|
+
if (this.modelId.startsWith("lyria-3-")) {
|
|
1827
|
+
if (!this.interactions)
|
|
1828
|
+
throw new ConfigurationError("Lyria 3 requires Vertex Interactions.");
|
|
1829
|
+
if (input.negativePrompt || input.outputMimeType)
|
|
1830
|
+
throw new UnsupportedFeatureError("Vertex Lyria 3 does not expose negativePrompt or outputMimeType; describe the desired music in prompt.");
|
|
1831
|
+
const content = [{ type: "text", text: input.prompt }];
|
|
1832
|
+
for (const image of input.images ?? []) {
|
|
1833
|
+
if (!image.mediaType.startsWith("image/") || (image.data === undefined) === (image.uri === undefined))
|
|
1834
|
+
throw new ConfigurationError("Lyria 3 image input requires exactly one data or uri and an image media type.");
|
|
1835
|
+
const part = mediaInputToPart(image);
|
|
1836
|
+
content.push({ type: "image", mime_type: image.mediaType,
|
|
1837
|
+
...(part.inlineData ? { data: part.inlineData.data } : { uri: image.uri }) });
|
|
1838
|
+
}
|
|
1839
|
+
const result = await this.interactions.create({ ...input, modelId: this.modelId, input: content, background: false });
|
|
1840
|
+
const audio = (result.outputs ?? []).filter((output) => !!output && typeof output === "object" && "type" in output && output.type === "audio").map((output) => {
|
|
1841
|
+
if (typeof output.data !== "string")
|
|
1842
|
+
throw new ConfigurationError("Lyria 3 returned audio without inline data; use interactions for asynchronous output.");
|
|
1843
|
+
return { data: decodeMedia(output.data, "interactions"), mediaType: typeof output.mime_type === "string" ? output.mime_type : "audio/mpeg" };
|
|
1844
|
+
});
|
|
1845
|
+
if (result.status !== "completed" || !audio.length)
|
|
1846
|
+
throw new ConfigurationError("Lyria 3 did not return completed audio; use interactions to inspect the operation.");
|
|
1847
|
+
return { audio, text: result.outputText, rawResponse: sanitizeMediaResponse(result.rawResponse) };
|
|
1848
|
+
}
|
|
1705
1849
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1706
1850
|
try {
|
|
1707
1851
|
if (this.modelId === "lyria-002") {
|
|
1708
|
-
const
|
|
1852
|
+
const json = await withRetry(() => this.fetcher(`${this.baseURL}/publishers/google/models/${encodeVertexPathSegment(this.modelId, "Vertex model ID")}:predict`, {
|
|
1709
1853
|
method: "POST",
|
|
1710
1854
|
headers: this.headers(),
|
|
1711
1855
|
signal,
|
|
@@ -1720,8 +1864,7 @@ class VertexMusicGenerationModel {
|
|
|
1720
1864
|
...input.providerOptions
|
|
1721
1865
|
}
|
|
1722
1866
|
})
|
|
1723
|
-
}), input);
|
|
1724
|
-
const json = await parseJson(response);
|
|
1867
|
+
}).then(parseJson), { ...input, abortSignal: signal });
|
|
1725
1868
|
return {
|
|
1726
1869
|
audio: (Array.isArray(json.predictions) ? json.predictions : []).map((prediction) => ({
|
|
1727
1870
|
data: decodeMedia(prediction.audioContent ?? "", "models.predict"),
|
|
@@ -1732,7 +1875,7 @@ class VertexMusicGenerationModel {
|
|
|
1732
1875
|
};
|
|
1733
1876
|
}
|
|
1734
1877
|
const { generationConfig, providerOptions } = splitGenerationConfig(input.providerOptions);
|
|
1735
|
-
const
|
|
1878
|
+
const json = await withRetry(() => this.fetcher(`${this.baseURL}/publishers/google/models/${encodeVertexPathSegment(this.modelId, "Vertex model ID")}:generateContent`, {
|
|
1736
1879
|
method: "POST",
|
|
1737
1880
|
headers: this.headers(),
|
|
1738
1881
|
signal,
|
|
@@ -1753,8 +1896,7 @@ class VertexMusicGenerationModel {
|
|
|
1753
1896
|
...generationConfig
|
|
1754
1897
|
}
|
|
1755
1898
|
})
|
|
1756
|
-
}), input);
|
|
1757
|
-
const json = await parseJson(response);
|
|
1899
|
+
}).then(parseJson), { ...input, abortSignal: signal });
|
|
1758
1900
|
const { media, text } = collectInlineMedia(json, input.outputMimeType ?? "audio/mpeg", "models.generateContent");
|
|
1759
1901
|
return {
|
|
1760
1902
|
audio: media,
|
|
@@ -1772,13 +1914,15 @@ class VertexVideoGenerationModel {
|
|
|
1772
1914
|
baseURL;
|
|
1773
1915
|
accessToken;
|
|
1774
1916
|
fetcher;
|
|
1917
|
+
interactions;
|
|
1775
1918
|
provider = "vertex";
|
|
1776
1919
|
capabilities = videoGenerationCapabilities;
|
|
1777
|
-
constructor(modelId, baseURL, accessToken, fetcher) {
|
|
1920
|
+
constructor(modelId, baseURL, accessToken, fetcher, interactions) {
|
|
1778
1921
|
this.modelId = modelId;
|
|
1779
1922
|
this.baseURL = baseURL;
|
|
1780
1923
|
this.accessToken = accessToken;
|
|
1781
1924
|
this.fetcher = fetcher;
|
|
1925
|
+
this.interactions = interactions;
|
|
1782
1926
|
}
|
|
1783
1927
|
headers() {
|
|
1784
1928
|
return {
|
|
@@ -1786,11 +1930,58 @@ class VertexVideoGenerationModel {
|
|
|
1786
1930
|
};
|
|
1787
1931
|
}
|
|
1788
1932
|
async generateVideo(input) {
|
|
1933
|
+
if (this.modelId.startsWith("gemini-omni-")) {
|
|
1934
|
+
if (!this.interactions)
|
|
1935
|
+
throw new ConfigurationError("Gemini Omni video requires Vertex Interactions.");
|
|
1936
|
+
if (input.count !== undefined && input.count !== 1)
|
|
1937
|
+
throw new UnsupportedFeatureError("Gemini Omni generates one video per interaction.");
|
|
1938
|
+
if (input.pollIntervalMs !== undefined)
|
|
1939
|
+
throw new UnsupportedFeatureError("Gemini Omni synchronous generation does not poll; use interactions for asynchronous workflows.");
|
|
1940
|
+
if (input.negativePrompt !== undefined)
|
|
1941
|
+
throw new UnsupportedFeatureError("Gemini Omni does not expose negativePrompt; describe the desired video in prompt.");
|
|
1942
|
+
if (input.durationSeconds !== undefined && (!Number.isInteger(input.durationSeconds) || input.durationSeconds < 3 || input.durationSeconds > 10))
|
|
1943
|
+
throw new ConfigurationError("Gemini Omni durationSeconds must be an integer from 3 to 10.");
|
|
1944
|
+
if (input.aspectRatio !== undefined && !["16:9", "9:16"].includes(input.aspectRatio))
|
|
1945
|
+
throw new ConfigurationError("Gemini Omni aspectRatio must be 16:9 or 9:16.");
|
|
1946
|
+
if (input.outputStorageUri !== undefined && !/^gs:\/\/[^/\s]+\/[^\s]*$/.test(input.outputStorageUri))
|
|
1947
|
+
throw new ConfigurationError("Gemini Omni outputStorageUri must be a gs:// bucket path.");
|
|
1948
|
+
const options = input.providerOptions ?? {};
|
|
1949
|
+
for (const key of Object.keys(options))
|
|
1950
|
+
if (key !== "resolution")
|
|
1951
|
+
throw new UnsupportedFeatureError(`Gemini Omni video does not expose providerOptions.${key}; use interactions for advanced video workflows.`);
|
|
1952
|
+
const resolutions = this.modelId.startsWith("gemini-omni-1.1-") ? ["360p", "720p", "1080p", "4k"] : ["720p"];
|
|
1953
|
+
if (options.resolution !== undefined && !resolutions.includes(String(options.resolution)))
|
|
1954
|
+
throw new ConfigurationError("Unsupported Gemini Omni video resolution for this model.");
|
|
1955
|
+
const content = [{ type: "text", text: input.prompt }];
|
|
1956
|
+
if (input.image) {
|
|
1957
|
+
if (!input.image.mediaType.startsWith("image/") || (input.image.data === undefined) === (input.image.uri === undefined))
|
|
1958
|
+
throw new ConfigurationError("Gemini Omni image requires an image MIME type and exactly one data or uri.");
|
|
1959
|
+
const part = mediaInputToPart(input.image);
|
|
1960
|
+
content.push({ type: "image", mime_type: input.image.mediaType,
|
|
1961
|
+
...(part.inlineData ? { data: part.inlineData.data } : { uri: input.image.uri }) });
|
|
1962
|
+
}
|
|
1963
|
+
const result = await this.interactions.create({ modelId: this.modelId, input: content,
|
|
1964
|
+
responseFormat: [{ type: "video", ...(input.outputStorageUri ? { delivery: "uri", gcs_uri: input.outputStorageUri } : {}),
|
|
1965
|
+
...(input.aspectRatio ? { aspect_ratio: input.aspectRatio } : {}),
|
|
1966
|
+
...(input.durationSeconds !== undefined ? { duration: `${input.durationSeconds}s` } : {}),
|
|
1967
|
+
...(options.resolution ? { resolution: String(options.resolution) } : {}) }],
|
|
1968
|
+
generationConfig: { video_config: { task: input.image ? "image_to_video" : "text_to_video" } },
|
|
1969
|
+
background: false, store: false, abortSignal: input.abortSignal, timeoutMs: input.timeoutMs ?? 600_000,
|
|
1970
|
+
maxRetries: input.maxRetries, retryBackoffMs: input.retryBackoffMs });
|
|
1971
|
+
const videos = (result.outputs ?? []).filter((output) => !!output && typeof output === "object" && "type" in output && output.type === "video").map((output) => ({
|
|
1972
|
+
...(typeof output.data === "string" ? { data: decodeMedia(output.data, "interactions") } : {}),
|
|
1973
|
+
...(typeof output.uri === "string" ? { uri: output.uri } : {}),
|
|
1974
|
+
mediaType: typeof output.mime_type === "string" ? output.mime_type : "video/mp4"
|
|
1975
|
+
}));
|
|
1976
|
+
if (result.status !== "completed" || !videos.length || videos.some((video) => !video.data?.length && !video.uri))
|
|
1977
|
+
throw new ConfigurationError("Gemini Omni did not return completed video; use interactions for asynchronous workflows.");
|
|
1978
|
+
return { videos, rawResponse: sanitizeMediaResponse(result.rawResponse) };
|
|
1979
|
+
}
|
|
1789
1980
|
const timeoutMs = input.timeoutMs ?? 600_000;
|
|
1790
1981
|
const { signal, cleanup } = withTimeoutSignal({ ...input, timeoutMs });
|
|
1791
1982
|
const startedAt = Date.now();
|
|
1792
1983
|
try {
|
|
1793
|
-
const
|
|
1984
|
+
const json = await withRetry(() => this.fetcher(`${this.baseURL}/publishers/google/models/${encodeVertexPathSegment(this.modelId, "Vertex model ID")}:predictLongRunning`, {
|
|
1794
1985
|
method: "POST",
|
|
1795
1986
|
headers: this.headers(),
|
|
1796
1987
|
signal,
|
|
@@ -1810,8 +2001,8 @@ class VertexVideoGenerationModel {
|
|
|
1810
2001
|
...input.providerOptions
|
|
1811
2002
|
}
|
|
1812
2003
|
})
|
|
1813
|
-
}), input);
|
|
1814
|
-
let operation =
|
|
2004
|
+
}).then(parseJson), { ...input, abortSignal: signal });
|
|
2005
|
+
let operation = json;
|
|
1815
2006
|
const operationName = operation.name;
|
|
1816
2007
|
const pollIntervalMs = input.pollIntervalMs ?? 10_000;
|
|
1817
2008
|
while (!operation.done) {
|
|
@@ -1819,15 +2010,15 @@ class VertexVideoGenerationModel {
|
|
|
1819
2010
|
throw new Error(`Vertex video generation timed out after ${timeoutMs}ms.`);
|
|
1820
2011
|
}
|
|
1821
2012
|
await sleep(pollIntervalMs, signal);
|
|
1822
|
-
const
|
|
2013
|
+
const pollResult = await withRetry(() => this.fetcher(`${this.baseURL}/publishers/google/models/${encodeVertexPathSegment(this.modelId, "Vertex model ID")}:fetchPredictOperation`, {
|
|
1823
2014
|
method: "POST",
|
|
1824
2015
|
headers: this.headers(),
|
|
1825
2016
|
signal,
|
|
1826
2017
|
body: JSON.stringify({
|
|
1827
2018
|
operationName
|
|
1828
2019
|
})
|
|
1829
|
-
}), input);
|
|
1830
|
-
operation =
|
|
2020
|
+
}).then(parseJson), { ...input, abortSignal: signal });
|
|
2021
|
+
operation = pollResult;
|
|
1831
2022
|
}
|
|
1832
2023
|
return {
|
|
1833
2024
|
videos: collectVideos(operation, "models.predictLongRunning"),
|
|
@@ -1866,7 +2057,7 @@ class VertexGroundedLanguageModel {
|
|
|
1866
2057
|
assertCurrentGeminiGenerateInput("vertex", this.modelId, input);
|
|
1867
2058
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1868
2059
|
try {
|
|
1869
|
-
const
|
|
2060
|
+
const json = await withRetry(() => this.fetcher(this.url(), {
|
|
1870
2061
|
method: "POST",
|
|
1871
2062
|
headers: this.headers(),
|
|
1872
2063
|
signal,
|
|
@@ -1882,8 +2073,7 @@ class VertexGroundedLanguageModel {
|
|
|
1882
2073
|
reasoning: input.reasoning
|
|
1883
2074
|
})
|
|
1884
2075
|
})
|
|
1885
|
-
}), input);
|
|
1886
|
-
const json = await parseJson(response);
|
|
2076
|
+
}).then(parseJson), { ...input, abortSignal: signal });
|
|
1887
2077
|
const candidate = json.candidates?.[0];
|
|
1888
2078
|
const assistantMessage = parseAssistantMessage(candidate);
|
|
1889
2079
|
return {
|
|
@@ -1894,6 +2084,7 @@ class VertexGroundedLanguageModel {
|
|
|
1894
2084
|
sources: extractGroundingSources(candidate),
|
|
1895
2085
|
finishReason: normalizeFinishReason(candidate?.finishReason),
|
|
1896
2086
|
providerFinishReason: candidate?.finishReason,
|
|
2087
|
+
usage: normalizeGenerateContentUsage(json.usageMetadata ?? json.usage_metadata),
|
|
1897
2088
|
rawResponse: json
|
|
1898
2089
|
};
|
|
1899
2090
|
}
|
|
@@ -1910,9 +2101,10 @@ class VertexRealtimeModel {
|
|
|
1910
2101
|
connectionFactory;
|
|
1911
2102
|
realtimeURL;
|
|
1912
2103
|
allowUnsafeEndpoints;
|
|
2104
|
+
projectResource;
|
|
1913
2105
|
provider = "vertex";
|
|
1914
2106
|
capabilities;
|
|
1915
|
-
constructor(modelId, auth, location, apiVersion, connectionFactory, realtimeURL, allowUnsafeEndpoints = false) {
|
|
2107
|
+
constructor(modelId, auth, location, apiVersion, connectionFactory, realtimeURL, allowUnsafeEndpoints = false, projectResource) {
|
|
1916
2108
|
this.modelId = modelId;
|
|
1917
2109
|
this.auth = auth;
|
|
1918
2110
|
this.location = location;
|
|
@@ -1920,6 +2112,7 @@ class VertexRealtimeModel {
|
|
|
1920
2112
|
this.connectionFactory = connectionFactory;
|
|
1921
2113
|
this.realtimeURL = realtimeURL;
|
|
1922
2114
|
this.allowUnsafeEndpoints = allowUnsafeEndpoints;
|
|
2115
|
+
this.projectResource = projectResource;
|
|
1923
2116
|
this.capabilities = realtimeCapabilities(modelId);
|
|
1924
2117
|
}
|
|
1925
2118
|
async connect(config = {}, options) {
|
|
@@ -1927,10 +2120,22 @@ class VertexRealtimeModel {
|
|
|
1927
2120
|
if (this.auth.type === "api-key") {
|
|
1928
2121
|
throw new UnsupportedFeatureError('Provider "vertex" realtime sessions require accessToken or getAccessToken auth.');
|
|
1929
2122
|
}
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
2123
|
+
if (!this.projectResource)
|
|
2124
|
+
throw new ConfigurationError("Vertex Live requires a project ID or project-scoped baseURL.");
|
|
2125
|
+
const modelResource = `${this.projectResource}/publishers/google/models/${this.modelId}`;
|
|
2126
|
+
const started = performance.now();
|
|
2127
|
+
const authDeadline = withTimeoutSignal({ timeoutMs: options?.timeoutMs, abortSignal: options?.signal });
|
|
2128
|
+
let accessToken;
|
|
2129
|
+
try {
|
|
2130
|
+
accessToken = await awaitVertexToken(this.auth.getAccessToken, authDeadline.signal);
|
|
1933
2131
|
}
|
|
2132
|
+
finally {
|
|
2133
|
+
authDeadline.cleanup();
|
|
2134
|
+
}
|
|
2135
|
+
const remainingTimeout = options?.timeoutMs === undefined ? undefined : Math.floor(options.timeoutMs - (performance.now() - started));
|
|
2136
|
+
if (remainingTimeout !== undefined && remainingTimeout <= 0)
|
|
2137
|
+
throw new DOMException("The operation timed out.", "TimeoutError");
|
|
2138
|
+
options?.signal?.throwIfAborted();
|
|
1934
2139
|
const providerOptions = (config.providerOptions ?? {});
|
|
1935
2140
|
const expectedRealtimeURL = vertexRealtimeURL(this.location, this.apiVersion, undefined, this.realtimeURL);
|
|
1936
2141
|
const realtimeEndpoint = assertTrustedEndpoint(vertexRealtimeURL(this.location, this.apiVersion, providerOptions, this.realtimeURL), {
|
|
@@ -1939,7 +2144,8 @@ class VertexRealtimeModel {
|
|
|
1939
2144
|
allowedHosts: [new URL(expectedRealtimeURL).hostname],
|
|
1940
2145
|
allowUnsafe: this.allowUnsafeEndpoints
|
|
1941
2146
|
}).toString();
|
|
1942
|
-
const connection = await (this.connectionFactory ?? openWebSocketConnection)(realtimeEndpoint, vertexRealtimeHeaders(accessToken, providerOptions), options);
|
|
2147
|
+
const connection = await (this.connectionFactory ?? openWebSocketConnection)(realtimeEndpoint, vertexRealtimeHeaders(accessToken, providerOptions), { ...options, timeoutMs: remainingTimeout });
|
|
2148
|
+
let inputMuted = false;
|
|
1943
2149
|
const session = new CallbackRealtimeSession({
|
|
1944
2150
|
provider: this.provider,
|
|
1945
2151
|
modelId: this.modelId,
|
|
@@ -1948,9 +2154,9 @@ class VertexRealtimeModel {
|
|
|
1948
2154
|
connection,
|
|
1949
2155
|
initializationTimeoutMs: options?.timeoutMs,
|
|
1950
2156
|
callbacks: {
|
|
1951
|
-
parseEvent: createVertexRealtimeEventParser(),
|
|
2157
|
+
parseEvent: createVertexRealtimeEventParser(isVertexLiveTranscribeModel(this.modelId)),
|
|
1952
2158
|
isReadyPayload: (payload) => "setupComplete" in payload || "setup_complete" in payload,
|
|
1953
|
-
buildAudioPayloads: (frame) => [
|
|
2159
|
+
buildAudioPayloads: (frame) => inputMuted ? [] : [
|
|
1954
2160
|
{
|
|
1955
2161
|
realtimeInput: {
|
|
1956
2162
|
audio: {
|
|
@@ -1961,23 +2167,23 @@ class VertexRealtimeModel {
|
|
|
1961
2167
|
}
|
|
1962
2168
|
],
|
|
1963
2169
|
buildMediaPayloads: (frame) => {
|
|
1964
|
-
if (isGeminiLiveTranslateModel(this.modelId)) {
|
|
1965
|
-
throw new UnsupportedFeatureError(
|
|
2170
|
+
if (isGeminiLiveTranslateModel(this.modelId) || isVertexLiveTranscribeModel(this.modelId)) {
|
|
2171
|
+
throw new UnsupportedFeatureError(`Model "vertex/${this.modelId}" only supports audio input.`);
|
|
1966
2172
|
}
|
|
1967
2173
|
return [
|
|
1968
2174
|
{
|
|
1969
2175
|
realtimeInput: {
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
2176
|
+
mediaChunks: [{
|
|
2177
|
+
mimeType: frame.mediaType,
|
|
2178
|
+
data: encodeMediaFrame(frame)
|
|
2179
|
+
}]
|
|
1974
2180
|
}
|
|
1975
2181
|
}
|
|
1976
2182
|
];
|
|
1977
2183
|
},
|
|
1978
2184
|
buildTextPayloads: (text) => {
|
|
1979
|
-
if (isGeminiLiveTranslateModel(this.modelId)) {
|
|
1980
|
-
throw new UnsupportedFeatureError(
|
|
2185
|
+
if (isGeminiLiveTranslateModel(this.modelId) || isVertexLiveTranscribeModel(this.modelId)) {
|
|
2186
|
+
throw new UnsupportedFeatureError(`Model "vertex/${this.modelId}" only supports audio input.`);
|
|
1981
2187
|
}
|
|
1982
2188
|
return [
|
|
1983
2189
|
{
|
|
@@ -1993,26 +2199,58 @@ class VertexRealtimeModel {
|
|
|
1993
2199
|
}
|
|
1994
2200
|
];
|
|
1995
2201
|
},
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2202
|
+
buildInputMutePayloads: (muted) => {
|
|
2203
|
+
if (!isVertexLiveTranscribeModel(this.modelId) && !isGeminiLiveTranslateModel(this.modelId))
|
|
2204
|
+
throw new UnsupportedFeatureError("Vertex input muting is only exposed for Live Transcribe and Live Translate.");
|
|
2205
|
+
if (inputMuted === muted)
|
|
2206
|
+
return [];
|
|
2207
|
+
inputMuted = muted;
|
|
2208
|
+
return muted ? [{ realtimeInput: { audioStreamEnd: true } }] : [];
|
|
2209
|
+
},
|
|
2210
|
+
buildToolResultPayloads: (result) => {
|
|
2211
|
+
if (isVertexLiveTranscribeModel(this.modelId))
|
|
2212
|
+
throw new UnsupportedFeatureError("Live Transcribe does not support tool results.");
|
|
2213
|
+
return [
|
|
2214
|
+
{
|
|
2215
|
+
toolResponse: {
|
|
2216
|
+
functionResponses: [
|
|
2217
|
+
{
|
|
2218
|
+
id: result.toolCallId,
|
|
2219
|
+
name: result.toolName,
|
|
2220
|
+
response: toolResultPayload(result)
|
|
2221
|
+
}
|
|
2222
|
+
]
|
|
2223
|
+
}
|
|
2006
2224
|
}
|
|
2225
|
+
];
|
|
2226
|
+
},
|
|
2227
|
+
buildInterruptPayloads: (sessionConfig) => {
|
|
2228
|
+
if (isGeminiLiveTranslateModel(this.modelId) || isVertexLiveTranscribeModel(this.modelId)) {
|
|
2229
|
+
throw new UnsupportedFeatureError("This Vertex Live model does not support client-content interruption.");
|
|
2007
2230
|
}
|
|
2008
|
-
|
|
2009
|
-
|
|
2231
|
+
const inputConfig = sessionConfig.providerOptions?.realtimeInputConfig;
|
|
2232
|
+
if (inputConfig?.automaticActivityDetection?.disabled !== true) {
|
|
2233
|
+
throw new UnsupportedFeatureError("Vertex explicit interruption requires providerOptions.realtimeInputConfig.automaticActivityDetection.disabled=true; automatic VAD interrupts on user speech.");
|
|
2234
|
+
}
|
|
2235
|
+
return [{ realtimeInput: { activityStart: {} } }, { realtimeInput: { activityEnd: {} } }];
|
|
2236
|
+
},
|
|
2237
|
+
buildUpdatePayloads: (sessionConfig, previousConfig) => {
|
|
2010
2238
|
assertVertexRealtimeConfig(sessionConfig, this.modelId);
|
|
2011
|
-
|
|
2239
|
+
for (const key of new Set([...Object.keys(previousConfig), ...Object.keys(sessionConfig)])) {
|
|
2240
|
+
if (key !== "instructions" && !Object.is(previousConfig[key], sessionConfig[key])) {
|
|
2241
|
+
throw new UnsupportedFeatureError(`Vertex Live cannot update "${key}" after connection; reconnect with the new configuration.`);
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
if (sessionConfig.instructions === previousConfig.instructions)
|
|
2245
|
+
return [];
|
|
2246
|
+
if (typeof sessionConfig.instructions !== "string" || !sessionConfig.instructions.trim()) {
|
|
2247
|
+
throw new UnsupportedFeatureError("Vertex Live requires non-empty replacement instructions; reconnect to remove instructions.");
|
|
2248
|
+
}
|
|
2249
|
+
return [{ clientContent: { turns: [{ role: "system", parts: [{ text: sessionConfig.instructions }] }], turnComplete: false } }];
|
|
2012
2250
|
},
|
|
2013
2251
|
buildInitialPayloads: (sessionConfig) => {
|
|
2014
2252
|
assertVertexRealtimeConfig(sessionConfig, this.modelId);
|
|
2015
|
-
return [vertexRealtimeSetup(sessionConfig,
|
|
2253
|
+
return [vertexRealtimeSetup(sessionConfig, modelResource)];
|
|
2016
2254
|
}
|
|
2017
2255
|
}
|
|
2018
2256
|
});
|
|
@@ -2031,7 +2269,7 @@ export const createVertex = (options = {}) => {
|
|
|
2031
2269
|
}
|
|
2032
2270
|
const location = options.location ?? process.env.VERTEX_LOCATION ?? process.env.GOOGLE_CLOUD_LOCATION ?? "global";
|
|
2033
2271
|
const apiVersion = options.apiVersion ?? "v1";
|
|
2034
|
-
const apiHost = location
|
|
2272
|
+
const apiHost = vertexApiHost(location);
|
|
2035
2273
|
const configuredBaseURL = options.baseURL ??
|
|
2036
2274
|
(auth.type === "api-key"
|
|
2037
2275
|
? `https://aiplatform.googleapis.com/${apiVersion}`
|
|
@@ -2048,14 +2286,23 @@ export const createVertex = (options = {}) => {
|
|
|
2048
2286
|
: `https://${veoLocation}-aiplatform.googleapis.com/${apiVersion}/projects/${projectId}/locations/${veoLocation}`);
|
|
2049
2287
|
const rawFetch = options.fetch ?? globalThis.fetch;
|
|
2050
2288
|
const fetcher = createVertexAuthenticatedFetch(rawFetch, auth);
|
|
2289
|
+
const interactions = createVertexInteractionsClient(baseURL, fetcher, () => {
|
|
2290
|
+
if (auth.type !== "bearer" || !/\/projects\/[^/]+\/locations\/[^/]+$/.test(baseURL))
|
|
2291
|
+
throw new ConfigurationError("Vertex Interactions requires Google Cloud bearer credentials and a project-scoped endpoint.");
|
|
2292
|
+
});
|
|
2051
2293
|
const assertModelLocation = (modelId) => {
|
|
2294
|
+
if (modelId.replace(/^publishers\/google\/models\//, "").startsWith("virtual-try-on-"))
|
|
2295
|
+
throw new UnsupportedFeatureError("Virtual Try-On requires vertex.virtualTryOn.generate() with person and product images.");
|
|
2052
2296
|
if (modelId.startsWith("claude-")) {
|
|
2053
2297
|
throw new UnsupportedFeatureError("Claude on Vertex is available through languageModel(); this Google-specific surface is not supported.");
|
|
2054
2298
|
}
|
|
2299
|
+
if (isVertexChatModel(modelId) || /^(?:intfloat\/|publishers\/(?!google\/)|anthropic\/)/.test(modelId)) {
|
|
2300
|
+
throw new UnsupportedFeatureError(`Vertex model "${modelId}" cannot use this Google-specific surface; select its publisher API.`);
|
|
2301
|
+
}
|
|
2055
2302
|
if (options.baseURL || auth.type !== "bearer") {
|
|
2056
2303
|
return;
|
|
2057
2304
|
}
|
|
2058
|
-
const supportedLocations = modelId === "gemini-3.7-flash" || modelId === "gemini-3.6-flash"
|
|
2305
|
+
const supportedLocations = modelId === "gemini-3.7-flash" || modelId === "gemini-3.6-flash" || modelId === "gemini-3.5-transcribe-preview" || isVertexLiveTranscribeModel(modelId) || isGeminiLiveTranslateModel(modelId)
|
|
2059
2306
|
? ["global"]
|
|
2060
2307
|
: modelId === "gemini-3.8-flash" || modelId === "gemini-3.5-flash-lite"
|
|
2061
2308
|
? ["global", "us", "eu"]
|
|
@@ -2069,7 +2316,23 @@ export const createVertex = (options = {}) => {
|
|
|
2069
2316
|
assertModelLocation(modelId);
|
|
2070
2317
|
return modelId;
|
|
2071
2318
|
};
|
|
2319
|
+
const chatModel = (modelId, chatOptions) => {
|
|
2320
|
+
if (auth.type !== "bearer")
|
|
2321
|
+
throw new ConfigurationError("Vertex partner chat requires Google Cloud bearer credentials.");
|
|
2322
|
+
return createVertexChatModel(modelId, baseURL, fetcher, chatOptions);
|
|
2323
|
+
};
|
|
2324
|
+
const assertLanguageSurface = (modelId) => {
|
|
2325
|
+
if (isVertexLiveTranscribeModel(modelId) || isGeminiLiveTranslateModel(modelId)) {
|
|
2326
|
+
throw new UnsupportedFeatureError(`Vertex model "${modelId}" requires realtimeModel(), not a language model factory.`);
|
|
2327
|
+
}
|
|
2328
|
+
if (modelId === "gemini-3.5-transcribe-preview") {
|
|
2329
|
+
throw new UnsupportedFeatureError(`Vertex model "${modelId}" requires transcriptionModel(), not a language model factory.`);
|
|
2330
|
+
}
|
|
2331
|
+
};
|
|
2072
2332
|
const languageModel = (modelId) => {
|
|
2333
|
+
assertLanguageSurface(modelId);
|
|
2334
|
+
if (isVertexChatModel(modelId))
|
|
2335
|
+
return chatModel(modelId);
|
|
2073
2336
|
if (modelId.startsWith("claude-")) {
|
|
2074
2337
|
const encodedId = encodeVertexPathSegment(modelId, "Vertex Claude model ID");
|
|
2075
2338
|
if (auth.type !== "bearer") {
|
|
@@ -2081,6 +2344,7 @@ export const createVertex = (options = {}) => {
|
|
|
2081
2344
|
return new VertexLanguageModel(modelId, baseURL, "", fetcher);
|
|
2082
2345
|
};
|
|
2083
2346
|
const groundedLanguageModel = (modelId) => {
|
|
2347
|
+
assertLanguageSurface(modelId);
|
|
2084
2348
|
if (modelId.startsWith("claude-")) {
|
|
2085
2349
|
throw new UnsupportedFeatureError("Claude on Vertex does not support Google grounded generation; use languageModel().");
|
|
2086
2350
|
}
|
|
@@ -2089,13 +2353,107 @@ export const createVertex = (options = {}) => {
|
|
|
2089
2353
|
};
|
|
2090
2354
|
return createProviderAdapter({
|
|
2091
2355
|
name: "vertex",
|
|
2356
|
+
endpoints: { ...createVertexEndpointsClient(fetcher, (endpoint, action) => {
|
|
2357
|
+
if (!/^(?:projects\/[^/]+\/locations\/[^/]+\/)?endpoints\/[^/]+$/.test(endpoint))
|
|
2358
|
+
throw new ConfigurationError("Vertex endpoint must use endpoints/<id> or projects/<project>/locations/<location>/endpoints/<id>.");
|
|
2359
|
+
return `${vertexResourceURL(baseURL, endpoint)}:${action}`;
|
|
2360
|
+
}, () => {
|
|
2361
|
+
if (auth.type !== "bearer")
|
|
2362
|
+
throw new ConfigurationError("Vertex deployed endpoints require Google Cloud bearer credentials.");
|
|
2363
|
+
}, parseJson), ...createVertexGrpcClient(baseURL, (endpoint, allowPublisher) => {
|
|
2364
|
+
const deployed = /^(?:projects\/[^/]+\/locations\/[^/]+\/)?endpoints\/[^/]+$/;
|
|
2365
|
+
const publisher = /^(?:projects\/[^/]+\/locations\/[^/]+\/)?publishers\/[^/]+\/models\/[^/]+$/;
|
|
2366
|
+
if (!deployed.test(endpoint) && !(allowPublisher && publisher.test(endpoint)))
|
|
2367
|
+
throw new ConfigurationError(allowPublisher
|
|
2368
|
+
? "Vertex serverStreamingPredict requires an endpoint or publisher model resource."
|
|
2369
|
+
: "Vertex gRPC endpoint must be a deployed endpoint resource.");
|
|
2370
|
+
const encoded = new URL(vertexResourceURL(baseURL, endpoint)).pathname.replace(/^\/v1(?:beta1)?\//, "");
|
|
2371
|
+
// gRPC carries a resource name in protobuf, not an escaped HTTP URL path.
|
|
2372
|
+
const resource = decodeURIComponent(encoded);
|
|
2373
|
+
if (!/^projects\/[^/]+\/locations\/[^/]+\//.test(resource) || (!deployed.test(resource) && !(allowPublisher && publisher.test(resource))))
|
|
2374
|
+
throw new ConfigurationError("Vertex gRPC requires a project-scoped resource.");
|
|
2375
|
+
return resource;
|
|
2376
|
+
}, async (signal) => {
|
|
2377
|
+
if (auth.type !== "bearer")
|
|
2378
|
+
throw new ConfigurationError("Vertex gRPC requires Google Cloud bearer credentials.");
|
|
2379
|
+
return awaitVertexToken(auth.getAccessToken, signal);
|
|
2380
|
+
}) },
|
|
2381
|
+
virtualTryOn: createVertexVirtualTryOnClient(new VertexPredictionModel("virtual-try-on-001", baseURL, "", fetcher), () => {
|
|
2382
|
+
if (auth.type !== "bearer")
|
|
2383
|
+
throw new ConfigurationError("Virtual Try-On requires Google Cloud bearer credentials.");
|
|
2384
|
+
}, sanitizeMediaResponse),
|
|
2385
|
+
interactions,
|
|
2386
|
+
claude: createVertexClaudeClient(baseURL, fetcher, () => {
|
|
2387
|
+
if (auth.type !== "bearer")
|
|
2388
|
+
throw new ConfigurationError("Vertex Claude token counting requires Google Cloud bearer credentials.");
|
|
2389
|
+
if (!options.baseURL && !["global", "us", "eu", "asia-southeast1"].includes(location))
|
|
2390
|
+
throw new ConfigurationError("Vertex Claude token counting requires global, us, eu or asia-southeast1.");
|
|
2391
|
+
}),
|
|
2392
|
+
...createVertexSpecializedClients(baseURL, fetcher, () => {
|
|
2393
|
+
if (auth.type !== "bearer")
|
|
2394
|
+
throw new ConfigurationError("Vertex OCR and FIM require Google Cloud bearer credentials.");
|
|
2395
|
+
}),
|
|
2092
2396
|
languageModel,
|
|
2093
|
-
|
|
2094
|
-
|
|
2397
|
+
chatModel,
|
|
2398
|
+
responsesModel: (modelId) => {
|
|
2399
|
+
if (auth.type !== "bearer")
|
|
2400
|
+
throw new ConfigurationError("Vertex Grok Responses requires Google Cloud bearer credentials.");
|
|
2401
|
+
return createVertexResponsesModel(modelId, baseURL, fetcher, options.allowUnsafeEndpoints);
|
|
2402
|
+
},
|
|
2403
|
+
gemini: {
|
|
2404
|
+
async countTokens(input) {
|
|
2405
|
+
const modelId = googleModelId(input.modelId.replace(/^publishers\/google\/models\//, ""));
|
|
2406
|
+
if (!/^gemini-[a-z0-9@.-]+$/.test(modelId))
|
|
2407
|
+
throw new ConfigurationError("Vertex Gemini token counting requires a Gemini model ID.");
|
|
2408
|
+
const body = JSON.stringify({ contents: mapMessages(input.messages),
|
|
2409
|
+
systemInstruction: input.system !== undefined ? { parts: [{ text: input.system }] } : systemInstruction(input.messages),
|
|
2410
|
+
tools: input.tools ? mapTools(toToolSet(input.tools)) : undefined,
|
|
2411
|
+
generationConfig: input.generationConfig });
|
|
2412
|
+
const { signal, cleanup } = withTimeoutSignal(input);
|
|
2413
|
+
try {
|
|
2414
|
+
const json = await withRetry(() => fetcher(`${baseURL}/publishers/google/models/${encodeVertexPathSegment(modelId, "Vertex model ID")}:countTokens`, {
|
|
2415
|
+
method: "POST", headers: { "content-type": "application/json" }, body, signal, redirect: "error"
|
|
2416
|
+
}).then(parseJson), { ...input, abortSignal: signal });
|
|
2417
|
+
if (!Number.isSafeInteger(json.totalTokens) || json.totalTokens < 0)
|
|
2418
|
+
throw new ConfigurationError("Vertex returned invalid token count.");
|
|
2419
|
+
if (json.totalBillableCharacters !== undefined && (!Number.isSafeInteger(json.totalBillableCharacters) || json.totalBillableCharacters < 0))
|
|
2420
|
+
throw new ConfigurationError("Vertex returned invalid billable character count.");
|
|
2421
|
+
return { inputTokens: json.totalTokens, ...(json.totalBillableCharacters !== undefined ? { totalBillableCharacters: json.totalBillableCharacters } : {}), rawResponse: json };
|
|
2422
|
+
}
|
|
2423
|
+
finally {
|
|
2424
|
+
cleanup();
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
},
|
|
2428
|
+
multimodalEmbeddings: { embed: (input) => new VertexLegacyMultimodalEmbeddingModel(baseURL, fetcher, () => {
|
|
2429
|
+
if (auth.type !== "bearer")
|
|
2430
|
+
throw new ConfigurationError("Vertex legacy multimodal embeddings require Google Cloud bearer credentials.");
|
|
2431
|
+
}).embedMultimodal(input) },
|
|
2432
|
+
embeddingModel: (modelId) => {
|
|
2433
|
+
if (modelId.replace(/^publishers\/google\/models\//, "") === "multimodalembedding@001")
|
|
2434
|
+
return new VertexLegacyMultimodalEmbeddingModel(baseURL, fetcher, () => {
|
|
2435
|
+
if (auth.type !== "bearer")
|
|
2436
|
+
throw new ConfigurationError("Vertex legacy multimodal embeddings require Google Cloud bearer credentials.");
|
|
2437
|
+
});
|
|
2438
|
+
const openModel = modelId.replace(/^publishers\/intfloat\/models\//, "intfloat/");
|
|
2439
|
+
if (openModel.startsWith("intfloat/")) {
|
|
2440
|
+
if (!/^intfloat\/multilingual-e5-(?:small|large-instruct)-maas$/.test(openModel))
|
|
2441
|
+
throw new ConfigurationError(`Unsupported Vertex E5 embedding model "${modelId}".`);
|
|
2442
|
+
if (auth.type !== "bearer")
|
|
2443
|
+
throw new ConfigurationError("Vertex E5 embeddings require Google Cloud bearer credentials.");
|
|
2444
|
+
return new VertexOpenEmbeddingModel(openModel, baseURL, fetcher);
|
|
2445
|
+
}
|
|
2446
|
+
return new VertexEmbeddingModel(googleModelId(modelId), baseURL, fetcher);
|
|
2447
|
+
},
|
|
2448
|
+
transcriptionModel: (modelId) => {
|
|
2449
|
+
if (modelId === "gemini-3.5-transcribe-live-preview")
|
|
2450
|
+
throw new UnsupportedFeatureError("The Live transcription model requires a dedicated realtime session, not transcriptionModel().");
|
|
2451
|
+
return new VertexTranscriptionModel(googleModelId(modelId), baseURL, "", fetcher);
|
|
2452
|
+
},
|
|
2095
2453
|
speechModel: (modelId) => new VertexSpeechModel(googleModelId(modelId), baseURL, "", fetcher),
|
|
2096
2454
|
imageGenerationModel: (modelId) => new VertexImageGenerationModel(googleModelId(modelId), baseURL, "", fetcher),
|
|
2097
|
-
videoGenerationModel: (modelId) => new VertexVideoGenerationModel(googleModelId(modelId), isVeoModel(modelId) ? veoBaseURL : baseURL, "", fetcher),
|
|
2098
|
-
musicGenerationModel: (modelId) => new VertexMusicGenerationModel(googleModelId(modelId), baseURL, "", fetcher),
|
|
2455
|
+
videoGenerationModel: (modelId) => new VertexVideoGenerationModel(googleModelId(modelId), isVeoModel(modelId) ? veoBaseURL : baseURL, "", fetcher, interactions),
|
|
2456
|
+
musicGenerationModel: (modelId) => new VertexMusicGenerationModel(googleModelId(modelId), baseURL, "", fetcher, interactions),
|
|
2099
2457
|
realtimeModel: (modelId) => new VertexRealtimeModel(googleModelId(modelId), auth, location, apiVersion, options.realtimeConnectionFactory, options.realtimeURL
|
|
2100
2458
|
? assertTrustedEndpoint(options.realtimeURL, {
|
|
2101
2459
|
label: "Vertex realtimeURL",
|
|
@@ -2103,10 +2461,13 @@ export const createVertex = (options = {}) => {
|
|
|
2103
2461
|
allowedHosts: [apiHost],
|
|
2104
2462
|
allowUnsafe: options.allowUnsafeEndpoints
|
|
2105
2463
|
}).toString()
|
|
2106
|
-
: undefined, options.allowUnsafeEndpoints),
|
|
2464
|
+
: undefined, options.allowUnsafeEndpoints, baseURL.match(/\/(projects\/[^/]+\/locations\/[^/]+)(?:\/|$)/)?.[1] ?? (projectId ? `projects/${projectId}/locations/${location}` : undefined)),
|
|
2107
2465
|
groundedLanguageModel,
|
|
2108
2466
|
caches: new VertexContextCachesClient(baseURL, "", fetcher, assertModelLocation),
|
|
2109
|
-
batches: new VertexBatchesClient(baseURL,
|
|
2467
|
+
batches: new VertexBatchesClient(baseURL, fetcher, () => {
|
|
2468
|
+
if (auth.type !== "bearer")
|
|
2469
|
+
throw new ConfigurationError("Vertex batch jobs require Google Cloud bearer credentials and a project-scoped endpoint.");
|
|
2470
|
+
}),
|
|
2110
2471
|
predictionModel: (modelId) => {
|
|
2111
2472
|
const resource = vertexPublisherResource(modelId);
|
|
2112
2473
|
if (modelId.startsWith("claude-")) {
|